mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merged in develop
This commit is contained in:
commit
4082ffebe9
@ -340,6 +340,7 @@
|
||||
<package>org.sleuthkit.autopsy.report</package>
|
||||
<package>org.sleuthkit.autopsy.textextractors</package>
|
||||
<package>org.sleuthkit.autopsy.textextractors.extractionconfigs</package>
|
||||
<package>org.sleuthkit.autopsy.texttranslation</package>
|
||||
<package>org.sleuthkit.datamodel</package>
|
||||
</public-packages>
|
||||
<class-path-extension>
|
||||
|
@ -430,13 +430,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
CorrelationCase corCase = EamDb.getInstance().getCase(Case.getCurrentCase());
|
||||
try {
|
||||
ret.add(new CorrelationAttributeInstance(
|
||||
md5,
|
||||
aType,
|
||||
md5,
|
||||
corCase,
|
||||
CorrelationDataSource.fromTSKDataSource(corCase, file.getDataSource()),
|
||||
file.getParentPath() + file.getName(),
|
||||
"",
|
||||
file.getKnown()));
|
||||
file.getKnown(),
|
||||
file.getId()));
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
LOGGER.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for value %s and type %s.", md5, aType.toString()), ex);
|
||||
}
|
||||
@ -458,8 +459,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
|
||||
.filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
|
||||
.findAny()
|
||||
.get();
|
||||
|
||||
ret.add(new CorrelationAttributeInstance(fileAttributeType, md5));
|
||||
//The Central Repository is not enabled
|
||||
ret.add(new CorrelationAttributeInstance(fileAttributeType, md5, null, null, "", "", TskData.FileKnown.UNKNOWN, this.file.getId()));
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
|
@ -71,7 +71,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
private static final Cache<Integer, CorrelationCase> caseCacheById = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES).
|
||||
build();
|
||||
private static final Cache<String, CorrelationDataSource> dataSourceCacheByDeviceId = CacheBuilder.newBuilder()
|
||||
private static final Cache<String, CorrelationDataSource> dataSourceCacheByDsObjectId = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(DATA_SOURCE_CACHE_TIMEOUT, TimeUnit.MINUTES).
|
||||
build();
|
||||
private static final Cache<String, CorrelationDataSource> dataSourceCacheById = CacheBuilder.newBuilder()
|
||||
@ -95,7 +95,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
defaultCorrelationTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes();
|
||||
defaultCorrelationTypes.forEach((type) -> {
|
||||
bulkArtifacts.put(type.getDbTableName(), new ArrayList<>());
|
||||
bulkArtifacts.put(EamDbUtil.correlationTypeToInstanceTableName(type), new ArrayList<>());
|
||||
});
|
||||
}
|
||||
|
||||
@ -133,6 +133,24 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDataSourceObjectId(int rowId, long dataSourceObjectId) throws EamDbException {
|
||||
Connection conn = connect();
|
||||
PreparedStatement preparedStatement = null;
|
||||
String sql = "UPDATE data_sources SET datasource_obj_id=? WHERE id=?";
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(sql);
|
||||
preparedStatement.setLong(1, dataSourceObjectId);
|
||||
preparedStatement.setInt(2, rowId);
|
||||
preparedStatement.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException("Error updating data source object id for data_sources row " + rowId, ex);
|
||||
} finally {
|
||||
EamDbUtil.closeStatement(preparedStatement);
|
||||
EamDbUtil.closeConnection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for the given name from the name/value db_info table.
|
||||
*
|
||||
@ -175,7 +193,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
typeCache.invalidateAll();
|
||||
caseCacheByUUID.invalidateAll();
|
||||
caseCacheById.invalidateAll();
|
||||
dataSourceCacheByDeviceId.invalidateAll();
|
||||
dataSourceCacheByDsObjectId.invalidateAll();
|
||||
dataSourceCacheById.invalidateAll();
|
||||
}
|
||||
|
||||
@ -551,16 +569,17 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a key to the DataSourceCacheByDeviceId
|
||||
* Create a key to the dataSourceCacheByDsObjectId
|
||||
*
|
||||
* @param caseId - the id of the CorrelationCase in the Central
|
||||
* Repository
|
||||
* @param dataSourceDeviceId - the device Id of the data source
|
||||
* @param dataSourceObjectId - the object id if of the data source in the
|
||||
* case db
|
||||
*
|
||||
* @return a String to be used as a key for the dataSourceCacheByDeviceId
|
||||
* @return a String to be used as a key for the dataSourceCacheByDsObjectId
|
||||
*/
|
||||
private static String getDataSourceByDeviceIdCacheKey(int caseId, String dataSourceDeviceId) {
|
||||
return "Case" + caseId + "DeviceId" + dataSourceDeviceId; //NON-NLS
|
||||
private static String getDataSourceByDSObjectIdCacheKey(int caseId, Long dataSourceObjectId) {
|
||||
return "Case" + caseId + "DsObjectId" + dataSourceObjectId; //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
@ -582,18 +601,18 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
* @param eamDataSource the data source to add
|
||||
*/
|
||||
@Override
|
||||
public void newDataSource(CorrelationDataSource eamDataSource) throws EamDbException {
|
||||
public CorrelationDataSource newDataSource(CorrelationDataSource eamDataSource) throws EamDbException {
|
||||
if (eamDataSource.getCaseID() == -1) {
|
||||
throw new EamDbException("Case ID is -1");
|
||||
} else if (eamDataSource.getID() != -1) {
|
||||
// This data source is already in the central repo
|
||||
return;
|
||||
return eamDataSource;
|
||||
}
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement preparedStatement = null;
|
||||
|
||||
String sql = "INSERT INTO data_sources(device_id, case_id, name) VALUES (?, ?, ?) "
|
||||
String sql = "INSERT INTO data_sources(device_id, case_id, name, datasource_obj_id) VALUES (?, ?, ?, ?) "
|
||||
+ getConflictClause();
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
@ -602,6 +621,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
preparedStatement.setString(1, eamDataSource.getDeviceID());
|
||||
preparedStatement.setInt(2, eamDataSource.getCaseID());
|
||||
preparedStatement.setString(3, eamDataSource.getName());
|
||||
preparedStatement.setLong(4, eamDataSource.getDataSourceObjectID());
|
||||
|
||||
preparedStatement.executeUpdate();
|
||||
resultSet = preparedStatement.getGeneratedKeys();
|
||||
@ -609,9 +629,10 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
throw new EamDbException(String.format("Failed to INSERT data source %s in central repo", eamDataSource.getName()));
|
||||
}
|
||||
int dataSourceId = resultSet.getInt(1); //last_insert_rowid()
|
||||
CorrelationDataSource dataSource = new CorrelationDataSource(eamDataSource.getCaseID(), dataSourceId, eamDataSource.getDeviceID(), eamDataSource.getName());
|
||||
dataSourceCacheByDeviceId.put(getDataSourceByDeviceIdCacheKey(dataSource.getCaseID(), dataSource.getDeviceID()), dataSource);
|
||||
CorrelationDataSource dataSource = new CorrelationDataSource(eamDataSource.getCaseID(), dataSourceId, eamDataSource.getDeviceID(), eamDataSource.getName(), eamDataSource.getDataSourceObjectID());
|
||||
dataSourceCacheByDsObjectId.put(getDataSourceByDSObjectIdCacheKey(dataSource.getCaseID(), dataSource.getDataSourceObjectID()), dataSource);
|
||||
dataSourceCacheById.put(getDataSourceByIdCacheKey(dataSource.getCaseID(), dataSource.getID()), dataSource);
|
||||
return dataSource;
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException("Error inserting new data source.", ex); // NON-NLS
|
||||
} finally {
|
||||
@ -622,24 +643,24 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Data Source details based on data source device ID
|
||||
* Retrieves Data Source details based on data source object ID
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceDeviceId the data source device ID number
|
||||
* @param dataSourceObjectId the object id of the data source
|
||||
*
|
||||
* @return The data source
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException {
|
||||
public CorrelationDataSource getDataSource(CorrelationCase correlationCase, Long dataSourceObjectId) throws EamDbException {
|
||||
|
||||
if (correlationCase == null) {
|
||||
throw new EamDbException("Correlation case is null");
|
||||
}
|
||||
try {
|
||||
return dataSourceCacheByDeviceId.get(getDataSourceByDeviceIdCacheKey(correlationCase.getID(), dataSourceDeviceId), () -> getDataSourceFromCr(correlationCase, dataSourceDeviceId));
|
||||
return dataSourceCacheByDsObjectId.get(getDataSourceByDSObjectIdCacheKey(correlationCase.getID(), dataSourceObjectId), () -> getDataSourceFromCr(correlationCase, dataSourceObjectId));
|
||||
} catch (CacheLoader.InvalidCacheLoadException ignored) {
|
||||
//lambda valueloader returned a null value and cache can not store null values this is normal if the dataSource does not exist in the central repo yet
|
||||
return null;
|
||||
@ -654,24 +675,24 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceDeviceId the data source device ID number
|
||||
* @param dataSourceDeviceId the object id of the data source
|
||||
*
|
||||
* @return The data source
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
private CorrelationDataSource getDataSourceFromCr(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException {
|
||||
private CorrelationDataSource getDataSourceFromCr(CorrelationCase correlationCase, Long dataSourceObjectId) throws EamDbException {
|
||||
Connection conn = connect();
|
||||
|
||||
CorrelationDataSource eamDataSourceResult = null;
|
||||
PreparedStatement preparedStatement = null;
|
||||
ResultSet resultSet = null;
|
||||
|
||||
String sql = "SELECT * FROM data_sources WHERE device_id=? AND case_id=?"; // NON-NLS
|
||||
String sql = "SELECT * FROM data_sources WHERE datasource_obj_id=? AND case_id=?"; // NON-NLS
|
||||
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(sql);
|
||||
preparedStatement.setString(1, dataSourceDeviceId);
|
||||
preparedStatement.setLong(1, dataSourceObjectId);
|
||||
preparedStatement.setInt(2, correlationCase.getID());
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
@ -742,7 +763,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
eamDataSourceResult = getEamDataSourceFromResultSet(resultSet);
|
||||
}
|
||||
if (eamDataSourceResult != null) {
|
||||
dataSourceCacheByDeviceId.put(getDataSourceByDeviceIdCacheKey(correlationCase.getID(), eamDataSourceResult.getDeviceID()), eamDataSourceResult);
|
||||
dataSourceCacheByDsObjectId.put(getDataSourceByDSObjectIdCacheKey(correlationCase.getID(), eamDataSourceResult.getDataSourceObjectID()), eamDataSourceResult);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException("Error getting data source.", ex); // NON-NLS
|
||||
@ -808,27 +829,26 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
String sql
|
||||
= "INSERT INTO "
|
||||
+ tableName
|
||||
+ "(case_id, data_source_id, value, file_path, known_status, comment) "
|
||||
+ "VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), "
|
||||
+ "(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) "
|
||||
+ "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id) "
|
||||
+ "VALUES (?, ?, ?, ?, ?, ?, ?) "
|
||||
+ getConflictClause();
|
||||
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(sql);
|
||||
|
||||
if (!eamArtifact.getCorrelationValue().isEmpty()) {
|
||||
preparedStatement.setInt(1, eamArtifact.getCorrelationCase().getID());
|
||||
preparedStatement.setInt(2, eamArtifact.getCorrelationDataSource().getID());
|
||||
preparedStatement.setString(3, eamArtifact.getCorrelationValue());
|
||||
preparedStatement.setString(4, eamArtifact.getFilePath().toLowerCase());
|
||||
preparedStatement.setByte(5, eamArtifact.getKnownStatus().getFileKnownValue());
|
||||
|
||||
preparedStatement.setString(1, eamArtifact.getCorrelationCase().getCaseUUID());
|
||||
preparedStatement.setString(2, eamArtifact.getCorrelationDataSource().getDeviceID());
|
||||
preparedStatement.setInt(3, eamArtifact.getCorrelationDataSource().getCaseID());
|
||||
preparedStatement.setString(4, eamArtifact.getCorrelationValue());
|
||||
preparedStatement.setString(5, eamArtifact.getFilePath().toLowerCase());
|
||||
preparedStatement.setByte(6, eamArtifact.getKnownStatus().getFileKnownValue());
|
||||
if ("".equals(eamArtifact.getComment())) {
|
||||
preparedStatement.setNull(7, Types.INTEGER);
|
||||
preparedStatement.setNull(6, Types.INTEGER);
|
||||
} else {
|
||||
preparedStatement.setString(7, eamArtifact.getComment());
|
||||
preparedStatement.setString(6, eamArtifact.getComment());
|
||||
}
|
||||
preparedStatement.setLong(7, eamArtifact.getFileObjectId());
|
||||
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
@ -900,7 +920,9 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
+ ".id,"
|
||||
+ tableName
|
||||
+ ".value,"
|
||||
+ " cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "
|
||||
+ tableName
|
||||
+ ".file_obj_id,"
|
||||
+ " cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id, data_sources.datasource_obj_id FROM "
|
||||
+ tableName
|
||||
+ " LEFT JOIN cases ON "
|
||||
+ tableName
|
||||
@ -963,7 +985,9 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
+ ".id, "
|
||||
+ tableName
|
||||
+ ".value,"
|
||||
+ " cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "
|
||||
+ tableName
|
||||
+ ".file_obj_id,"
|
||||
+ " cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id, data_sources.datasource_obj_id FROM "
|
||||
+ tableName
|
||||
+ " LEFT JOIN cases ON "
|
||||
+ tableName
|
||||
@ -1133,7 +1157,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
* dataSource
|
||||
*/
|
||||
@Override
|
||||
public Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException {
|
||||
public Long getCountArtifactInstancesByCaseDataSource(CorrelationDataSource correlationDataSource) throws EamDbException {
|
||||
Connection conn = connect();
|
||||
|
||||
Long instanceCount = 0L;
|
||||
@ -1141,26 +1165,19 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
PreparedStatement preparedStatement = null;
|
||||
ResultSet resultSet = null;
|
||||
|
||||
// Figure out sql variables or subqueries
|
||||
//Create query to get count of all instances in the database for the specified case specific data source
|
||||
String sql = "SELECT 0 ";
|
||||
|
||||
for (CorrelationAttributeInstance.Type type : artifactTypes) {
|
||||
String table_name = EamDbUtil.correlationTypeToInstanceTableName(type);
|
||||
|
||||
sql
|
||||
+= "+ (SELECT count(*) FROM "
|
||||
+ table_name
|
||||
+ " WHERE data_source_id=(SELECT data_sources.id FROM cases INNER JOIN data_sources ON cases.id = data_sources.case_id WHERE case_uid=? and device_id=?))";
|
||||
+ " WHERE data_source_id=" + correlationDataSource.getID() + ")";
|
||||
}
|
||||
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(sql);
|
||||
|
||||
for (int i = 0; i < artifactTypes.size(); ++i) {
|
||||
preparedStatement.setString(2 * i + 1, caseUUID);
|
||||
preparedStatement.setString(2 * i + 2, dataSourceID);
|
||||
}
|
||||
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
resultSet.next();
|
||||
instanceCount = resultSet.getLong(1);
|
||||
@ -1190,7 +1207,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
}
|
||||
|
||||
synchronized (bulkArtifacts) {
|
||||
bulkArtifacts.get(eamArtifact.getCorrelationType().getDbTableName()).add(eamArtifact);
|
||||
bulkArtifacts.get(EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType())).add(eamArtifact);
|
||||
bulkArtifactsCount++;
|
||||
|
||||
if (bulkArtifactsCount >= bulkArtifactsThreshold) {
|
||||
@ -1223,20 +1240,19 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CorrelationAttributeInstance.Type type : artifactTypes) {
|
||||
for (String tableName : bulkArtifacts.keySet()) {
|
||||
|
||||
String tableName = EamDbUtil.correlationTypeToInstanceTableName(type);
|
||||
String sql
|
||||
= "INSERT INTO "
|
||||
+ tableName
|
||||
+ " (case_id, data_source_id, value, file_path, known_status, comment) "
|
||||
+ " (case_id, data_source_id, value, file_path, known_status, comment, file_obj_id) "
|
||||
+ "VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), "
|
||||
+ "(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) "
|
||||
+ "(SELECT id FROM data_sources WHERE datasource_obj_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?, ?) "
|
||||
+ getConflictClause();
|
||||
|
||||
bulkPs = conn.prepareStatement(sql);
|
||||
|
||||
Collection<CorrelationAttributeInstance> eamArtifacts = bulkArtifacts.get(type.getDbTableName());
|
||||
Collection<CorrelationAttributeInstance> eamArtifacts = bulkArtifacts.get(tableName);
|
||||
for (CorrelationAttributeInstance eamArtifact : eamArtifacts) {
|
||||
|
||||
if (!eamArtifact.getCorrelationValue().isEmpty()) {
|
||||
@ -1265,7 +1281,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
if (eamArtifact.getCorrelationValue().length() < MAX_VALUE_LENGTH) {
|
||||
bulkPs.setString(1, eamArtifact.getCorrelationCase().getCaseUUID());
|
||||
bulkPs.setString(2, eamArtifact.getCorrelationDataSource().getDeviceID());
|
||||
bulkPs.setLong(2, eamArtifact.getCorrelationDataSource().getDataSourceObjectID());
|
||||
bulkPs.setInt(3, eamArtifact.getCorrelationDataSource().getCaseID());
|
||||
bulkPs.setString(4, eamArtifact.getCorrelationValue());
|
||||
bulkPs.setString(5, eamArtifact.getFilePath());
|
||||
@ -1275,6 +1291,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
} else {
|
||||
bulkPs.setString(7, eamArtifact.getComment());
|
||||
}
|
||||
bulkPs.setLong(8, eamArtifact.getFileObjectId());
|
||||
bulkPs.addBatch();
|
||||
} else {
|
||||
logger.log(Level.WARNING, ("Artifact value too long for central repository."
|
||||
@ -1291,7 +1308,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
}
|
||||
|
||||
bulkPs.executeBatch();
|
||||
bulkArtifacts.get(type.getDbTableName()).clear();
|
||||
bulkArtifacts.get(tableName).clear();
|
||||
}
|
||||
|
||||
TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Bulk insert");
|
||||
@ -1409,25 +1426,23 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
if (eamArtifact.getCorrelationDataSource() == null) {
|
||||
throw new EamDbException("Correlation data source is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
PreparedStatement preparedQuery = null;
|
||||
String tableName = EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType());
|
||||
|
||||
String sqlUpdate
|
||||
= "UPDATE "
|
||||
+ tableName
|
||||
+ " SET comment=? "
|
||||
+ "WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) "
|
||||
+ "AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) "
|
||||
+ "WHERE case_id=? "
|
||||
+ "AND data_source_id=? "
|
||||
+ "AND value=? "
|
||||
+ "AND file_path=?";
|
||||
|
||||
try {
|
||||
preparedQuery = conn.prepareStatement(sqlUpdate);
|
||||
preparedQuery.setString(1, eamArtifact.getComment());
|
||||
preparedQuery.setString(2, eamArtifact.getCorrelationCase().getCaseUUID());
|
||||
preparedQuery.setString(3, eamArtifact.getCorrelationDataSource().getDeviceID());
|
||||
preparedQuery.setInt(2, eamArtifact.getCorrelationCase().getID());
|
||||
preparedQuery.setInt(3, eamArtifact.getCorrelationDataSource().getID());
|
||||
preparedQuery.setString(4, eamArtifact.getCorrelationValue());
|
||||
preparedQuery.setString(5, eamArtifact.getFilePath().toLowerCase());
|
||||
preparedQuery.executeUpdate();
|
||||
@ -1439,6 +1454,68 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a correlation attribute in the Central Repository database given the
|
||||
* instance type, case, data source, object id.
|
||||
*
|
||||
* @param type The type of instance.
|
||||
* @param correlationCase The case tied to the instance.
|
||||
* @param correlationDataSource The data source tied to the instance.
|
||||
* @param objectID The object id of the file tied to the
|
||||
* instance.
|
||||
*
|
||||
* @return The correlation attribute if it exists; otherwise null.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
public CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttributeInstance.Type type, CorrelationCase correlationCase,
|
||||
CorrelationDataSource correlationDataSource, long objectID) throws EamDbException, CorrelationAttributeNormalizationException {
|
||||
|
||||
if (correlationCase == null) {
|
||||
throw new EamDbException("Correlation case is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement preparedStatement = null;
|
||||
ResultSet resultSet = null;
|
||||
CorrelationAttributeInstance correlationAttributeInstance = null;
|
||||
|
||||
try {
|
||||
|
||||
String tableName = EamDbUtil.correlationTypeToInstanceTableName(type);
|
||||
String sql
|
||||
= "SELECT id, value, file_path, known_status, comment FROM "
|
||||
+ tableName
|
||||
+ " WHERE case_id=?"
|
||||
+ " AND file_obj_id=?";
|
||||
|
||||
preparedStatement = conn.prepareStatement(sql);
|
||||
preparedStatement.setInt(1, correlationCase.getID());
|
||||
preparedStatement.setInt(2, (int) objectID);
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
int instanceId = resultSet.getInt(1);
|
||||
String value = resultSet.getString(2);
|
||||
String filePath = resultSet.getString(3);
|
||||
int knownStatus = resultSet.getInt(4);
|
||||
String comment = resultSet.getString(5);
|
||||
|
||||
correlationAttributeInstance = new CorrelationAttributeInstance(type, value,
|
||||
instanceId, correlationCase, correlationDataSource, filePath, comment, TskData.FileKnown.valueOf((byte) knownStatus), objectID);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS
|
||||
} finally {
|
||||
EamDbUtil.closeStatement(preparedStatement);
|
||||
EamDbUtil.closeResultSet(resultSet);
|
||||
EamDbUtil.closeConnection(conn);
|
||||
}
|
||||
|
||||
return correlationAttributeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a correlation attribute in the Central Repository database given the
|
||||
* instance type, case, data source, value, and file path.
|
||||
@ -1495,9 +1572,9 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
int instanceId = resultSet.getInt(1);
|
||||
int knownStatus = resultSet.getInt(2);
|
||||
String comment = resultSet.getString(3);
|
||||
|
||||
//null objectId used because we only fall back to using this method when objectID was not available
|
||||
correlationAttributeInstance = new CorrelationAttributeInstance(type, value,
|
||||
instanceId, correlationCase, correlationDataSource, filePath, comment, TskData.FileKnown.valueOf((byte) knownStatus));
|
||||
instanceId, correlationCase, correlationDataSource, filePath, comment, TskData.FileKnown.valueOf((byte) knownStatus), null);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS
|
||||
@ -1547,8 +1624,8 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
String sqlQuery
|
||||
= "SELECT id FROM "
|
||||
+ tableName
|
||||
+ " WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) "
|
||||
+ "AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) "
|
||||
+ " WHERE case_id=? "
|
||||
+ "AND data_source_id=? "
|
||||
+ "AND value=? "
|
||||
+ "AND file_path=?";
|
||||
|
||||
@ -1560,8 +1637,8 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
try {
|
||||
preparedQuery = conn.prepareStatement(sqlQuery);
|
||||
preparedQuery.setString(1, eamArtifact.getCorrelationCase().getCaseUUID());
|
||||
preparedQuery.setString(2, eamArtifact.getCorrelationDataSource().getDeviceID());
|
||||
preparedQuery.setInt(1, eamArtifact.getCorrelationCase().getID());
|
||||
preparedQuery.setInt(2, eamArtifact.getCorrelationDataSource().getID());
|
||||
preparedQuery.setString(3, eamArtifact.getCorrelationValue());
|
||||
preparedQuery.setString(4, eamArtifact.getFilePath());
|
||||
resultSet = preparedQuery.executeQuery();
|
||||
@ -1591,8 +1668,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
if (null == correlationCaseWithId) {
|
||||
correlationCaseWithId = newCase(eamArtifact.getCorrelationCase());
|
||||
}
|
||||
|
||||
if (null == getDataSource(correlationCaseWithId, eamArtifact.getCorrelationDataSource().getDeviceID())) {
|
||||
if (null == getDataSource(correlationCaseWithId, eamArtifact.getCorrelationDataSource().getDataSourceObjectID())) {
|
||||
newDataSource(eamArtifact.getCorrelationDataSource());
|
||||
}
|
||||
eamArtifact.setKnownStatus(knownStatus);
|
||||
@ -1637,7 +1713,9 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
+ ".id, "
|
||||
+ tableName
|
||||
+ ".value, "
|
||||
+ "cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "
|
||||
+ tableName
|
||||
+ ".file_obj_id,"
|
||||
+ "cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id, data_sources.datasource_obj_id FROM "
|
||||
+ tableName
|
||||
+ " LEFT JOIN cases ON "
|
||||
+ tableName
|
||||
@ -1694,7 +1772,7 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType);
|
||||
String sql
|
||||
= "SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id, id, value FROM "
|
||||
= "SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id, id, value, file_obj_id, data_sources.datasource_obj_id FROM "
|
||||
+ tableName
|
||||
+ " LEFT JOIN cases ON "
|
||||
+ tableName
|
||||
@ -2976,7 +3054,8 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
resultSet.getInt("case_id"),
|
||||
resultSet.getInt("id"),
|
||||
resultSet.getString("device_id"),
|
||||
resultSet.getString("name")
|
||||
resultSet.getString("name"),
|
||||
resultSet.getLong("datasource_obj_id")
|
||||
);
|
||||
|
||||
return eamDataSource;
|
||||
@ -3017,11 +3096,11 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
resultSet.getString("value"),
|
||||
resultSet.getInt("id"),
|
||||
new CorrelationCase(resultSet.getInt("case_id"), resultSet.getString("case_uid"), resultSet.getString("case_name")),
|
||||
new CorrelationDataSource(resultSet.getInt("case_id"), resultSet.getInt("data_source_id"), resultSet.getString("device_id"), resultSet.getString("name")),
|
||||
new CorrelationDataSource(resultSet.getInt("case_id"), resultSet.getInt("data_source_id"), resultSet.getString("device_id"), resultSet.getString("name"), resultSet.getLong("datasource_obj_id")),
|
||||
resultSet.getString("file_path"),
|
||||
resultSet.getString("comment"),
|
||||
TskData.FileKnown.valueOf(resultSet.getByte("known_status"))
|
||||
);
|
||||
TskData.FileKnown.valueOf(resultSet.getByte("known_status")),
|
||||
resultSet.getLong("file_obj_id"));
|
||||
}
|
||||
|
||||
private EamOrganization getEamOrganizationFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
@ -3069,6 +3148,18 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a specific column already exists in a specific table
|
||||
*
|
||||
* @param tableName the table to check for the specified column
|
||||
* @param columnName the name of the column to check for
|
||||
*
|
||||
* @return true if the column exists, false if the column does not exist
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
abstract boolean doesColumnExist(Connection conn, String tableName, String columnName) throws SQLException;
|
||||
|
||||
/**
|
||||
* Upgrade the schema of the database (if needed)
|
||||
*
|
||||
@ -3132,53 +3223,125 @@ abstract class AbstractSqlEamDb implements EamDb {
|
||||
}
|
||||
//Update to 1.2
|
||||
if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 2)) < 0) {
|
||||
//update central repository to be able to store new correlation attributes
|
||||
EamDbPlatformEnum selectedPlatform = EamDbPlatformEnum.getSelectedPlatform();
|
||||
final String addIntegerColumnTemplate = "ALTER TABLE %s ADD COLUMN %s INTEGER;"; //NON-NLS
|
||||
final String addSsidTableTemplate;
|
||||
final String addCaseIdIndexTemplate;
|
||||
final String addDataSourceIdIndexTemplate;
|
||||
final String addValueIndexTemplate;
|
||||
final String addKnownStatusIndexTemplate;
|
||||
final String addObjectIdIndexTemplate;
|
||||
|
||||
final String addAttributeSql;
|
||||
//get the data base specific code for creating a new _instance table
|
||||
switch (selectedPlatform) {
|
||||
case POSTGRESQL:
|
||||
addAttributeSql = "INSERT INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?) " + getConflictClause();
|
||||
addAttributeSql = "INSERT INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?) " + getConflictClause(); //NON-NLS
|
||||
|
||||
addSsidTableTemplate = PostgresEamDbSettings.getCreateArtifactInstancesTableTemplate();
|
||||
addCaseIdIndexTemplate = PostgresEamDbSettings.getAddCaseIdIndexTemplate();
|
||||
addDataSourceIdIndexTemplate = PostgresEamDbSettings.getAddDataSourceIdIndexTemplate();
|
||||
addValueIndexTemplate = PostgresEamDbSettings.getAddValueIndexTemplate();
|
||||
addKnownStatusIndexTemplate = PostgresEamDbSettings.getAddKnownStatusIndexTemplate();
|
||||
addObjectIdIndexTemplate = PostgresEamDbSettings.getAddObjectIdIndexTemplate();
|
||||
break;
|
||||
case SQLITE:
|
||||
addAttributeSql = "INSERT OR IGNORE INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?)";
|
||||
addAttributeSql = "INSERT OR IGNORE INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?)"; //NON-NLS
|
||||
|
||||
addSsidTableTemplate = SqliteEamDbSettings.getCreateArtifactInstancesTableTemplate();
|
||||
addCaseIdIndexTemplate = SqliteEamDbSettings.getAddCaseIdIndexTemplate();
|
||||
addDataSourceIdIndexTemplate = SqliteEamDbSettings.getAddDataSourceIdIndexTemplate();
|
||||
addValueIndexTemplate = SqliteEamDbSettings.getAddValueIndexTemplate();
|
||||
addKnownStatusIndexTemplate = SqliteEamDbSettings.getAddKnownStatusIndexTemplate();
|
||||
addObjectIdIndexTemplate = SqliteEamDbSettings.getAddObjectIdIndexTemplate();
|
||||
break;
|
||||
default:
|
||||
throw new EamDbException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.");
|
||||
}
|
||||
final String wirelessNetworsDbTableName = "wireless_networks";
|
||||
final String wirelessNetworksTableInstanceName = wirelessNetworsDbTableName + "_instances";
|
||||
final String dataSourcesTableName = "data_sources";
|
||||
final String dataSourceObjectIdColumnName = "datasource_obj_id";
|
||||
if (!doesColumnExist(conn, dataSourcesTableName, dataSourceObjectIdColumnName)) {
|
||||
statement.execute(String.format(addIntegerColumnTemplate, dataSourcesTableName, dataSourceObjectIdColumnName)); //NON-NLS
|
||||
}
|
||||
final String dataSourceObjectIdIndexTemplate = "CREATE INDEX IF NOT EXISTS datasource_object_id ON data_sources (%s)";
|
||||
statement.execute(String.format(dataSourceObjectIdIndexTemplate, dataSourceObjectIdColumnName));
|
||||
List<String> instaceTablesToAdd = new ArrayList<>();
|
||||
//update central repository to be able to store new correlation attributes
|
||||
final String wirelessNetworksDbTableName = "wireless_networks";
|
||||
instaceTablesToAdd.add(wirelessNetworksDbTableName + "_instances");
|
||||
final String macAddressDbTableName = "mac_address";
|
||||
instaceTablesToAdd.add(macAddressDbTableName + "_instances");
|
||||
final String imeiNumberDbTableName = "imei_number";
|
||||
instaceTablesToAdd.add(imeiNumberDbTableName + "_instances");
|
||||
final String iccidNumberDbTableName = "iccid_number";
|
||||
instaceTablesToAdd.add(iccidNumberDbTableName + "_instances");
|
||||
final String imsiNumberDbTableName = "imsi_number";
|
||||
instaceTablesToAdd.add(imsiNumberDbTableName + "_instances");
|
||||
|
||||
//add the wireless_networks attribute to the correlation_types table
|
||||
preparedStatement = conn.prepareStatement(addAttributeSql);
|
||||
preparedStatement.setInt(1, CorrelationAttributeInstance.SSID_TYPE_ID);
|
||||
preparedStatement.setString(2, Bundle.CorrelationType_SSID_displayName());
|
||||
preparedStatement.setString(3, wirelessNetworsDbTableName);
|
||||
preparedStatement.setString(3, wirelessNetworksDbTableName);
|
||||
preparedStatement.setInt(4, 1);
|
||||
preparedStatement.setInt(5, 1);
|
||||
preparedStatement.execute();
|
||||
|
||||
//create a new wireless_networks_instances table and add indexes for its columns
|
||||
statement.execute(String.format(addSsidTableTemplate, wirelessNetworksTableInstanceName, wirelessNetworksTableInstanceName));
|
||||
statement.execute(String.format(addCaseIdIndexTemplate, wirelessNetworksTableInstanceName, wirelessNetworksTableInstanceName));
|
||||
statement.execute(String.format(addDataSourceIdIndexTemplate, wirelessNetworksTableInstanceName, wirelessNetworksTableInstanceName));
|
||||
statement.execute(String.format(addValueIndexTemplate, wirelessNetworksTableInstanceName, wirelessNetworksTableInstanceName));
|
||||
statement.execute(String.format(addKnownStatusIndexTemplate, wirelessNetworksTableInstanceName, wirelessNetworksTableInstanceName));
|
||||
//add the mac_address attribute to the correlation_types table
|
||||
preparedStatement = conn.prepareStatement(addAttributeSql);
|
||||
preparedStatement.setInt(1, CorrelationAttributeInstance.MAC_TYPE_ID);
|
||||
preparedStatement.setString(2, Bundle.CorrelationType_MAC_displayName());
|
||||
preparedStatement.setString(3, macAddressDbTableName);
|
||||
preparedStatement.setInt(4, 1);
|
||||
preparedStatement.setInt(5, 1);
|
||||
preparedStatement.execute();
|
||||
|
||||
//add the imei_number attribute to the correlation_types table
|
||||
preparedStatement = conn.prepareStatement(addAttributeSql);
|
||||
preparedStatement.setInt(1, CorrelationAttributeInstance.IMEI_TYPE_ID);
|
||||
preparedStatement.setString(2, Bundle.CorrelationType_IMEI_displayName());
|
||||
preparedStatement.setString(3, imeiNumberDbTableName);
|
||||
preparedStatement.setInt(4, 1);
|
||||
preparedStatement.setInt(5, 1);
|
||||
preparedStatement.execute();
|
||||
|
||||
//add the imsi_number attribute to the correlation_types table
|
||||
preparedStatement = conn.prepareStatement(addAttributeSql);
|
||||
preparedStatement.setInt(1, CorrelationAttributeInstance.IMSI_TYPE_ID);
|
||||
preparedStatement.setString(2, Bundle.CorrelationType_IMSI_displayName());
|
||||
preparedStatement.setString(3, imsiNumberDbTableName);
|
||||
preparedStatement.setInt(4, 1);
|
||||
preparedStatement.setInt(5, 1);
|
||||
preparedStatement.execute();
|
||||
|
||||
//add the iccid_number attribute to the correlation_types table
|
||||
preparedStatement = conn.prepareStatement(addAttributeSql);
|
||||
preparedStatement.setInt(1, CorrelationAttributeInstance.ICCID_TYPE_ID);
|
||||
preparedStatement.setString(2, Bundle.CorrelationType_ICCID_displayName());
|
||||
preparedStatement.setString(3, iccidNumberDbTableName);
|
||||
preparedStatement.setInt(4, 1);
|
||||
preparedStatement.setInt(5, 1);
|
||||
preparedStatement.execute();
|
||||
|
||||
//create a new _instances tables and add indexes for their columns
|
||||
for (String tableName : instaceTablesToAdd) {
|
||||
statement.execute(String.format(addSsidTableTemplate, tableName, tableName));
|
||||
statement.execute(String.format(addCaseIdIndexTemplate, tableName, tableName));
|
||||
statement.execute(String.format(addDataSourceIdIndexTemplate, tableName, tableName));
|
||||
statement.execute(String.format(addValueIndexTemplate, tableName, tableName));
|
||||
statement.execute(String.format(addKnownStatusIndexTemplate, tableName, tableName));
|
||||
}
|
||||
|
||||
//add file_obj_id column to _instances table which do not already have it
|
||||
String instance_type_dbname;
|
||||
final String objectIdColumnName = "file_obj_id";
|
||||
for (CorrelationAttributeInstance.Type type : CorrelationAttributeInstance.getDefaultCorrelationTypes()) {
|
||||
instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
|
||||
if (!doesColumnExist(conn, instance_type_dbname, objectIdColumnName)) {
|
||||
statement.execute(String.format(addIntegerColumnTemplate, instance_type_dbname, objectIdColumnName)); //NON-NLS
|
||||
}
|
||||
statement.execute(String.format(addObjectIdIndexTemplate, instance_type_dbname, instance_type_dbname));
|
||||
}
|
||||
}
|
||||
if (!updateSchemaVersion(conn)) {
|
||||
throw new EamDbException("Error updating schema version");
|
||||
|
@ -48,46 +48,18 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
private String filePath;
|
||||
private String comment;
|
||||
private TskData.FileKnown knownStatus;
|
||||
private Long objectId;
|
||||
|
||||
public CorrelationAttributeInstance(
|
||||
String correlationValue,
|
||||
CorrelationAttributeInstance.Type correlationType,
|
||||
CorrelationCase eamCase,
|
||||
CorrelationDataSource eamDataSource,
|
||||
String filePath
|
||||
) throws EamDbException, CorrelationAttributeNormalizationException {
|
||||
this(correlationType, correlationValue, -1, eamCase, eamDataSource, filePath, null, TskData.FileKnown.UNKNOWN);
|
||||
}
|
||||
|
||||
public CorrelationAttributeInstance(
|
||||
String correlationValue,
|
||||
CorrelationAttributeInstance.Type correlationType,
|
||||
CorrelationCase eamCase,
|
||||
CorrelationDataSource eamDataSource,
|
||||
String filePath,
|
||||
String comment,
|
||||
TskData.FileKnown knownStatus
|
||||
) throws EamDbException, CorrelationAttributeNormalizationException {
|
||||
this(correlationType, correlationValue, -1, eamCase, eamDataSource, filePath, comment, knownStatus);
|
||||
}
|
||||
|
||||
public CorrelationAttributeInstance(
|
||||
Type correlationType,
|
||||
String correlationValue,
|
||||
CorrelationCase correlationCase,
|
||||
CorrelationDataSource fromTSKDataSource,
|
||||
String string) throws EamDbException, CorrelationAttributeNormalizationException {
|
||||
this(correlationType, correlationValue, -1, correlationCase, fromTSKDataSource, string, "", TskData.FileKnown.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Only used for when EamDB is NOT enabled.
|
||||
*
|
||||
* @param aType CorrelationAttributeInstance.Type
|
||||
* @param value correlation value
|
||||
*/
|
||||
public CorrelationAttributeInstance(Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
|
||||
this(aType, value, -1, null, null, "", "", TskData.FileKnown.UNKNOWN);
|
||||
TskData.FileKnown knownStatus,
|
||||
long fileObjectId) throws EamDbException, CorrelationAttributeNormalizationException {
|
||||
this(correlationType, correlationValue, -1, eamCase, eamDataSource, filePath, comment, knownStatus, fileObjectId);
|
||||
}
|
||||
|
||||
CorrelationAttributeInstance(
|
||||
@ -98,7 +70,8 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
CorrelationDataSource eamDataSource,
|
||||
String filePath,
|
||||
String comment,
|
||||
TskData.FileKnown knownStatus
|
||||
TskData.FileKnown knownStatus,
|
||||
Long fileObjectId
|
||||
) throws EamDbException, CorrelationAttributeNormalizationException {
|
||||
if (filePath == null) {
|
||||
throw new EamDbException("file path is null");
|
||||
@ -113,6 +86,7 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
this.filePath = filePath.toLowerCase();
|
||||
this.comment = comment;
|
||||
this.knownStatus = knownStatus;
|
||||
this.objectId = fileObjectId;
|
||||
}
|
||||
|
||||
public Boolean equals(CorrelationAttributeInstance otherInstance) {
|
||||
@ -145,14 +119,6 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
return correlationValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param correlationValue the correlationValue to set
|
||||
*/
|
||||
public void setCorrelationValue(String correlationValue) {
|
||||
// Lower-case all values to normalize and improve correlation hits, going forward make sure this makes sense for all correlation types
|
||||
this.correlationValue = correlationValue.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the correlation Type
|
||||
*/
|
||||
@ -160,13 +126,6 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
return correlationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param correlationType the correlation Type to set
|
||||
*/
|
||||
public void setCorrelationType(Type correlationType) {
|
||||
this.correlationType = correlationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a database instance?
|
||||
*
|
||||
@ -240,6 +199,16 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
this.knownStatus = knownStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the objectId of the file associated with the correlation attribute or
|
||||
* NULL if the objectId is not available.
|
||||
*
|
||||
* @return the objectId of the file
|
||||
*/
|
||||
public Long getFileObjectId() {
|
||||
return objectId;
|
||||
}
|
||||
|
||||
// Type ID's for Default Correlation Types
|
||||
public static final int FILES_TYPE_ID = 0;
|
||||
public static final int DOMAIN_TYPE_ID = 1;
|
||||
@ -247,6 +216,10 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
public static final int PHONE_TYPE_ID = 3;
|
||||
public static final int USBID_TYPE_ID = 4;
|
||||
public static final int SSID_TYPE_ID = 5;
|
||||
public static final int MAC_TYPE_ID = 6;
|
||||
public static final int IMEI_TYPE_ID = 7;
|
||||
public static final int IMSI_TYPE_ID = 8;
|
||||
public static final int ICCID_TYPE_ID = 9;
|
||||
|
||||
/**
|
||||
* Load the default correlation types
|
||||
@ -259,7 +232,11 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
"CorrelationType.EMAIL.displayName=Email Addresses",
|
||||
"CorrelationType.PHONE.displayName=Phone Numbers",
|
||||
"CorrelationType.USBID.displayName=USB Devices",
|
||||
"CorrelationType.SSID.displayName=Wireless Networks"})
|
||||
"CorrelationType.SSID.displayName=Wireless Networks",
|
||||
"CorrelationType.MAC.displayName=MAC Addresses",
|
||||
"CorrelationType.IMEI.displayName=IMEI Number",
|
||||
"CorrelationType.IMSI.displayName=IMSI Number",
|
||||
"CorrelationType.ICCID.displayName=ICCID Number"})
|
||||
public static List<CorrelationAttributeInstance.Type> getDefaultCorrelationTypes() throws EamDbException {
|
||||
List<CorrelationAttributeInstance.Type> DEFAULT_CORRELATION_TYPES = new ArrayList<>();
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(FILES_TYPE_ID, Bundle.CorrelationType_FILES_displayName(), "file", true, true)); // NON-NLS
|
||||
@ -268,6 +245,10 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(PHONE_TYPE_ID, Bundle.CorrelationType_PHONE_displayName(), "phone_number", true, true)); // NON-NLS
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(USBID_TYPE_ID, Bundle.CorrelationType_USBID_displayName(), "usb_devices", true, true)); // NON-NLS
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(SSID_TYPE_ID, Bundle.CorrelationType_SSID_displayName(), "wireless_networks", true, true)); // NON-NLS
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(MAC_TYPE_ID, Bundle.CorrelationType_MAC_displayName(), "mac_address", true, true)); //NON-NLS
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(IMEI_TYPE_ID, Bundle.CorrelationType_IMEI_displayName(), "imei_number", true, true)); //NON-NLS
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(IMSI_TYPE_ID, Bundle.CorrelationType_IMSI_displayName(), "imsi_number", true, true)); //NON-NLS
|
||||
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(ICCID_TYPE_ID, Bundle.CorrelationType_ICCID_displayName(), "iccid_number", true, true)); //NON-NLS
|
||||
return DEFAULT_CORRELATION_TYPES;
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,14 @@ final public class CorrelationAttributeNormalizer {
|
||||
return normalizeUsbId(data);
|
||||
case CorrelationAttributeInstance.SSID_TYPE_ID:
|
||||
return data;
|
||||
case CorrelationAttributeInstance.MAC_TYPE_ID:
|
||||
return data;
|
||||
case CorrelationAttributeInstance.IMEI_TYPE_ID:
|
||||
return data;
|
||||
case CorrelationAttributeInstance.IMSI_TYPE_ID:
|
||||
return data;
|
||||
case CorrelationAttributeInstance.ICCID_TYPE_ID:
|
||||
return data;
|
||||
default:
|
||||
final String errorMessage = String.format(
|
||||
"Validator function not found for attribute type: %s",
|
||||
|
@ -36,38 +36,49 @@ public class CorrelationDataSource implements Serializable {
|
||||
|
||||
private final int caseID; //the value in the id column of the case table in the central repo
|
||||
private final int dataSourceID; //< Id in the central repo
|
||||
private final Long dataSourceObjectID; //< Id for data source in the caseDB
|
||||
private final String deviceID; //< Unique to its associated case (not necessarily globally unique)
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* @param correlationCase CorrelationCase object data source is associated with. Must have been created by EamDB and have a valid ID.
|
||||
* @param deviceId User specified case-specific ID
|
||||
* @param name Display name of data source
|
||||
* Create a CorrelationDataSource object, the object will not have the data
|
||||
* source id for the row in the central repository.
|
||||
*
|
||||
* @param correlationCase CorrelationCase object data source is
|
||||
* associated with. Must have been created by
|
||||
* EamDB and have a valid ID.
|
||||
* @param deviceId User specified case-specific ID
|
||||
* @param name Display name of data source
|
||||
* @param dataSourceObjectId The object ID for the datasource
|
||||
*/
|
||||
public CorrelationDataSource(CorrelationCase correlationCase, String deviceId, String name) {
|
||||
this(correlationCase.getID(), -1, deviceId, name);
|
||||
}
|
||||
|
||||
public CorrelationDataSource(CorrelationCase correlationCase, String deviceId, String name, long dataSourceObjectId) {
|
||||
this(correlationCase.getID(), -1, deviceId, name, dataSourceObjectId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param caseId Row ID for Case in DB
|
||||
* @param dataSourceId Row ID for this data source in DB (or -1)
|
||||
* @param deviceId User specified ID for device (unique per case)
|
||||
* @param name User specified name
|
||||
* Create a CorrelationDataSource object.
|
||||
*
|
||||
* @param caseId Row ID for Case in DB
|
||||
* @param dataSourceId Row ID for this data source in DB (or -1)
|
||||
* @param deviceId User specified ID for device (unique per case)
|
||||
* @param name User specified name
|
||||
* @param dataSourceObjectId The object ID for the datasource
|
||||
*/
|
||||
CorrelationDataSource(int caseId,
|
||||
int dataSourceId,
|
||||
String deviceId,
|
||||
String name) {
|
||||
String name,
|
||||
Long dataSourceObjectId) {
|
||||
this.caseID = caseId;
|
||||
this.dataSourceID = dataSourceId;
|
||||
this.deviceID = deviceId;
|
||||
this.name = name;
|
||||
this.dataSourceObjectID = dataSourceObjectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CorrelationDataSource object from a TSK Content object.
|
||||
* This will add it to the central repository.
|
||||
* Create a CorrelationDataSource object from a TSK Content object. This
|
||||
* will add it to the central repository.
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
@ -85,21 +96,24 @@ public class CorrelationDataSource implements Serializable {
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
throw new EamDbException("Autopsy case is closed");
|
||||
}
|
||||
String deviceId;
|
||||
try {
|
||||
deviceId = curCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId();
|
||||
} catch (TskDataException | TskCoreException ex) {
|
||||
throw new EamDbException("Error getting data source info: " + ex.getMessage());
|
||||
}
|
||||
|
||||
|
||||
CorrelationDataSource correlationDataSource = null;
|
||||
if (EamDbUtil.useCentralRepo()) {
|
||||
correlationDataSource = EamDb.getInstance().getDataSource(correlationCase, deviceId);
|
||||
boolean useCR = EamDbUtil.useCentralRepo();
|
||||
if (useCR) {
|
||||
correlationDataSource = EamDb.getInstance().getDataSource(correlationCase, dataSource.getId());
|
||||
}
|
||||
|
||||
if (correlationDataSource == null) {
|
||||
correlationDataSource = new CorrelationDataSource(correlationCase, deviceId, dataSource.getName());
|
||||
if (EamDbUtil.useCentralRepo()) {
|
||||
EamDb.getInstance().newDataSource(correlationDataSource);
|
||||
String deviceId;
|
||||
try {
|
||||
deviceId = curCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId();
|
||||
} catch (TskDataException | TskCoreException ex) {
|
||||
throw new EamDbException("Error getting data source info: " + ex.getMessage());
|
||||
}
|
||||
correlationDataSource = new CorrelationDataSource(correlationCase, deviceId, dataSource.getName(), dataSource.getId());
|
||||
if (useCR) {
|
||||
//add the correlation data source to the central repository and fill in the Central repository data source id in the object
|
||||
correlationDataSource = EamDb.getInstance().newDataSource(correlationDataSource);
|
||||
}
|
||||
}
|
||||
return correlationDataSource;
|
||||
@ -144,6 +158,15 @@ public class CorrelationDataSource implements Serializable {
|
||||
return caseID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object id for the data source in the case db
|
||||
*
|
||||
* @return dataSourceObjectID or NULL if not available
|
||||
*/
|
||||
public Long getDataSourceObjectID() {
|
||||
return dataSourceObjectID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2018 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.centralrepository.datamodel;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.appservices.AutopsyService;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Class which updates the data sources in the central repository to include the
|
||||
* object id which ties them to the current case.
|
||||
*
|
||||
*/
|
||||
@ServiceProvider(service = AutopsyService.class)
|
||||
public class DataSourceUpdateService implements AutopsyService {
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({"DataSourceUpdateService.serviceName.text=Update Central Repository Data Sources"})
|
||||
public String getServiceName() {
|
||||
return Bundle.DataSourceUpdateService_serviceName_text();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openCaseResources(CaseContext context) throws AutopsyServiceException {
|
||||
if (EamDb.isEnabled()) {
|
||||
try {
|
||||
EamDb centralRepository = EamDb.getInstance();
|
||||
CorrelationCase correlationCase = centralRepository.getCase(context.getCase());
|
||||
//if the case isn't in the central repository yet there won't be data sources in it to update
|
||||
if (correlationCase != null) {
|
||||
for (CorrelationDataSource correlationDataSource : centralRepository.getDataSources()) {
|
||||
//ResultSet.getLong has a value of 0 when the value is null
|
||||
if (correlationDataSource.getCaseID() == correlationCase.getID() && correlationDataSource.getDataSourceObjectID() == 0) {
|
||||
for (Content dataSource : context.getCase().getDataSources()) {
|
||||
if (((DataSource) dataSource).getDeviceId().equals(correlationDataSource.getDeviceID())) {
|
||||
centralRepository.addDataSourceObjectId(correlationDataSource.getID(), dataSource.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (EamDbException | TskCoreException ex) {
|
||||
throw new AutopsyServiceException("Unabe to update datasources in central repository", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.HashUtility;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -54,119 +55,108 @@ public class EamArtifactUtil {
|
||||
* EamArtifact with a single EamArtifactInstance within. If not, return
|
||||
* null.
|
||||
*
|
||||
* @param bbArtifact BlackboardArtifact to examine
|
||||
* @param artifact BlackboardArtifact to examine
|
||||
* @param checkEnabled If true, only create a CorrelationAttribute if it is
|
||||
* enabled
|
||||
*
|
||||
* @return List of EamArtifacts
|
||||
*/
|
||||
public static List<CorrelationAttributeInstance> makeInstancesFromBlackboardArtifact(BlackboardArtifact bbArtifact,
|
||||
public static List<CorrelationAttributeInstance> makeInstancesFromBlackboardArtifact(BlackboardArtifact artifact,
|
||||
boolean checkEnabled) {
|
||||
|
||||
List<CorrelationAttributeInstance> eamArtifacts = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// Cycle through the types and see if there is a correlation attribute that works
|
||||
// for the given blackboard artifact
|
||||
//
|
||||
// @@@ This seems ineffecient. Instead of cycling based on correlation type, we should just
|
||||
// have switch based on artifact type
|
||||
for (CorrelationAttributeInstance.Type aType : EamDb.getInstance().getDefinedCorrelationTypes()) {
|
||||
if ((checkEnabled && aType.isEnabled()) || !checkEnabled) {
|
||||
// Now always adds the instance details associated with this occurance.
|
||||
CorrelationAttributeInstance correlationAttribute = EamArtifactUtil.makeInstanceFromBlackboardArtifact(aType, bbArtifact);
|
||||
if (correlationAttribute != null) {
|
||||
eamArtifacts.add(correlationAttribute);
|
||||
BlackboardArtifact artifactForInstance = null;
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifact.getArtifactTypeID()) {
|
||||
// Get the associated artifactForInstance
|
||||
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
|
||||
if (attribute != null) {
|
||||
artifactForInstance = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong());
|
||||
}
|
||||
} else {
|
||||
artifactForInstance = artifact;
|
||||
}
|
||||
if (artifactForInstance != null) {
|
||||
switch (BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactForInstance.getArtifactTypeID())) {
|
||||
case TSK_KEYWORD_HIT: {
|
||||
BlackboardAttribute setNameAttr = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||
if (setNameAttr != null
|
||||
&& EamArtifactUtil.getEmailAddressAttrString().equals(setNameAttr.getValueString())) {
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TSK_WEB_BOOKMARK:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
|
||||
break;
|
||||
case TSK_WEB_COOKIE:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
|
||||
break;
|
||||
case TSK_WEB_DOWNLOAD:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
|
||||
break;
|
||||
case TSK_WEB_HISTORY:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
|
||||
break;
|
||||
case TSK_CONTACT:
|
||||
//generates the same correlation attrs as tsk_message
|
||||
case TSK_CALLLOG:
|
||||
//generates the same correlation attrs as tsk_message
|
||||
case TSK_MESSAGE: {
|
||||
String value = null;
|
||||
if (null != artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) {
|
||||
value = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString();
|
||||
} else if (null != artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) {
|
||||
value = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString();
|
||||
} else if (null != artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) {
|
||||
value = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString();
|
||||
}
|
||||
// Remove all non-numeric symbols to semi-normalize phone numbers, preserving leading "+" character
|
||||
if (value != null) {
|
||||
String newValue = value.replaceAll("\\D", "");
|
||||
if (value.startsWith("+")) {
|
||||
newValue = "+" + newValue;
|
||||
}
|
||||
value = newValue;
|
||||
// Only add the correlation attribute if the resulting phone number large enough to be of use
|
||||
// (these 3-5 digit numbers can be valid, but are not useful for correlation)
|
||||
if (value.length() > 5) {
|
||||
eamArtifacts.add(makeCorrelationAttributeInstanceUsingTypeValue(artifactForInstance, EamDb.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TSK_DEVICE_ATTACHED:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID);
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
|
||||
break;
|
||||
case TSK_WIFI_NETWORK:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID);
|
||||
break;
|
||||
case TSK_WIFI_NETWORK_ADAPTER:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
|
||||
break;
|
||||
case TSK_BLUETOOTH_PAIRING:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
|
||||
break;
|
||||
case TSK_BLUETOOTH_ADAPTER:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
|
||||
break;
|
||||
case TSK_DEVICE_INFO:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID);
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
|
||||
break;
|
||||
case TSK_SIM_ATTACHED:
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
|
||||
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting defined correlation types.", ex); // NON-NLS
|
||||
return eamArtifacts;
|
||||
}
|
||||
|
||||
return eamArtifacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EamArtifact of type correlationType if one can be generated
|
||||
* based on the data in the blackboard artifact.
|
||||
*
|
||||
* @param correlationType The Central Repository artifact type to create
|
||||
* @param bbArtifact The blackboard artifact to pull data from
|
||||
*
|
||||
* @return the new EamArtifact, or null if one was not created because
|
||||
* bbArtifact did not contain the needed data
|
||||
*/
|
||||
private static CorrelationAttributeInstance makeInstanceFromBlackboardArtifact(CorrelationAttributeInstance.Type correlationType,
|
||||
BlackboardArtifact bbArtifact) throws EamDbException {
|
||||
String value = null;
|
||||
int artifactTypeID = bbArtifact.getArtifactTypeID();
|
||||
|
||||
try {
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifactTypeID) {
|
||||
// Get the associated artifact
|
||||
BlackboardAttribute attribute = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
|
||||
if (attribute != null) {
|
||||
BlackboardArtifact associatedArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong());
|
||||
return EamArtifactUtil.makeInstanceFromBlackboardArtifact(correlationType, associatedArtifact);
|
||||
}
|
||||
|
||||
} else if (correlationType.getId() == CorrelationAttributeInstance.EMAIL_TYPE_ID
|
||||
&& BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() == artifactTypeID) {
|
||||
|
||||
BlackboardAttribute setNameAttr = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||
if (setNameAttr != null
|
||||
&& EamArtifactUtil.getEmailAddressAttrString().equals(setNameAttr.getValueString())) {
|
||||
value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD)).getValueString();
|
||||
}
|
||||
} else if (correlationType.getId() == CorrelationAttributeInstance.DOMAIN_TYPE_ID
|
||||
&& (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID() == artifactTypeID
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() == artifactTypeID
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifactTypeID
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() == artifactTypeID)) {
|
||||
|
||||
// Lower-case this to normalize domains
|
||||
BlackboardAttribute attribute = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN));
|
||||
if (attribute != null) {
|
||||
value = attribute.getValueString();
|
||||
}
|
||||
} else if (correlationType.getId() == CorrelationAttributeInstance.PHONE_TYPE_ID
|
||||
&& (BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() == artifactTypeID
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() == artifactTypeID
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == artifactTypeID)) {
|
||||
|
||||
if (null != bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) {
|
||||
value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString();
|
||||
} else if (null != bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) {
|
||||
value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString();
|
||||
} else if (null != bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) {
|
||||
value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString();
|
||||
}
|
||||
|
||||
// Remove all non-numeric symbols to semi-normalize phone numbers, preserving leading "+" character
|
||||
if (value != null) {
|
||||
String newValue = value.replaceAll("\\D", "");
|
||||
if (value.startsWith("+")) {
|
||||
newValue = "+" + newValue;
|
||||
}
|
||||
|
||||
value = newValue;
|
||||
|
||||
// If the resulting phone number is too small to be of use, return null
|
||||
// (these 3-5 digit numbers can be valid, but are not useful for correlation)
|
||||
if (value.length() <= 5) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else if (correlationType.getId() == CorrelationAttributeInstance.USBID_TYPE_ID
|
||||
&& BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID() == artifactTypeID) {
|
||||
|
||||
value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID)).getValueString();
|
||||
} else if (correlationType.getId() == CorrelationAttributeInstance.SSID_TYPE_ID
|
||||
&& BlackboardArtifact.ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID() == artifactTypeID) {
|
||||
value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID)).getValueString();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting attribute while getting type from BlackboardArtifact.", ex); // NON-NLS
|
||||
return null;
|
||||
@ -174,11 +164,34 @@ public class EamArtifactUtil {
|
||||
logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
return eamArtifacts;
|
||||
}
|
||||
|
||||
if ((null != value) && (value.isEmpty() == false)) {
|
||||
return makeCorrelationAttributeInstanceUsingTypeValue(bbArtifact, correlationType, value);
|
||||
} else {
|
||||
return null;
|
||||
/**
|
||||
* Add a CorrelationAttributeInstance of the specified type to the provided
|
||||
list if the artifactForInstance has an Attribute of the given type with a non empty
|
||||
value.
|
||||
*
|
||||
* @param eamArtifacts the list of CorrelationAttributeInstance objects
|
||||
* which should be added to
|
||||
* @param artifact the blackboard artifactForInstance which we are creating a
|
||||
CorrelationAttributeInstance for
|
||||
* @param bbAttributeType the type of BlackboardAttribute we expect to exist
|
||||
* for a CorrelationAttributeInstance of this type
|
||||
* generated from this Blackboard Artifact
|
||||
* @param typeId the integer type id of the
|
||||
* CorrelationAttributeInstance type
|
||||
*
|
||||
* @throws EamDbException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private static void addCorrelationAttributeToList(List<CorrelationAttributeInstance> eamArtifacts, BlackboardArtifact artifact, ATTRIBUTE_TYPE bbAttributeType, int typeId) throws EamDbException, TskCoreException {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(bbAttributeType));
|
||||
if (attribute != null) {
|
||||
String value = attribute.getValueString();
|
||||
if ((null != value) && (value.isEmpty() == false)) {
|
||||
eamArtifacts.add(makeCorrelationAttributeInstanceUsingTypeValue(artifact, EamDb.getInstance().getCorrelationTypeById(typeId), value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,9 +199,9 @@ public class EamArtifactUtil {
|
||||
* Uses the determined type and vallue, then looks up instance details to
|
||||
* create proper CorrelationAttributeInstance.
|
||||
*
|
||||
* @param bbArtifact the blackboard artifact
|
||||
* @param bbArtifact the blackboard artifactForInstance
|
||||
* @param correlationType the given type
|
||||
* @param value the artifact value
|
||||
* @param value the artifactForInstance value
|
||||
*
|
||||
* @return CorrelationAttributeInstance from details
|
||||
*/
|
||||
@ -207,14 +220,14 @@ public class EamArtifactUtil {
|
||||
correlationCase = EamDb.getInstance().newCase(Case.getCurrentCaseThrows());
|
||||
}
|
||||
return new CorrelationAttributeInstance(
|
||||
value,
|
||||
correlationType,
|
||||
value,
|
||||
correlationCase,
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, bbSourceFile.getDataSource()),
|
||||
bbSourceFile.getParentPath() + bbSourceFile.getName(),
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN
|
||||
);
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
bbSourceFile.getId());
|
||||
|
||||
} catch (TskCoreException | EamDbException | CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.SEVERE, "Error creating artifact instance.", ex); // NON-NLS
|
||||
@ -247,8 +260,6 @@ public class EamArtifactUtil {
|
||||
CorrelationAttributeInstance.Type type;
|
||||
CorrelationCase correlationCase;
|
||||
CorrelationDataSource correlationDataSource;
|
||||
String value;
|
||||
String filePath;
|
||||
|
||||
try {
|
||||
type = EamDb.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
|
||||
@ -258,8 +269,6 @@ public class EamArtifactUtil {
|
||||
return null;
|
||||
}
|
||||
correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource());
|
||||
value = file.getMd5Hash();
|
||||
filePath = (file.getParentPath() + file.getName()).toLowerCase();
|
||||
} catch (TskCoreException | EamDbException ex) {
|
||||
logger.log(Level.SEVERE, "Error retrieving correlation attribute.", ex);
|
||||
return null;
|
||||
@ -270,25 +279,38 @@ public class EamArtifactUtil {
|
||||
|
||||
CorrelationAttributeInstance correlationAttributeInstance;
|
||||
try {
|
||||
correlationAttributeInstance = EamDb.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, value, filePath);
|
||||
correlationAttributeInstance = EamDb.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, file.getId());
|
||||
} catch (EamDbException | CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.WARNING, String.format(
|
||||
"Correlation attribute could not be retrieved for '%s' (id=%d): %s",
|
||||
content.getName(), content.getId(), ex.getMessage()));
|
||||
return null;
|
||||
}
|
||||
//if there was no correlation attribute found for the item using object_id then check for attributes added with schema 1,1 which lack object_id
|
||||
if (correlationAttributeInstance == null) {
|
||||
String value = file.getMd5Hash();
|
||||
String filePath = (file.getParentPath() + file.getName()).toLowerCase();
|
||||
try {
|
||||
correlationAttributeInstance = EamDb.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, value, filePath);
|
||||
} catch (EamDbException | CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.WARNING, String.format(
|
||||
"Correlation attribute could not be retrieved for '%s' (id=%d): %s",
|
||||
content.getName(), content.getId(), ex.getMessage()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return correlationAttributeInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EamArtifact from the given Content. Will return null if an
|
||||
* artifact can not be created - this is not necessarily an error case, it
|
||||
* just means an artifact can't be made. If creation fails due to an error
|
||||
* (and not that the file is the wrong type or it has no hash), the error
|
||||
* will be logged before returning.
|
||||
*
|
||||
* Does not add the artifact to the database.
|
||||
artifactForInstance can not be created - this is not necessarily an error case, it
|
||||
just means an artifactForInstance can't be made. If creation fails due to an error
|
||||
(and not that the file is the wrong type or it has no hash), the error
|
||||
will be logged before returning.
|
||||
|
||||
Does not add the artifactForInstance to the database.
|
||||
*
|
||||
* @param content The content object
|
||||
*
|
||||
@ -306,7 +328,7 @@ public class EamArtifactUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We need a hash to make the artifact
|
||||
// We need a hash to make the artifactForInstance
|
||||
String md5 = af.getMd5Hash();
|
||||
if (md5 == null || md5.isEmpty() || HashUtility.isNoDataMd5(md5)) {
|
||||
return null;
|
||||
@ -324,7 +346,10 @@ public class EamArtifactUtil {
|
||||
af.getMd5Hash(),
|
||||
correlationCase,
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, af.getDataSource()),
|
||||
af.getParentPath() + af.getName());
|
||||
af.getParentPath() + af.getName(),
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
af.getId());
|
||||
|
||||
} catch (TskCoreException | EamDbException | CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.SEVERE, "Error making correlation attribute.", ex);
|
||||
|
@ -34,8 +34,7 @@ public interface EamDb {
|
||||
public static final int SCHEMA_VERSION = 2;
|
||||
public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
|
||||
= new CaseDbSchemaVersionNumber(1, 2);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the instance
|
||||
*
|
||||
@ -111,6 +110,16 @@ public interface EamDb {
|
||||
*/
|
||||
public void newDbInfo(String name, String value) throws EamDbException;
|
||||
|
||||
/**
|
||||
* Set the data source object id for a specific entry in the data_sources
|
||||
* table
|
||||
*
|
||||
* @param rowId - the row id for the data_sources table entry
|
||||
* @param dataSourceObjectId - the object id for the data source from the
|
||||
* caseDb
|
||||
*/
|
||||
void addDataSourceObjectId(int rowId, long dataSourceObjectId) throws EamDbException;
|
||||
|
||||
/**
|
||||
* Get the value for the given name from the name/value db_info table.
|
||||
*
|
||||
@ -183,43 +192,45 @@ public interface EamDb {
|
||||
* @return The retrieved case
|
||||
*/
|
||||
CorrelationCase getCaseById(int caseId) throws EamDbException;
|
||||
|
||||
/**
|
||||
* Retrieves cases that are in DB.
|
||||
*
|
||||
* @return List of cases
|
||||
*/
|
||||
List<CorrelationCase> getCases() throws EamDbException;
|
||||
|
||||
|
||||
/**
|
||||
* Creates new Data Source in the database
|
||||
*
|
||||
* @param eamDataSource the data source to add
|
||||
*
|
||||
* @return - A CorrelationDataSource object with data source's central repository id
|
||||
*/
|
||||
void newDataSource(CorrelationDataSource eamDataSource) throws EamDbException;
|
||||
CorrelationDataSource newDataSource(CorrelationDataSource eamDataSource) throws EamDbException;
|
||||
|
||||
/**
|
||||
* Retrieves Data Source details based on data source device ID
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceDeviceId the data source device ID number
|
||||
* @param caseDbDataSourceId the data source device ID number
|
||||
*
|
||||
* @return The data source
|
||||
*/
|
||||
CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException;
|
||||
CorrelationDataSource getDataSource(CorrelationCase correlationCase, Long caseDbDataSourceId) throws EamDbException;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves Data Source details based on data source ID
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceId the data source ID number
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceId the data source ID number
|
||||
*
|
||||
* @return The data source
|
||||
*/
|
||||
CorrelationDataSource getDataSourceById(CorrelationCase correlationCase, int dataSourceId) throws EamDbException;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves data sources that are in DB
|
||||
*
|
||||
@ -245,7 +256,7 @@ public interface EamDb {
|
||||
* @return List of artifact instances for a given type/value
|
||||
*/
|
||||
List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves eamArtifact instances from the database that are associated
|
||||
* with the aType and filePath
|
||||
@ -301,21 +312,19 @@ public interface EamDb {
|
||||
|
||||
/**
|
||||
* Retrieves number of eamArtifact instances in the database that are
|
||||
* associated with the caseDisplayName and dataSource of the given
|
||||
* eamArtifact instance.
|
||||
* associated with the given data source.
|
||||
*
|
||||
* @param caseUUID Case ID to search for
|
||||
* @param dataSourceID Data source ID to search for
|
||||
* @param correlationDataSource Data source to search for
|
||||
*
|
||||
* @return Number of artifact instances having caseDisplayName and
|
||||
* dataSource
|
||||
*/
|
||||
Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException;
|
||||
Long getCountArtifactInstancesByCaseDataSource(CorrelationDataSource correlationDataSource) throws EamDbException;
|
||||
|
||||
/**
|
||||
* Adds an eamArtifact to an internal list to be later added to DB. Artifact
|
||||
can have 1 or more Artifact Instances. Insert will be triggered by a
|
||||
threshold or a call to commitAttributeInstancesBulk().
|
||||
* can have 1 or more Artifact Instances. Insert will be triggered by a
|
||||
* threshold or a call to commitAttributeInstancesBulk().
|
||||
*
|
||||
* @param eamArtifact The artifact to add
|
||||
*/
|
||||
@ -323,7 +332,7 @@ public interface EamDb {
|
||||
|
||||
/**
|
||||
* Executes a bulk insert of the eamArtifacts added from the
|
||||
addAttributeInstanceBulk() method
|
||||
* addAttributeInstanceBulk() method
|
||||
*/
|
||||
void commitAttributeInstancesBulk() throws EamDbException;
|
||||
|
||||
@ -347,6 +356,9 @@ public interface EamDb {
|
||||
* Find a correlation attribute in the Central Repository database given the
|
||||
* instance type, case, data source, value, and file path.
|
||||
*
|
||||
* Method exists to support instances added using Central Repository version
|
||||
* 1,1 and older
|
||||
*
|
||||
* @param type The type of instance.
|
||||
* @param correlationCase The case tied to the instance.
|
||||
* @param correlationDataSource The data source tied to the instance.
|
||||
@ -360,6 +372,23 @@ public interface EamDb {
|
||||
CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttributeInstance.Type type, CorrelationCase correlationCase,
|
||||
CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException, CorrelationAttributeNormalizationException;
|
||||
|
||||
/**
|
||||
* Find a correlation attribute in the Central Repository database given the
|
||||
* instance type, case, data source, object id.
|
||||
*
|
||||
* @param type The type of instance.
|
||||
* @param correlationCase The case tied to the instance.
|
||||
* @param correlationDataSource The data source tied to the instance.
|
||||
* @param objectID The object id of the file tied to the
|
||||
* instance.
|
||||
*
|
||||
* @return The correlation attribute if it exists; otherwise null.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttributeInstance.Type type, CorrelationCase correlationCase,
|
||||
CorrelationDataSource correlationDataSource, long objectID) throws EamDbException, CorrelationAttributeNormalizationException;
|
||||
|
||||
/**
|
||||
* Sets an eamArtifact instance to the given known status. If eamArtifact
|
||||
* exists, it is updated. If eamArtifact does not exist nothing happens
|
||||
@ -383,12 +412,15 @@ public interface EamDb {
|
||||
/**
|
||||
* Gets list of matching eamArtifact instances that have knownStatus =
|
||||
* "Bad".
|
||||
*
|
||||
*
|
||||
* @param aType EamArtifact.Type to search for
|
||||
*
|
||||
* @return List with 0 or more matching eamArtifact instances.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType) throws EamDbException;
|
||||
|
||||
/**
|
||||
* Count matching eamArtifacts instances that have knownStatus = "Bad".
|
||||
*
|
||||
@ -490,7 +522,7 @@ public interface EamDb {
|
||||
*
|
||||
* @param eamOrg The organization to add
|
||||
*
|
||||
* @return The organization with the org ID set.
|
||||
* @return The organization with the org ID set.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@ -700,18 +732,20 @@ public interface EamDb {
|
||||
/**
|
||||
* Process the Artifact instance in the EamDb
|
||||
*
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param instanceTableCallback callback to process the instance
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
void processInstanceTable(CorrelationAttributeInstance.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException;
|
||||
|
||||
|
||||
/**
|
||||
* Process the Artifact instance in the EamDb
|
||||
*
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param instanceTableCallback callback to process the instance
|
||||
* @param whereClause query string to execute
|
||||
* @param whereClause query string to execute
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
void processInstanceTableWhere(CorrelationAttributeInstance.Type type, String whereClause, InstanceTableCallback instanceTableCallback) throws EamDbException;
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.centralrepository.datamodel;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -29,8 +30,7 @@ import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* Central Repository database implementation using Postgres as a
|
||||
* backend
|
||||
* Central Repository database implementation using Postgres as a backend
|
||||
*/
|
||||
final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
|
||||
@ -47,10 +47,11 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
|
||||
/**
|
||||
* Get the singleton instance of PostgresEamDb
|
||||
*
|
||||
*
|
||||
* @return the singleton instance of PostgresEamDb
|
||||
*
|
||||
* @throws EamDbException if one or more default correlation type(s) have an invalid db table name.
|
||||
*
|
||||
* @throws EamDbException if one or more default correlation type(s) have an
|
||||
* invalid db table name.
|
||||
*/
|
||||
public synchronized static PostgresEamDb getInstance() throws EamDbException {
|
||||
if (instance == null) {
|
||||
@ -61,9 +62,10 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws EamDbException if the AbstractSqlEamDb class has one or more default
|
||||
* correlation type(s) having an invalid db table name.
|
||||
*
|
||||
* @throws EamDbException if the AbstractSqlEamDb class has one or more
|
||||
* default correlation type(s) having an invalid db
|
||||
* table name.
|
||||
*/
|
||||
private PostgresEamDb() throws EamDbException {
|
||||
dbSettings = new PostgresEamDbSettings();
|
||||
@ -73,8 +75,8 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
@Override
|
||||
public void shutdownConnections() throws EamDbException {
|
||||
try {
|
||||
synchronized(this) {
|
||||
if(connectionPool != null){
|
||||
synchronized (this) {
|
||||
if (connectionPool != null) {
|
||||
connectionPool.close();
|
||||
connectionPool = null; // force it to be re-created on next connect()
|
||||
}
|
||||
@ -148,7 +150,7 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
connectionURL.append(dbSettings.getPort());
|
||||
connectionURL.append("/");
|
||||
connectionURL.append(dbSettings.getDbName());
|
||||
|
||||
|
||||
connectionPool.setUrl(connectionURL.toString());
|
||||
connectionPool.setUsername(dbSettings.getUserName());
|
||||
connectionPool.setPassword(dbSettings.getPassword());
|
||||
@ -189,31 +191,34 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
protected String getConflictClause() {
|
||||
return CONFLICT_CLAUSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets an exclusive lock (if applicable).
|
||||
* Will return the lock if successful, null if unsuccessful because locking
|
||||
* isn't supported, and throw an exception if we should have been able to get the
|
||||
* lock but failed (meaning the database is in use).
|
||||
* Gets an exclusive lock (if applicable). Will return the lock if
|
||||
* successful, null if unsuccessful because locking isn't supported, and
|
||||
* throw an exception if we should have been able to get the lock but failed
|
||||
* (meaning the database is in use).
|
||||
*
|
||||
* @return the lock, or null if locking is not supported
|
||||
* @throws EamDbException if the coordination service is running but we fail to get the lock
|
||||
*
|
||||
* @throws EamDbException if the coordination service is running but we fail
|
||||
* to get the lock
|
||||
*/
|
||||
@Override
|
||||
public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException{
|
||||
public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException {
|
||||
try {
|
||||
// First check if multi user mode is enabled - if not there's no point trying to get a lock
|
||||
if( ! UserPreferences.getIsMultiUserModeEnabled()){
|
||||
if (!UserPreferences.getIsMultiUserModeEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
String databaseNodeName = dbSettings.getHost() + "_" + dbSettings.getDbName();
|
||||
CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CENTRAL_REPO, databaseNodeName, 5, TimeUnit.MINUTES);
|
||||
|
||||
if(lock != null){
|
||||
if (lock != null) {
|
||||
return lock;
|
||||
}
|
||||
throw new EamDbException("Error acquiring database lock");
|
||||
} catch (InterruptedException ex){
|
||||
} catch (InterruptedException ex) {
|
||||
throw new EamDbException("Error acquiring database lock");
|
||||
} catch (CoordinationService.CoordinationServiceException ex) {
|
||||
// This likely just means the coordination service isn't running, which is ok
|
||||
@ -221,4 +226,23 @@ final class PostgresEamDb extends AbstractSqlEamDb {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean doesColumnExist(Connection conn, String tableName, String columnName) throws SQLException {
|
||||
final String objectIdColumnExistsTemplate = "SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='%s' AND column_name='%s')"; //NON-NLS
|
||||
ResultSet resultSet = null;
|
||||
Statement statement = null;
|
||||
boolean columnExists = false;
|
||||
try {
|
||||
statement = conn.createStatement();
|
||||
resultSet = statement.executeQuery(String.format(objectIdColumnExistsTemplate, tableName, columnName));
|
||||
if (resultSet.next()) {
|
||||
columnExists = resultSet.getBoolean(1);
|
||||
}
|
||||
} finally {
|
||||
EamDbUtil.closeResultSet(resultSet);
|
||||
EamDbUtil.closeStatement(statement);
|
||||
}
|
||||
return columnExists;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -344,11 +344,13 @@ public final class PostgresEamDbSettings {
|
||||
createDataSourcesTable.append("case_id integer NOT NULL,");
|
||||
createDataSourcesTable.append("device_id text NOT NULL,");
|
||||
createDataSourcesTable.append("name text NOT NULL,");
|
||||
createDataSourcesTable.append("datasource_obj_id integer,");
|
||||
createDataSourcesTable.append("foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
|
||||
createDataSourcesTable.append("CONSTRAINT datasource_unique UNIQUE (case_id, device_id, name)");
|
||||
createDataSourcesTable.append(")");
|
||||
|
||||
String dataSourceIdx1 = "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
|
||||
String dataSourceIdx2 = "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
|
||||
|
||||
StringBuilder createReferenceSetsTable = new StringBuilder();
|
||||
createReferenceSetsTable.append("CREATE TABLE IF NOT EXISTS reference_sets (");
|
||||
@ -394,11 +396,11 @@ public final class PostgresEamDbSettings {
|
||||
|
||||
String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
|
||||
|
||||
String instancesIdx1 = getAddCaseIdIndexTemplate();
|
||||
String instancesIdx2 = getAddDataSourceIdIndexTemplate();
|
||||
|
||||
String instancesIdx3 = getAddValueIndexTemplate();
|
||||
String instancesIdx4 = getAddKnownStatusIndexTemplate();
|
||||
String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
|
||||
String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
|
||||
String instancesValueIdx = getAddValueIndexTemplate();
|
||||
String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
|
||||
String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
|
||||
|
||||
StringBuilder createDbInfoTable = new StringBuilder();
|
||||
createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info (");
|
||||
@ -425,7 +427,8 @@ public final class PostgresEamDbSettings {
|
||||
|
||||
stmt.execute(createDataSourcesTable.toString());
|
||||
stmt.execute(dataSourceIdx1);
|
||||
|
||||
stmt.execute(dataSourceIdx2);
|
||||
|
||||
stmt.execute(createReferenceSetsTable.toString());
|
||||
stmt.execute(referenceSetsIdx1);
|
||||
|
||||
@ -443,10 +446,11 @@ public final class PostgresEamDbSettings {
|
||||
instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
|
||||
|
||||
stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx1, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx2, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx3, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx4, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
|
||||
|
||||
// FUTURE: allow more than the FILES type
|
||||
if (type.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
|
||||
@ -486,6 +490,7 @@ public final class PostgresEamDbSettings {
|
||||
createArtifactInstancesTableTemplate.append("file_path text NOT NULL,");
|
||||
createArtifactInstancesTableTemplate.append("known_status integer NOT NULL,");
|
||||
createArtifactInstancesTableTemplate.append("comment text,");
|
||||
createArtifactInstancesTableTemplate.append("file_obj_id integer,");
|
||||
createArtifactInstancesTableTemplate.append("CONSTRAINT %s_multi_unique_ UNIQUE (data_source_id, value, file_path),");
|
||||
createArtifactInstancesTableTemplate.append("foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
|
||||
createArtifactInstancesTableTemplate.append("foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL");
|
||||
@ -545,6 +550,19 @@ public final class PostgresEamDbSettings {
|
||||
return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template for creating an index on the file_obj_id column of an
|
||||
* instance table. %s will exist in the template where the name of the new
|
||||
* table will be addedd.
|
||||
*
|
||||
* @return a String which is a template for adding an index to the file_obj_id
|
||||
* column of a _instances table
|
||||
*/
|
||||
static String getAddObjectIdIndexTemplate() {
|
||||
// Each "%s" will be replaced with the relevant TYPE_instances table name.
|
||||
return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
|
||||
}
|
||||
|
||||
public boolean insertDefaultDatabaseContent() {
|
||||
Connection conn = getEphemeralConnection(false);
|
||||
if (null == conn) {
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.centralrepository.datamodel;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Arrays;
|
||||
@ -57,7 +58,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* @return the singleton instance of SqliteEamDb
|
||||
*
|
||||
* @throws EamDbException if one or more default correlation type(s) have an
|
||||
* invalid db table name.
|
||||
* invalid db table name.
|
||||
*/
|
||||
public synchronized static SqliteEamDb getInstance() throws EamDbException {
|
||||
if (instance == null) {
|
||||
@ -70,7 +71,8 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
/**
|
||||
*
|
||||
* @throws EamDbException if the AbstractSqlEamDb class has one or more
|
||||
* default correlation type(s) having an invalid db table name.
|
||||
* default correlation type(s) having an invalid db
|
||||
* table name.
|
||||
*/
|
||||
private SqliteEamDb() throws EamDbException {
|
||||
dbSettings = new SqliteEamDbSettings();
|
||||
@ -205,7 +207,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
/**
|
||||
* Add a new name/value pair in the db_info table.
|
||||
*
|
||||
* @param name Key to set
|
||||
* @param name Key to set
|
||||
* @param value Value to set
|
||||
*
|
||||
* @throws EamDbException
|
||||
@ -242,7 +244,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
/**
|
||||
* Update the value for a name in the name/value db_info table.
|
||||
*
|
||||
* @param name Name to find
|
||||
* @param name Name to find
|
||||
* @param value Value to assign to name.
|
||||
*
|
||||
* @throws EamDbException
|
||||
@ -272,6 +274,16 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDataSourceObjectId(int rowId, long dataSourceObjectId) throws EamDbException{
|
||||
try {
|
||||
acquireExclusiveLock();
|
||||
super.addDataSourceObjectId(rowId, dataSourceObjectId);
|
||||
} finally {
|
||||
releaseExclusiveLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new Case in the database
|
||||
*
|
||||
@ -360,10 +372,10 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* @param eamDataSource the data source to add
|
||||
*/
|
||||
@Override
|
||||
public void newDataSource(CorrelationDataSource eamDataSource) throws EamDbException {
|
||||
public CorrelationDataSource newDataSource(CorrelationDataSource eamDataSource) throws EamDbException {
|
||||
try {
|
||||
acquireExclusiveLock();
|
||||
super.newDataSource(eamDataSource);
|
||||
return super.newDataSource(eamDataSource);
|
||||
} finally {
|
||||
releaseExclusiveLock();
|
||||
}
|
||||
@ -372,28 +384,28 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
/**
|
||||
* Retrieves Data Source details based on data source device ID
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceDeviceId the data source device ID number
|
||||
*
|
||||
* @return The data source
|
||||
*/
|
||||
@Override
|
||||
public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException {
|
||||
public CorrelationDataSource getDataSource(CorrelationCase correlationCase, Long caseDbDataSourceId) throws EamDbException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
return super.getDataSource(correlationCase, dataSourceDeviceId);
|
||||
return super.getDataSource(correlationCase, caseDbDataSourceId);
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves Data Source details based on data source ID
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceId the data source ID number
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceId the data source ID number
|
||||
*
|
||||
* @return The data source
|
||||
*/
|
||||
@ -461,7 +473,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* Retrieves eamArtifact instances from the database that are associated
|
||||
* with the aType and filePath
|
||||
*
|
||||
* @param aType EamArtifact.Type to search for
|
||||
* @param aType EamArtifact.Type to search for
|
||||
* @param filePath File path to search for
|
||||
*
|
||||
* @return List of 0 or more EamArtifactInstances
|
||||
@ -486,7 +498,8 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* @param value The value to search for
|
||||
*
|
||||
* @return Number of artifact instances having ArtifactType and
|
||||
* ArtifactValue.
|
||||
* ArtifactValue.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
@ -518,6 +531,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* @param value The value to search for
|
||||
*
|
||||
* @return Number of unique tuples
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
@ -545,17 +559,17 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* associated with the caseDisplayName and dataSource of the given
|
||||
* eamArtifact instance.
|
||||
*
|
||||
* @param caseUUID Case ID to search for
|
||||
* @param caseUUID Case ID to search for
|
||||
* @param dataSourceID Data source ID to search for
|
||||
*
|
||||
* @return Number of artifact instances having caseDisplayName and
|
||||
* dataSource
|
||||
* dataSource
|
||||
*/
|
||||
@Override
|
||||
public Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException {
|
||||
public Long getCountArtifactInstancesByCaseDataSource(CorrelationDataSource correlationDataSource) throws EamDbException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
return super.getCountArtifactInstancesByCaseDataSource(caseUUID, dataSourceID);
|
||||
return super.getCountArtifactInstancesByCaseDataSource(correlationDataSource);
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
}
|
||||
@ -563,7 +577,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
|
||||
/**
|
||||
* Executes a bulk insert of the eamArtifacts added from the
|
||||
addAttributeInstanceBulk() method
|
||||
* addAttributeInstanceBulk() method
|
||||
*/
|
||||
@Override
|
||||
public void commitAttributeInstancesBulk() throws EamDbException {
|
||||
@ -596,7 +610,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
*
|
||||
* @param eamArtifact Artifact containing exactly one (1) ArtifactInstance.
|
||||
* @param knownStatus The status to change the artifact to. Should never be
|
||||
* KNOWN
|
||||
* KNOWN
|
||||
*/
|
||||
@Override
|
||||
public void setAttributeInstanceKnownStatus(CorrelationAttributeInstance eamArtifact, TskData.FileKnown knownStatus) throws EamDbException {
|
||||
@ -633,7 +647,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* "Bad".
|
||||
*
|
||||
* @param aType EamArtifact.Type to search for
|
||||
*
|
||||
* @return List with 0 or more matching eamArtifact instances.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
@ -672,7 +688,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* @param value Value to search for
|
||||
*
|
||||
* @return List of cases containing this artifact with instances marked as
|
||||
* bad
|
||||
* bad
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@ -690,6 +706,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* Remove a reference set and all values contained in it.
|
||||
*
|
||||
* @param referenceSetID
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
@ -708,6 +725,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* @param value
|
||||
* @param referenceSetID
|
||||
* @param correlationTypeID
|
||||
*
|
||||
* @return true if the hash is found in the reference set
|
||||
*/
|
||||
@Override
|
||||
@ -723,8 +741,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
/**
|
||||
* Process the Artifact instance in the EamDb
|
||||
*
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param instanceTableCallback callback to process the instance
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
@ -736,12 +755,13 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the Artifact instance in the EamDb
|
||||
*
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param instanceTableCallback callback to process the instance
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
@ -752,7 +772,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a reference set with the given name/version is in the
|
||||
@ -761,7 +781,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
*
|
||||
* @param referenceSetName
|
||||
* @param version
|
||||
*
|
||||
* @return true if a matching set is found
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@Override
|
||||
@ -928,7 +950,8 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* Add a new reference instance
|
||||
*
|
||||
* @param eamGlobalFileInstance The reference instance to add
|
||||
* @param correlationType Correlation Type that this Reference Instance is
|
||||
* @param correlationType Correlation Type that this Reference
|
||||
* Instance is
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@ -960,7 +983,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
/**
|
||||
* Get all reference entries having a given correlation type and value
|
||||
*
|
||||
* @param aType Type to use for matching
|
||||
* @param aType Type to use for matching
|
||||
* @param aValue Value to use for matching
|
||||
*
|
||||
* @return List of all global file instances with a type and value
|
||||
@ -1001,7 +1024,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* artifacts.
|
||||
*
|
||||
* @return List of EamArtifact.Type's. If none are defined in the database,
|
||||
* the default list will be returned.
|
||||
* the default list will be returned.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@ -1020,7 +1043,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* artifacts.
|
||||
*
|
||||
* @return List of enabled EamArtifact.Type's. If none are defined in the
|
||||
* database, the default list will be returned.
|
||||
* database, the default list will be returned.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@ -1039,7 +1062,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* correlate artifacts.
|
||||
*
|
||||
* @return List of supported EamArtifact.Type's. If none are defined in the
|
||||
* database, the default list will be returned.
|
||||
* database, the default list will be returned.
|
||||
*
|
||||
* @throws EamDbException
|
||||
*/
|
||||
@ -1111,8 +1134,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
* (meaning the database is in use).
|
||||
*
|
||||
* @return the lock, or null if locking is not supported
|
||||
*
|
||||
* @throws EamDbException if the coordination service is running but we fail
|
||||
* to get the lock
|
||||
* to get the lock
|
||||
*/
|
||||
@Override
|
||||
public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException {
|
||||
@ -1156,4 +1180,26 @@ final class SqliteEamDb extends AbstractSqlEamDb {
|
||||
rwLock.readLock().unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean doesColumnExist(Connection conn, String tableName, String columnName) throws SQLException {
|
||||
final String tableInfoQueryTemplate = "PRAGMA table_info(%s)"; //NON-NLS
|
||||
ResultSet resultSet = null;
|
||||
Statement statement = null;
|
||||
boolean columnExists = false;
|
||||
try {
|
||||
statement = conn.createStatement();
|
||||
resultSet = statement.executeQuery(String.format(tableInfoQueryTemplate, tableName));
|
||||
while (resultSet.next()) {
|
||||
// the second value ( 2 ) is the column name
|
||||
if (resultSet.getString(2).equals(columnName)) {
|
||||
columnExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
EamDbUtil.closeResultSet(resultSet);
|
||||
EamDbUtil.closeStatement(statement);
|
||||
}
|
||||
return columnExists;
|
||||
}
|
||||
}
|
||||
|
@ -287,11 +287,13 @@ public final class SqliteEamDbSettings {
|
||||
createDataSourcesTable.append("case_id integer NOT NULL,");
|
||||
createDataSourcesTable.append("device_id text NOT NULL,");
|
||||
createDataSourcesTable.append("name text NOT NULL,");
|
||||
createDataSourcesTable.append("datasource_obj_id integer,");
|
||||
createDataSourcesTable.append("foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
|
||||
createDataSourcesTable.append("CONSTRAINT datasource_unique UNIQUE (case_id, device_id, name)");
|
||||
createDataSourcesTable.append(")");
|
||||
|
||||
String dataSourceIdx1 = "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
|
||||
String dataSourceIdx2 = "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
|
||||
|
||||
StringBuilder createReferenceSetsTable = new StringBuilder();
|
||||
createReferenceSetsTable.append("CREATE TABLE IF NOT EXISTS reference_sets (");
|
||||
@ -337,12 +339,12 @@ public final class SqliteEamDbSettings {
|
||||
|
||||
String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
|
||||
|
||||
String instancesIdx1 = getAddCaseIdIndexTemplate();
|
||||
String instancesIdx2 = getAddDataSourceIdIndexTemplate();
|
||||
|
||||
String instancesIdx3 = getAddValueIndexTemplate();
|
||||
String instancesIdx4 = getAddKnownStatusIndexTemplate();
|
||||
|
||||
String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
|
||||
String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
|
||||
String instancesValueIdx = getAddValueIndexTemplate();
|
||||
String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
|
||||
String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
|
||||
|
||||
StringBuilder createDbInfoTable = new StringBuilder();
|
||||
createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info (");
|
||||
createDbInfoTable.append("id integer primary key NOT NULL,");
|
||||
@ -374,6 +376,7 @@ public final class SqliteEamDbSettings {
|
||||
|
||||
stmt.execute(createDataSourcesTable.toString());
|
||||
stmt.execute(dataSourceIdx1);
|
||||
stmt.execute(dataSourceIdx2);
|
||||
|
||||
stmt.execute(createReferenceSetsTable.toString());
|
||||
stmt.execute(referenceSetsIdx1);
|
||||
@ -392,10 +395,11 @@ public final class SqliteEamDbSettings {
|
||||
instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
|
||||
|
||||
stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx1, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx2, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx3, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesIdx4, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
|
||||
stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
|
||||
|
||||
// FUTURE: allow more than the FILES type
|
||||
if (type.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
|
||||
@ -434,6 +438,7 @@ public final class SqliteEamDbSettings {
|
||||
createArtifactInstancesTableTemplate.append("file_path text NOT NULL,");
|
||||
createArtifactInstancesTableTemplate.append("known_status integer NOT NULL,");
|
||||
createArtifactInstancesTableTemplate.append("comment text,");
|
||||
createArtifactInstancesTableTemplate.append("file_obj_id integer,");
|
||||
createArtifactInstancesTableTemplate.append("CONSTRAINT %s_multi_unique UNIQUE(data_source_id, value, file_path) ON CONFLICT IGNORE,");
|
||||
createArtifactInstancesTableTemplate.append("foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
|
||||
createArtifactInstancesTableTemplate.append("foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL");
|
||||
@ -493,6 +498,19 @@ public final class SqliteEamDbSettings {
|
||||
return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template for creating an index on the file_obj_id column of an
|
||||
* instance table. %s will exist in the template where the name of the new
|
||||
* table will be addedd.
|
||||
*
|
||||
* @return a String which is a template for adding an index to the file_obj_id
|
||||
* column of a _instances table
|
||||
*/
|
||||
static String getAddObjectIdIndexTemplate() {
|
||||
// Each "%s" will be replaced with the relevant TYPE_instances table name.
|
||||
return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
|
||||
}
|
||||
|
||||
public boolean insertDefaultDatabaseContent() {
|
||||
Connection conn = getEphemeralConnection();
|
||||
if (null == conn) {
|
||||
|
@ -443,19 +443,16 @@ final class CaseEventListener implements PropertyChangeListener {
|
||||
Content newDataSource = dataSourceAddedEvent.getDataSource();
|
||||
|
||||
try {
|
||||
String deviceId = openCase.getSleuthkitCase().getDataSource(newDataSource.getId()).getDeviceId();
|
||||
CorrelationCase correlationCase = dbManager.getCase(openCase);
|
||||
if (null == correlationCase) {
|
||||
correlationCase = dbManager.newCase(openCase);
|
||||
}
|
||||
if (null == dbManager.getDataSource(correlationCase, deviceId)) {
|
||||
if (null == dbManager.getDataSource(correlationCase, newDataSource.getId())) {
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, newDataSource);
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error adding new data source to the central repository", ex); //NON-NLS
|
||||
} catch (TskCoreException | TskDataException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error getting data source from DATA_SOURCE_ADDED event content.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
} // DATA_SOURCE_ADDED
|
||||
}
|
||||
|
||||
|
@ -152,14 +152,14 @@ final class IngestModule implements FileIngestModule {
|
||||
// insert this file into the central repository
|
||||
try {
|
||||
CorrelationAttributeInstance cefi = new CorrelationAttributeInstance(
|
||||
filesType,
|
||||
md5,
|
||||
filesType,
|
||||
eamCase,
|
||||
eamDataSource,
|
||||
abstractFile.getParentPath() + abstractFile.getName(),
|
||||
null,
|
||||
TskData.FileKnown.UNKNOWN // NOTE: Known status in the CR is based on tagging, not hashes like the Case Database.
|
||||
);
|
||||
, abstractFile.getId());
|
||||
dbManager.addAttributeInstanceBulk(cefi);
|
||||
} catch (EamDbException ex) {
|
||||
logger.log(Level.SEVERE, "Error adding artifact to bulk artifacts.", ex); // NON-NLS
|
||||
@ -192,7 +192,7 @@ final class IngestModule implements FileIngestModule {
|
||||
logger.log(Level.SEVERE, "Error doing bulk insert of artifacts.", ex); // NON-NLS
|
||||
}
|
||||
try {
|
||||
Long count = dbManager.getCountArtifactInstancesByCaseDataSource(eamCase.getCaseUUID(), eamDataSource.getDeviceID());
|
||||
Long count = dbManager.getCountArtifactInstancesByCaseDataSource(eamDataSource);
|
||||
logger.log(Level.INFO, "{0} artifacts in db for case: {1} ds:{2}", new Object[]{count, eamCase.getDisplayName(), eamDataSource.getName()}); // NON-NLS
|
||||
} catch (EamDbException ex) {
|
||||
logger.log(Level.SEVERE, "Error counting artifacts.", ex); // NON-NLS
|
||||
@ -303,7 +303,7 @@ final class IngestModule implements FileIngestModule {
|
||||
== 1) {
|
||||
// ensure we have this data source in the EAM DB
|
||||
try {
|
||||
if (null == centralRepoDb.getDataSource(eamCase, eamDataSource.getDeviceID())) {
|
||||
if (null == centralRepoDb.getDataSource(eamCase, eamDataSource.getDataSourceObjectID())) {
|
||||
centralRepoDb.newDataSource(eamDataSource);
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
|
@ -61,7 +61,7 @@ EamDbSettingsDialog.lbFullDbPath.text=
|
||||
GlobalSettingsPanel.cbUseCentralRepo.text=Use a Central Repository
|
||||
GlobalSettingsPanel.organizationTextArea.text=Organization information can be tracked in the Central Repository.
|
||||
GlobalSettingsPanel.manageOrganizationButton.text=Manage Organizations
|
||||
GlobalSettingsPanel.lbCentralRepository.text=A Central Repository allows you to correlate files and results between cases.
|
||||
GlobalSettingsPanel.lbCentralRepository.text=A Central Repository allows you to correlate files and results between cases. Central Repository configuration can not be modified while a case is open.
|
||||
GlobalSettingsPanel.pnCorrelationProperties.border.title=Correlation Properties
|
||||
GlobalSettingsPanel.organizationPanel.border.title=Organizations
|
||||
GlobalSettingsPanel.casesPanel.border.title=Case Details
|
||||
|
@ -140,7 +140,7 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="bnDbConfigure" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
@ -150,7 +150,7 @@
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Component id="lbDbNameValue" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="lbDbNameValue" alignment="0" pref="936" max="32767" attributes="0"/>
|
||||
<Component id="lbDbPlatformValue" max="32767" attributes="0"/>
|
||||
<Component id="lbDbLocationValue" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
|
@ -23,11 +23,13 @@ import java.awt.EventQueue;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.EnumSet;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.netbeans.spi.options.OptionsPanelController;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
@ -58,6 +60,10 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
addIngestJobEventsListener();
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> {
|
||||
//disable when case is open, enable when case is closed
|
||||
ingestStateUpdated(evt.getNewValue() != null);
|
||||
});
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
@ -66,7 +72,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
|
||||
private void addIngestJobEventsListener() {
|
||||
IngestManager.getInstance().addIngestJobEventListener(ingestJobEventListener);
|
||||
ingestStateUpdated();
|
||||
ingestStateUpdated(Case.isCaseOpen());
|
||||
}
|
||||
|
||||
@Messages({"GlobalSettingsPanel.updateFailed.title=Update failed",
|
||||
@ -173,7 +179,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
.addGroup(pnDatabaseConfigurationLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnDatabaseConfigurationLayout.createSequentialGroup()
|
||||
.addComponent(bnDbConfigure)
|
||||
.addContainerGap())
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGroup(pnDatabaseConfigurationLayout.createSequentialGroup()
|
||||
.addGroup(pnDatabaseConfigurationLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(lbDbPlatformTypeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
@ -181,7 +187,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
.addComponent(lbDbLocationLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(pnDatabaseConfigurationLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(lbDbNameValue, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(lbDbNameValue, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 936, Short.MAX_VALUE)
|
||||
.addComponent(lbDbPlatformValue, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(lbDbLocationValue, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))))
|
||||
);
|
||||
@ -429,7 +435,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
store();
|
||||
updateDatabase();
|
||||
load();
|
||||
this.ingestStateUpdated();
|
||||
this.ingestStateUpdated(Case.isCaseOpen());
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}//GEN-LAST:event_cbUseCentralRepoActionPerformed
|
||||
|
||||
@ -447,7 +453,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
@Messages({"GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module."})
|
||||
public void load() {
|
||||
tbOops.setText("");
|
||||
enableAllSubComponents(false);
|
||||
enableButtonSubComponents(false);
|
||||
EamDbPlatformEnum selectedPlatform = EamDbPlatformEnum.getSelectedPlatform();
|
||||
cbUseCentralRepo.setSelected(EamDbUtil.useCentralRepo()); // NON-NLS
|
||||
switch (selectedPlatform) {
|
||||
@ -456,20 +462,19 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
lbDbPlatformValue.setText(EamDbPlatformEnum.POSTGRESQL.toString());
|
||||
lbDbNameValue.setText(dbSettingsPg.getDbName());
|
||||
lbDbLocationValue.setText(dbSettingsPg.getHost());
|
||||
enableAllSubComponents(true);
|
||||
enableButtonSubComponents(cbUseCentralRepo.isSelected());
|
||||
break;
|
||||
case SQLITE:
|
||||
SqliteEamDbSettings dbSettingsSqlite = new SqliteEamDbSettings();
|
||||
lbDbPlatformValue.setText(EamDbPlatformEnum.SQLITE.toString());
|
||||
lbDbNameValue.setText(dbSettingsSqlite.getDbName());
|
||||
lbDbLocationValue.setText(dbSettingsSqlite.getDbDirectory());
|
||||
enableAllSubComponents(true);
|
||||
enableButtonSubComponents(cbUseCentralRepo.isSelected());
|
||||
break;
|
||||
default:
|
||||
lbDbPlatformValue.setText(EamDbPlatformEnum.DISABLED.toString());
|
||||
lbDbNameValue.setText("");
|
||||
lbDbLocationValue.setText("");
|
||||
enableDatabaseConfigureButton(cbUseCentralRepo.isSelected());
|
||||
tbOops.setText(Bundle.GlobalSettingsPanel_validationerrMsg_mustConfigure());
|
||||
break;
|
||||
}
|
||||
@ -521,7 +526,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ingestStateUpdated();
|
||||
ingestStateUpdated(Case.isCaseOpen());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -550,38 +555,25 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
}
|
||||
|
||||
@Messages({"GlobalSettingsPanel.validationErrMsg.ingestRunning=You cannot change settings while ingest is running."})
|
||||
private void ingestStateUpdated() {
|
||||
private void ingestStateUpdated(boolean caseIsOpen) {
|
||||
if (!SwingUtilities.isEventDispatchThread()) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
ingestStateUpdated();
|
||||
ingestStateUpdated(caseIsOpen);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
cbUseCentralRepo.setEnabled(!caseIsOpen);
|
||||
if (IngestManager.getInstance().isIngestRunning()) {
|
||||
tbOops.setText(Bundle.GlobalSettingsPanel_validationErrMsg_ingestRunning());
|
||||
tbOops.setVisible(true);
|
||||
cbUseCentralRepo.setEnabled(false);
|
||||
enableAllSubComponents(false);
|
||||
} else if (!cbUseCentralRepo.isEnabled()) {
|
||||
cbUseCentralRepo.setEnabled(true);
|
||||
enableButtonSubComponents(cbUseCentralRepo.isSelected());
|
||||
} else {
|
||||
load();
|
||||
enableDatabaseConfigureButton(cbUseCentralRepo.isSelected() && !caseIsOpen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around each of the enableComponentXYZ methods to enable/disable
|
||||
* them all at the same time.
|
||||
*
|
||||
* @param enable
|
||||
*
|
||||
* @return True
|
||||
*/
|
||||
private boolean enableAllSubComponents(Boolean enable) {
|
||||
enableDatabaseConfigureButton(cbUseCentralRepo.isSelected() && enable);
|
||||
enableButtonSubComponents(cbUseCentralRepo.isSelected() && enable);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,17 +18,16 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.commonfilesearch;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.datamodel.FileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
|
||||
/**
|
||||
* Node that wraps CaseDBCommonAttributeInstance to represent a file instance
|
||||
@ -75,33 +74,25 @@ public class CaseDBCommonAttributeInstanceNode extends FileNode {
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = new Sheet();
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
Set<String> keepProps = new HashSet<>(Arrays.asList(
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.nameColLbl"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.score.name"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.comment.name"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.count.name"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.mimeType")));
|
||||
|
||||
for(Property<?> p : sheetSet.getProperties()) {
|
||||
if(!keepProps.contains(p.getName())){
|
||||
sheetSet.remove(p.getName());
|
||||
}
|
||||
}
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
|
||||
final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText();
|
||||
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName()));
|
||||
|
||||
addScoreProperty(sheetSet, tags);
|
||||
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) {
|
||||
correlationAttribute = getCorrelationAttributeInstance();
|
||||
}
|
||||
addCommentProperty(sheetSet, tags, correlationAttribute);
|
||||
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) {
|
||||
addCountProperty(sheetSet, correlationAttribute);
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, this.getContent().getParentPath()));
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSource()));
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, StringUtils.defaultString(this.getContent().getMIMEType())));
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), NO_DESCR, caseName));
|
||||
|
||||
return sheet;
|
||||
}
|
||||
}
|
@ -67,19 +67,19 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
initComponents();
|
||||
Utilities.configureTextPaneAsHtml(jTextPane1);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setNode(Node node) {
|
||||
if ((node == null) || (!isSupported(node))) {
|
||||
resetComponent();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
StringBuilder html = new StringBuilder();
|
||||
|
||||
|
||||
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
||||
Content sourceFile = null;
|
||||
|
||||
|
||||
try {
|
||||
if (artifact != null) {
|
||||
/*
|
||||
@ -100,32 +100,32 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
"Exception while trying to retrieve a Content instance from the BlackboardArtifact '%s' (id=%d).",
|
||||
artifact.getDisplayName(), artifact.getArtifactID()), ex);
|
||||
}
|
||||
|
||||
|
||||
if (artifact != null) {
|
||||
populateTagData(html, artifact, sourceFile);
|
||||
} else {
|
||||
populateTagData(html, sourceFile);
|
||||
}
|
||||
|
||||
|
||||
if (sourceFile instanceof AbstractFile) {
|
||||
populateCentralRepositoryData(html, artifact, (AbstractFile) sourceFile);
|
||||
}
|
||||
|
||||
|
||||
setText(html.toString());
|
||||
jTextPane1.setCaretPosition(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate the "Selected Item" sections with tag data for the supplied
|
||||
* content.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to update.
|
||||
* @param content Selected content.
|
||||
*/
|
||||
private void populateTagData(StringBuilder html, Content content) {
|
||||
try {
|
||||
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
|
||||
|
||||
startSection(html, "Selected Item");
|
||||
List<ContentTag> fileTagsList = tskCase.getContentTagsByContent(content);
|
||||
if (fileTagsList.isEmpty()) {
|
||||
@ -142,11 +142,11 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate the "Selected Item" and "Source File" sections with tag data for
|
||||
* a supplied artifact.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to update.
|
||||
* @param artifact A selected artifact.
|
||||
* @param sourceFile The source content of the selected artifact.
|
||||
@ -154,7 +154,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
private void populateTagData(StringBuilder html, BlackboardArtifact artifact, Content sourceFile) {
|
||||
try {
|
||||
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
|
||||
|
||||
startSection(html, "Selected Item");
|
||||
List<BlackboardArtifactTag> artifactTagsList = tskCase.getBlackboardArtifactTagsByArtifact(artifact);
|
||||
if (artifactTagsList.isEmpty()) {
|
||||
@ -165,7 +165,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
}
|
||||
}
|
||||
endSection(html);
|
||||
|
||||
|
||||
if (sourceFile != null) {
|
||||
startSection(html, "Source File");
|
||||
List<ContentTag> fileTagsList = tskCase.getContentTagsByContent(sourceFile);
|
||||
@ -184,10 +184,10 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate the "Central Repository Comments" section with data.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to update.
|
||||
* @param artifact A selected artifact (can be null).
|
||||
* @param sourceFile A selected file, or a source file of the selected
|
||||
@ -208,23 +208,24 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
if (attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
|
||||
CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getCurrentCase());
|
||||
instancesList.add(new CorrelationAttributeInstance(
|
||||
md5,
|
||||
attributeType,
|
||||
md5,
|
||||
correlationCase,
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, sourceFile.getDataSource()),
|
||||
sourceFile.getParentPath() + sourceFile.getName(),
|
||||
"",
|
||||
sourceFile.getKnown()));
|
||||
sourceFile.getKnown(),
|
||||
sourceFile.getId()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean commentDataFound = false;
|
||||
|
||||
|
||||
for (CorrelationAttributeInstance instance : instancesList) {
|
||||
List<CorrelationAttributeInstance> correlatedInstancesList =
|
||||
EamDb.getInstance().getArtifactInstancesByTypeValue(instance.getCorrelationType(), instance.getCorrelationValue());
|
||||
List<CorrelationAttributeInstance> correlatedInstancesList
|
||||
= EamDb.getInstance().getArtifactInstancesByTypeValue(instance.getCorrelationType(), instance.getCorrelationValue());
|
||||
for (CorrelationAttributeInstance correlatedInstance : correlatedInstancesList) {
|
||||
if (correlatedInstance.getComment() != null && correlatedInstance.getComment().isEmpty() == false) {
|
||||
commentDataFound = true;
|
||||
@ -232,7 +233,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (commentDataFound == false) {
|
||||
addMessage(html, "There is no comment data for the selected content in the Central Repository.");
|
||||
}
|
||||
@ -247,16 +248,16 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
|
||||
/**
|
||||
* Set the text of the text panel.
|
||||
*
|
||||
*
|
||||
* @param text The text to set to the text panel.
|
||||
*/
|
||||
private void setText(String text) {
|
||||
jTextPane1.setText("<html><body>" + text + "</body></html>"); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start a new data section.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to add the section to.
|
||||
* @param sectionName The name of the section.
|
||||
*/
|
||||
@ -265,10 +266,10 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
.append(sectionName)
|
||||
.append("</p><br>"); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a message.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to add the message to.
|
||||
* @param message The message text.
|
||||
*/
|
||||
@ -277,10 +278,10 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
.append(message)
|
||||
.append("</p><br>"); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a data table containing information about a tag.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to add the table to.
|
||||
* @param tag The tag whose information will be used to populate the table.
|
||||
*/
|
||||
@ -296,11 +297,11 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), formatHtmlString(tag.getComment()));
|
||||
endTable(html);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a data table containing information about a correlation attribute
|
||||
* instance in the Central Repository.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to add the table to.
|
||||
* @param attributeInstance The attribute instance whose information will be
|
||||
* used to populate the table.
|
||||
@ -319,10 +320,10 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), attributeInstance.getFilePath());
|
||||
endTable(html);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start a data table.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to add the table to.
|
||||
*/
|
||||
private void startTable(StringBuilder html) {
|
||||
@ -331,7 +332,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
|
||||
/**
|
||||
* Add a data row to a table.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text to add the row to.
|
||||
* @param key The key for the left column of the data row.
|
||||
* @param value The value for the right column of the data row.
|
||||
@ -343,10 +344,10 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
html.append(value);
|
||||
html.append("</td></tr>"); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* End a data table.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text on which to end a table.
|
||||
*/
|
||||
private void endTable(StringBuilder html) {
|
||||
@ -355,18 +356,19 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
|
||||
/**
|
||||
* End a data section.
|
||||
*
|
||||
*
|
||||
* @param html The HTML text on which to end a section.
|
||||
*/
|
||||
private void endSection(StringBuilder html) {
|
||||
html.append("<br>"); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply escape sequence to special characters. Line feed and carriage
|
||||
* return character combinations will be converted to HTML line breaks.
|
||||
*
|
||||
*
|
||||
* @param text The text to format.
|
||||
*
|
||||
* @return The formatted text.
|
||||
*/
|
||||
private String formatHtmlString(String text) {
|
||||
@ -428,7 +430,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
@Override
|
||||
public boolean isSupported(Node node) {
|
||||
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
||||
|
||||
|
||||
try {
|
||||
if (artifact != null) {
|
||||
if (artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()) != null) {
|
||||
@ -444,7 +446,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
||||
"Exception while trying to retrieve a Content instance from the BlackboardArtifact '%s' (id=%d).",
|
||||
artifact.getDisplayName(), artifact.getArtifactID()), ex);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -43,6 +44,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.FileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
|
||||
@ -721,31 +723,23 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = new Sheet();
|
||||
Sheet sheet = super.createSheet();
|
||||
Set<String> keepProps = new HashSet<>(Arrays.asList(
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.nameColLbl"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.score.name"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.comment.name"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.count.name"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.sizeColLbl"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.mimeType"),
|
||||
NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.knownColLbl")));
|
||||
|
||||
//Remove all other props except for the ones above
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
for(Property<?> p : sheetSet.getProperties()) {
|
||||
if(!keepProps.contains(p.getName())){
|
||||
sheetSet.remove(p.getName());
|
||||
}
|
||||
}
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
|
||||
AbstractFile file = getContent();
|
||||
sheetSet.put(new NodeProperty<>("Name", "Name", "Name", file.getName()));
|
||||
|
||||
addScoreProperty(sheetSet, tags);
|
||||
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) {
|
||||
correlationAttribute = getCorrelationAttributeInstance();
|
||||
}
|
||||
addCommentProperty(sheetSet, tags, correlationAttribute);
|
||||
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) {
|
||||
addCountProperty(sheetSet, correlationAttribute);
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>("Size", "Size", "Size", file.getSize()));
|
||||
sheetSet.put(new NodeProperty<>("Mime Type", "Mime Type", "Mime Type", StringUtils.defaultString(file.getMIMEType())));
|
||||
sheetSet.put(new NodeProperty<>("Known", "Known", "Known", file.getKnown().getName()));
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
@ -24,21 +24,15 @@ import java.awt.Cursor;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
@ -48,11 +42,11 @@ import org.apache.commons.io.FilenameUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.SQLiteTableReaderException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.autopsy.coreutils.SQLiteTableReader;
|
||||
|
||||
/**
|
||||
* A file content viewer for SQLite database files.
|
||||
@ -66,8 +60,14 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
private static final Logger logger = Logger.getLogger(FileViewer.class.getName());
|
||||
private final SQLiteTableView selectedTableView = new SQLiteTableView();
|
||||
private AbstractFile sqliteDbFile;
|
||||
private File tmpDbFile;
|
||||
private Connection connection;
|
||||
|
||||
private SQLiteTableReader viewReader;
|
||||
|
||||
private Map<String, Object> row = new LinkedHashMap<>();
|
||||
private List<Map<String, Object>> pageOfTableRows = new ArrayList<>();
|
||||
private List<String> currentTableHeader = new ArrayList<>();
|
||||
private String prevTableName;
|
||||
|
||||
private int numRows; // num of rows in the selected table
|
||||
private int currPage = 0; // curr page of rows being displayed
|
||||
|
||||
@ -264,18 +264,18 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
}//GEN-LAST:event_tablesDropdownListActionPerformed
|
||||
|
||||
/**
|
||||
* The action when the Export Csv button is pressed. The file chooser window will pop
|
||||
* up to choose where the user wants to save the csv file. The default location is case export directory.
|
||||
* The action when the Export Csv button is pressed. The file chooser window
|
||||
* will pop up to choose where the user wants to save the csv file. The
|
||||
* default location is case export directory.
|
||||
*
|
||||
* @param evt the action event
|
||||
*/
|
||||
|
||||
@NbBundle.Messages({"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.",
|
||||
"SQLiteViewer.csvExport.title=Export to csv file",
|
||||
"SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"})
|
||||
"SQLiteViewer.csvExport.title=Export to csv file",
|
||||
"SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"})
|
||||
private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCsvButtonActionPerformed
|
||||
Case openCase = Case.getCurrentCase();
|
||||
File caseDirectory = new File(openCase.getExportDirectory());
|
||||
File caseDirectory = new File(openCase.getExportDirectory());
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setDragEnabled(false);
|
||||
fileChooser.setCurrentDirectory(caseDirectory);
|
||||
@ -292,14 +292,14 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
File file = fileChooser.getSelectedFile();
|
||||
if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("csv")) {
|
||||
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this,
|
||||
Bundle.SQLiteViewer_csvExport_confirm_msg(),
|
||||
Bundle.SQLiteViewer_csvExport_title(),
|
||||
Bundle.SQLiteViewer_csvExport_confirm_msg(),
|
||||
Bundle.SQLiteViewer_csvExport_title(),
|
||||
JOptionPane.YES_NO_OPTION)) {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exportTableToCsv(file);
|
||||
}
|
||||
}//GEN-LAST:event_exportCsvButtonActionPerformed
|
||||
@ -328,6 +328,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
public void setFile(AbstractFile file) {
|
||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
sqliteDbFile = file;
|
||||
initReader();
|
||||
processSQLiteFile();
|
||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
@ -343,16 +344,15 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
tablesDropdownList.removeAllItems();
|
||||
numEntriesField.setText("");
|
||||
|
||||
// close DB connection to file
|
||||
if (null != connection) {
|
||||
try {
|
||||
connection.close();
|
||||
connection = null;
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS
|
||||
}
|
||||
try {
|
||||
viewReader.close();
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
//Could not successfully close the reader, nothing we can do to recover.
|
||||
}
|
||||
|
||||
row = new LinkedHashMap<>();
|
||||
pageOfTableRows = new ArrayList<>();
|
||||
currentTableHeader = new ArrayList<>();
|
||||
viewReader = null;
|
||||
sqliteDbFile = null;
|
||||
}
|
||||
|
||||
@ -368,17 +368,10 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
"SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.",
|
||||
"# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",})
|
||||
private void processSQLiteFile() {
|
||||
|
||||
tablesDropdownList.removeAllItems();
|
||||
|
||||
try {
|
||||
String localDiskPath = SqliteUtil.writeAbstractFileToLocalDisk(sqliteDbFile);
|
||||
SqliteUtil.findAndCopySQLiteMetaFile(sqliteDbFile);
|
||||
// Load the SQLite JDBC driver, if necessary.
|
||||
Class.forName("org.sqlite.JDBC"); //NON-NLS
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + localDiskPath); //NON-NLS
|
||||
tablesDropdownList.removeAllItems();
|
||||
|
||||
Collection<String> dbTablesMap = getTables();
|
||||
Collection<String> dbTablesMap = viewReader.getTableNames();
|
||||
if (dbTablesMap.isEmpty()) {
|
||||
tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
|
||||
tablesDropdownList.setEnabled(false);
|
||||
@ -387,46 +380,20 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
tablesDropdownList.addItem(tableName);
|
||||
});
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to initialize JDBC SQLite '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver());
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to get tables from DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
logger.log(Level.WARNING, String.format("Unable to get table names "
|
||||
+ "from sqlite file [%s] with id=[%d].", sqliteDbFile.getName(),
|
||||
sqliteDbFile.getId(), ex.getMessage()));
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase());
|
||||
} catch (IOException | NoCurrentCaseException | TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to create temp copy of DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToExtractFile());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a collection of table names from the SQLite database file.
|
||||
*
|
||||
* @return A collection of table names
|
||||
*/
|
||||
private Collection<String> getTables() throws SQLException {
|
||||
Collection<String> tableNames = new LinkedList<>();
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT name FROM sqlite_master "
|
||||
+ " WHERE type= 'table' ")){
|
||||
while (resultSet.next()) {
|
||||
tableNames.add(resultSet.getString("name")); //NON-NLS
|
||||
}
|
||||
}
|
||||
return tableNames;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"# {0} - tableName",
|
||||
"SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}"
|
||||
})
|
||||
private void selectTable(String tableName) {
|
||||
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT count (*) as count FROM " + "\"" + tableName + "\"")) { //NON-NLS
|
||||
|
||||
numRows = resultSet.getInt("count");
|
||||
try {
|
||||
numRows = viewReader.getRowCount(tableName);
|
||||
numEntriesField.setText(numRows + " entries");
|
||||
|
||||
currPage = 1;
|
||||
@ -442,25 +409,19 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
} else {
|
||||
exportCsvButton.setEnabled(false);
|
||||
nextPageButton.setEnabled(false);
|
||||
|
||||
//Execute a dummy SELECT * statement so that the metadata
|
||||
//contains all column names
|
||||
Map<String, Object> columnRow;
|
||||
try (ResultSet metaDataResultSet = statement.executeQuery(
|
||||
"SELECT * FROM " + "\"" + tableName + "\"")) {
|
||||
//Column names are not found in the metadata of the result set
|
||||
//above.
|
||||
ResultSetMetaData metaData = metaDataResultSet.getMetaData();
|
||||
columnRow = new LinkedHashMap<>();
|
||||
for(int i = 1; i < metaData.getColumnCount(); i++){
|
||||
columnRow.put(metaData.getColumnName(i), "");
|
||||
}
|
||||
|
||||
currentTableHeader = new ArrayList<>();
|
||||
viewReader.read(tableName);
|
||||
Map<String, Object> columnRow = new LinkedHashMap<>();
|
||||
for(int i = 0; i< currentTableHeader.size(); i++){
|
||||
columnRow.put(currentTableHeader.get(i), "");
|
||||
}
|
||||
|
||||
selectedTableView.setupTable(Collections.singletonList(columnRow));
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to load table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
}
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
logger.log(Level.WARNING, String.format("Failed to load table %s " //NON-NLS
|
||||
+ "from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), //NON-NLS
|
||||
sqliteDbFile.getId()), ex.getMessage());
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName));
|
||||
}
|
||||
}
|
||||
@ -468,110 +429,192 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
@NbBundle.Messages({"# {0} - tableName",
|
||||
"SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
|
||||
private void readTable(String tableName, int startRow, int numRowsToRead) {
|
||||
|
||||
try (
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT * FROM " + "\"" + tableName + "\""
|
||||
+ " LIMIT " + Integer.toString(numRowsToRead)
|
||||
+ " OFFSET " + Integer.toString(startRow - 1))) {
|
||||
|
||||
List<Map<String, Object>> rows = resultSetToArrayList(resultSet);
|
||||
if (Objects.nonNull(rows)) {
|
||||
selectedTableView.setupTable(rows);
|
||||
} else {
|
||||
selectedTableView.setupTable(Collections.emptyList());
|
||||
try {
|
||||
//If the table name has changed, then clear our table header. SQLiteTableReader
|
||||
//will also detect the table name has changed and begin reading it as if it
|
||||
//were a brand new table.
|
||||
if (!tableName.equals(prevTableName)) {
|
||||
prevTableName = tableName;
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
currentTableHeader = new ArrayList<>();
|
||||
viewReader.read(tableName, numRowsToRead, startRow - 1);
|
||||
selectedTableView.setupTable(pageOfTableRows);
|
||||
pageOfTableRows = new ArrayList<>();
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
logger.log(Level.WARNING, String.format("Failed to read table %s from DB file '%s' " //NON-NLS
|
||||
+ "(objId=%d) starting at row [%d] and limit [%d]", //NON-NLS
|
||||
tableName, sqliteDbFile.getName(), sqliteDbFile.getId(),
|
||||
startRow - 1, numRowsToRead), ex.getMessage());
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages("SQLiteViewer.BlobNotShown.message=BLOB Data not shown")
|
||||
private List<Map<String, Object>> resultSetToArrayList(ResultSet resultSet) throws SQLException {
|
||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||
int columns = metaData.getColumnCount();
|
||||
ArrayList<Map<String, Object>> rowlist = new ArrayList<>();
|
||||
while (resultSet.next()) {
|
||||
Map<String, Object> row = new LinkedHashMap<>(columns);
|
||||
for (int i = 1; i <= columns; ++i) {
|
||||
if (resultSet.getObject(i) == null) {
|
||||
row.put(metaData.getColumnName(i), "");
|
||||
} else {
|
||||
if (metaData.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) {
|
||||
row.put(metaData.getColumnName(i), Bundle.SQLiteViewer_BlobNotShown_message());
|
||||
} else {
|
||||
row.put(metaData.getColumnName(i), resultSet.getObject(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
rowlist.add(row);
|
||||
}
|
||||
|
||||
return rowlist;
|
||||
/**
|
||||
* Creates a new SQLiteTableReader. This class will iterate through the
|
||||
* table row by row and pass each value to the correct function based on its
|
||||
* data type. For our use, we want to define an action when encountering
|
||||
* column names and an action for all other data types.
|
||||
*/
|
||||
private void initReader() {
|
||||
viewReader = new SQLiteTableReader.Builder(sqliteDbFile)
|
||||
.onColumnNames((columnName) -> {
|
||||
currentTableHeader.add(columnName);
|
||||
})
|
||||
.forAll(getForAllStrategy()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For every database value we encounter on our read of the table do the
|
||||
* following: 1) Get the string representation of the value 2) Collect the
|
||||
* values until we have a full database row. 3) If we have the full row,
|
||||
* write it to the UI.
|
||||
*
|
||||
* rowIndex is purely for indicating if we have read the full row.
|
||||
*
|
||||
* @return Consumer that will perform the actions above. When the
|
||||
* SQLiteTableReader is reading, values will be passed to this
|
||||
* consumer.
|
||||
*/
|
||||
private Consumer<Object> getForAllStrategy() {
|
||||
return new Consumer<Object>() {
|
||||
private int rowIndex = 0;
|
||||
|
||||
@Override
|
||||
public void accept(Object t) {
|
||||
rowIndex++;
|
||||
String objectStr = (t instanceof byte[]) ? "BLOB Data not shown"
|
||||
: Objects.toString(t, "");
|
||||
|
||||
row.put(currentTableHeader.get(rowIndex - 1), objectStr);
|
||||
|
||||
//If we have built up a full database row, then add it to our page
|
||||
//of rows to be displayed in the UI.
|
||||
if (rowIndex == currentTableHeader.size()) {
|
||||
pageOfTableRows.add(row);
|
||||
row = new LinkedHashMap<>();
|
||||
}
|
||||
rowIndex %= currentTableHeader.size();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private int totalColumnCount;
|
||||
|
||||
@NbBundle.Messages({"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
|
||||
"SQLiteViewer.exportTableToCsv.FileName=File name: ",
|
||||
"SQLiteViewer.exportTableToCsv.TableName=Table name: "
|
||||
"SQLiteViewer.exportTableToCsv.FileName=File name: ",
|
||||
"SQLiteViewer.exportTableToCsv.TableName=Table name: "
|
||||
})
|
||||
private void exportTableToCsv(File file) {
|
||||
File csvFile = new File(file.toString() + ".csv");
|
||||
String tableName = (String) this.tablesDropdownList.getSelectedItem();
|
||||
try (
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT * FROM " + "\"" + tableName + "\"")) {
|
||||
List<Map<String, Object>> currentTableRows = resultSetToArrayList(resultSet);
|
||||
|
||||
if (Objects.isNull(currentTableRows) || currentTableRows.isEmpty()) {
|
||||
logger.log(Level.INFO, String.format("The table %s is empty. (objId=%d)", tableName, sqliteDbFile.getId())); //NON-NLS
|
||||
} else {
|
||||
File csvFile;
|
||||
String fileName = file.getName();
|
||||
if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) {
|
||||
csvFile = file;
|
||||
} else {
|
||||
csvFile = new File(file.toString() + ".csv");
|
||||
}
|
||||
|
||||
try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
|
||||
|
||||
out.write((Bundle.SQLiteViewer_exportTableToCsv_FileName() + csvFile.getName() + "\n").getBytes());
|
||||
out.write((Bundle.SQLiteViewer_exportTableToCsv_TableName() + tableName + "\n").getBytes());
|
||||
// Set up the column names
|
||||
Map<String, Object> row = currentTableRows.get(0);
|
||||
StringBuffer header = new StringBuffer();
|
||||
for (Map.Entry<String, Object> col : row.entrySet()) {
|
||||
String colName = col.getKey();
|
||||
if (header.length() > 0) {
|
||||
header.append(',').append(colName);
|
||||
} else {
|
||||
header.append(colName);
|
||||
}
|
||||
}
|
||||
out.write(header.append('\n').toString().getBytes());
|
||||
|
||||
for (Map<String, Object> maps : currentTableRows) {
|
||||
StringBuffer valueLine = new StringBuffer();
|
||||
maps.values().forEach((value) -> {
|
||||
if (valueLine.length() > 0) {
|
||||
valueLine.append(',').append(value.toString());
|
||||
} else {
|
||||
valueLine.append(value.toString());
|
||||
}
|
||||
});
|
||||
out.write(valueLine.append('\n').toString().getBytes());
|
||||
}
|
||||
}
|
||||
try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
|
||||
try (SQLiteTableReader sqliteStream = new SQLiteTableReader.Builder(sqliteDbFile)
|
||||
.onColumnNames(getColumnNameCSVStrategy(out))
|
||||
.forAll(getForAllCSVStrategy(out)).build()) {
|
||||
totalColumnCount = sqliteStream.getColumnCount(tableName);
|
||||
sqliteStream.read(tableName);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to export table %s to file '%s'", tableName, file.getName()), ex); //NON-NLS
|
||||
} catch (IOException | SQLiteTableReaderException | RuntimeException ex) {
|
||||
logger.log(Level.WARNING, String.format("Failed to export table [%s]"
|
||||
+ " to CSV in sqlite file '%s' (objId=%d)", tableName, sqliteDbFile.getName(),
|
||||
sqliteDbFile.getId()), ex.getMessage()); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_exportTableToCsv_write_errText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For every column name we encounter on our read of the table do the
|
||||
* following: 1) Format the name so that it is comma seperated 2) Write the
|
||||
* value to the output stream.
|
||||
*
|
||||
* columnIndex is purely for keeping track of where the column name is in
|
||||
* the table so the value can be correctly formatted.
|
||||
*
|
||||
* @param out Output stream that this database table is being written to.
|
||||
*
|
||||
* @return Consumer that will perform the actions above. When the
|
||||
* SQLiteTableReader is reading, values will be passed to this
|
||||
* consumer.
|
||||
*/
|
||||
private Consumer<String> getColumnNameCSVStrategy(FileOutputStream out) {
|
||||
return new Consumer<String>() {
|
||||
private int columnIndex = 0;
|
||||
|
||||
@Override
|
||||
public void accept(String columnName) {
|
||||
columnIndex++;
|
||||
|
||||
//Format the value to adhere to the format of a CSV file
|
||||
if (columnIndex == 1) {
|
||||
columnName = "\"" + columnName + "\"";
|
||||
} else {
|
||||
columnName = ",\"" + columnName + "\"";
|
||||
}
|
||||
if (columnIndex == totalColumnCount) {
|
||||
columnName += "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
out.write(columnName.getBytes());
|
||||
} catch (IOException ex) {
|
||||
/*
|
||||
* If we can no longer write to the output stream, toss a
|
||||
* runtime exception to get out of iteration. We explicitly
|
||||
* catch this in exportTableToCsv() above.
|
||||
*/
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For every database value we encounter on our read of the table do the
|
||||
* following: 1) Get the string representation of the value 2) Format it so
|
||||
* that it adheres to the CSV format. 3) Write it to the output file.
|
||||
*
|
||||
* rowIndex is purely for keeping track of positioning of the database value
|
||||
* in the row, so that it can be properly formatted.
|
||||
*
|
||||
* @param out Output file
|
||||
*
|
||||
* @return Consumer that will perform the actions above. When the
|
||||
* SQLiteTableReader is reading, values will be passed to this
|
||||
* consumer.
|
||||
*/
|
||||
private Consumer<Object> getForAllCSVStrategy(FileOutputStream out) {
|
||||
return new Consumer<Object>() {
|
||||
private int rowIndex = 0;
|
||||
|
||||
@Override
|
||||
public void accept(Object tableValue) {
|
||||
rowIndex++;
|
||||
//Substitute string representation of blob with placeholder text.
|
||||
//Automatically wrap the value in quotes in case it contains commas.
|
||||
String objectStr = (tableValue instanceof byte[])
|
||||
? "BLOB Data not shown" : Objects.toString(tableValue, "");
|
||||
objectStr = "\"" + objectStr + "\"";
|
||||
|
||||
if (rowIndex > 1) {
|
||||
objectStr = "," + objectStr;
|
||||
}
|
||||
if (rowIndex == totalColumnCount) {
|
||||
objectStr += "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
out.write(objectStr.getBytes());
|
||||
} catch (IOException ex) {
|
||||
/*
|
||||
* If we can no longer write to the output stream, toss a
|
||||
* runtime exception to get out of iteration. We explicitly
|
||||
* catch this in exportTableToCsv() above.
|
||||
*/
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
rowIndex = rowIndex % totalColumnCount;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.contentviewers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Sqlite utility class. Find and copy metafiles, write sqlite abstract files to
|
||||
* temp directory, and generate unique temp directory paths.
|
||||
*/
|
||||
final class SqliteUtil {
|
||||
|
||||
private SqliteUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded implementation of
|
||||
* {@link #findAndCopySQLiteMetaFile(AbstractFile, String) findAndCopySQLiteMetaFile}
|
||||
* , automatically tries to copy -wal and -shm files without needing to know
|
||||
* their existence.
|
||||
*
|
||||
* @param sqliteFile file which has -wal and -shm meta files
|
||||
*
|
||||
* @throws NoCurrentCaseException Case has been closed.
|
||||
* @throws TskCoreException fileManager cannot find AbstractFile
|
||||
* files.
|
||||
* @throws IOException Issue during writing to file.
|
||||
*/
|
||||
public static void findAndCopySQLiteMetaFile(AbstractFile sqliteFile)
|
||||
throws NoCurrentCaseException, TskCoreException, IOException {
|
||||
|
||||
findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-wal");
|
||||
findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-shm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a meta file associated with the give SQLite database. If
|
||||
* found, it copies this file into the temp directory of the current case.
|
||||
*
|
||||
* @param sqliteFile file being processed
|
||||
* @param metaFileName name of meta file to look for
|
||||
*
|
||||
* @throws NoCurrentCaseException Case has been closed.
|
||||
* @throws TskCoreException fileManager cannot find AbstractFile
|
||||
* files.
|
||||
* @throws IOException Issue during writing to file.
|
||||
*/
|
||||
public static void findAndCopySQLiteMetaFile(AbstractFile sqliteFile,
|
||||
String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
|
||||
|
||||
Case openCase = Case.getCurrentCaseThrows();
|
||||
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
|
||||
Services services = new Services(sleuthkitCase);
|
||||
FileManager fileManager = services.getFileManager();
|
||||
|
||||
List<AbstractFile> metaFiles = fileManager.findFiles(
|
||||
sqliteFile.getDataSource(), metaFileName,
|
||||
sqliteFile.getParent().getName());
|
||||
|
||||
if (metaFiles != null) {
|
||||
for (AbstractFile metaFile : metaFiles) {
|
||||
writeAbstractFileToLocalDisk(metaFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the file contents into a unique path in the current case temp
|
||||
* directory.
|
||||
*
|
||||
* @param file AbstractFile from the data source
|
||||
*
|
||||
* @return The path of the file on disk
|
||||
*
|
||||
* @throws IOException Exception writing file contents
|
||||
* @throws NoCurrentCaseException Current case closed during file copying
|
||||
*/
|
||||
public static String writeAbstractFileToLocalDisk(AbstractFile file)
|
||||
throws IOException, NoCurrentCaseException {
|
||||
|
||||
String localDiskPath = getUniqueTempDirectoryPath(file);
|
||||
File localDatabaseFile = new File(localDiskPath);
|
||||
if (!localDatabaseFile.exists()) {
|
||||
ContentUtils.writeToFile(file, localDatabaseFile);
|
||||
}
|
||||
return localDiskPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique local disk path that resides in the temp directory of
|
||||
* the current case.
|
||||
*
|
||||
* @param file The database abstract file
|
||||
*
|
||||
* @return Unique local disk path living in the temp directory of the case
|
||||
*
|
||||
* @throws org.sleuthkit.autopsy.casemodule.NoCurrentCaseException
|
||||
*/
|
||||
public static String getUniqueTempDirectoryPath(AbstractFile file) throws NoCurrentCaseException {
|
||||
return Case.getCurrentCaseThrows().getTempDirectory()
|
||||
+ File.separator + file.getId() + file.getName();
|
||||
}
|
||||
}
|
@ -74,7 +74,8 @@ public final class UserPreferences {
|
||||
public static final String GROUP_ITEMS_IN_TREE_BY_DATASOURCE = "GroupItemsInTreeByDataSource"; //NON-NLS
|
||||
public static final String SHOW_ONLY_CURRENT_USER_TAGS = "ShowOnlyCurrentUserTags";
|
||||
public static final String HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES = "HideCentralRepoCommentsAndOccurrences";
|
||||
|
||||
public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames";
|
||||
|
||||
// Prevent instantiation.
|
||||
private UserPreferences() {
|
||||
}
|
||||
@ -254,6 +255,14 @@ public final class UserPreferences {
|
||||
public static void setHideCentralRepoCommentsAndOccurrences(boolean value) {
|
||||
preferences.putBoolean(HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES, value);
|
||||
}
|
||||
|
||||
public static void setDisplayTranslatedFileNames(boolean value) {
|
||||
preferences.putBoolean(DISPLAY_TRANSLATED_NAMES, value);
|
||||
}
|
||||
|
||||
public static boolean displayTranslatedFileNames() {
|
||||
return preferences.getBoolean(DISPLAY_TRANSLATED_NAMES, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads persisted case database connection info.
|
||||
|
@ -165,30 +165,33 @@ AutopsyOptionsPanel.runtimePanel.border.title=Runtime
|
||||
DataResultPanel.matchLabel.text=Results
|
||||
DataResultPanel.numberOfChildNodesLabel.text=0
|
||||
DataResultPanel.descriptionLabel.text=directoryPath
|
||||
ViewPreferencesPanel.selectFileLabel.text=When selecting a file:
|
||||
ViewPreferencesPanel.globalSettingsPanel.border.title=Global Settings
|
||||
ViewPreferencesPanel.displayTimeLabel.text=When displaying times:
|
||||
ViewPreferencesPanel.hideSlackFilesLabel.text=Hide slack files in the:
|
||||
ViewPreferencesPanel.groupByDataSourceCheckbox.text=Group by data source
|
||||
ViewPreferencesPanel.hideKnownFilesLabel.text=Hide known files (i.e. those in the NIST NSRL) in the:
|
||||
ViewPreferencesPanel.hideOtherUsersTagsCheckbox.text=Tags area in the tree
|
||||
ViewPreferencesPanel.currentCaseSettingsPanel.border.title=Current Case Settings
|
||||
OptionsCategory_Name_View=View
|
||||
OptionsCategory_Keywords_View=View
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.toolTipText=For example, change from Hex to Media when a JPEG is selected.
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.text=Change to the most specific file viewer
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.toolTipText=For example, stay in Hex view when a JPEG is selected.
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.text=Stay on the same file viewer
|
||||
ViewPreferencesPanel.useLocalTimeRadioButton.text=Use local time zone
|
||||
ViewPreferencesPanel.dataSourcesHideKnownCheckbox.text=Data Sources area (the directory hierarchy)
|
||||
ViewPreferencesPanel.viewsHideKnownCheckbox.text=Views area
|
||||
ViewPreferencesPanel.dataSourcesHideSlackCheckbox.text=Data Sources area (the directory hierarchy)
|
||||
ViewPreferencesPanel.viewsHideSlackCheckbox.text=Views area
|
||||
ViewPreferencesPanel.currentSessionSettingsPanel.border.title=Current Session Settings
|
||||
ViewPreferencesPanel.hideRejectedResultsCheckbox.text=Hide rejected results
|
||||
ViewPreferencesPanel.hideOtherUsersTagsLabel.text=Hide other users' tags in the:
|
||||
ViewPreferencesPanel.centralRepoLabel.text=Do not use Central Repository for:
|
||||
ViewPreferencesPanel.commentsOccurencesColumnsCheckbox.text=C(omments) and O(ccurences) columns to reduce loading times
|
||||
ViewPreferencesPanel.deletedFilesLimitCheckbox.text=Limit to 10,000
|
||||
ViewPreferencesPanel.translateTextLabel.text=Translate text in the:
|
||||
ViewPreferencesPanel.globalSettingsPanel.border.title=Global Settings
|
||||
ViewPreferencesPanel.translateNamesInTableRadioButton.text=Table
|
||||
ViewPreferencesPanel.deletedFilesLimitLabel.text=Limit number of deleted files displayed:
|
||||
ViewPreferencesPanel.deletedFilesLimitCheckbox.text=Limit to 10,000
|
||||
ViewPreferencesPanel.commentsOccurencesColumnsCheckbox.text=C(omments) and O(ccurences) columns
|
||||
ViewPreferencesPanel.centralRepoLabel.text=Do not use Central Repository for:
|
||||
ViewPreferencesPanel.hideOtherUsersTagsLabel.text=Hide other users' tags in the:
|
||||
ViewPreferencesPanel.hideOtherUsersTagsCheckbox.text=Tags area in the tree
|
||||
ViewPreferencesPanel.useAnotherTimeRadioButton.text=Use another time zone
|
||||
ViewPreferencesPanel.useLocalTimeRadioButton.text=Use local time zone
|
||||
ViewPreferencesPanel.displayTimeLabel.text=When displaying times:
|
||||
ViewPreferencesPanel.viewsHideSlackCheckbox.text=Views area
|
||||
ViewPreferencesPanel.dataSourcesHideSlackCheckbox.text=Data Sources area (the directory hierarchy)
|
||||
ViewPreferencesPanel.hideSlackFilesLabel.text=Hide slack files in the:
|
||||
ViewPreferencesPanel.viewsHideKnownCheckbox.text=Views area
|
||||
ViewPreferencesPanel.dataSourcesHideKnownCheckbox.text=Data Sources area (the directory hierarchy)
|
||||
ViewPreferencesPanel.hideKnownFilesLabel.text=Hide known files (i.e. those in the NIST NSRL) in the:
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.toolTipText=For example, stay in Hex view when a JPEG is selected.
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.text=Stay on the same file viewer
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.toolTipText=For example, change from Hex to Media when a JPEG is selected.
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.text=Change to the most specific file viewer
|
||||
ViewPreferencesPanel.selectFileLabel.text=When selecting a file:
|
||||
ViewPreferencesPanel.commentsOccurencesColumnWrapAroundText.text=to reduce loading times
|
||||
|
@ -121,12 +121,12 @@ DataResultPanel.matchLabel.text=\u7d50\u679c
|
||||
DataResultPanel.numberOfChildNodesLabel.text=0
|
||||
DataResultPanel.descriptionLabel.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30d1\u30b9
|
||||
ViewPreferencesPanel.selectFileLabel.text=\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e\u3059\u308b\u5834\u5408\uff1a
|
||||
ViewPreferencesPanel.useLocalTimeRadioButton.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3\u3092\u4f7f\u7528
|
||||
ViewPreferencesPanel.displayTimeLabel.text=\u6642\u9593\u3092\u8868\u793a\u3059\u308b\u5834\u5408\uff1a
|
||||
ViewPreferencesPanel.viewsHideKnownCheckbox.text=\u30d3\u30e5\u30fc\u30a8\u30ea\u30a2
|
||||
ViewPreferencesPanel.dataSourcesHideKnownCheckbox.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30a8\u30ea\u30a2\uff08\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u968e\u5c64\uff09
|
||||
ViewPreferencesPanel.hideKnownFilesLabel.text=\u65e2\u77e5\u30d5\u30a1\u30a4\u30eb\uff08NIST NSRL\u5185\u306e\uff09\u3092\u6b21\u306b\u96a0\u3059\uff1a
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.toolTipText=\u4f8b\u3048\u3070\u3001JPEG\u304c\u9078\u629e\u3055\u308c\u305f\u5834\u5408\u306b\u305d\u306e\u307e\u307eHEX\u30d3\u30e5\u30fc\u3092\u4f7f\u7528\u3002
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.text=\u305d\u306e\u307e\u307e\u540c\u3058\u30d5\u30a1\u30a4\u30eb\u30d3\u30e5\u30fc\u30a2\u3092\u4f7f\u7528
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.toolTipText=\u4f8b\u3048\u3070\u3001JPEG\u304c\u9078\u629e\u3055\u308c\u305f\u5834\u5408\u306b\u306fHEX\u304b\u3089\u30e1\u30c7\u30a3\u30a2\u306b\u5909\u66f4\u3059\u308b\u3002
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.text=\u6700\u3082\u5c02\u9580\u7684\u306a\u30d5\u30a1\u30a4\u30eb\u30d3\u30e5\u30fc\u30a2\u306b\u5909\u66f4
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.text=\u305d\u306e\u307e\u307e\u540c\u3058\u30d5\u30a1\u30a4\u30eb\u30d3\u30e5\u30fc\u30a2\u3092\u4f7f\u7528
|
||||
ViewPreferencesPanel.keepCurrentViewerRadioButton.toolTipText=\u4f8b\u3048\u3070\u3001JPEG\u304c\u9078\u629e\u3055\u308c\u305f\u5834\u5408\u306b\u305d\u306e\u307e\u307eHEX\u30d3\u30e5\u30fc\u3092\u4f7f\u7528\u3002
|
||||
ViewPreferencesPanel.useLocalTimeRadioButton.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3\u3092\u4f7f\u7528
|
||||
ViewPreferencesPanel.dataSourcesHideKnownCheckbox.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30a8\u30ea\u30a2\uff08\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u968e\u5c64\uff09
|
||||
ViewPreferencesPanel.viewsHideKnownCheckbox.text=\u30d3\u30e5\u30fc\u30a8\u30ea\u30a2
|
||||
|
@ -1,6 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[625, 465]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
@ -32,7 +37,7 @@
|
||||
<Border info="null"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[625, 452]"/>
|
||||
<Dimension value="[625, 465]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
@ -41,7 +46,7 @@
|
||||
<Container class="javax.swing.JPanel" name="viewPreferencesPanel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[625, 452]"/>
|
||||
<Dimension value="[625, 465]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
@ -90,73 +95,70 @@
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="centralRepoLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="135" max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" min="-2" pref="272" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="hideOtherUsersTagsLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="hideKnownFilesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" alignment="0" groupAlignment="1" attributes="0">
|
||||
<Group type="103" alignment="1" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="dataSourcesHideSlackCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="viewsHideSlackCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="hideSlackFilesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="dataSourcesHideKnownCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="viewsHideKnownCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Component id="commentsOccurencesColumnsCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="32" max="-2" attributes="0"/>
|
||||
<Component id="commentsOccurencesColumnWrapAroundText" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="displayTimeLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="selectFileLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="translateTextLabel" alignment="0" min="-2" pref="120" max="-2" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="10" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="keepCurrentViewerRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="useBestViewerRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="useLocalTimeRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="useAnotherTimeRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="translateNamesInTableRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="deletedFilesLimitLabel" min="-2" pref="215" max="-2" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="10" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="commentsOccurencesColumnsCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="hideOtherUsersTagsCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="deletedFilesLimitCheckbox" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="399" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="deletedFilesLimitCheckbox" pref="567" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="centralRepoLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="135" max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" min="-2" pref="272" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="hideKnownFilesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" alignment="0" groupAlignment="1" attributes="0">
|
||||
<Group type="103" alignment="1" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="dataSourcesHideSlackCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="viewsHideSlackCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="hideSlackFilesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="dataSourcesHideKnownCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="viewsHideKnownCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="displayTimeLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="10" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="keepCurrentViewerRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="useBestViewerRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="useLocalTimeRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="useAnotherTimeRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="selectFileLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="hideOtherUsersTagsLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="deletedFilesLimitLabel" alignment="0" min="-2" pref="215" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -183,9 +185,11 @@
|
||||
<Component id="hideOtherUsersTagsCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="centralRepoLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
|
||||
<Component id="commentsOccurencesColumnsCheckbox" min="-2" pref="18" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="commentsOccurencesColumnsCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="commentsOccurencesColumnWrapAroundText" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="11" max="-2" attributes="0"/>
|
||||
<Component id="deletedFilesLimitLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
@ -202,11 +206,15 @@
|
||||
<Component id="useAnotherTimeRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" min="-2" pref="67" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="translateTextLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="translateNamesInTableRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="deletedFilesLimitCheckbox" min="-2" pref="33" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
<Component id="deletedFilesLimitCheckbox" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="8" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -343,16 +351,6 @@
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="commentsOccurencesColumnsCheckbox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.commentsOccurencesColumnsCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="commentsOccurencesColumnsCheckboxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="centralRepoLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
@ -360,6 +358,17 @@
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="commentsOccurencesColumnsCheckbox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.commentsOccurencesColumnsCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="horizontalAlignment" type="int" value="11"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="commentsOccurencesColumnsCheckboxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="deletedFilesLimitCheckbox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
@ -399,6 +408,30 @@
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="translateTextLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.translateTextLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="commentsOccurencesColumnWrapAroundText">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.commentsOccurencesColumnWrapAroundText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JRadioButton" name="translateNamesInTableRadioButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.translateNamesInTableRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="translateNamesInTableRadioButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="currentCaseSettingsPanel">
|
||||
|
@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.corecomponents;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
import javax.swing.JPanel;
|
||||
@ -29,6 +31,7 @@ import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
|
||||
import org.sleuthkit.autopsy.deletedFiles.DeletedFilePreferences;
|
||||
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
|
||||
import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
|
||||
|
||||
/**
|
||||
* Panel for configuring view preferences.
|
||||
@ -46,7 +49,11 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
public ViewPreferencesPanel(boolean immediateUpdates) {
|
||||
initComponents();
|
||||
this.immediateUpdates = immediateUpdates;
|
||||
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> {
|
||||
//disable when case is closed, enable when case is open
|
||||
currentCaseSettingsPanel.setEnabled(evt.getNewValue() != null);
|
||||
groupByDataSourceCheckbox.setEnabled(evt.getNewValue() != null);
|
||||
});
|
||||
this.timeZoneList.setListData(TimeZoneUtils.createTimeZoneList().stream().toArray(String[]::new));
|
||||
}
|
||||
|
||||
@ -70,17 +77,26 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
viewsHideSlackCheckbox.setSelected(UserPreferences.hideSlackFilesInViewsTree());
|
||||
|
||||
commentsOccurencesColumnsCheckbox.setEnabled(EamDbUtil.useCentralRepo());
|
||||
commentsOccurencesColumnWrapAroundText.setEnabled(EamDbUtil.useCentralRepo());
|
||||
commentsOccurencesColumnsCheckbox.setSelected(UserPreferences.hideCentralRepoCommentsAndOccurrences());
|
||||
|
||||
|
||||
hideOtherUsersTagsCheckbox.setSelected(UserPreferences.showOnlyCurrentUserTags());
|
||||
deletedFilesLimitCheckbox.setSelected(DeletedFilePreferences.getDefault().getShouldLimitDeletedFiles());
|
||||
translateNamesInTableRadioButton.setSelected(UserPreferences.displayTranslatedFileNames());
|
||||
|
||||
TextTranslationService tts = TextTranslationService.getInstance();
|
||||
translateNamesInTableRadioButton.setEnabled(tts.hasProvider());
|
||||
|
||||
// Current Case Settings
|
||||
boolean caseIsOpen = Case.isCaseOpen();
|
||||
currentCaseSettingsPanel.setEnabled(caseIsOpen);
|
||||
groupByDataSourceCheckbox.setEnabled(caseIsOpen);
|
||||
|
||||
hideOtherUsersTagsCheckbox.setSelected(UserPreferences.showOnlyCurrentUserTags());
|
||||
groupByDataSourceCheckbox.setSelected(Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true));
|
||||
if (caseIsOpen) {
|
||||
groupByDataSourceCheckbox.setSelected(Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true));
|
||||
} else {
|
||||
groupByDataSourceCheckbox.setSelected(false);
|
||||
}
|
||||
|
||||
// Current Session Settings
|
||||
hideRejectedResultsCheckbox.setSelected(DirectoryTreeTopComponent.getDefault().getShowRejectedResults() == false);
|
||||
@ -99,6 +115,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCheckbox.isSelected());
|
||||
UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected());
|
||||
UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected());
|
||||
UserPreferences.setDisplayTranslatedFileNames(translateNamesInTableRadioButton.isSelected());
|
||||
|
||||
storeGroupItemsInTreeByDataSource();
|
||||
|
||||
@ -147,21 +164,26 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
useAnotherTimeRadioButton = new javax.swing.JRadioButton();
|
||||
hideOtherUsersTagsCheckbox = new javax.swing.JCheckBox();
|
||||
hideOtherUsersTagsLabel = new javax.swing.JLabel();
|
||||
commentsOccurencesColumnsCheckbox = new javax.swing.JCheckBox();
|
||||
centralRepoLabel = new javax.swing.JLabel();
|
||||
commentsOccurencesColumnsCheckbox = new javax.swing.JCheckBox();
|
||||
deletedFilesLimitCheckbox = new javax.swing.JCheckBox();
|
||||
deletedFilesLimitLabel = new javax.swing.JLabel();
|
||||
jScrollPane1 = new javax.swing.JScrollPane();
|
||||
timeZoneList = new javax.swing.JList<>();
|
||||
translateTextLabel = new javax.swing.JLabel();
|
||||
commentsOccurencesColumnWrapAroundText = new javax.swing.JLabel();
|
||||
translateNamesInTableRadioButton = new javax.swing.JRadioButton();
|
||||
currentCaseSettingsPanel = new javax.swing.JPanel();
|
||||
groupByDataSourceCheckbox = new javax.swing.JCheckBox();
|
||||
currentSessionSettingsPanel = new javax.swing.JPanel();
|
||||
hideRejectedResultsCheckbox = new javax.swing.JCheckBox();
|
||||
|
||||
viewPreferencesScrollPane.setBorder(null);
|
||||
viewPreferencesScrollPane.setPreferredSize(new java.awt.Dimension(625, 452));
|
||||
setPreferredSize(new java.awt.Dimension(625, 465));
|
||||
|
||||
viewPreferencesPanel.setPreferredSize(new java.awt.Dimension(625, 452));
|
||||
viewPreferencesScrollPane.setBorder(null);
|
||||
viewPreferencesScrollPane.setPreferredSize(new java.awt.Dimension(625, 465));
|
||||
|
||||
viewPreferencesPanel.setPreferredSize(new java.awt.Dimension(625, 465));
|
||||
|
||||
globalSettingsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.globalSettingsPanel.border.title"))); // NOI18N
|
||||
|
||||
@ -240,15 +262,16 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(hideOtherUsersTagsLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.hideOtherUsersTagsLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(centralRepoLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.centralRepoLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(commentsOccurencesColumnsCheckbox, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.commentsOccurencesColumnsCheckbox.text")); // NOI18N
|
||||
commentsOccurencesColumnsCheckbox.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
|
||||
commentsOccurencesColumnsCheckbox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
commentsOccurencesColumnsCheckboxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(centralRepoLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.centralRepoLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(deletedFilesLimitCheckbox, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.deletedFilesLimitCheckbox.text")); // NOI18N
|
||||
deletedFilesLimitCheckbox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
@ -265,6 +288,17 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
});
|
||||
jScrollPane1.setViewportView(timeZoneList);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(translateTextLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.translateTextLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(commentsOccurencesColumnWrapAroundText, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.commentsOccurencesColumnWrapAroundText.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(translateNamesInTableRadioButton, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.translateNamesInTableRadioButton.text")); // NOI18N
|
||||
translateNamesInTableRadioButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
translateNamesInTableRadioButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout globalSettingsPanelLayout = new javax.swing.GroupLayout(globalSettingsPanel);
|
||||
globalSettingsPanel.setLayout(globalSettingsPanelLayout);
|
||||
globalSettingsPanelLayout.setHorizontalGroup(
|
||||
@ -273,52 +307,52 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
.addContainerGap()
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(commentsOccurencesColumnsCheckbox)
|
||||
.addComponent(hideOtherUsersTagsCheckbox)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addComponent(deletedFilesLimitCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGap(399, 399, 399))))
|
||||
.addComponent(centralRepoLabel)
|
||||
.addGap(135, 135, 135)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 272, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addComponent(hideOtherUsersTagsLabel)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addComponent(centralRepoLabel)
|
||||
.addGap(135, 135, 135)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 272, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addComponent(hideKnownFilesLabel)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(hideKnownFilesLabel)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(dataSourcesHideSlackCheckbox)
|
||||
.addComponent(viewsHideSlackCheckbox)))
|
||||
.addComponent(hideSlackFilesLabel))
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(dataSourcesHideKnownCheckbox)
|
||||
.addComponent(viewsHideKnownCheckbox)))))
|
||||
.addGap(18, 18, 18)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(displayTimeLabel)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(keepCurrentViewerRadioButton)
|
||||
.addComponent(useBestViewerRadioButton)
|
||||
.addComponent(useLocalTimeRadioButton)
|
||||
.addComponent(useAnotherTimeRadioButton)))
|
||||
.addComponent(selectFileLabel)))
|
||||
.addComponent(hideOtherUsersTagsLabel)
|
||||
.addComponent(deletedFilesLimitLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(0, 0, Short.MAX_VALUE)))
|
||||
.addContainerGap())))
|
||||
.addComponent(dataSourcesHideSlackCheckbox)
|
||||
.addComponent(viewsHideSlackCheckbox)))
|
||||
.addComponent(hideSlackFilesLabel))
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(dataSourcesHideKnownCheckbox)
|
||||
.addComponent(viewsHideKnownCheckbox))))
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addComponent(commentsOccurencesColumnsCheckbox))
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(32, 32, 32)
|
||||
.addComponent(commentsOccurencesColumnWrapAroundText)))
|
||||
.addGap(18, 18, 18)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(displayTimeLabel)
|
||||
.addComponent(selectFileLabel)
|
||||
.addComponent(translateTextLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(keepCurrentViewerRadioButton)
|
||||
.addComponent(useBestViewerRadioButton)
|
||||
.addComponent(useLocalTimeRadioButton)
|
||||
.addComponent(useAnotherTimeRadioButton)
|
||||
.addComponent(translateNamesInTableRadioButton)))))
|
||||
.addComponent(deletedFilesLimitLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(hideOtherUsersTagsCheckbox)
|
||||
.addComponent(deletedFilesLimitCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, 567, Short.MAX_VALUE))))
|
||||
.addContainerGap())
|
||||
);
|
||||
globalSettingsPanelLayout.setVerticalGroup(
|
||||
globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
@ -343,9 +377,11 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
.addComponent(hideOtherUsersTagsCheckbox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(centralRepoLabel)
|
||||
.addGap(3, 3, 3)
|
||||
.addComponent(commentsOccurencesColumnsCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(commentsOccurencesColumnsCheckbox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(commentsOccurencesColumnWrapAroundText)
|
||||
.addGap(11, 11, 11)
|
||||
.addComponent(deletedFilesLimitLabel))
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addComponent(selectFileLabel)
|
||||
@ -360,10 +396,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(useAnotherTimeRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(translateTextLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(translateNamesInTableRadioButton)))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(deletedFilesLimitCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(0, 0, 0))
|
||||
.addComponent(deletedFilesLimitCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(8, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
currentCaseSettingsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.currentCaseSettingsPanel.border.title"))); // NOI18N
|
||||
@ -453,88 +493,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void useBestViewerRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRadioButtonActionPerformed
|
||||
useBestViewerRadioButton.setSelected(true);
|
||||
keepCurrentViewerRadioButton.setSelected(false);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setKeepPreferredContentViewer(false);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_useBestViewerRadioButtonActionPerformed
|
||||
|
||||
private void keepCurrentViewerRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keepCurrentViewerRadioButtonActionPerformed
|
||||
useBestViewerRadioButton.setSelected(false);
|
||||
keepCurrentViewerRadioButton.setSelected(true);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setKeepPreferredContentViewer(true);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_keepCurrentViewerRadioButtonActionPerformed
|
||||
|
||||
private void useLocalTimeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useLocalTimeRadioButtonActionPerformed
|
||||
useLocalTimeRadioButton.setSelected(true);
|
||||
useAnotherTimeRadioButton.setSelected(false);
|
||||
timeZoneList.setEnabled(false);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setDisplayTimesInLocalTime(true);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_useLocalTimeRadioButtonActionPerformed
|
||||
|
||||
private void useAnotherTimeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useAnotherTimeRadioButtonActionPerformed
|
||||
useLocalTimeRadioButton.setSelected(false);
|
||||
useAnotherTimeRadioButton.setSelected(true);
|
||||
timeZoneList.setEnabled(true);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setDisplayTimesInLocalTime(false);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_useAnotherTimeRadioButtonActionPerformed
|
||||
|
||||
private void dataSourcesHideKnownCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideKnownCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideKnownFilesInDataSourcesTree(dataSourcesHideKnownCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_dataSourcesHideKnownCheckboxActionPerformed
|
||||
|
||||
private void viewsHideKnownCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideKnownCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideKnownFilesInViewsTree(viewsHideKnownCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_viewsHideKnownCheckboxActionPerformed
|
||||
|
||||
private void dataSourcesHideSlackCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideSlackCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideSlackFilesInDataSourcesTree(dataSourcesHideSlackCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_dataSourcesHideSlackCheckboxActionPerformed
|
||||
|
||||
private void viewsHideSlackCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideSlackCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_viewsHideSlackCheckboxActionPerformed
|
||||
|
||||
private void hideOtherUsersTagsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hideOtherUsersTagsCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_hideOtherUsersTagsCheckboxActionPerformed
|
||||
|
||||
private void groupByDataSourceCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_groupByDataSourceCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
storeGroupItemsInTreeByDataSource();
|
||||
@ -551,21 +509,13 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
}
|
||||
}//GEN-LAST:event_hideRejectedResultsCheckboxActionPerformed
|
||||
|
||||
private void commentsOccurencesColumnsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_commentsOccurencesColumnsCheckboxActionPerformed
|
||||
private void translateNamesInTableRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_translateNamesInTableRadioButtonActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected());
|
||||
UserPreferences.setDisplayTranslatedFileNames(translateNamesInTableRadioButton.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_commentsOccurencesColumnsCheckboxActionPerformed
|
||||
|
||||
private void deletedFilesLimitCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deletedFilesLimitCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
DeletedFilePreferences.getDefault().setShouldLimitDeletedFiles(deletedFilesLimitCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_deletedFilesLimitCheckboxActionPerformed
|
||||
}//GEN-LAST:event_translateNamesInTableRadioButtonActionPerformed
|
||||
|
||||
private void timeZoneListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_timeZoneListValueChanged
|
||||
if (immediateUpdates && useAnotherTimeRadioButton.isSelected()) {
|
||||
@ -575,9 +525,108 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
}
|
||||
}//GEN-LAST:event_timeZoneListValueChanged
|
||||
|
||||
private void deletedFilesLimitCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deletedFilesLimitCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
DeletedFilePreferences.getDefault().setShouldLimitDeletedFiles(deletedFilesLimitCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_deletedFilesLimitCheckboxActionPerformed
|
||||
|
||||
private void commentsOccurencesColumnsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_commentsOccurencesColumnsCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_commentsOccurencesColumnsCheckboxActionPerformed
|
||||
|
||||
private void hideOtherUsersTagsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hideOtherUsersTagsCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_hideOtherUsersTagsCheckboxActionPerformed
|
||||
|
||||
private void useAnotherTimeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useAnotherTimeRadioButtonActionPerformed
|
||||
useLocalTimeRadioButton.setSelected(false);
|
||||
useAnotherTimeRadioButton.setSelected(true);
|
||||
timeZoneList.setEnabled(true);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setDisplayTimesInLocalTime(false);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_useAnotherTimeRadioButtonActionPerformed
|
||||
|
||||
private void useLocalTimeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useLocalTimeRadioButtonActionPerformed
|
||||
useLocalTimeRadioButton.setSelected(true);
|
||||
useAnotherTimeRadioButton.setSelected(false);
|
||||
timeZoneList.setEnabled(false);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setDisplayTimesInLocalTime(true);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_useLocalTimeRadioButtonActionPerformed
|
||||
|
||||
private void viewsHideSlackCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideSlackCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_viewsHideSlackCheckboxActionPerformed
|
||||
|
||||
private void dataSourcesHideSlackCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideSlackCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideSlackFilesInDataSourcesTree(dataSourcesHideSlackCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_dataSourcesHideSlackCheckboxActionPerformed
|
||||
|
||||
private void viewsHideKnownCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideKnownCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideKnownFilesInViewsTree(viewsHideKnownCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_viewsHideKnownCheckboxActionPerformed
|
||||
|
||||
private void dataSourcesHideKnownCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideKnownCheckboxActionPerformed
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setHideKnownFilesInDataSourcesTree(dataSourcesHideKnownCheckbox.isSelected());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_dataSourcesHideKnownCheckboxActionPerformed
|
||||
|
||||
private void keepCurrentViewerRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keepCurrentViewerRadioButtonActionPerformed
|
||||
useBestViewerRadioButton.setSelected(false);
|
||||
keepCurrentViewerRadioButton.setSelected(true);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setKeepPreferredContentViewer(true);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_keepCurrentViewerRadioButtonActionPerformed
|
||||
|
||||
private void useBestViewerRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRadioButtonActionPerformed
|
||||
useBestViewerRadioButton.setSelected(true);
|
||||
keepCurrentViewerRadioButton.setSelected(false);
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setKeepPreferredContentViewer(false);
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_useBestViewerRadioButtonActionPerformed
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel centralRepoLabel;
|
||||
private javax.swing.JLabel commentsOccurencesColumnWrapAroundText;
|
||||
private javax.swing.JCheckBox commentsOccurencesColumnsCheckbox;
|
||||
private javax.swing.JPanel currentCaseSettingsPanel;
|
||||
private javax.swing.JPanel currentSessionSettingsPanel;
|
||||
@ -597,6 +646,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
private javax.swing.JRadioButton keepCurrentViewerRadioButton;
|
||||
private javax.swing.JLabel selectFileLabel;
|
||||
private javax.swing.JList<String> timeZoneList;
|
||||
private javax.swing.JRadioButton translateNamesInTableRadioButton;
|
||||
private javax.swing.JLabel translateTextLabel;
|
||||
private javax.swing.JRadioButton useAnotherTimeRadioButton;
|
||||
private javax.swing.JRadioButton useBestViewerRadioButton;
|
||||
private javax.swing.JRadioButton useLocalTimeRadioButton;
|
||||
@ -605,4 +656,4 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
private javax.swing.JCheckBox viewsHideKnownCheckbox;
|
||||
private javax.swing.JCheckBox viewsHideSlackCheckbox;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
}
|
561
Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReader.java
Executable file
561
Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReader.java
Executable file
@ -0,0 +1,561 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.coreutils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Reads row by row through SQLite tables and performs user-defined actions on
|
||||
* the row values. Table values are processed by data type. Users configure
|
||||
* these actions for certain data types in the Builder. Example usage:
|
||||
*
|
||||
* SQLiteTableReader reader = new SQLiteTableReader.Builder(file)
|
||||
* .onInteger((i)
|
||||
* -> { System.out.println(i); })
|
||||
* .build();
|
||||
*
|
||||
* reader.read(tableName);
|
||||
*
|
||||
* or
|
||||
*
|
||||
* SQLiteTableReader reader = new SQLiteTableReader.Builder(file)
|
||||
* .onInteger(new Consumer<Integer>() {
|
||||
* (atSymbol)Override public void accept(Integer i) {
|
||||
* System.out.println(i);
|
||||
* }
|
||||
* }).build();
|
||||
*
|
||||
* reader.reader(tableName);
|
||||
*
|
||||
* Invocation of read(String tableName) reads row by row. When an Integer is
|
||||
* encountered, its value will be passed to the Consumer that was defined above.
|
||||
*/
|
||||
public class SQLiteTableReader implements AutoCloseable {
|
||||
|
||||
/**
|
||||
* Builder patten for configuring SQLiteTableReader instances.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final AbstractFile file;
|
||||
|
||||
private Consumer<String> onColumnNameAction;
|
||||
private Consumer<String> onStringAction;
|
||||
private Consumer<Long> onLongAction;
|
||||
private Consumer<Integer> onIntegerAction;
|
||||
private Consumer<Double> onFloatAction;
|
||||
private Consumer<byte[]> onBlobAction;
|
||||
private Consumer<Object> forAllAction;
|
||||
|
||||
static <T> Consumer<T> doNothing() {
|
||||
return NOOP -> {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Builder for this abstract file.
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public Builder(AbstractFile file) {
|
||||
this.file = file;
|
||||
|
||||
this.onColumnNameAction = Builder.doNothing();
|
||||
this.onStringAction = Builder.doNothing();
|
||||
this.onLongAction = Builder.doNothing();
|
||||
this.onIntegerAction = Builder.doNothing();
|
||||
this.onFloatAction = Builder.doNothing();
|
||||
this.onBlobAction = Builder.doNothing();
|
||||
this.forAllAction = Builder.doNothing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function to do on column names. Column names will be read
|
||||
* from left to right.
|
||||
*
|
||||
* @param action Consumer of column name strings
|
||||
*
|
||||
* @return Builder reference
|
||||
*/
|
||||
public Builder onColumnNames(Consumer<String> action) {
|
||||
this.onColumnNameAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function to do when encountering a database value that is
|
||||
* of java type String.
|
||||
*
|
||||
* @param action Consumer of strings
|
||||
*
|
||||
* @return Builder reference
|
||||
*/
|
||||
public Builder onString(Consumer<String> action) {
|
||||
this.onStringAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function to do when encountering a database value that is
|
||||
* of java type Integer.
|
||||
*
|
||||
* @param action Consumer of integer
|
||||
*
|
||||
* @return Builder reference
|
||||
*/
|
||||
public Builder onInteger(Consumer<Integer> action) {
|
||||
this.onIntegerAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function to do when encountering a database value that is
|
||||
* of java type Double.
|
||||
*
|
||||
* @param action Consumer of doubles
|
||||
*
|
||||
* @return Builder reference
|
||||
*/
|
||||
public Builder onFloat(Consumer<Double> action) {
|
||||
this.onFloatAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function to do when encountering a database value that is
|
||||
* of java type Long.
|
||||
*
|
||||
* @param action Consumer of longs
|
||||
*
|
||||
* @return Builder reference
|
||||
*/
|
||||
public Builder onLong(Consumer<Long> action) {
|
||||
this.onLongAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function to do when encountering a database value that is
|
||||
* of java type byte[] aka blob.
|
||||
*
|
||||
* @param action Consumer of blobs
|
||||
*
|
||||
* @return Builder reference
|
||||
*/
|
||||
public Builder onBlob(Consumer<byte[]> action) {
|
||||
this.onBlobAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function to do when encountering any database value,
|
||||
* regardless of type. This function only captures database values, not
|
||||
* column names.
|
||||
*
|
||||
* @param action Consumer of objects
|
||||
*
|
||||
* @return Builder reference
|
||||
*/
|
||||
public Builder forAll(Consumer<Object> action) {
|
||||
this.forAllAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SQLiteTableReader instance given this Builder
|
||||
* configuration.
|
||||
*
|
||||
* @return SQLiteTableReader instance
|
||||
*/
|
||||
public SQLiteTableReader build() {
|
||||
return new SQLiteTableReader(this);
|
||||
}
|
||||
}
|
||||
|
||||
private final AbstractFile file;
|
||||
private final Builder builder;
|
||||
|
||||
private static final String SELECT_ALL_QUERY = "SELECT * FROM \"%s\"";
|
||||
private static final Logger logger = Logger.getLogger(SQLiteTableReader.class.getName());
|
||||
|
||||
private Connection conn;
|
||||
private PreparedStatement statement;
|
||||
private ResultSet queryResults;
|
||||
private ResultSetMetaData currentMetadata;
|
||||
|
||||
//Iteration state
|
||||
private int currRowColumnIndex;
|
||||
private int columnNameIndex;
|
||||
private int totalColumnCount;
|
||||
private boolean unfinishedRow;
|
||||
private boolean liveResultSet;
|
||||
private String prevTableName;
|
||||
|
||||
/**
|
||||
* Holds reference to the builder instance so that we can use its actions
|
||||
* during iteration.
|
||||
*/
|
||||
private SQLiteTableReader(Builder builder) {
|
||||
this.builder = builder;
|
||||
this.file = builder.file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all table names from the database.
|
||||
*
|
||||
* @return List of all table names found while querying the sqlite_master
|
||||
* table
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
public List<String> getTableNames() throws SQLiteTableReaderException {
|
||||
ensureOpen();
|
||||
try (ResultSet tableNameResult = conn.createStatement()
|
||||
.executeQuery("SELECT name FROM sqlite_master "
|
||||
+ " WHERE type= 'table' ")) {
|
||||
List<String> tableNames = new ArrayList<>();
|
||||
while (tableNameResult.next()) {
|
||||
tableNames.add(tableNameResult.getString("name")); //NON-NLS
|
||||
}
|
||||
return tableNames;
|
||||
} catch (SQLException ex) {
|
||||
throw new SQLiteTableReaderException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the row count.
|
||||
*
|
||||
* @param tableName Source table to count
|
||||
*
|
||||
* @return Count as an integer
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
public int getRowCount(String tableName) throws SQLiteTableReaderException {
|
||||
ensureOpen();
|
||||
try (ResultSet countResult = conn.createStatement()
|
||||
.executeQuery("SELECT count (*) as count FROM "
|
||||
+ "\"" + tableName + "\"")) {
|
||||
return countResult.getInt("count");
|
||||
} catch (SQLException ex) {
|
||||
throw new SQLiteTableReaderException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the column count of the table.
|
||||
*
|
||||
* @param tableName Source table to count
|
||||
*
|
||||
* @return Count as an integer
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
public int getColumnCount(String tableName) throws SQLiteTableReaderException {
|
||||
ensureOpen();
|
||||
try (ResultSet columnCount = conn.createStatement()
|
||||
.executeQuery(String.format(SELECT_ALL_QUERY, tableName))) {
|
||||
return columnCount.getMetaData().getColumnCount();
|
||||
} catch (SQLException ex) {
|
||||
throw new SQLiteTableReaderException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads column names and values from the table. Only actions that were
|
||||
* configured in the Builder will be invoked during iteration. Iteration
|
||||
* will stop when the table read has completed or an exception was
|
||||
* encountered.
|
||||
*
|
||||
* @param tableName Source table to read
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
public void read(String tableName) throws SQLiteTableReaderException {
|
||||
readHelper(String.format(SELECT_ALL_QUERY, tableName), () -> false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads column names and values from the table. Only actions that were
|
||||
* configured in the Builder will be invoked during iteration. Iteration
|
||||
* will stop when the table read has completed or an exception was
|
||||
* encountered.
|
||||
*
|
||||
* @param tableName Source table to perform a read
|
||||
* @param limit Number of rows to read from the table
|
||||
* @param offset Starting row to read from in the table
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*
|
||||
*/
|
||||
public void read(String tableName, int limit, int offset) throws SQLiteTableReaderException {
|
||||
readHelper(String.format(SELECT_ALL_QUERY, tableName) + " LIMIT " + limit
|
||||
+ " OFFSET " + offset, () -> false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads column names and values from the table. Iteration will stop when
|
||||
* the condition is true.
|
||||
*
|
||||
* @param tableName Source table to perform a read
|
||||
* @param condition Condition to stop iteration when true
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*
|
||||
*/
|
||||
public void read(String tableName, BooleanSupplier condition) throws SQLiteTableReaderException {
|
||||
if (Objects.isNull(prevTableName) || !prevTableName.equals(tableName)) {
|
||||
prevTableName = tableName;
|
||||
closeTableResources();
|
||||
}
|
||||
readHelper(String.format(SELECT_ALL_QUERY, tableName), condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the result set iteration and is responsible for maintaining
|
||||
* state of the read over multiple invocations.
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
private void readHelper(String query, BooleanSupplier condition) throws SQLiteTableReaderException {
|
||||
try {
|
||||
if (!liveResultSet) {
|
||||
openTableResources(query);
|
||||
columnNameIndex = 0;
|
||||
}
|
||||
|
||||
//Process column names before reading the database table values
|
||||
while (columnNameIndex < totalColumnCount) {
|
||||
if (condition.getAsBoolean()) {
|
||||
return;
|
||||
}
|
||||
builder.onColumnNameAction.accept(currentMetadata
|
||||
.getColumnName(++columnNameIndex));
|
||||
}
|
||||
|
||||
while (unfinishedRow || queryResults.next()) {
|
||||
while (currRowColumnIndex < totalColumnCount) {
|
||||
if (condition.getAsBoolean()) {
|
||||
unfinishedRow = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Object item = queryResults.getObject(++currRowColumnIndex);
|
||||
if (item instanceof String) {
|
||||
builder.onStringAction.accept((String) item);
|
||||
} else if (item instanceof Integer) {
|
||||
builder.onIntegerAction.accept((Integer) item);
|
||||
} else if (item instanceof Double) {
|
||||
builder.onFloatAction.accept((Double) item);
|
||||
} else if (item instanceof Long) {
|
||||
builder.onLongAction.accept((Long) item);
|
||||
} else if (item instanceof byte[]) {
|
||||
builder.onBlobAction.accept((byte[]) item);
|
||||
}
|
||||
|
||||
builder.forAllAction.accept(item);
|
||||
}
|
||||
unfinishedRow = false;
|
||||
//Wrap column index back around if we've reached the end of the row
|
||||
currRowColumnIndex = currRowColumnIndex % totalColumnCount;
|
||||
}
|
||||
closeTableResources();
|
||||
} catch (SQLException ex) {
|
||||
closeTableResources();
|
||||
throw new SQLiteTableReaderException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the underlying database connection is open. This entails
|
||||
* copying the abstract file contents to temp directory, copying over any
|
||||
* WAL or SHM files and getting the connection from the DriverManager.
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
private void ensureOpen() throws SQLiteTableReaderException {
|
||||
if (Objects.isNull(conn)) {
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC"); //NON-NLS
|
||||
String localDiskPath = copyFileToTempDirectory(file, file.getId());
|
||||
|
||||
//Find and copy both WAL and SHM meta files
|
||||
findAndCopySQLiteMetaFile(file, file.getName() + "-wal");
|
||||
findAndCopySQLiteMetaFile(file, file.getName() + "-shm");
|
||||
conn = DriverManager.getConnection("jdbc:sqlite:" + localDiskPath);
|
||||
} catch (NoCurrentCaseException | TskCoreException | IOException
|
||||
| ClassNotFoundException | SQLException ex) {
|
||||
throw new SQLiteTableReaderException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a meta file associated with the give SQLite database. If
|
||||
* found, it copies this file into the temp directory of the current case.
|
||||
*
|
||||
* @param sqliteFile file being processed
|
||||
* @param metaFileName name of meta file to look for
|
||||
*
|
||||
* @throws NoCurrentCaseException Case has been closed.
|
||||
* @throws TskCoreException fileManager cannot find AbstractFile
|
||||
* files.
|
||||
* @throws IOException Issue during writing to file.
|
||||
*/
|
||||
private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile,
|
||||
String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
|
||||
|
||||
Case openCase = Case.getCurrentCaseThrows();
|
||||
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
|
||||
Services services = new Services(sleuthkitCase);
|
||||
FileManager fileManager = services.getFileManager();
|
||||
|
||||
List<AbstractFile> metaFiles = fileManager.findFiles(
|
||||
sqliteFile.getDataSource(), metaFileName,
|
||||
sqliteFile.getParent().getName());
|
||||
|
||||
if (metaFiles != null) {
|
||||
for (AbstractFile metaFile : metaFiles) {
|
||||
copyFileToTempDirectory(metaFile, sqliteFile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the file contents into a unique path in the current case temp
|
||||
* directory.
|
||||
*
|
||||
* @param file AbstractFile from the data source
|
||||
* @param fileId The input files id value
|
||||
*
|
||||
* @return The path of the file on disk
|
||||
*
|
||||
* @throws IOException Exception writing file contents
|
||||
* @throws NoCurrentCaseException Current case closed during file copying
|
||||
*/
|
||||
private String copyFileToTempDirectory(AbstractFile file, long fileId)
|
||||
throws IOException, NoCurrentCaseException {
|
||||
|
||||
String localDiskPath = Case.getCurrentCaseThrows().getTempDirectory()
|
||||
+ File.separator + fileId + file.getName();
|
||||
File localDatabaseFile = new File(localDiskPath);
|
||||
if (!localDatabaseFile.exists()) {
|
||||
ContentUtils.writeToFile(file, localDatabaseFile);
|
||||
}
|
||||
return localDiskPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query and assigns resource references to instance variables.
|
||||
*
|
||||
* @param query Input query to execute
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
private void openTableResources(String query) throws SQLiteTableReaderException {
|
||||
try {
|
||||
ensureOpen();
|
||||
statement = conn.prepareStatement(query);
|
||||
queryResults = statement.executeQuery();
|
||||
currentMetadata = queryResults.getMetaData();
|
||||
totalColumnCount = currentMetadata.getColumnCount();
|
||||
liveResultSet = true;
|
||||
} catch (SQLException ex) {
|
||||
throw new SQLiteTableReaderException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures both the statement and the result set for a table are closed.
|
||||
*/
|
||||
private void closeTableResources() {
|
||||
try {
|
||||
if (Objects.nonNull(statement)) {
|
||||
statement.close();
|
||||
}
|
||||
if (Objects.nonNull(queryResults)) {
|
||||
queryResults.close();
|
||||
}
|
||||
liveResultSet = false;
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to close table resources", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all resources attached to the database file.
|
||||
*
|
||||
* @throws SQLiteTableReaderException
|
||||
*/
|
||||
@Override
|
||||
public void close() throws SQLiteTableReaderException {
|
||||
try {
|
||||
if (Objects.nonNull(conn)) {
|
||||
conn.close();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new SQLiteTableReaderException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides status of the current read operation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isFinished() {
|
||||
return !liveResultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Last ditch effort to close the connections during garbage collection.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
close();
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to close reader in finalizer", ex);
|
||||
}
|
||||
super.finalize();
|
||||
}
|
||||
}
|
44
Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReaderException.java
Executable file
44
Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReaderException.java
Executable file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.coreutils;
|
||||
|
||||
/**
|
||||
* Provides a system exception for the SQLiteTableReader class.
|
||||
*/
|
||||
public class SQLiteTableReaderException extends Exception {
|
||||
|
||||
/**
|
||||
* Accepts both a message and a parent exception.
|
||||
*
|
||||
* @param msg Message detailing the cause
|
||||
* @param parentEx Parent exception
|
||||
*/
|
||||
public SQLiteTableReaderException(String msg, Throwable parentEx) {
|
||||
super(msg, parentEx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts only a parent exception.
|
||||
*
|
||||
* @param parentEx Parent exception
|
||||
*/
|
||||
public SQLiteTableReaderException(Throwable parentEx) {
|
||||
super(parentEx);
|
||||
}
|
||||
}
|
@ -18,16 +18,22 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
@ -43,13 +49,18 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
|
||||
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
|
||||
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
|
||||
import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
|
||||
import org.sleuthkit.autopsy.texttranslation.TranslationException;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@ -71,6 +82,9 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
|
||||
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED);
|
||||
|
||||
private static final ExecutorService translationPool;
|
||||
private static final Integer MAX_POOL_SIZE = 10;
|
||||
|
||||
/**
|
||||
* @param abstractFile file to wrap
|
||||
*/
|
||||
@ -90,6 +104,13 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
|
||||
}
|
||||
|
||||
static {
|
||||
//Initialize this pool only once! This will be used by every instance of AAFN
|
||||
//to do their heavy duty SCO column and translation updates.
|
||||
translationPool = Executors.newFixedThreadPool(MAX_POOL_SIZE,
|
||||
new ThreadFactoryBuilder().setNameFormat("translation-task-thread-%d").build());
|
||||
}
|
||||
|
||||
/**
|
||||
* The finalizer removes event listeners as the BlackboardArtifactNode is
|
||||
* being garbage collected. Yes, we know that finalizers are considered to
|
||||
@ -110,6 +131,16 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event signals to indicate the background tasks have completed processing.
|
||||
* Currently, we have one property task in the background:
|
||||
*
|
||||
* 1) Retreiving the translation of the file name
|
||||
*/
|
||||
enum NodeSpecificEvents {
|
||||
TRANSLATION_AVAILABLE,
|
||||
}
|
||||
|
||||
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
||||
String eventType = evt.getPropertyName();
|
||||
|
||||
@ -146,24 +177,47 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
// case was closed. Remove listeners so that we don't get called with a stale case handle
|
||||
removeListeners();
|
||||
}
|
||||
/*
|
||||
* No need to do any asynchrony around tag added, deleted or CR
|
||||
* change events, they are so infrequent and user driven that we can
|
||||
* just keep a simple blocking approach, where we go out to the
|
||||
* database ourselves.
|
||||
*/
|
||||
} else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
|
||||
ContentTagAddedEvent event = (ContentTagAddedEvent) evt;
|
||||
if (event.getAddedTag().getContent().equals(content)) {
|
||||
updateSheet();
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription(tags);
|
||||
Score value = scorePropAndDescr.getLeft();
|
||||
String descr = scorePropAndDescr.getRight();
|
||||
CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
|
||||
updateSheet(new NodeProperty<>(SCORE.toString(),SCORE.toString(),descr,value),
|
||||
new NodeProperty<>(COMMENT.toString(),COMMENT.toString(),NO_DESCR,getCommentProperty(tags, attribute))
|
||||
);
|
||||
}
|
||||
} else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
|
||||
ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt;
|
||||
if (event.getDeletedTagInfo().getContentID() == content.getId()) {
|
||||
updateSheet();
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription(tags);
|
||||
Score value = scorePropAndDescr.getLeft();
|
||||
String descr = scorePropAndDescr.getRight();
|
||||
CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
|
||||
updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(),descr,value),
|
||||
new NodeProperty<>(COMMENT.toString(), COMMENT.toString(),NO_DESCR,getCommentProperty(tags, attribute))
|
||||
);
|
||||
}
|
||||
} else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
|
||||
CommentChangedEvent event = (CommentChangedEvent) evt;
|
||||
if (event.getContentID() == content.getId()) {
|
||||
updateSheet();
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
|
||||
updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(),NO_DESCR,getCommentProperty(tags, attribute)));
|
||||
}
|
||||
} else if (eventType.equals(NodeSpecificEvents.TRANSLATION_AVAILABLE.toString())) {
|
||||
updateSheet(new NodeProperty<>(TRANSLATION.toString(),TRANSLATION.toString(),NO_DESCR,evt.getNewValue()));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* We pass a weak reference wrapper around the listener to the event
|
||||
* publisher. This allows Netbeans to delete the node when the user
|
||||
@ -174,11 +228,70 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
*/
|
||||
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
|
||||
|
||||
private void updateSheet() {
|
||||
this.setSheet(createSheet());
|
||||
/**
|
||||
* Updates the values of the properties in the current property sheet with
|
||||
* the new properties being passed in. Only if that property exists in the
|
||||
* current sheet will it be applied. That way, we allow for subclasses to
|
||||
* add their own (or omit some!) properties and we will not accidentally
|
||||
* disrupt their UI.
|
||||
*
|
||||
* Race condition if not synchronized. Only one update should be applied at
|
||||
* a time.
|
||||
*
|
||||
* @param newProps New file property instances to be updated in the current
|
||||
* sheet.
|
||||
*/
|
||||
private synchronized void updateSheet(NodeProperty<?>... newProps) {
|
||||
//Refresh ONLY those properties in the sheet currently. Subclasses may have
|
||||
//only added a subset of our properties or their own props. Let's keep their UI correct.
|
||||
Sheet visibleSheet = this.getSheet();
|
||||
Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES);
|
||||
Property<?>[] visibleProps = visibleSheetSet.getProperties();
|
||||
for(NodeProperty<?> newProp: newProps) {
|
||||
for(int i = 0; i < visibleProps.length; i++) {
|
||||
if(visibleProps[i].getName().equals(newProp.getName())) {
|
||||
visibleProps[i] = newProp;
|
||||
}
|
||||
}
|
||||
}
|
||||
visibleSheetSet.put(visibleProps);
|
||||
visibleSheet.put(visibleSheetSet);
|
||||
//setSheet() will notify Netbeans to update this node in the UI.
|
||||
this.setSheet(visibleSheet);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called when the node is first initialized. Any new updates or
|
||||
* changes happen by directly manipulating the sheet. That means we can fire
|
||||
* off background events everytime this method is called and not worry about
|
||||
* duplicated jobs.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized Sheet createSheet() {
|
||||
Sheet sheet = new Sheet();
|
||||
Sheet.Set sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
|
||||
//This will fire off fresh background tasks.
|
||||
List<NodeProperty<?>> newProperties = getProperties();
|
||||
newProperties.forEach((property) -> {
|
||||
sheetSet.put(property);
|
||||
});
|
||||
|
||||
/*
|
||||
* Submit the translation task ASAP. Keep all weak references so
|
||||
* this task doesn't block the ability of this node to be GC'd.
|
||||
*/
|
||||
translationPool.submit(new TranslationTask(new WeakReference<>(this), weakPcl));
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"AbstractAbstractFileNode.nameColLbl=Name",
|
||||
"AbstractAbstractFileNode.translateFileName=Translated Name",
|
||||
"AbstractAbstractFileNode.createSheet.score.name=S",
|
||||
"AbstractAbstractFileNode.createSheet.comment.name=C",
|
||||
"AbstractAbstractFileNode.createSheet.count.name=O",
|
||||
"AbstractAbstractFileNode.locationColLbl=Location",
|
||||
"AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time",
|
||||
"AbstractAbstractFileNode.changeTimeColLbl=Change Time",
|
||||
@ -202,6 +315,10 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
public enum AbstractFilePropertyType {
|
||||
|
||||
NAME(AbstractAbstractFileNode_nameColLbl()),
|
||||
TRANSLATION(AbstractAbstractFileNode_translateFileName()),
|
||||
SCORE(AbstractAbstractFileNode_createSheet_score_name()),
|
||||
COMMENT(AbstractAbstractFileNode_createSheet_comment_name()),
|
||||
OCCURRENCES(AbstractAbstractFileNode_createSheet_count_name()),
|
||||
LOCATION(AbstractAbstractFileNode_locationColLbl()),
|
||||
MOD_TIME(AbstractAbstractFileNode_modifiedTimeColLbl()),
|
||||
CHANGED_TIME(AbstractAbstractFileNode_changeTimeColLbl()),
|
||||
@ -236,144 +353,94 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill map with AbstractFile properties
|
||||
*
|
||||
* @param map map with preserved ordering, where property names/values
|
||||
* are put
|
||||
* @param content The content to get properties for.
|
||||
* Creates and populates a list of properties for this nodes property sheet.
|
||||
*/
|
||||
static public void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
|
||||
map.put(NAME.toString(), getContentDisplayName(content));
|
||||
map.put(LOCATION.toString(), getContentPath(content));
|
||||
map.put(MOD_TIME.toString(), ContentUtils.getStringTime(content.getMtime(), content));
|
||||
map.put(CHANGED_TIME.toString(), ContentUtils.getStringTime(content.getCtime(), content));
|
||||
map.put(ACCESS_TIME.toString(), ContentUtils.getStringTime(content.getAtime(), content));
|
||||
map.put(CREATED_TIME.toString(), ContentUtils.getStringTime(content.getCrtime(), content));
|
||||
map.put(SIZE.toString(), content.getSize());
|
||||
map.put(FLAGS_DIR.toString(), content.getDirFlagAsString());
|
||||
map.put(FLAGS_META.toString(), content.getMetaFlagsAsString());
|
||||
map.put(MODE.toString(), content.getModesAsString());
|
||||
map.put(USER_ID.toString(), content.getUid());
|
||||
map.put(GROUP_ID.toString(), content.getGid());
|
||||
map.put(META_ADDR.toString(), content.getMetaAddr());
|
||||
map.put(ATTR_ADDR.toString(), content.getAttrType().getValue() + "-" + content.getAttributeId());
|
||||
map.put(TYPE_DIR.toString(), content.getDirType().getLabel());
|
||||
map.put(TYPE_META.toString(), content.getMetaType().toString());
|
||||
map.put(KNOWN.toString(), content.getKnown().getName());
|
||||
map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash()));
|
||||
map.put(ObjectID.toString(), content.getId());
|
||||
map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
|
||||
map.put(EXTENSION.toString(), content.getNameExtension());
|
||||
private List<NodeProperty<?>> getProperties() {
|
||||
List<NodeProperty<?>> properties = new ArrayList<>();
|
||||
properties.add(new NodeProperty<>(NAME.toString(), NAME.toString(), NO_DESCR, getContentDisplayName(content)));
|
||||
/*
|
||||
* Initialize an empty place holder value. At the bottom, we kick off a
|
||||
* background task that promises to update these values.
|
||||
*/
|
||||
|
||||
if (UserPreferences.displayTranslatedFileNames()) {
|
||||
properties.add(new NodeProperty<>(TRANSLATION.toString(), TRANSLATION.toString(), NO_DESCR, ""));
|
||||
}
|
||||
|
||||
//SCO column prereq info..
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
|
||||
|
||||
Pair<DataResultViewerTable.Score, String> scoreAndDescription = getScorePropertyAndDescription(tags);
|
||||
properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoreAndDescription.getRight(), scoreAndDescription.getLeft()));
|
||||
DataResultViewerTable.HasCommentStatus comment = getCommentProperty(tags, attribute);
|
||||
properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, comment));
|
||||
if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
|
||||
Pair<Long, String> countAndDescription = getCountPropertyAndDescription(attribute);
|
||||
properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), countAndDescription.getRight(), countAndDescription.getLeft()));
|
||||
}
|
||||
properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content)));
|
||||
properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getMtime(), content)));
|
||||
properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getCtime(), content)));
|
||||
properties.add(new NodeProperty<>(ACCESS_TIME.toString(), ACCESS_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getAtime(), content)));
|
||||
properties.add(new NodeProperty<>(CREATED_TIME.toString(), CREATED_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getCrtime(), content)));
|
||||
properties.add(new NodeProperty<>(SIZE.toString(), SIZE.toString(), NO_DESCR, content.getSize()));
|
||||
properties.add(new NodeProperty<>(FLAGS_DIR.toString(), FLAGS_DIR.toString(), NO_DESCR, content.getDirFlagAsString()));
|
||||
properties.add(new NodeProperty<>(FLAGS_META.toString(), FLAGS_META.toString(), NO_DESCR, content.getMetaFlagsAsString()));
|
||||
properties.add(new NodeProperty<>(KNOWN.toString(), KNOWN.toString(), NO_DESCR, content.getKnown().getName()));
|
||||
properties.add(new NodeProperty<>(MD5HASH.toString(), MD5HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getMd5Hash())));
|
||||
properties.add(new NodeProperty<>(MIMETYPE.toString(), MIMETYPE.toString(), NO_DESCR, StringUtils.defaultString(content.getMIMEType())));
|
||||
properties.add(new NodeProperty<>(EXTENSION.toString(), EXTENSION.toString(), NO_DESCR, content.getNameExtension()));
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tags from the case database that are associated with the file
|
||||
*
|
||||
* @return a list of tags that are associated with the file
|
||||
*/
|
||||
protected final List<ContentTag> getContentTagsFromDatabase() {
|
||||
List<ContentTag> tags = new ArrayList<>();
|
||||
try {
|
||||
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
protected final CorrelationAttributeInstance getCorrelationAttributeInstance() {
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo()) {
|
||||
correlationAttribute = EamArtifactUtil.getInstanceFromContent(content);
|
||||
}
|
||||
return correlationAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by subclasses of AbstractAbstractFileNode to add the comment
|
||||
* property to their sheets.
|
||||
*
|
||||
* @param sheetSet the modifiable Sheet.Set returned by
|
||||
* Sheet.get(Sheet.PROPERTIES)
|
||||
* @param tags the list of tags associated with the file
|
||||
* @param attribute the correlation attribute associated with this file,
|
||||
* null if central repo is not enabled
|
||||
*/
|
||||
@NbBundle.Messages({"AbstractAbstractFileNode.createSheet.comment.name=C",
|
||||
"AbstractAbstractFileNode.createSheet.comment.displayName=C"})
|
||||
protected final void addCommentProperty(Sheet.Set sheetSet, List<ContentTag> tags, CorrelationAttributeInstance attribute) {
|
||||
|
||||
HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT;
|
||||
|
||||
for (ContentTag tag : tags) {
|
||||
if (!StringUtils.isBlank(tag.getComment())) {
|
||||
//if the tag is null or empty or contains just white space it will indicate there is not a comment
|
||||
status = HasCommentStatus.TAG_COMMENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attribute != null && !StringUtils.isBlank(attribute.getComment())) {
|
||||
if (status == HasCommentStatus.TAG_COMMENT) {
|
||||
status = HasCommentStatus.CR_AND_TAG_COMMENTS;
|
||||
} else {
|
||||
status = HasCommentStatus.CR_COMMENT;
|
||||
}
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>(AbstractAbstractFileNode_createSheet_comment_name(), AbstractAbstractFileNode_createSheet_comment_displayName(), NO_DESCR,
|
||||
status));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by subclasses of AbstractAbstractFileNode to add the Score property
|
||||
* Used by subclasses of AbstractAbstractFileNode to add the tags property
|
||||
* to their sheets.
|
||||
*
|
||||
* @param sheetSet the modifiable Sheet.Set returned by
|
||||
* Sheet.get(Sheet.PROPERTIES)
|
||||
* @param tags the list of tags associated with the file
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
@NbBundle.Messages({"AbstractAbstractFileNode.createSheet.score.name=S",
|
||||
"AbstractAbstractFileNode.createSheet.score.displayName=S",
|
||||
"AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.",
|
||||
"AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.",
|
||||
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
|
||||
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.",
|
||||
"AbstractAbstractFileNode.createSheet.noScore.description=No score"})
|
||||
protected final void addScoreProperty(Sheet.Set sheetSet, List<ContentTag> tags) {
|
||||
Score score = Score.NO_SCORE;
|
||||
String description = Bundle.AbstractAbstractFileNode_createSheet_noScore_description();
|
||||
if (content.getKnown() == TskData.FileKnown.BAD) {
|
||||
score = Score.NOTABLE_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableFile_description();
|
||||
}
|
||||
try {
|
||||
if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).isEmpty()) {
|
||||
score = Score.INTERESTING_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_interestingResult_description();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error getting artifacts for file: " + content.getName(), ex);
|
||||
}
|
||||
if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) {
|
||||
score = Score.INTERESTING_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
|
||||
for (ContentTag tag : tags) {
|
||||
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
|
||||
score = Score.NOTABLE_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>(Bundle.AbstractAbstractFileNode_createSheet_score_name(), Bundle.AbstractAbstractFileNode_createSheet_score_displayName(), description, score));
|
||||
@NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags")
|
||||
@Deprecated
|
||||
protected void addTagProperty(Sheet.Set sheetSet) {
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
sheetSet.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(),
|
||||
NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName())
|
||||
.distinct()
|
||||
.collect(Collectors.joining(", "))));
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"AbstractAbstractFileNode.createSheet.count.name=O",
|
||||
/**
|
||||
* Gets a comma-separated values list of the names of the hash sets
|
||||
* currently identified as including a given file.
|
||||
*
|
||||
* @param file The file.
|
||||
*
|
||||
* @return The CSV list of hash set names.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
protected static String getHashSetHitsCsvList(AbstractFile file) {
|
||||
try {
|
||||
return StringUtils.join(file.getHashSetNames(), ", ");
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.WARNING, "Error getting hashset hits: ", tskCoreException); //NON-NLS
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"AbstractAbstractFileNode.createSheet.count.displayName=O",
|
||||
"AbstractAbstractFileNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
|
||||
"AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
|
||||
"# {0} - occuranceCount",
|
||||
"AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
|
||||
protected final void addCountProperty(Sheet.Set sheetSet, CorrelationAttributeInstance attribute) {
|
||||
Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
|
||||
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
|
||||
String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description();
|
||||
try {
|
||||
@ -389,49 +456,142 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
|
||||
}
|
||||
|
||||
sheetSet.put(
|
||||
new NodeProperty<>(Bundle.AbstractAbstractFileNode_createSheet_count_name(), Bundle.AbstractAbstractFileNode_createSheet_count_displayName(), description, count));
|
||||
}
|
||||
|
||||
return Pair.of(count, description);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"AbstractAbstractFileNode.createSheet.score.displayName=S",
|
||||
"AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.",
|
||||
"AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.",
|
||||
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
|
||||
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.",
|
||||
"AbstractAbstractFileNode.createSheet.noScore.description=No score"})
|
||||
Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<ContentTag> tags) {
|
||||
DataResultViewerTable.Score score = DataResultViewerTable.Score.NO_SCORE;
|
||||
String description = "";
|
||||
if (content.getKnown() == TskData.FileKnown.BAD) {
|
||||
score = DataResultViewerTable.Score.NOTABLE_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableFile_description();
|
||||
}
|
||||
try {
|
||||
if (score == DataResultViewerTable.Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).isEmpty()) {
|
||||
score = DataResultViewerTable.Score.INTERESTING_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_interestingResult_description();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error getting artifacts for file: " + content.getName(), ex);
|
||||
}
|
||||
if (!tags.isEmpty() && (score == DataResultViewerTable.Score.NO_SCORE || score == DataResultViewerTable.Score.INTERESTING_SCORE)) {
|
||||
score = DataResultViewerTable.Score.INTERESTING_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
|
||||
for (ContentTag tag : tags) {
|
||||
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
|
||||
score = DataResultViewerTable.Score.NOTABLE_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair.of(score, description);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"AbstractAbstractFileNode.createSheet.comment.displayName=C"})
|
||||
HasCommentStatus getCommentProperty(List<ContentTag> tags, CorrelationAttributeInstance attribute) {
|
||||
|
||||
DataResultViewerTable.HasCommentStatus status = !tags.isEmpty() ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT;
|
||||
|
||||
for (ContentTag tag : tags) {
|
||||
if (!StringUtils.isBlank(tag.getComment())) {
|
||||
//if the tag is null or empty or contains just white space it will indicate there is not a comment
|
||||
status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attribute != null && !StringUtils.isBlank(attribute.getComment())) {
|
||||
if (status == DataResultViewerTable.HasCommentStatus.TAG_COMMENT) {
|
||||
status = DataResultViewerTable.HasCommentStatus.CR_AND_TAG_COMMENTS;
|
||||
} else {
|
||||
status = DataResultViewerTable.HasCommentStatus.CR_COMMENT;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by subclasses of AbstractAbstractFileNode to add the tags property
|
||||
* to their sheets.
|
||||
*
|
||||
* @param sheetSet the modifiable Sheet.Set returned by
|
||||
* Sheet.get(Sheet.PROPERTIES)
|
||||
* @deprecated
|
||||
* Translates this nodes content name. Doesn't attempt translation if
|
||||
* the name is in english or if there is now translation service available.
|
||||
*/
|
||||
@NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags")
|
||||
@Deprecated
|
||||
protected void addTagProperty(Sheet.Set sheetSet) {
|
||||
String getTranslatedFileName() {
|
||||
//If already in complete English, don't translate.
|
||||
if (content.getName().matches("^\\p{ASCII}+$")) {
|
||||
return "";
|
||||
}
|
||||
TextTranslationService tts = TextTranslationService.getInstance();
|
||||
if (tts.hasProvider()) {
|
||||
//Seperate out the base and ext from the contents file name.
|
||||
String base = FilenameUtils.getBaseName(content.getName());
|
||||
try {
|
||||
String translation = tts.translate(base);
|
||||
String ext = FilenameUtils.getExtension(content.getName());
|
||||
|
||||
//If we have no extension, then we shouldn't add the .
|
||||
String extensionDelimiter = (ext.isEmpty()) ? "" : ".";
|
||||
|
||||
//Talk directly to this nodes pcl, fire an update when the translation
|
||||
//is complete.
|
||||
if (!translation.isEmpty()) {
|
||||
return translation + extensionDelimiter + ext;
|
||||
}
|
||||
} catch (NoServiceProviderException noServiceEx) {
|
||||
logger.log(Level.WARNING, "Translate unsuccessful because no TextTranslator "
|
||||
+ "implementation was provided.", noServiceEx.getMessage());
|
||||
} catch (TranslationException noTranslationEx) {
|
||||
logger.log(Level.WARNING, "Could not successfully translate file name "
|
||||
+ content.getName(), noTranslationEx.getMessage());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tags from the case database that are associated with the file
|
||||
*
|
||||
* @return a list of tags that are associated with the file
|
||||
*/
|
||||
List<ContentTag> getContentTagsFromDatabase() {
|
||||
List<ContentTag> tags = new ArrayList<>();
|
||||
try {
|
||||
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(),
|
||||
NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName())
|
||||
.distinct()
|
||||
.collect(Collectors.joining(", "))));
|
||||
return tags;
|
||||
}
|
||||
|
||||
private static String getContentPath(AbstractFile file) {
|
||||
CorrelationAttributeInstance getCorrelationAttributeInstance() {
|
||||
CorrelationAttributeInstance attribute = null;
|
||||
if (EamDbUtil.useCentralRepo()) {
|
||||
attribute = EamArtifactUtil.getInstanceFromContent(content);
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
|
||||
static String getContentPath(AbstractFile file) {
|
||||
try {
|
||||
return file.getUniquePath();
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Except while calling Content.getUniquePath() on " + file, ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Except while calling Content.getUniquePath() on " + file.getName(), ex); //NON-NLS
|
||||
return ""; //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
static String getContentDisplayName(AbstractFile file) {
|
||||
static String getContentDisplayName(AbstractFile file) {
|
||||
String name = file.getName();
|
||||
switch (name) {
|
||||
case "..":
|
||||
return DirectoryNode.DOTDOTDIR;
|
||||
|
||||
case ".":
|
||||
return DirectoryNode.DOTDIR;
|
||||
default:
|
||||
@ -440,21 +600,28 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a comma-separated values list of the names of the hash sets
|
||||
* currently identified as including a given file.
|
||||
* Fill map with AbstractFile properties
|
||||
*
|
||||
* @param file The file.
|
||||
*
|
||||
* @return The CSV list of hash set names.
|
||||
* @deprecated
|
||||
* @param map map with preserved ordering, where property names/values
|
||||
* are put
|
||||
* @param content The content to get properties for.
|
||||
*
|
||||
* TODO JIRA-4421: Deprecate this method and resolve warnings that appear
|
||||
* in other locations.
|
||||
*/
|
||||
@Deprecated
|
||||
protected static String getHashSetHitsCsvList(AbstractFile file) {
|
||||
try {
|
||||
return StringUtils.join(file.getHashSetNames(), ", ");
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.WARNING, "Error getting hashset hits: ", tskCoreException); //NON-NLS
|
||||
return "";
|
||||
}
|
||||
static public void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
|
||||
map.put(NAME.toString(), getContentDisplayName(content));
|
||||
map.put(LOCATION.toString(), getContentPath(content));
|
||||
map.put(MOD_TIME.toString(), ContentUtils.getStringTime(content.getMtime(), content));
|
||||
map.put(CHANGED_TIME.toString(), ContentUtils.getStringTime(content.getCtime(), content));
|
||||
map.put(ACCESS_TIME.toString(), ContentUtils.getStringTime(content.getAtime(), content));
|
||||
map.put(CREATED_TIME.toString(), ContentUtils.getStringTime(content.getCrtime(), content));
|
||||
map.put(SIZE.toString(), content.getSize());
|
||||
map.put(FLAGS_DIR.toString(), content.getDirFlagAsString());
|
||||
map.put(FLAGS_META.toString(), content.getMetaFlagsAsString());
|
||||
map.put(KNOWN.toString(), content.getKnown().getName());
|
||||
map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash()));
|
||||
map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
|
||||
map.put(EXTENSION.toString(), content.getNameExtension());
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,10 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
|
||||
/**
|
||||
* Abstract class that implements the commonality between File and Directory
|
||||
@ -58,7 +51,7 @@ public abstract class AbstractFsContentNode<T extends AbstractFile> extends Abst
|
||||
*/
|
||||
AbstractFsContentNode(T content, boolean directoryBrowseMode) {
|
||||
super(content);
|
||||
this.setDisplayName(AbstractAbstractFileNode.getContentDisplayName(content));
|
||||
this.setDisplayName(getContentDisplayName(content));
|
||||
this.directoryBrowseMode = directoryBrowseMode;
|
||||
}
|
||||
|
||||
@ -71,37 +64,6 @@ public abstract class AbstractFsContentNode<T extends AbstractFile> extends Abst
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
}
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
fillPropertyMap(map, getContent());
|
||||
final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text();
|
||||
//add the name property before the comment property to ensure it is first column
|
||||
sheetSet.put(new NodeProperty<>(AbstractFilePropertyType.NAME.toString(),
|
||||
AbstractFilePropertyType.NAME.toString(),
|
||||
NO_DESCR,
|
||||
getName()));
|
||||
|
||||
addScoreProperty(sheetSet, tags);
|
||||
|
||||
//add the comment property before the propertyMap to ensure it is early in column order
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) {
|
||||
correlationAttribute = getCorrelationAttributeInstance();
|
||||
}
|
||||
addCommentProperty(sheetSet, tags, correlationAttribute);
|
||||
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) {
|
||||
addCountProperty(sheetSet, correlationAttribute);
|
||||
}
|
||||
|
||||
for (AbstractFilePropertyType propType : AbstractFilePropertyType.values()) {
|
||||
final String propString = propType.toString();
|
||||
sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString)));
|
||||
}
|
||||
if (directoryBrowseMode) {
|
||||
sheetSet.put(new NodeProperty<>(HIDE_PARENT, HIDE_PARENT, HIDE_PARENT, HIDE_PARENT));
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
|
||||
}
|
||||
return srcName;
|
||||
}
|
||||
|
||||
|
||||
@NbBundle.Messages({
|
||||
"BlackboardArtifactNode.createSheet.artifactType.displayName=Artifact Type",
|
||||
"BlackboardArtifactNode.createSheet.artifactType.name=Artifact Type",
|
||||
|
@ -29,6 +29,7 @@ import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNod
|
||||
import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode;
|
||||
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
|
||||
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
|
||||
import org.sleuthkit.autopsy.othercasessearch.CorrelationAttributeInstanceNode;
|
||||
|
||||
/**
|
||||
* Visitor pattern that goes over all nodes in the directory tree. This includes
|
||||
@ -125,6 +126,8 @@ public interface DisplayableItemNodeVisitor<T> {
|
||||
T visit(CentralRepoCommonAttributeInstanceNode crfin);
|
||||
|
||||
T visit(InstanceCountNode icn);
|
||||
|
||||
T visit(CorrelationAttributeInstanceNode cain);
|
||||
|
||||
/*
|
||||
* Tags
|
||||
@ -214,6 +217,11 @@ public interface DisplayableItemNodeVisitor<T> {
|
||||
return defaultVisit(icn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CorrelationAttributeInstanceNode cain) {
|
||||
return defaultVisit(cain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CentralRepoCommonAttributeInstanceNode crfin){
|
||||
return defaultVisit(crfin);
|
||||
|
@ -22,24 +22,18 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.Action;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.actions.AddContentTagAction;
|
||||
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
|
||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.LayoutFile;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
@ -75,45 +69,6 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
}
|
||||
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
fillPropertyMap(map);
|
||||
|
||||
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.name"),
|
||||
NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.displayName"),
|
||||
NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.desc"),
|
||||
getName()));
|
||||
|
||||
addScoreProperty(sheetSet, tags);
|
||||
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
correlationAttribute = getCorrelationAttributeInstance();
|
||||
}
|
||||
addCommentProperty(sheetSet, tags, correlationAttribute);
|
||||
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
addCountProperty(sheetSet, correlationAttribute);
|
||||
}
|
||||
final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.noDescr.text");
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ContentNodeVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
@ -151,10 +106,6 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
|
||||
return actionsList.toArray(new Action[actionsList.size()]);
|
||||
}
|
||||
|
||||
void fillPropertyMap(Map<String, Object> map) {
|
||||
AbstractAbstractFileNode.fillPropertyMap(map, getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType() {
|
||||
return getClass().getName();
|
||||
|
@ -18,15 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.LocalDirectory;
|
||||
|
||||
/**
|
||||
@ -46,51 +37,6 @@ public class LocalDirectoryNode extends SpecialDirectoryNode {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"LocalDirectoryNode.createSheet.name.name=Name",
|
||||
"LocalDirectoryNode.createSheet.name.displayName=Name",
|
||||
"LocalDirectoryNode.createSheet.name.desc=no description",
|
||||
"LocalDirectoryNode.createSheet.noDesc=no description"})
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
}
|
||||
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
sheetSet.put(new NodeProperty<>(Bundle.LocalDirectoryNode_createSheet_name_name(),
|
||||
Bundle.LocalDirectoryNode_createSheet_name_displayName(),
|
||||
Bundle.LocalDirectoryNode_createSheet_name_desc(),
|
||||
getName()));
|
||||
|
||||
addScoreProperty(sheetSet, tags);
|
||||
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
correlationAttribute = getCorrelationAttributeInstance();
|
||||
}
|
||||
addCommentProperty(sheetSet, tags, correlationAttribute);
|
||||
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
addCountProperty(sheetSet, correlationAttribute);
|
||||
}
|
||||
// At present, a LocalDirectory will never be a datasource - the top level of a logical
|
||||
// file set is a VirtualDirectory
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
fillPropertyMap(map, getContent());
|
||||
|
||||
final String NO_DESCR = Bundle.LocalDirectoryNode_createSheet_noDesc();
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ContentNodeVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
@ -22,19 +22,13 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.Action;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.actions.AddContentTagAction;
|
||||
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
|
||||
@ -44,7 +38,6 @@ import org.sleuthkit.autopsy.directorytree.ViewContextAction;
|
||||
import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -68,43 +61,6 @@ public class LocalFileNode extends AbstractAbstractFileNode<AbstractFile> {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
}
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
fillPropertyMap(map, getContent());
|
||||
|
||||
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.name"),
|
||||
NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.displayName"),
|
||||
NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.desc"),
|
||||
getName()));
|
||||
|
||||
addScoreProperty(sheetSet, tags);
|
||||
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
correlationAttribute = getCorrelationAttributeInstance();
|
||||
}
|
||||
addCommentProperty(sheetSet, tags, correlationAttribute);
|
||||
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
addCountProperty(sheetSet, correlationAttribute);
|
||||
}
|
||||
final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.noDescr.text");
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action[] getActions(boolean context) {
|
||||
List<Action> actionsList = new ArrayList<>();
|
||||
actionsList.addAll(Arrays.asList(super.getActions(true)));
|
||||
|
59
Core/src/org/sleuthkit/autopsy/datamodel/TranslationTask.java
Executable file
59
Core/src/org/sleuthkit/autopsy/datamodel/TranslationTask.java
Executable file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.datamodel;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.ref.WeakReference;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
|
||||
/**
|
||||
* Completes the tasks needed to populate the Translation columns in the
|
||||
* background so that the UI is not blocked while waiting for responses from the
|
||||
* translation service. Once the event is done, it fires a PropertyChangeEvent
|
||||
* to let the AbstractAbstractFileNode know it's time to update.
|
||||
*/
|
||||
class TranslationTask implements Runnable {
|
||||
|
||||
private final WeakReference<AbstractAbstractFileNode<?>> weakNodeRef;
|
||||
private final PropertyChangeListener listener;
|
||||
|
||||
public TranslationTask(WeakReference<AbstractAbstractFileNode<?>> weakContentRef, PropertyChangeListener listener) {
|
||||
this.weakNodeRef = weakContentRef;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
AbstractAbstractFileNode<?> fileNode = weakNodeRef.get();
|
||||
//Check for stale reference
|
||||
if (fileNode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String translatedFileName = fileNode.getTranslatedFileName();
|
||||
if (!translatedFileName.isEmpty() && listener != null) {
|
||||
//Only fire if the result is meaningful and the listener is not a stale reference
|
||||
listener.propertyChange(new PropertyChangeEvent(
|
||||
AutopsyEvent.SourceType.LOCAL.toString(),
|
||||
AbstractAbstractFileNode.NodeSpecificEvents.TRANSLATION_AVAILABLE.toString(),
|
||||
null, translatedFileName));
|
||||
}
|
||||
}
|
||||
}
|
@ -21,18 +21,13 @@ package org.sleuthkit.autopsy.datamodel;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
@ -78,39 +73,18 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode {
|
||||
"VirtualDirectoryNode.createSheet.deviceId.displayName=Device ID",
|
||||
"VirtualDirectoryNode.createSheet.deviceId.desc=Device ID of the image"})
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
//Do a special strategy for virtual directories..
|
||||
if(this.content.isDataSource()){
|
||||
Sheet sheet = new Sheet();
|
||||
Sheet.Set sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
}
|
||||
List<ContentTag> tags = getContentTagsFromDatabase();
|
||||
|
||||
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.name"),
|
||||
|
||||
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.name"),
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"VirtualDirectoryNode.createSheet.name.displayName"),
|
||||
NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.desc"),
|
||||
getName()));
|
||||
if (!this.content.isDataSource()) {
|
||||
addScoreProperty(sheetSet, tags);
|
||||
|
||||
CorrelationAttributeInstance correlationAttribute = null;
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
correlationAttribute = getCorrelationAttributeInstance();
|
||||
}
|
||||
addCommentProperty(sheetSet, tags, correlationAttribute);
|
||||
|
||||
if (EamDbUtil.useCentralRepo() && UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
|
||||
addCountProperty(sheetSet, correlationAttribute);
|
||||
}
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
fillPropertyMap(map, getContent());
|
||||
|
||||
final String NO_DESCR = NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.noDesc");
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
|
||||
}
|
||||
} else {
|
||||
|
||||
sheetSet.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_type_name(),
|
||||
Bundle.VirtualDirectoryNode_createSheet_type_displayName(),
|
||||
Bundle.VirtualDirectoryNode_createSheet_type_desc(),
|
||||
@ -141,10 +115,11 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode {
|
||||
} catch (SQLException | TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get device id for the following image: " + this.content.getId(), ex);
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
return sheet;
|
||||
//Otherwise default to the AAFN createSheet method.
|
||||
return super.createSheet();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -171,6 +171,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
||||
case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE:
|
||||
case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE:
|
||||
case UserPreferences.HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES:
|
||||
case UserPreferences.DISPLAY_TRANSLATED_NAMES:
|
||||
case UserPreferences.KEEP_PREFERRED_VIEWER:
|
||||
refreshContentTreeSafe();
|
||||
break;
|
||||
|
@ -827,7 +827,7 @@ public final class FilesSet implements Serializable {
|
||||
/**
|
||||
* Construct a case-insensitive file name extension condition.
|
||||
*
|
||||
* @param extension The file name extension to be matched.
|
||||
* @param extensions The file name extensions to be matched.
|
||||
*/
|
||||
public ExtensionCondition(List<String> extensions) {
|
||||
// If there is a leading ".", strip it since
|
||||
|
10
Core/src/org/sleuthkit/autopsy/othercasessearch/Bundle.properties
Executable file
10
Core/src/org/sleuthkit/autopsy/othercasessearch/Bundle.properties
Executable file
@ -0,0 +1,10 @@
|
||||
|
||||
OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription=
|
||||
OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleName=Search
|
||||
OtherCasesSearchDialog.searchButton.text=Search
|
||||
OtherCasesSearchDialog.correlationValueTextField.text=
|
||||
OtherCasesSearchDialog.correlationValueLabel.text=Correlation Property Value:
|
||||
OtherCasesSearchDialog.descriptionLabel.text=Search data in the Central Repository from other cases.
|
||||
OtherCasesSearchDialog.errorLabel.text=\
|
||||
OtherCasesSearchDialog.correlationTypeLabel.text=Correlation Property Type:
|
||||
OtherCasesSearchDialog.casesLabel.text=\
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.othercasessearch;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.swing.Action;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource;
|
||||
import org.sleuthkit.autopsy.othercasessearch.Bundle;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
|
||||
/**
|
||||
* Used by the Other Cases Search feature to encapsulate instances of a given
|
||||
* search match.
|
||||
*/
|
||||
public final class CorrelationAttributeInstanceNode extends DisplayableItemNode {
|
||||
|
||||
private final CorrelationAttributeInstance instance;
|
||||
|
||||
CorrelationAttributeInstanceNode(CorrelationAttributeInstance content) {
|
||||
super(Children.LEAF, Lookups.fixed(content));
|
||||
this.instance = content;
|
||||
this.setDisplayName(new File(this.instance.getFilePath()).getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CorrelationAttributeInstance attached to the node.
|
||||
*
|
||||
* @return The CorrelationAttributeInstance object.
|
||||
*/
|
||||
public CorrelationAttributeInstance getCorrelationAttributeInstance(){
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action[] getActions(boolean context){
|
||||
List<Action> actionsList = new ArrayList<>();
|
||||
|
||||
actionsList.addAll(Arrays.asList(super.getActions(true)));
|
||||
|
||||
return actionsList.toArray(new Action[actionsList.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeafTypeNode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType() {
|
||||
return CorrelationAttributeInstanceNode.class.getName();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"CorrelationAttributeInstanceNode.columnName.name=Name",
|
||||
"CorrelationAttributeInstanceNode.columnName.case=Case",
|
||||
"CorrelationAttributeInstanceNode.columnName.dataSource=Data Source",
|
||||
"CorrelationAttributeInstanceNode.columnName.known=Known",
|
||||
"CorrelationAttributeInstanceNode.columnName.path=Path",
|
||||
"CorrelationAttributeInstanceNode.columnName.comment=Comment",
|
||||
"CorrelationAttributeInstanceNode.columnName.device=Device"
|
||||
})
|
||||
@Override
|
||||
protected Sheet createSheet(){
|
||||
Sheet sheet = new Sheet();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
|
||||
if(sheetSet == null){
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
sheet.put(sheetSet);
|
||||
}
|
||||
|
||||
final CorrelationAttributeInstance centralRepoFile = this.getCorrelationAttributeInstance();
|
||||
|
||||
final String path = centralRepoFile.getFilePath();
|
||||
final File file = new File(path);
|
||||
final String name = file.getName();
|
||||
final String caseName = centralRepoFile.getCorrelationCase().getDisplayName();
|
||||
final CorrelationDataSource dataSource = centralRepoFile.getCorrelationDataSource();
|
||||
final String dataSourceName = dataSource.getName();
|
||||
final String known = centralRepoFile.getKnownStatus().getName();
|
||||
final String comment = centralRepoFile.getComment();
|
||||
final String device = dataSource.getDeviceID();
|
||||
|
||||
final String NO_DESCR = "";
|
||||
|
||||
sheetSet.put(new NodeProperty<>(
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_name(),
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_name(), NO_DESCR, name));
|
||||
sheetSet.put(new NodeProperty<>(
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_case(),
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_case(), NO_DESCR, caseName));
|
||||
sheetSet.put(new NodeProperty<>(
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_dataSource(),
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_dataSource(), NO_DESCR, dataSourceName));
|
||||
sheetSet.put(new NodeProperty<>(
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_known(),
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_known(), NO_DESCR, known));
|
||||
sheetSet.put(new NodeProperty<>(
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_path(),
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_path(), NO_DESCR, path));
|
||||
sheetSet.put(new NodeProperty<>(
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_comment(),
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_comment(), NO_DESCR, comment));
|
||||
sheetSet.put(new NodeProperty<>(
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_device(),
|
||||
Bundle.CorrelationAttributeInstanceNode_columnName_device(), NO_DESCR, device));
|
||||
|
||||
return sheet;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.othercasessearch;
|
||||
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.FilterNode;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
|
||||
|
||||
/**
|
||||
* A <code>Children</code> implementation for a
|
||||
* <code>CorrelationPropertyFilterNode</code>.
|
||||
*/
|
||||
final class OtherCasesFilterChildren extends FilterNode.Children {
|
||||
|
||||
/**
|
||||
* Create a new Children instance.
|
||||
*
|
||||
* @param wrappedNode The node to be wrapped.
|
||||
* @param createChildren If false, return LEAF. Otherwise, return a new
|
||||
* CorrelationPropertyFilterChildren instance.
|
||||
*
|
||||
* @return A Children instance.
|
||||
*/
|
||||
static Children createInstance(Node wrappedNode, boolean createChildren) {
|
||||
|
||||
if (createChildren) {
|
||||
return new OtherCasesFilterChildren(wrappedNode);
|
||||
} else {
|
||||
return Children.LEAF;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a children (child factory) implementation for a
|
||||
* <code>CorrelationPropertyFilterNode</code>.
|
||||
*
|
||||
* @param wrappedNode The node wrapped by CorrelationPropertyFilterNode.
|
||||
*/
|
||||
OtherCasesFilterChildren(Node wrappedNode) {
|
||||
super(wrappedNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a CorrelationPropertyFilterNode, with the children (child factory)
|
||||
* flag set to false.
|
||||
*
|
||||
* @param nodeToCopy The CorrelationPropertyFilterNode to copy.
|
||||
*
|
||||
* @return A copy of a CorrelationPropertyFilterNode.
|
||||
*/
|
||||
@Override
|
||||
protected Node copyNode(Node nodeToCopy) {
|
||||
return new TableFilterNode(nodeToCopy, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the child nodes represented by this children (child factory)
|
||||
* object.
|
||||
*
|
||||
* @param key The key, i.e., the node, for which to create the child nodes.
|
||||
*
|
||||
* @return A single-element node array.
|
||||
*/
|
||||
@Override
|
||||
protected Node[] createNodes(Node key) {
|
||||
return new Node[]{this.copyNode(key)};
|
||||
}
|
||||
}
|
69
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchAction.java
Executable file
69
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchAction.java
Executable file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.othercasessearch;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import org.openide.awt.ActionID;
|
||||
import org.openide.awt.ActionReference;
|
||||
import org.openide.awt.ActionRegistration;
|
||||
import org.openide.util.HelpCtx;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.actions.CallableSystemAction;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
|
||||
import org.sleuthkit.autopsy.othercasessearch.Bundle;
|
||||
|
||||
/**
|
||||
* Action for accessing the Search Other Cases dialog.
|
||||
*/
|
||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.othercasessearch.OtherCasesSearchAction")
|
||||
@ActionRegistration(displayName = "#CTL_OtherCasesSearchAction=Search Other Cases", lazy = false)
|
||||
@ActionReference(path = "Menu/Tools", position = 104)
|
||||
@NbBundle.Messages({"CTL_OtherCasesSearchAction=Search Other Cases"})
|
||||
public class OtherCasesSearchAction extends CallableSystemAction {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return EamDb.isEnabled() && Case.isCaseOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
performAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performAction() {
|
||||
OtherCasesSearchDialog dialog = new OtherCasesSearchDialog();
|
||||
dialog.display();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"OtherCasesSearchAction.getName.text=Search Other Cases"})
|
||||
@Override
|
||||
public String getName() {
|
||||
return Bundle.OtherCasesSearchAction_getName_text();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HelpCtx getHelpCtx() {
|
||||
return HelpCtx.DEFAULT_HELP;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.othercasessearch;
|
||||
|
||||
import java.util.List;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
|
||||
/**
|
||||
* Creates CorrelationAttributeInstanceNodes from a collection of
|
||||
* CorrelationAttributeInstances.
|
||||
*/
|
||||
class OtherCasesSearchChildren extends Children.Keys<CorrelationAttributeInstance> {
|
||||
|
||||
/**
|
||||
* Create an instance of OtherCasesSearchChildren.
|
||||
*
|
||||
* @param lazy Lazy load?
|
||||
* @param fileList List of CorrelationAttributeInstances.
|
||||
*/
|
||||
OtherCasesSearchChildren(boolean lazy, List<CorrelationAttributeInstance> instances) {
|
||||
super(lazy);
|
||||
this.setKeys(instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node[] createNodes(CorrelationAttributeInstance t) {
|
||||
Node[] node = new Node[1];
|
||||
node[0] = new CorrelationAttributeInstanceNode(t);
|
||||
return node;
|
||||
}
|
||||
}
|
159
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.form
Executable file
159
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.form
Executable file
@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
|
||||
<Properties>
|
||||
<Property name="defaultCloseOperation" type="int" value="2"/>
|
||||
<Property name="resizable" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<SyntheticProperties>
|
||||
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
|
||||
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
|
||||
</SyntheticProperties>
|
||||
<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" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="casesLabel" max="32767" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="searchButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="descriptionLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="correlationValueLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="correlationTypeLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="correlationTypeComboBox" pref="289" max="32767" attributes="0"/>
|
||||
<Component id="correlationValueTextField" max="32767" attributes="0"/>
|
||||
<Component id="errorLabel" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="descriptionLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="correlationTypeComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="correlationTypeLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="correlationValueLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="correlationValueTextField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="errorLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="11" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="searchButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="casesLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="correlationValueLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.correlationValueLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="correlationValueTextField">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.correlationValueTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="searchButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.searchButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AccessibilityProperties>
|
||||
<Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleName" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="AccessibleContext.accessibleDescription" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</AccessibilityProperties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="correlationTypeComboBox">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="correlationTypeLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.correlationTypeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="errorLabel">
|
||||
<Properties>
|
||||
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="0" green="0" red="ff" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.errorLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="descriptionLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.descriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="casesLabel">
|
||||
<Properties>
|
||||
<Property name="horizontalAlignment" type="int" value="0"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/othercasessearch/Bundle.properties" key="OtherCasesSearchDialog.casesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
381
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.java
Executable file
381
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.java
Executable file
@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.othercasessearch;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
|
||||
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
|
||||
import org.sleuthkit.autopsy.corecomponents.TextPrompt;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.EmptyNode;
|
||||
|
||||
@Messages({
|
||||
"OtherCasesSearchDialog.dialogTitle.text=Search Other Cases",
|
||||
"OtherCasesSearchDialog.resultsTitle.text=Other Cases",
|
||||
"OtherCasesSearchDialog.resultsDescription.text=Other Cases Search",
|
||||
"OtherCasesSearchDialog.emptyNode.text=No results found.",
|
||||
"OtherCasesSearchDialog.validation.invalidHash=The supplied value is not a valid MD5 hash.",
|
||||
"# {0} - number of cases",
|
||||
"OtherCasesSearchDialog.caseLabel.text=The current Central Repository contains {0} case(s)."
|
||||
})
|
||||
/**
|
||||
* The Search Other Cases dialog allows users to search for specific
|
||||
* types of correlation properties in the Central Repository.
|
||||
*/
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
final class OtherCasesSearchDialog extends javax.swing.JDialog {
|
||||
private static final Logger logger = Logger.getLogger(OtherCasesSearchDialog.class.getName());
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String FILES_CORRELATION_TYPE = "Files";
|
||||
|
||||
private final List<CorrelationAttributeInstance.Type> correlationTypes;
|
||||
private TextPrompt correlationValueTextFieldPrompt;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Search Other Cases dialog.
|
||||
*/
|
||||
OtherCasesSearchDialog() {
|
||||
super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.OtherCasesSearchDialog_dialogTitle_text(), true);
|
||||
this.correlationTypes = new ArrayList<>();
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the other cases search.
|
||||
*/
|
||||
private void search() {
|
||||
new SwingWorker<List<CorrelationAttributeInstance>, Void>() {
|
||||
|
||||
@Override
|
||||
protected List<CorrelationAttributeInstance> doInBackground() {
|
||||
List<CorrelationAttributeInstance.Type> correlationTypes;
|
||||
List<CorrelationAttributeInstance> correlationInstances = new ArrayList<>();
|
||||
|
||||
try {
|
||||
correlationTypes = EamDb.getInstance().getDefinedCorrelationTypes();
|
||||
for (CorrelationAttributeInstance.Type type : correlationTypes) {
|
||||
if (type.getDisplayName().equals((String) correlationTypeComboBox.getSelectedItem())) {
|
||||
correlationInstances = EamDb.getInstance().getArtifactInstancesByTypeValue(type, correlationValueTextField.getText());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (EamDbException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex);
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to retrieve data from the Central Repository.", ex);
|
||||
}
|
||||
|
||||
return correlationInstances;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
super.done();
|
||||
List<CorrelationAttributeInstance> correlationInstances = this.get();
|
||||
DataResultViewerTable table = new DataResultViewerTable();
|
||||
Collection<DataResultViewer> viewers = new ArrayList<>(1);
|
||||
viewers.add(table);
|
||||
|
||||
OtherCasesSearchNode searchNode = new OtherCasesSearchNode(correlationInstances);
|
||||
TableFilterNode tableFilterNode = new TableFilterNode(searchNode, true, searchNode.getName());
|
||||
|
||||
String resultsText = String.format("%s (%s; \"%s\")",
|
||||
Bundle.OtherCasesSearchDialog_resultsTitle_text(),
|
||||
(String) correlationTypeComboBox.getSelectedItem(),
|
||||
correlationValueTextField.getText());
|
||||
final TopComponent searchResultWin;
|
||||
if (correlationInstances.isEmpty()) {
|
||||
Node emptyNode = new TableFilterNode(
|
||||
new EmptyNode(Bundle.OtherCasesSearchDialog_emptyNode_text()), true);
|
||||
searchResultWin = DataResultTopComponent.createInstance(
|
||||
resultsText, Bundle.OtherCasesSearchDialog_resultsDescription_text(), emptyNode, 0);
|
||||
} else {
|
||||
searchResultWin = DataResultTopComponent.createInstance(
|
||||
resultsText, Bundle.OtherCasesSearchDialog_resultsDescription_text(), tableFilterNode, correlationInstances.size(), viewers);
|
||||
}
|
||||
searchResultWin.requestActive(); // make it the active top component
|
||||
} catch (ExecutionException | InterruptedException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to get CorrelationAttributeInstances.", ex);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
correlationValueLabel = new javax.swing.JLabel();
|
||||
correlationValueTextField = new javax.swing.JTextField();
|
||||
searchButton = new javax.swing.JButton();
|
||||
correlationTypeComboBox = new javax.swing.JComboBox<>();
|
||||
correlationTypeLabel = new javax.swing.JLabel();
|
||||
errorLabel = new javax.swing.JLabel();
|
||||
descriptionLabel = new javax.swing.JLabel();
|
||||
casesLabel = new javax.swing.JLabel();
|
||||
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setResizable(false);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(correlationValueLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.correlationValueLabel.text")); // NOI18N
|
||||
|
||||
correlationValueTextField.setText(org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.correlationValueTextField.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.searchButton.text")); // NOI18N
|
||||
searchButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
searchButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(correlationTypeLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.correlationTypeLabel.text")); // NOI18N
|
||||
|
||||
errorLabel.setForeground(new java.awt.Color(255, 0, 0));
|
||||
org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.errorLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.descriptionLabel.text")); // NOI18N
|
||||
|
||||
casesLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
|
||||
org.openide.awt.Mnemonics.setLocalizedText(casesLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.casesLabel.text")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||
getContentPane().setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(casesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(searchButton))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(descriptionLabel)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(correlationValueLabel)
|
||||
.addComponent(correlationTypeLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(correlationTypeComboBox, 0, 289, Short.MAX_VALUE)
|
||||
.addComponent(correlationValueTextField)
|
||||
.addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(descriptionLabel)
|
||||
.addGap(18, 18, 18)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(correlationTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(correlationTypeLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(correlationValueLabel)
|
||||
.addComponent(correlationValueTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(errorLabel)
|
||||
.addGap(11, 11, 11)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(searchButton)
|
||||
.addComponent(casesLabel))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
searchButton.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleName")); // NOI18N
|
||||
searchButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription")); // NOI18N
|
||||
|
||||
pack();
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed
|
||||
if (validateInputs()) {
|
||||
/*
|
||||
* Just in case, we'll lock down the type and value components to
|
||||
* avoid the possibly of a race condition.
|
||||
*/
|
||||
correlationTypeComboBox.setEnabled(false);
|
||||
correlationValueTextField.setEnabled(false);
|
||||
|
||||
search();
|
||||
dispose();
|
||||
} else {
|
||||
searchButton.setEnabled(false);
|
||||
errorLabel.setText(Bundle.OtherCasesSearchDialog_validation_invalidHash());
|
||||
correlationValueTextField.grabFocus();
|
||||
}
|
||||
}//GEN-LAST:event_searchButtonActionPerformed
|
||||
|
||||
/**
|
||||
* Further customize the components beyond the standard initialization.
|
||||
*/
|
||||
private void customizeComponents() {
|
||||
searchButton.setEnabled(false);
|
||||
|
||||
/*
|
||||
* Add correlation types to the combo-box.
|
||||
*/
|
||||
try {
|
||||
EamDb dbManager = EamDb.getInstance();
|
||||
correlationTypes.clear();
|
||||
correlationTypes.addAll(dbManager.getDefinedCorrelationTypes());
|
||||
int numberOfCases = dbManager.getCases().size();
|
||||
casesLabel.setText(Bundle.OtherCasesSearchDialog_caseLabel_text(numberOfCases));
|
||||
} catch (EamDbException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex);
|
||||
}
|
||||
|
||||
for (CorrelationAttributeInstance.Type type : correlationTypes) {
|
||||
// We only support the "Files" type for now.
|
||||
if (type.getDisplayName().equals(FILES_CORRELATION_TYPE)) {
|
||||
correlationTypeComboBox.addItem(type.getDisplayName());
|
||||
}
|
||||
}
|
||||
correlationTypeComboBox.setSelectedIndex(0);
|
||||
|
||||
correlationTypeComboBox.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
updateSearchButton();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Create listener for text input.
|
||||
*/
|
||||
correlationValueTextField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
updateSearchButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
updateSearchButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
updateSearchButton();
|
||||
}
|
||||
});
|
||||
|
||||
updateCorrelationValueTextFieldPrompt();
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"OtherCasesSearchDialog.correlationValueTextField.filesExample=Example: \"f0e1d2c3b4a5968778695a4b3c2d1e0f\""
|
||||
})
|
||||
/**
|
||||
* Update the text prompt of the name text field based on the input type
|
||||
* selection.
|
||||
*/
|
||||
private void updateCorrelationValueTextFieldPrompt() {
|
||||
/**
|
||||
* Add text prompt to the text field.
|
||||
*/
|
||||
String text = Bundle.OtherCasesSearchDialog_correlationValueTextField_filesExample();
|
||||
correlationValueTextFieldPrompt = new TextPrompt(text, correlationValueTextField);
|
||||
|
||||
/**
|
||||
* Sets the foreground color and transparency of the text prompt.
|
||||
*/
|
||||
correlationValueTextFieldPrompt.setForeground(Color.LIGHT_GRAY);
|
||||
correlationValueTextFieldPrompt.changeAlpha(0.9f); // Mostly opaque
|
||||
|
||||
validate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the Search button depending on whether or not text has
|
||||
* been provided for the correlation property value.
|
||||
*/
|
||||
private void updateSearchButton() {
|
||||
searchButton.setEnabled(correlationValueTextField.getText().isEmpty() == false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the value input.
|
||||
*
|
||||
* @return True if the input is valid for the selected type; otherwise false.
|
||||
*/
|
||||
private boolean validateInputs() {
|
||||
Pattern md5Pattern = Pattern.compile("^[a-fA-F0-9]{32}$"); // NON-NLS
|
||||
Matcher matcher = md5Pattern.matcher(correlationValueTextField.getText().trim());
|
||||
if (matcher.find()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Search Other Cases dialog.
|
||||
*/
|
||||
public void display() {
|
||||
this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel casesLabel;
|
||||
private javax.swing.JComboBox<String> correlationTypeComboBox;
|
||||
private javax.swing.JLabel correlationTypeLabel;
|
||||
private javax.swing.JLabel correlationValueLabel;
|
||||
private javax.swing.JTextField correlationValueTextField;
|
||||
private javax.swing.JLabel descriptionLabel;
|
||||
private javax.swing.JLabel errorLabel;
|
||||
private javax.swing.JButton searchButton;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
47
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchNode.java
Executable file
47
Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchNode.java
Executable file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 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.othercasessearch;
|
||||
|
||||
import java.util.List;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
|
||||
/**
|
||||
* Parent node to OtherCasesSearchChildren.
|
||||
*/
|
||||
class OtherCasesSearchNode extends AbstractNode {
|
||||
|
||||
/**
|
||||
* Create an instance of OtherCasesSearchNode.
|
||||
*
|
||||
* @param keys The list of CorrelationAttributeInstances.
|
||||
*/
|
||||
OtherCasesSearchNode(List<CorrelationAttributeInstance> keys) {
|
||||
super(new OtherCasesSearchChildren(true, keys));
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"OtherCasesSearchNode.getName.text=Other Cases Search"
|
||||
})
|
||||
@Override
|
||||
public String getName() {
|
||||
return Bundle.OtherCasesSearchNode_getName_text();
|
||||
}
|
||||
}
|
@ -50,8 +50,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.report.ReportWizardAction")
|
||||
@ActionRegistration(displayName = "#CTL_ReportWizardAction", lazy = false)
|
||||
@ActionReferences(value = {
|
||||
@ActionReference(path = "Menu/Tools", position = 103),
|
||||
@ActionReference(path = "Toolbars/Case", position = 103)})
|
||||
@ActionReference(path = "Menu/Tools", position = 105),
|
||||
@ActionReference(path = "Toolbars/Case", position = 105)})
|
||||
public final class ReportWizardAction extends CallableSystemAction implements Presenter.Toolbar, ActionListener {
|
||||
|
||||
private final JButton toolbarButton = new JButton();
|
||||
|
@ -1,41 +1,35 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.textextractors;
|
||||
|
||||
import com.google.common.io.CharSource;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.SQLiteTableReaderException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.SQLiteTableReader;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Dedicated SqliteTextExtractor to solve the problems associated with Tika's
|
||||
@ -49,7 +43,6 @@ public class SqliteTextExtractor extends ContentTextExtractor {
|
||||
|
||||
private static final String SQLITE_MIMETYPE = "application/x-sqlite3";
|
||||
private static final Logger logger = Logger.getLogger(SqliteTextExtractor.class.getName());
|
||||
private static final CharSequence EMPTY_CHARACTER_SEQUENCE = "";
|
||||
|
||||
@Override
|
||||
public boolean isContentTypeSpecific() {
|
||||
@ -80,7 +73,7 @@ public class SqliteTextExtractor extends ContentTextExtractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream that will read from a sqlite database.
|
||||
* Returns a stream that will read from a sqlite database.
|
||||
*
|
||||
* @param source Content file
|
||||
*
|
||||
@ -91,19 +84,16 @@ public class SqliteTextExtractor extends ContentTextExtractor {
|
||||
*/
|
||||
@Override
|
||||
public Reader getReader(Content source) throws TextExtractorException {
|
||||
try {
|
||||
//Firewall for any content that is not an AbstractFile
|
||||
if (!AbstractFile.class.isInstance(source)) {
|
||||
return CharSource.wrap(EMPTY_CHARACTER_SEQUENCE).openStream();
|
||||
//Firewall for any content that is not an AbstractFile
|
||||
if (!AbstractFile.class.isInstance(source)) {
|
||||
try {
|
||||
return CharSource.wrap("").openStream();
|
||||
} catch (IOException ex) {
|
||||
throw new TextExtractorException("", ex);
|
||||
}
|
||||
return new SQLiteTableReader((AbstractFile) source);
|
||||
} catch (NoCurrentCaseException | IOException | TskCoreException
|
||||
| ClassNotFoundException | SQLException ex) {
|
||||
throw new TextExtractorException(
|
||||
String.format("Encountered an issue while trying to initialize " //NON-NLS
|
||||
+ "a sqlite table steamer for abstract file with id: [%s], name: " //NON-NLS
|
||||
+ "[%s].", source.getId(), source.getName()), ex); //NON-NLS
|
||||
}
|
||||
|
||||
return new SQLiteStreamReader((AbstractFile) source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -112,251 +102,237 @@ public class SqliteTextExtractor extends ContentTextExtractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily loads tables from the database during reading to conserve memory.
|
||||
* Produces a continuous stream of characters from a database file. To
|
||||
* achieve this, all table names are queues up and a SQLiteTableReader is
|
||||
* used to do the actual queries and table iteration.
|
||||
*/
|
||||
private class SQLiteTableReader extends Reader {
|
||||
public class SQLiteStreamReader extends Reader {
|
||||
|
||||
private final Iterator<String> tableIterator;
|
||||
private final Connection connection;
|
||||
private Reader currentTableReader;
|
||||
private final AbstractFile source;
|
||||
private final SQLiteTableReader reader;
|
||||
private final AbstractFile file;
|
||||
|
||||
private Iterator<String> tableNames;
|
||||
private String currentTableName;
|
||||
|
||||
private char[] buf;
|
||||
private ExcessBytes leftOvers;
|
||||
private int totalColumns;
|
||||
|
||||
private int bufIndex;
|
||||
|
||||
/**
|
||||
* Creates a reader that streams each table into memory and wraps a
|
||||
* reader around it. Designed to save memory for large databases.
|
||||
* Creates a new reader for the sqlite file. This table reader class
|
||||
* will iterate through a table row by row and pass the values to
|
||||
* different functions based on data type. Here we define what to do on
|
||||
* the column names and we define what to do for all data types.
|
||||
*
|
||||
* @param file Sqlite database file
|
||||
*
|
||||
* @throws NoCurrentCaseException Current case has closed
|
||||
* @throws IOException Exception copying abstract file over
|
||||
* to local temp directory
|
||||
* @throws TskCoreException Exception using file manager to find
|
||||
* meta files
|
||||
* @throws ClassNotFoundException Could not find sqlite JDBC class
|
||||
* @throws SQLException Could not establish jdbc connection
|
||||
* @param file Sqlite file
|
||||
*/
|
||||
public SQLiteTableReader(AbstractFile file) throws NoCurrentCaseException,
|
||||
IOException, TskCoreException, ClassNotFoundException, SQLException {
|
||||
source = file;
|
||||
|
||||
String localDiskPath = SqliteUtil.writeAbstractFileToLocalDisk(file);
|
||||
SqliteUtil.findAndCopySQLiteMetaFile(file);
|
||||
Class.forName("org.sqlite.JDBC"); //NON-NLS
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + localDiskPath); //NON-NLS
|
||||
tableIterator = getTables().iterator();
|
||||
public SQLiteStreamReader(AbstractFile file) {
|
||||
this.file = file;
|
||||
reader = new SQLiteTableReader.Builder(file)
|
||||
.onColumnNames(getColumnNameStrategy())
|
||||
.forAll(getForAllTableValuesStrategy()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the table names from the SQLite database file.
|
||||
* On every item in the database we want to do the following series of
|
||||
* steps: 1) Get it's string representation (ignore blobs with empty
|
||||
* string). 2) Format it based on its positioning in the row. 3) Write
|
||||
* it to buffer
|
||||
*
|
||||
* @return Collection of table names from the database schema
|
||||
*/
|
||||
private Collection<String> getTables() throws SQLException {
|
||||
Collection<String> tableNames = new LinkedList<>();
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT name FROM sqlite_master "
|
||||
+ " WHERE type= 'table' ")) {
|
||||
while (resultSet.next()) {
|
||||
tableNames.add(resultSet.getString("name")); //NON-NLS
|
||||
}
|
||||
}
|
||||
return tableNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from a database table and loads the contents into a table
|
||||
* builder so that its properly formatted during indexing.
|
||||
* rowIndex is purely for keeping track of where the object is in the
|
||||
* table, hence the bounds checking with the mod function.
|
||||
*
|
||||
* @param tableName Database table to be read
|
||||
* @return Our consumer class defined to do the steps above.
|
||||
*/
|
||||
private String getTableAsString(String tableName) {
|
||||
TableBuilder table = new TableBuilder();
|
||||
table.addTableName(tableName);
|
||||
String quotedTableName = "\"" + tableName + "\"";
|
||||
private Consumer<Object> getForAllTableValuesStrategy() {
|
||||
return new Consumer<Object>() {
|
||||
private int columnIndex = 0;
|
||||
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT * FROM " + quotedTableName)) { //NON-NLS
|
||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||
int columnCount = resultSet.getMetaData().getColumnCount();
|
||||
Collection<String> row = new LinkedList<>();
|
||||
@Override
|
||||
public void accept(Object value) {
|
||||
columnIndex++;
|
||||
//Ignore blobs
|
||||
String objectStr = (value instanceof byte[]) ? "" : Objects.toString(value, "");
|
||||
|
||||
//Add column names once from metadata
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
row.add(metaData.getColumnName(i));
|
||||
}
|
||||
|
||||
table.addHeader(row);
|
||||
while (resultSet.next()) {
|
||||
row = new LinkedList<>();
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
Object result = resultSet.getObject(i);
|
||||
String type = metaData.getColumnTypeName(i);
|
||||
if (isValuableResult(result, type)) {
|
||||
row.add(resultSet.getObject(i).toString());
|
||||
}
|
||||
if (columnIndex > 1 && columnIndex < totalColumns) {
|
||||
objectStr += " ";
|
||||
}
|
||||
table.addRow(row);
|
||||
if (columnIndex == 1) {
|
||||
objectStr = "\t" + objectStr + " ";
|
||||
}
|
||||
if (columnIndex == totalColumns) {
|
||||
objectStr += "\n";
|
||||
}
|
||||
|
||||
fillBuffer(objectStr);
|
||||
columnIndex = columnIndex % totalColumns;
|
||||
}
|
||||
table.addCell("\n");
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, String.format(
|
||||
"Error attempting to read file table: [%s]" //NON-NLS
|
||||
+ " for file: [%s] (id=%d).", tableName, //NON-NLS
|
||||
source.getName(), source.getId()), ex);
|
||||
}
|
||||
|
||||
return table.toString();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the result from the result set is worth adding to the
|
||||
* row. Ignores nulls and blobs for the time being.
|
||||
* On every column name in the header do the following series of steps:
|
||||
* 1) Write the tableName before the header. 2) Format the column name
|
||||
* based on row positioning 3) Reset the count if we are at the end,
|
||||
* that way if we want to read multiple tables we can do so without
|
||||
* having to build new consumers.
|
||||
*
|
||||
* @param result Object result retrieved from resultSet
|
||||
* @param type Type of objet retrieved from resultSet
|
||||
* columnIndex is purely for keeping track of where the column name is
|
||||
* in the table, hence the bounds checking with the mod function.
|
||||
*
|
||||
* @return boolean where true means valuable, false implies it can be
|
||||
* skipped.
|
||||
* @return Our consumer class defined to do the steps above.
|
||||
*/
|
||||
private boolean isValuableResult(Object result, String type) {
|
||||
//Ignore nulls and blobs
|
||||
return result != null && type.compareToIgnoreCase("blob") != 0;
|
||||
private Consumer<String> getColumnNameStrategy() {
|
||||
return new Consumer<String>() {
|
||||
private int columnIndex = 0;
|
||||
|
||||
@Override
|
||||
public void accept(String columnName) {
|
||||
if (columnIndex == 0) {
|
||||
fillBuffer("\n" + currentTableName + "\n\n\t");
|
||||
}
|
||||
columnIndex++;
|
||||
|
||||
fillBuffer(columnName + ((columnIndex == totalColumns) ? "\n" : " "));
|
||||
|
||||
//Reset the columnCount to 0 for next table read
|
||||
columnIndex = columnIndex % totalColumns;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a database file into the character buffer. The underlying
|
||||
* implementation here only loads one table at a time to conserve
|
||||
* memory.
|
||||
* This functions writes the string representation of a database value
|
||||
* into the read buffer. If the buffer becomes full, we save the extra
|
||||
* characters and hold on to them until the next call to read().
|
||||
*
|
||||
* @param cbuf Buffer to copy database content characters into
|
||||
* @param off offset to begin loading in buffer
|
||||
* @param len length of the buffer
|
||||
* @param val Formatted database value string
|
||||
*/
|
||||
private void fillBuffer(String val) {
|
||||
for (int i = 0; i < val.length(); i++) {
|
||||
if (bufIndex != buf.length) {
|
||||
buf[bufIndex++] = val.charAt(i);
|
||||
} else {
|
||||
leftOvers = new ExcessBytes(val, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads database values into the buffer. This function is responsible for
|
||||
* getting the next table in the queue, initiating calls to the SQLiteTableReader,
|
||||
* and filling in any excess bytes that are lingering from the previous call.
|
||||
*
|
||||
* @return The number of characters read from the reader
|
||||
*
|
||||
* @throws IOException If there is an error with the CharSource wrapping
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||
if (currentTableReader == null) {
|
||||
String tableResults = getNextTable();
|
||||
if (tableResults == null) {
|
||||
buf = cbuf;
|
||||
|
||||
bufIndex = off;
|
||||
|
||||
//Lazily wait to get table names until first call to read.
|
||||
if (Objects.isNull(tableNames)) {
|
||||
try {
|
||||
tableNames = reader.getTableNames().iterator();
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
//Can't get table names so can't read the file!
|
||||
return -1;
|
||||
}
|
||||
currentTableReader = CharSource.wrap(tableResults).openStream();
|
||||
}
|
||||
|
||||
int charactersRead = currentTableReader.read(cbuf, off, len);
|
||||
while (charactersRead == -1) {
|
||||
String tableResults = getNextTable();
|
||||
if (tableResults == null) {
|
||||
return -1;
|
||||
//If there are excess bytes from last read, then copy thoses in.
|
||||
if (Objects.nonNull(leftOvers) && !leftOvers.isFinished()) {
|
||||
bufIndex += leftOvers.read(cbuf, off, len);
|
||||
}
|
||||
|
||||
//Keep grabbing table names from the queue and reading them until
|
||||
//our buffer is full.
|
||||
while (bufIndex != len) {
|
||||
if (Objects.isNull(currentTableName) || reader.isFinished()) {
|
||||
if (tableNames.hasNext()) {
|
||||
currentTableName = tableNames.next();
|
||||
try {
|
||||
totalColumns = reader.getColumnCount(currentTableName);
|
||||
reader.read(currentTableName, () -> bufIndex == len);
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
logger.log(Level.WARNING, String.format(
|
||||
"Error attempting to read file table: [%s]" //NON-NLS
|
||||
+ " for file: [%s] (id=%d).", currentTableName, //NON-NLS
|
||||
file.getName(), file.getId()), ex.getMessage());
|
||||
}
|
||||
} else {
|
||||
if (bufIndex == off) {
|
||||
return -1;
|
||||
}
|
||||
return bufIndex;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
reader.read(currentTableName, () -> bufIndex == len);
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
logger.log(Level.WARNING, String.format(
|
||||
"Error attempting to read file table: [%s]" //NON-NLS
|
||||
+ " for file: [%s] (id=%d).", currentTableName, //NON-NLS
|
||||
file.getName(), file.getId()), ex.getMessage());
|
||||
}
|
||||
}
|
||||
currentTableReader = CharSource.wrap(tableResults).openStream();
|
||||
charactersRead = currentTableReader.read(cbuf, off, len);
|
||||
}
|
||||
|
||||
return charactersRead;
|
||||
return bufIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab the next table name from the collection of all table names, once
|
||||
* we no longer have a table to process, return null which will be
|
||||
* understood to mean the end of parsing.
|
||||
*
|
||||
* @return Current table contents or null meaning there are not more
|
||||
* tables to process
|
||||
*/
|
||||
private String getNextTable() {
|
||||
if (tableIterator.hasNext()) {
|
||||
return getTableAsString(tableIterator.next());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the underlying connection to the database.
|
||||
*
|
||||
* @throws IOException Not applicable, we can just catch the
|
||||
* SQLException
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException ex) {
|
||||
//Non-essential exception, user has no need for the connection
|
||||
//object at this stage so closing details are not important
|
||||
logger.log(Level.WARNING, "Could not close JDBC connection", ex);
|
||||
reader.close();
|
||||
} catch (SQLiteTableReaderException ex) {
|
||||
logger.log(Level.WARNING, "Could not close SQliteTableReader.", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats input so that it reads as a table in the console or in a text
|
||||
* viewer
|
||||
*/
|
||||
private class TableBuilder {
|
||||
|
||||
private final Integer DEFAULT_CAPACITY = 32000;
|
||||
private final StringBuilder table = new StringBuilder(DEFAULT_CAPACITY);
|
||||
|
||||
private static final String TAB = "\t";
|
||||
private static final String NEW_LINE = "\n";
|
||||
private static final String SPACE = " ";
|
||||
|
||||
/**
|
||||
* Add the section to the top left corner of the table. This is where
|
||||
* the name of the table should go
|
||||
*
|
||||
* @param tableName Table name
|
||||
* Wrapper that holds the excess bytes that were left over from the previous
|
||||
* call to read().
|
||||
*/
|
||||
public void addTableName(String tableName) {
|
||||
table.append(tableName)
|
||||
.append(NEW_LINE)
|
||||
.append(NEW_LINE);
|
||||
}
|
||||
private class ExcessBytes {
|
||||
|
||||
/**
|
||||
* Adds a formatted header row to the underlying StringBuilder
|
||||
*
|
||||
* @param vals
|
||||
*/
|
||||
public void addHeader(Collection<String> vals) {
|
||||
addRow(vals);
|
||||
}
|
||||
private final String entity;
|
||||
private Integer pointer;
|
||||
|
||||
/**
|
||||
* Adds a formatted row to the underlying StringBuilder
|
||||
*
|
||||
* @param vals
|
||||
*/
|
||||
public void addRow(Collection<String> vals) {
|
||||
table.append(TAB);
|
||||
vals.forEach((val) -> {
|
||||
table.append(val);
|
||||
table.append(SPACE);
|
||||
});
|
||||
table.append(NEW_LINE);
|
||||
}
|
||||
public ExcessBytes(String entity, Integer pointer) {
|
||||
this.entity = entity;
|
||||
this.pointer = pointer;
|
||||
}
|
||||
|
||||
public void addCell(String cell) {
|
||||
table.append(cell);
|
||||
}
|
||||
public boolean isFinished() {
|
||||
return entity.length() == pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string version of the table, with all of the escape
|
||||
* sequences necessary to print nicely in the console output.
|
||||
*
|
||||
* @return Formated table contents
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return table.toString();
|
||||
/**
|
||||
* Copies the excess bytes this instance is holding onto into the
|
||||
* buffer.
|
||||
*
|
||||
* @param buf buffer to write into
|
||||
* @param off index in buffer to start the write
|
||||
* @param len length of the write
|
||||
*
|
||||
* @return number of characters read into the buffer
|
||||
*/
|
||||
public int read(char[] buf, int off, int len) {
|
||||
for (int i = off; i < len; i++) {
|
||||
if (isFinished()) {
|
||||
return i - off;
|
||||
}
|
||||
|
||||
buf[i] = entity.charAt(pointer++);
|
||||
}
|
||||
|
||||
return len - off;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.textextractors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Sqlite utility class. Find and copy metafiles, write sqlite abstract files to
|
||||
* temp directory, and generate unique temp directory paths.
|
||||
*/
|
||||
final class SqliteUtil {
|
||||
|
||||
private SqliteUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded implementation of
|
||||
* {@link #findAndCopySQLiteMetaFile(AbstractFile, String) findAndCopySQLiteMetaFile}
|
||||
* , automatically tries to copy -wal and -shm files without needing to know
|
||||
* their existence.
|
||||
*
|
||||
* @param sqliteFile file which has -wal and -shm meta files
|
||||
*
|
||||
* @throws NoCurrentCaseException Case has been closed.
|
||||
* @throws TskCoreException fileManager cannot find AbstractFile
|
||||
* files.
|
||||
* @throws IOException Issue during writing to file.
|
||||
*/
|
||||
public static void findAndCopySQLiteMetaFile(AbstractFile sqliteFile)
|
||||
throws NoCurrentCaseException, TskCoreException, IOException {
|
||||
|
||||
findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-wal");
|
||||
findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-shm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a meta file associated with the give SQLite database. If
|
||||
* found, it copies this file into the temp directory of the current case.
|
||||
*
|
||||
* @param sqliteFile file being processed
|
||||
* @param metaFileName name of meta file to look for
|
||||
*
|
||||
* @throws NoCurrentCaseException Case has been closed.
|
||||
* @throws TskCoreException fileManager cannot find AbstractFile
|
||||
* files.
|
||||
* @throws IOException Issue during writing to file.
|
||||
*/
|
||||
public static void findAndCopySQLiteMetaFile(AbstractFile sqliteFile,
|
||||
String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
|
||||
|
||||
Case openCase = Case.getCurrentCaseThrows();
|
||||
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
|
||||
Services services = new Services(sleuthkitCase);
|
||||
FileManager fileManager = services.getFileManager();
|
||||
|
||||
List<AbstractFile> metaFiles = fileManager.findFiles(
|
||||
sqliteFile.getDataSource(), metaFileName,
|
||||
sqliteFile.getParent().getName());
|
||||
|
||||
if (metaFiles != null) {
|
||||
for (AbstractFile metaFile : metaFiles) {
|
||||
writeAbstractFileToLocalDisk(metaFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the file contents into a unique path in the current case temp
|
||||
* directory.
|
||||
*
|
||||
* @param file AbstractFile from the data source
|
||||
*
|
||||
* @return The path of the file on disk
|
||||
*
|
||||
* @throws IOException Exception writing file contents
|
||||
* @throws NoCurrentCaseException Current case closed during file copying
|
||||
*/
|
||||
public static String writeAbstractFileToLocalDisk(AbstractFile file)
|
||||
throws IOException, NoCurrentCaseException {
|
||||
|
||||
String localDiskPath = getUniqueTempDirectoryPath(file);
|
||||
File localDatabaseFile = new File(localDiskPath);
|
||||
if (!localDatabaseFile.exists()) {
|
||||
ContentUtils.writeToFile(file, localDatabaseFile);
|
||||
}
|
||||
return localDiskPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique local disk path that resides in the temp directory of
|
||||
* the current case.
|
||||
*
|
||||
* @param file The database abstract file
|
||||
*
|
||||
* @return Unique local disk path living in the temp directory of the case
|
||||
*
|
||||
* @throws org.sleuthkit.autopsy.casemodule.NoCurrentCaseException
|
||||
*/
|
||||
public static String getUniqueTempDirectoryPath(AbstractFile file) throws NoCurrentCaseException {
|
||||
return Case.getCurrentCaseThrows().getTempDirectory()
|
||||
+ File.separator + file.getId() + file.getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.texttranslation;
|
||||
|
||||
/**
|
||||
* Exception to indicate that no Service Provider could be found during the
|
||||
* Lookup action.
|
||||
*/
|
||||
public class NoServiceProviderException extends Exception {
|
||||
|
||||
public NoServiceProviderException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
77
Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java
Executable file
77
Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslationService.java
Executable file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.texttranslation;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.openide.util.Lookup;
|
||||
|
||||
/**
|
||||
* Performs a lookup for a TextTranslator service provider and if present,
|
||||
* will use this provider to run translation on the input.
|
||||
*/
|
||||
public final class TextTranslationService {
|
||||
|
||||
private final static TextTranslationService tts = new TextTranslationService();
|
||||
|
||||
private final Optional<TextTranslator> translator;
|
||||
|
||||
private TextTranslationService(){
|
||||
//Perform look up for Text Translation implementations ONLY ONCE during
|
||||
//class loading.
|
||||
translator = Optional.ofNullable(Lookup.getDefault()
|
||||
.lookup(TextTranslator.class));
|
||||
}
|
||||
|
||||
public static TextTranslationService getInstance() {
|
||||
return tts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the input string using whichever TextTranslator Service Provider
|
||||
* was found during lookup.
|
||||
*
|
||||
* @param input Input string to be translated
|
||||
*
|
||||
* @return Translation string
|
||||
*
|
||||
* @throws NoServiceProviderException Failed to find a Translation service
|
||||
* provider
|
||||
* @throws TranslationException System exception for classes to use
|
||||
* when specific translation
|
||||
* implementations fail
|
||||
*/
|
||||
public String translate(String input) throws NoServiceProviderException, TranslationException {
|
||||
if (translator.isPresent()) {
|
||||
return translator.get().translate(input);
|
||||
}
|
||||
throw new NoServiceProviderException(
|
||||
"Could not find a TextTranslator service provider");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a TextTranslator lookup successfully found an implementing
|
||||
* class.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasProvider() {
|
||||
return translator.isPresent();
|
||||
}
|
||||
}
|
29
Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java
Executable file
29
Core/src/org/sleuthkit/autopsy/texttranslation/TextTranslator.java
Executable file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.texttranslation;
|
||||
|
||||
/**
|
||||
* Interface for creating text translators. Implementing classes will be picked
|
||||
* up and run by the Text Translation Service.
|
||||
*/
|
||||
public interface TextTranslator {
|
||||
|
||||
String translate(String input) throws TranslationException;
|
||||
|
||||
}
|
51
Core/src/org/sleuthkit/autopsy/texttranslation/TranslationException.java
Executable file
51
Core/src/org/sleuthkit/autopsy/texttranslation/TranslationException.java
Executable file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2018 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.texttranslation;
|
||||
|
||||
/**
|
||||
* Provides a system exception for the Text Translation errors
|
||||
*/
|
||||
public class TranslationException extends Exception {
|
||||
|
||||
/**
|
||||
* Constructs a new exception with null as its message.
|
||||
*/
|
||||
public TranslationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified message.
|
||||
*
|
||||
* @param message The message.
|
||||
*/
|
||||
public TranslationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified message and cause.
|
||||
*
|
||||
* @param message The message.
|
||||
* @param cause The cause.
|
||||
*/
|
||||
public TranslationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2017 Basis Technology Corp.
|
||||
* Copyright 2017-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -28,7 +28,7 @@ import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboardOpenAction")
|
||||
@ActionReference(path = "Menu/Tools", position = 104)
|
||||
@ActionReference(path = "Menu/Tools", position = 106)
|
||||
@ActionRegistration(displayName = "#CTL_AutoIngestDashboardOpenAction", lazy = false)
|
||||
@Messages({"CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard"})
|
||||
public final class AutoIngestDashboardOpenAction extends CallableSystemAction {
|
||||
|
@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -50,6 +51,7 @@ import javax.annotation.Nonnull;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
|
||||
@ -98,7 +100,7 @@ public final class ImageGalleryController {
|
||||
private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false);
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false);
|
||||
private final ReadOnlyBooleanWrapper isCaseStale = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false);
|
||||
private final SimpleDoubleProperty thumbnailSizeProp = new SimpleDoubleProperty(100);
|
||||
@ -159,26 +161,34 @@ public final class ImageGalleryController {
|
||||
}
|
||||
}
|
||||
|
||||
boolean isListeningEnabled() {
|
||||
public boolean isListeningEnabled() {
|
||||
synchronized (listeningEnabled) {
|
||||
return listeningEnabled.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param b True if any data source in the case is stale
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||
void setStale(Boolean b) {
|
||||
void setCaseStale(Boolean b) {
|
||||
Platform.runLater(() -> {
|
||||
stale.set(b);
|
||||
isCaseStale.set(b);
|
||||
});
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty staleProperty() {
|
||||
return stale.getReadOnlyProperty();
|
||||
return isCaseStale.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if any data source in the case is stale
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
boolean isStale() {
|
||||
return stale.get();
|
||||
boolean isCaseStale() {
|
||||
return isCaseStale.get();
|
||||
}
|
||||
|
||||
ImageGalleryController(@Nonnull Case newCase) throws TskCoreException {
|
||||
@ -196,7 +206,7 @@ public final class ImageGalleryController {
|
||||
tagsManager.registerListener(categoryManager);
|
||||
|
||||
hashSetManager = new HashSetManager(drawableDB);
|
||||
setStale(isDataSourcesTableStale());
|
||||
setCaseStale(isDataSourcesTableStale());
|
||||
|
||||
dbExecutor = getNewDBExecutor();
|
||||
|
||||
@ -206,8 +216,8 @@ public final class ImageGalleryController {
|
||||
// if we just turned on listening and a single-user case is open and that case is not up to date, then rebuild it
|
||||
// For multiuser cases, we defer DB rebuild till the user actually opens Image Gallery
|
||||
if (isEnabled && !wasPreviouslyEnabled
|
||||
&& isDataSourcesTableStale()
|
||||
&& (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE)) {
|
||||
&& isDataSourcesTableStale()
|
||||
&& (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE)) {
|
||||
//populate the db
|
||||
this.rebuildDB();
|
||||
}
|
||||
@ -326,6 +336,7 @@ public final class ImageGalleryController {
|
||||
groupManager.reset();
|
||||
|
||||
shutDownDBExecutor();
|
||||
drawableDB.close();
|
||||
dbExecutor = getNewDBExecutor();
|
||||
}
|
||||
|
||||
@ -342,7 +353,7 @@ public final class ImageGalleryController {
|
||||
* Returns a set of data source object ids that are stale.
|
||||
*
|
||||
* This includes any data sources already in the table, that are not in
|
||||
* COMPLETE status, or any data sources that might have been added to the
|
||||
* COMPLETE or IN_PROGRESS status, or any data sources that might have been added to the
|
||||
* case, but are not in the datasources table.
|
||||
*
|
||||
* @return list of data source object ids that are stale.
|
||||
@ -366,9 +377,27 @@ public final class ImageGalleryController {
|
||||
// collect all data sources already in the table, that are not yet COMPLETE
|
||||
knownDataSourceIds.entrySet().stream().forEach((Map.Entry<Long, DrawableDbBuildStatusEnum> t) -> {
|
||||
DrawableDbBuildStatusEnum status = t.getValue();
|
||||
if (DrawableDbBuildStatusEnum.COMPLETE != status) {
|
||||
staleDataSourceIds.add(t.getKey());
|
||||
switch (status) {
|
||||
case COMPLETE:
|
||||
case IN_PROGRESS:
|
||||
// not stale
|
||||
break;
|
||||
case REBUILT_STALE:
|
||||
staleDataSourceIds.add(t.getKey());
|
||||
break;
|
||||
case UNKNOWN:
|
||||
try {
|
||||
// stale if there are files in CaseDB with MIME types
|
||||
if (hasFilesWithMimeType(t.getKey())) {
|
||||
staleDataSourceIds.add(t.getKey());
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting MIME types", ex);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// collect any new data sources in the case.
|
||||
@ -383,16 +412,15 @@ public final class ImageGalleryController {
|
||||
logger.log(Level.SEVERE, "Image Gallery failed to check if datasources table is stale.", ex);
|
||||
return staleDataSourceIds;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all data source object ids, along with
|
||||
* their DB build status.
|
||||
/**
|
||||
* Returns a map of all data source object ids, along with their DB build
|
||||
* status.
|
||||
*
|
||||
* This includes any data sources already in the table,
|
||||
* and any data sources that might have been added to the
|
||||
* case, but are not in the datasources table.
|
||||
* This includes any data sources already in the table, and any data sources
|
||||
* that might have been added to the case, but are not in the datasources
|
||||
* table.
|
||||
*
|
||||
* @return map of data source object ids and their Db build status.
|
||||
*/
|
||||
@ -430,7 +458,7 @@ public final class ImageGalleryController {
|
||||
return dataSourceStatusMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean hasTooManyFiles(DataSource datasource) throws TskCoreException {
|
||||
String whereClause = (datasource == null)
|
||||
? "1 = 1"
|
||||
@ -439,26 +467,37 @@ public final class ImageGalleryController {
|
||||
return sleuthKitCase.countFilesWhere(whereClause) > FILE_LIMIT;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the given data source has any files with no mimetype
|
||||
*
|
||||
*
|
||||
* @param datasource
|
||||
*
|
||||
* @return true if the datasource has any files with no mime type
|
||||
* @throws TskCoreException
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public boolean hasFilesWithNoMimetype(Content datasource) throws TskCoreException {
|
||||
|
||||
public boolean hasFilesWithNoMimeType(long dataSourceId) throws TskCoreException {
|
||||
|
||||
// There are some special files/attributes in the root folder, like $BadClus:$Bad and $Security:$SDS
|
||||
// The IngestTasksScheduler does not push them down to the ingest modules,
|
||||
// and hence they do not have any assigned mimetype
|
||||
String whereClause = "data_source_obj_id = " + datasource.getId()
|
||||
+ " AND ( meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + ")"
|
||||
+ " AND ( mime_type IS NULL )"
|
||||
+ " AND ( meta_addr >= 32 ) "
|
||||
+ " AND ( parent_path <> '/' )"
|
||||
+ " AND ( name NOT like '$%:%' )";
|
||||
|
||||
String whereClause = "data_source_obj_id = " + dataSourceId
|
||||
+ " AND ( meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + ")"
|
||||
+ " AND ( mime_type IS NULL )"
|
||||
+ " AND ( meta_addr >= 32 ) "
|
||||
+ " AND ( parent_path <> '/' )"
|
||||
+ " AND ( name NOT like '$%:%' )";
|
||||
|
||||
return sleuthKitCase.countFilesWhere(whereClause) > 0;
|
||||
}
|
||||
|
||||
public boolean hasFilesWithMimeType(long dataSourceId) throws TskCoreException {
|
||||
|
||||
String whereClause = "data_source_obj_id = " + dataSourceId
|
||||
+ " AND ( meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + ")"
|
||||
+ " AND ( mime_type IS NOT NULL )";
|
||||
|
||||
return sleuthKitCase.countFilesWhere(whereClause) > 0;
|
||||
}
|
||||
|
||||
@ -595,10 +634,11 @@ public final class ImageGalleryController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Abstract base class for tasks associated with a file in the database
|
||||
* task that updates one file in database with results from ingest
|
||||
*/
|
||||
static abstract class FileTask extends BackgroundTask {
|
||||
static class UpdateFileTask extends BackgroundTask {
|
||||
|
||||
private final AbstractFile file;
|
||||
private final DrawableDB taskDB;
|
||||
@ -610,22 +650,12 @@ public final class ImageGalleryController {
|
||||
public AbstractFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
FileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
|
||||
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super();
|
||||
this.file = f;
|
||||
this.taskDB = taskDB;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* task that updates one file in database with results from ingest
|
||||
*/
|
||||
static class UpdateFileTask extends FileTask {
|
||||
|
||||
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a file in the database
|
||||
@ -635,41 +665,12 @@ public final class ImageGalleryController {
|
||||
try {
|
||||
DrawableFile drawableFile = DrawableFile.create(getFile(), true, false);
|
||||
getTaskDB().updateFile(drawableFile);
|
||||
} catch (NullPointerException ex) {
|
||||
// This is one of the places where we get many errors if the case is closed during processing.
|
||||
// We don't want to print out a ton of exceptions if this is the case.
|
||||
if (Case.isCaseOpen()) {
|
||||
Logger.getLogger(UpdateFileTask.class.getName()).log(Level.SEVERE, "Error in UpdateFile task"); //NON-NLS
|
||||
}
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
Logger.getLogger(UpdateFileTask.class.getName()).log(Level.SEVERE, "Error in update file task", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* task that updates one file in database with results from ingest
|
||||
*/
|
||||
static class RemoveFileTask extends FileTask {
|
||||
|
||||
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a file in the database
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getTaskDB().removeFile(getFile().getId());
|
||||
} catch (NullPointerException ex) {
|
||||
// This is one of the places where we get many errors if the case is closed during processing.
|
||||
// We don't want to print out a ton of exceptions if this is the case.
|
||||
if (Case.isCaseOpen()) {
|
||||
Logger.getLogger(RemoveFileTask.class.getName()).log(Level.SEVERE, "Case was closed out from underneath RemoveFile task"); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base abstract class for various methods of copying image files data, for
|
||||
@ -679,16 +680,10 @@ public final class ImageGalleryController {
|
||||
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
||||
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||
abstract static class BulkTransferTask extends BackgroundTask {
|
||||
|
||||
static private final String FILE_EXTENSION_CLAUSE
|
||||
= "(extension LIKE '" //NON-NLS
|
||||
+ String.join("' OR extension LIKE '", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
|
||||
+ "') ";
|
||||
|
||||
static private final String MIMETYPE_CLAUSE
|
||||
= "(mime_type LIKE '" //NON-NLS
|
||||
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||
+ "') ";
|
||||
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||
+ "') ";
|
||||
|
||||
private final String DRAWABLE_QUERY;
|
||||
private final String DATASOURCE_CLAUSE;
|
||||
@ -711,22 +706,18 @@ public final class ImageGalleryController {
|
||||
|
||||
DRAWABLE_QUERY
|
||||
= DATASOURCE_CLAUSE
|
||||
+ " AND ( meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + ")"
|
||||
+ " AND ( "
|
||||
+ //grab files with supported extension
|
||||
FILE_EXTENSION_CLAUSE
|
||||
//grab files with supported mime-types
|
||||
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
|
||||
//grab files with image or video mime-types even if we don't officially support them
|
||||
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
|
||||
+ " AND ( meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + ")"
|
||||
+ " AND ( "
|
||||
//grab files with supported mime-types
|
||||
+ MIMETYPE_CLAUSE //NON-NLS
|
||||
//grab files with image or video mime-types even if we don't officially support them
|
||||
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
* Do any cleanup for this task.
|
||||
*
|
||||
* @param success true if the transfer was successful
|
||||
*/
|
||||
abstract void cleanup(boolean success);
|
||||
abstract void cleanup();
|
||||
|
||||
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException;
|
||||
|
||||
@ -749,22 +740,29 @@ public final class ImageGalleryController {
|
||||
|
||||
DrawableDB.DrawableTransaction drawableDbTransaction = null;
|
||||
CaseDbTransaction caseDbTransaction = null;
|
||||
boolean hasFilesWithNoMime = true;
|
||||
boolean endedEarly = false;
|
||||
|
||||
try {
|
||||
//grab all files with supported extension or detected mime types
|
||||
// See if there are any files in the DS w/out a MIME TYPE
|
||||
hasFilesWithNoMime = controller.hasFilesWithNoMimeType(dataSourceObjId);
|
||||
|
||||
//grab all files with detected mime types
|
||||
final List<AbstractFile> files = getFiles();
|
||||
progressHandle.switchToDeterminate(files.size());
|
||||
|
||||
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
|
||||
|
||||
updateProgress(0.0);
|
||||
taskCompletionStatus = true;
|
||||
int workDone = 0;
|
||||
|
||||
|
||||
// Cycle through all of the files returned and call processFile on each
|
||||
//do in transaction
|
||||
drawableDbTransaction = taskDB.beginTransaction();
|
||||
|
||||
/* We are going to periodically commit the CaseDB transaction
|
||||
/*
|
||||
* We are going to periodically commit the CaseDB transaction
|
||||
* and sleep so that the user can have Autopsy do other stuff
|
||||
* while these bulk tasks are ongoing.
|
||||
*/
|
||||
@ -776,8 +774,9 @@ public final class ImageGalleryController {
|
||||
|
||||
if (isCancelled() || Thread.interrupted()) {
|
||||
logger.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS
|
||||
taskCompletionStatus = false;
|
||||
endedEarly = true;
|
||||
progressHandle.finish();
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
@ -813,42 +812,43 @@ public final class ImageGalleryController {
|
||||
taskDB.commitTransaction(drawableDbTransaction, true);
|
||||
drawableDbTransaction = null;
|
||||
|
||||
} catch (TskCoreException | InterruptedException ex) {
|
||||
progressHandle.progress(Bundle.BulkTask_stopCopy_status());
|
||||
logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
|
||||
cleanup(false);
|
||||
return;
|
||||
} finally {
|
||||
if (null != drawableDbTransaction) {
|
||||
taskDB.rollbackTransaction(drawableDbTransaction);
|
||||
}
|
||||
} catch (TskCoreException | SQLException | InterruptedException ex) {
|
||||
if (null != caseDbTransaction) {
|
||||
try {
|
||||
caseDbTransaction.rollback();
|
||||
} catch (TskCoreException ex2) {
|
||||
logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Failed to roll back case db transaction after error: %s", ex.getMessage()), ex2); //NON-NLS
|
||||
}
|
||||
}
|
||||
if (null != drawableDbTransaction) {
|
||||
try {
|
||||
taskDB.rollbackTransaction(drawableDbTransaction);
|
||||
} catch (SQLException ex2) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to roll back drawables db transaction after error: %s", ex.getMessage()), ex2); //NON-NLS
|
||||
}
|
||||
}
|
||||
progressHandle.progress(Bundle.BulkTask_stopCopy_status());
|
||||
logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
|
||||
endedEarly = true;
|
||||
} finally {
|
||||
progressHandle.finish();
|
||||
|
||||
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus =
|
||||
(taskCompletionStatus) ?
|
||||
DrawableDB.DrawableDbBuildStatusEnum.COMPLETE :
|
||||
DrawableDB.DrawableDbBuildStatusEnum.DEFAULT;
|
||||
|
||||
// Mark to REBUILT_STALE if some files didnt' have MIME (ingest was still ongoing) or
|
||||
// if there was cancellation or errors
|
||||
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus
|
||||
= ((hasFilesWithNoMime == true) || (endedEarly == true))
|
||||
? DrawableDB.DrawableDbBuildStatusEnum.REBUILT_STALE
|
||||
: DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
|
||||
taskDB.insertOrUpdateDataSource(dataSourceObjId, datasourceDrawableDBStatus);
|
||||
|
||||
|
||||
updateMessage("");
|
||||
updateProgress(-1.0);
|
||||
}
|
||||
cleanup(taskCompletionStatus);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
abstract ProgressHandle getInitialProgressHandle();
|
||||
|
||||
protected void setTaskCompletionStatus(boolean status) {
|
||||
taskCompletionStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -869,11 +869,11 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(boolean success) {
|
||||
protected void cleanup() {
|
||||
taskDB.freeFileMetaDataCache();
|
||||
// at the end of the task, set the stale status based on the
|
||||
// cumulative status of all data sources
|
||||
controller.setStale(controller.isDataSourcesTableStale());
|
||||
controller.setCaseStale(controller.isDataSourcesTableStale());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -883,12 +883,9 @@ public final class ImageGalleryController {
|
||||
if (known) {
|
||||
taskDB.removeFile(f.getId(), tr); //remove known files
|
||||
} else {
|
||||
// if mimetype of the file hasn't been ascertained, ingest might not have completed yet.
|
||||
if (null == f.getMIMEType()) {
|
||||
// set to false to force the DB to be marked as stale
|
||||
this.setTaskCompletionStatus(false);
|
||||
} //supported mimetype => analyzed
|
||||
else if (FileTypeUtils.hasDrawableMIMEType(f)) {
|
||||
// NOTE: Files are being processed because they have the right MIME type,
|
||||
// so we do not need to worry about this calculating them
|
||||
if (FileTypeUtils.hasDrawableMIMEType(f)) {
|
||||
taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction);
|
||||
} //unsupported mimtype => analyzed but shouldn't include
|
||||
else {
|
||||
|
@ -189,14 +189,7 @@ public class ImageGalleryModule {
|
||||
if (isDrawableAndNotKnown(file)) {
|
||||
con.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase()));
|
||||
}
|
||||
// Remove it from the DB if it is no longer relevant, but had the correct extension
|
||||
else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
|
||||
/* Doing this check results in fewer tasks queued
|
||||
* up, and faster completion of db update. This file
|
||||
* would have gotten scooped up in initial grab, but
|
||||
* actually we don't need it */
|
||||
con.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase()));
|
||||
}
|
||||
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error("Image Gallery Error",
|
||||
@ -284,7 +277,7 @@ public class ImageGalleryModule {
|
||||
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||
Content newDataSource = (Content) evt.getNewValue();
|
||||
if (con.isListeningEnabled()) {
|
||||
controller.getDatabase().insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.DEFAULT);
|
||||
controller.getDatabase().insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -336,10 +329,14 @@ public class ImageGalleryModule {
|
||||
|
||||
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||
if (controller.isListeningEnabled()) {
|
||||
DataSourceAnalysisStartedEvent dataSourceAnalysisStartedEvent = (DataSourceAnalysisStartedEvent) evt;
|
||||
DataSourceAnalysisStartedEvent dataSourceAnalysisStartedEvent = (DataSourceAnalysisStartedEvent) evt;
|
||||
Content dataSource = dataSourceAnalysisStartedEvent.getDataSource();
|
||||
|
||||
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
|
||||
|
||||
DrawableDB drawableDb = controller.getDatabase();
|
||||
// Don't update status if it is is already marked as COMPLETE
|
||||
if (drawableDb.getDataSourceDbBuildStatus(dataSource.getId()) != DrawableDB.DrawableDbBuildStatusEnum.COMPLETE) {
|
||||
drawableDb.insertOrUpdateDataSource(dataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (eventType == IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED) {
|
||||
@ -349,12 +346,18 @@ public class ImageGalleryModule {
|
||||
DataSourceAnalysisCompletedEvent dataSourceAnalysisCompletedEvent = (DataSourceAnalysisCompletedEvent) evt;
|
||||
Content dataSource = dataSourceAnalysisCompletedEvent.getDataSource();
|
||||
|
||||
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus =
|
||||
controller.hasFilesWithNoMimetype(dataSource) ?
|
||||
DrawableDB.DrawableDbBuildStatusEnum.DEFAULT :
|
||||
DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
|
||||
DrawableDB drawableDb = controller.getDatabase();
|
||||
if (drawableDb.getDataSourceDbBuildStatus(dataSource.getId()) == DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS) {
|
||||
|
||||
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), datasourceDrawableDBStatus);
|
||||
// If at least one file in CaseDB has mime type, then set to COMPLETE
|
||||
// Otherwise, back to UNKNOWN since we assume file type module was not run
|
||||
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus =
|
||||
controller.hasFilesWithMimeType(dataSource.getId()) ?
|
||||
DrawableDB.DrawableDbBuildStatusEnum.COMPLETE :
|
||||
DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN;
|
||||
|
||||
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), datasourceDrawableDBStatus);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -362,7 +365,7 @@ public class ImageGalleryModule {
|
||||
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
|
||||
// A remote node added a new data source and just finished ingest on it.
|
||||
//drawable db is stale, and if ImageGallery is open, ask user what to do
|
||||
controller.setStale(true);
|
||||
controller.setCaseStale(true);
|
||||
if (controller.isListeningEnabled()) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (ImageGalleryTopComponent.isImageGalleryOpen()) {
|
||||
|
@ -114,9 +114,6 @@
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.enabledByDefaultBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="enabledByDefaultBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSeparator" name="jSeparator1">
|
||||
</Component>
|
||||
@ -146,9 +143,6 @@
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.enabledForCaseBox.toolTipText" replaceFormat="NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="enabledForCaseBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="unavailableDuringInjestLabel">
|
||||
<Properties>
|
||||
|
@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.util.EnumSet;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
@ -30,8 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* The Image/Video Gallery panel in the NetBeans provided Options Dialogs
|
||||
* accessed via Tools -> Options
|
||||
*
|
||||
* Uses ImageGalleryPreferences and PerCaseProperties to persist
|
||||
* settings
|
||||
* Uses ImageGalleryPreferences and PerCaseProperties to persist settings
|
||||
*/
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
@ -46,6 +47,10 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
//disable during ingest
|
||||
enabledForCaseBox.setEnabled(Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false);
|
||||
});
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> {
|
||||
//disable when case is closed, enable when case is open
|
||||
enabledForCaseBox.setEnabled(evt.getNewValue() != null && IngestManager.getInstance().isIngestRunning() == false);
|
||||
});
|
||||
|
||||
enabledByDefaultBox.addActionListener(actionEvent -> controller.changed());
|
||||
enabledForCaseBox.addActionListener(actionEvent -> controller.changed());
|
||||
@ -76,11 +81,6 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
|
||||
enabledByDefaultBox.setFont(enabledByDefaultBox.getFont().deriveFont(enabledByDefaultBox.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
||||
org.openide.awt.Mnemonics.setLocalizedText(enabledByDefaultBox, org.openide.util.NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.enabledByDefaultBox.text")); // NOI18N
|
||||
enabledByDefaultBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
enabledByDefaultBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
infoIconLabel.setFont(infoIconLabel.getFont().deriveFont(infoIconLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
||||
infoIconLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"))); // NOI18N
|
||||
@ -88,11 +88,6 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
enabledForCaseBox.setFont(enabledForCaseBox.getFont().deriveFont(enabledForCaseBox.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
||||
org.openide.awt.Mnemonics.setLocalizedText(enabledForCaseBox, org.openide.util.NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.enabledForCaseBox.text")); // NOI18N
|
||||
enabledForCaseBox.setToolTipText(NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.enabledForCaseBox.toolTipText")); // NOI18N
|
||||
enabledForCaseBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
enabledForCaseBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
unavailableDuringInjestLabel.setFont(unavailableDuringInjestLabel.getFont().deriveFont(unavailableDuringInjestLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
||||
unavailableDuringInjestLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/warning16.png"))); // NOI18N
|
||||
@ -174,14 +169,6 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void enabledByDefaultBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enabledByDefaultBoxActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_enabledByDefaultBoxActionPerformed
|
||||
|
||||
private void enabledForCaseBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enabledForCaseBoxActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_enabledForCaseBoxActionPerformed
|
||||
|
||||
void load() {
|
||||
enabledByDefaultBox.setSelected(ImageGalleryPreferences.isEnabledByDefault());
|
||||
try {
|
||||
|
@ -41,7 +41,7 @@ import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"NextUnseenGroup.markGroupSeen=Mark Group Seen",
|
||||
"NextUnseenGroup.nextUnseenGroup=Next Unseen group",
|
||||
"NextUnseenGroup.nextUnseenGroup=Next Unseen Group",
|
||||
"NextUnseenGroup.allGroupsSeen=All Groups Have Been Seen"})
|
||||
public class NextUnseenGroup extends Action {
|
||||
|
||||
|
@ -39,6 +39,7 @@ import org.openide.awt.ActionID;
|
||||
import org.openide.awt.ActionReference;
|
||||
import org.openide.awt.ActionReferences;
|
||||
import org.openide.awt.ActionRegistration;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.HelpCtx;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
@ -192,47 +193,50 @@ public final class OpenAction extends CallableSystemAction {
|
||||
addFXCallback(dataSourceStatusMapFuture,
|
||||
dataSourceStatusMap -> {
|
||||
|
||||
boolean dbIsStale = false;
|
||||
int numStale = 0;
|
||||
int numNoAnalysis = 0;
|
||||
// NOTE: There is some overlapping code here with Controller.getStaleDataSourceIds(). We could possibly just use
|
||||
// that method to figure out stale and then do more simple stuff here to figure out if there is no data at all
|
||||
for (Map.Entry<Long, DrawableDbBuildStatusEnum> entry : dataSourceStatusMap.entrySet()) {
|
||||
DrawableDbBuildStatusEnum status = entry.getValue();
|
||||
if (DrawableDbBuildStatusEnum.COMPLETE != status) {
|
||||
dbIsStale = true;
|
||||
if (DrawableDbBuildStatusEnum.UNKNOWN == status) {
|
||||
try {
|
||||
// likely a data source analyzed on a remote node in multi-user case OR single-user case with listening off
|
||||
if (controller.hasFilesWithMimeType(entry.getKey())) {
|
||||
numStale++;
|
||||
// likely a data source (local or remote) that has no analysis yet (note there is also IN_PROGRESS state)
|
||||
} else {
|
||||
numNoAnalysis++;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error querying case database", ex);
|
||||
}
|
||||
}
|
||||
// was already rebuilt, but wasn't complete at the end
|
||||
else if (DrawableDbBuildStatusEnum.REBUILT_STALE == status) {
|
||||
numStale++;
|
||||
}
|
||||
}
|
||||
|
||||
//back on fx thread.
|
||||
if (false == dbIsStale) {
|
||||
//drawable db is not stale, just open it
|
||||
openTopComponent();
|
||||
} else {
|
||||
|
||||
// If there is only one datasource and it's in DEFAULT State -
|
||||
// ingest modules need to be run on the data source
|
||||
if (dataSourceStatusMap.size()== 1) {
|
||||
Map.Entry<Long, DrawableDB.DrawableDbBuildStatusEnum> entry = dataSourceStatusMap.entrySet().iterator().next();
|
||||
if (entry.getValue() == DrawableDbBuildStatusEnum.DEFAULT ) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.OpenAction_notAnalyzedDlg_msg(), ButtonType.OK);
|
||||
alert.setTitle(Bundle.OpenAction_stale_confDlg_title());
|
||||
alert.initModality(Modality.APPLICATION_MODAL);
|
||||
// NOTE: we are running on the fx thread.
|
||||
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//drawable db is stale,
|
||||
//ask what to do
|
||||
// If there are any that are STALE, give them a prompt to do so.
|
||||
if (numStale > 0) {
|
||||
// See if user wants to rebuild, cancel out, or open as is
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING,
|
||||
Bundle.OpenAction_stale_confDlg_msg(),
|
||||
ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
|
||||
Bundle.OpenAction_stale_confDlg_msg(),
|
||||
ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
|
||||
alert.initModality(Modality.APPLICATION_MODAL);
|
||||
alert.setTitle(Bundle.OpenAction_stale_confDlg_title());
|
||||
GuiUtils.setDialogIcons(alert);
|
||||
ButtonType answer = alert.showAndWait().orElse(ButtonType.CANCEL);
|
||||
|
||||
if (answer == ButtonType.CANCEL) {
|
||||
//just do nothing
|
||||
//just do nothing - don't open window
|
||||
return;
|
||||
} else if (answer == ButtonType.NO) {
|
||||
openTopComponent();
|
||||
// They don't want to rebuild. Just open the UI as is.
|
||||
// NOTE: There could be no data....
|
||||
} else if (answer == ButtonType.YES) {
|
||||
if (controller.getAutopsyCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
|
||||
/* For a single-user case, we favor user
|
||||
@ -255,9 +259,23 @@ public final class OpenAction extends CallableSystemAction {
|
||||
*/
|
||||
controller.rebuildDB();
|
||||
}
|
||||
openTopComponent();
|
||||
}
|
||||
openTopComponent();
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is no data to display, then let them know
|
||||
if (numNoAnalysis == dataSourceStatusMap.size()) {
|
||||
// give them a dialog to enable modules if no data sources have been analyzed
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.OpenAction_notAnalyzedDlg_msg(), ButtonType.OK);
|
||||
alert.setTitle(Bundle.OpenAction_stale_confDlg_title());
|
||||
alert.initModality(Modality.APPLICATION_MODAL);
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, lets open the UI
|
||||
openTopComponent();
|
||||
},
|
||||
throwable -> logger.log(Level.SEVERE, "Error checking if drawable db is stale.", throwable)//NON-NLS
|
||||
);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -33,7 +33,9 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
*/
|
||||
public class HashSetManager {
|
||||
|
||||
/** The db that initial values are loaded from. */
|
||||
/**
|
||||
* The db that initial values are loaded from.
|
||||
*/
|
||||
private final DrawableDB drawableDB;
|
||||
|
||||
public HashSetManager(DrawableDB drawableDB) {
|
||||
@ -54,14 +56,9 @@ public class HashSetManager {
|
||||
*/
|
||||
private Set<String> getHashSetsForFileHelper(long fileID) {
|
||||
try {
|
||||
if (drawableDB.isClosed()) {
|
||||
Logger.getLogger(HashSetManager.class.getName()).log(Level.WARNING, "Failed to get Hash Sets for file. The Db connection was already closed."); //NON-NLS
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return drawableDB.getHashSetsForFile(fileID);
|
||||
}
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file."); //NON-NLS
|
||||
return drawableDB.getHashSetsForFile(fileID);
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, String.format("Failed to get hash sets for file (id=%d)", fileID), ex); //NON-NLS
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
@ -99,18 +99,24 @@ public class GroupManager {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GroupManager.class.getName());
|
||||
|
||||
/** An executor to submit async UI related background tasks to. */
|
||||
/**
|
||||
* An executor to submit async UI related background tasks to.
|
||||
*/
|
||||
private final ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
|
||||
new BasicThreadFactory.Builder().namingPattern("GroupManager BG Thread-%d").build())); //NON-NLS
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
/** list of all analyzed groups */
|
||||
/**
|
||||
* list of all analyzed groups
|
||||
*/
|
||||
@GuardedBy("this") //NOPMD
|
||||
private final ObservableList<DrawableGroup> analyzedGroups = FXCollections.observableArrayList();
|
||||
private final ObservableList<DrawableGroup> unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups);
|
||||
|
||||
/** list of unseen groups */
|
||||
/**
|
||||
* list of unseen groups
|
||||
*/
|
||||
@GuardedBy("this") //NOPMD
|
||||
private final ObservableList<DrawableGroup> unSeenGroups = FXCollections.observableArrayList();
|
||||
private final ObservableList<DrawableGroup> unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups);
|
||||
@ -273,7 +279,7 @@ public class GroupManager {
|
||||
updateUnSeenGroups(group);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Error setting seen status for group: %s", group.getGroupKey().getValue().toString()), ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -487,8 +493,8 @@ public class GroupManager {
|
||||
setSortOrder(sortOrder);
|
||||
//only re-query the db if the data source or group by attribute changed or it is forced
|
||||
if (dataSource != getDataSource()
|
||||
|| groupBy != getGroupBy()
|
||||
|| force) {
|
||||
|| groupBy != getGroupBy()
|
||||
|| force) {
|
||||
|
||||
setDataSource(dataSource);
|
||||
setGroupBy(groupBy);
|
||||
@ -645,9 +651,9 @@ public class GroupManager {
|
||||
* analyzed because we don't know all the files that will be a part of
|
||||
* that group. just show them no matter what.
|
||||
*/
|
||||
if (groupKey.getAttribute() != DrawableAttribute.PATH
|
||||
|| getDrawableDB().isGroupAnalyzed(groupKey)) {
|
||||
try {
|
||||
try {
|
||||
if (groupKey.getAttribute() != DrawableAttribute.PATH
|
||||
|| getDrawableDB().isGroupAnalyzed(groupKey)) {
|
||||
Set<Long> fileIDs = getFileIDsInGroup(groupKey);
|
||||
if (Objects.nonNull(fileIDs)) {
|
||||
|
||||
@ -673,9 +679,9 @@ public class GroupManager {
|
||||
|
||||
return group;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS
|
||||
}
|
||||
} catch (SQLException | TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -735,7 +741,7 @@ public class GroupManager {
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@NbBundle.Messages({"# {0} - groupBy attribute Name",
|
||||
"ReGroupTask.displayTitle=regrouping by {0}: " })
|
||||
"ReGroupTask.displayTitle=regrouping by {0}: "})
|
||||
class ReGroupTask<AttrValType extends Comparable<AttrValType>> extends LoggedTask<Void> {
|
||||
|
||||
private final DataSource dataSource;
|
||||
@ -744,13 +750,13 @@ public class GroupManager {
|
||||
private final SortOrder sortOrder;
|
||||
|
||||
ReGroupTask(DataSource dataSource, DrawableAttribute<AttrValType> groupBy, GroupSortBy sortBy, SortOrder sortOrder) {
|
||||
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString() ), true);
|
||||
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString()), true);
|
||||
this.dataSource = dataSource;
|
||||
this.groupBy = groupBy;
|
||||
this.sortBy = sortBy;
|
||||
this.sortOrder = sortOrder;
|
||||
|
||||
updateTitle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString() ));
|
||||
updateTitle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -791,8 +797,8 @@ public class GroupManager {
|
||||
= viewedKey.map(GroupKey::getAttribute).orElse(null);
|
||||
|
||||
if (viewedGroup.isPresent() == false //if no group was being viewed,
|
||||
|| (dataSource != null && notEqual(dataSourceOfCurrentGroup, dataSource)) //or the datasource of the viewed group is wrong,
|
||||
|| groupBy != attributeOfCurrentGroup) { // or the groupBy attribute is wrong...
|
||||
|| (dataSource != null && notEqual(dataSourceOfCurrentGroup, dataSource)) //or the datasource of the viewed group is wrong,
|
||||
|| groupBy != attributeOfCurrentGroup) { // or the groupBy attribute is wrong...
|
||||
|
||||
//the current group should not be visible so ...
|
||||
if (isNotEmpty(unSeenGroups)) {
|
||||
|
@ -50,6 +50,11 @@ public class DataSourceCell extends ListCell<Optional<DataSource>> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @param empty
|
||||
*/
|
||||
@Override
|
||||
protected void updateItem(Optional<DataSource> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
@ -57,31 +62,42 @@ public class DataSourceCell extends ListCell<Optional<DataSource>> {
|
||||
setText("");
|
||||
} else {
|
||||
DataSource dataSource = item.orElse(null);
|
||||
String text = (dataSource == null) ? "All" : dataSource.getName() + " (Id: " + dataSource.getId() + ")";
|
||||
Boolean tooManyFilesInDataSource = dataSourcesTooManyFiles.getOrDefault(dataSource, false);
|
||||
String dataSourceName;
|
||||
boolean shouldEnable = true; // false if user should not be able to select the item
|
||||
|
||||
DrawableDbBuildStatusEnum dataSourceDBStatus = (dataSource != null) ?
|
||||
dataSourcesDrawableDBStatus.get(dataSource.getId()) : DrawableDbBuildStatusEnum.UNKNOWN;
|
||||
|
||||
Boolean dataSourceNotAnalyzed = (dataSourceDBStatus == DrawableDbBuildStatusEnum.DEFAULT);
|
||||
if (tooManyFilesInDataSource) {
|
||||
text += " - Too many files";
|
||||
}
|
||||
if (dataSourceNotAnalyzed) {
|
||||
text += " - Not Analyzed";
|
||||
}
|
||||
|
||||
// check if item should be disabled
|
||||
if (tooManyFilesInDataSource || dataSourceNotAnalyzed) {
|
||||
setDisable(true);
|
||||
setStyle("-fx-opacity : .5");
|
||||
if (dataSource == null) {
|
||||
dataSourceName = "All";
|
||||
// NOTE: openAction verifies that there is at least one data source with data.
|
||||
// So, at this point, "All" should never need to be disabled because none of the data sources
|
||||
// are analyzed.
|
||||
}
|
||||
else {
|
||||
dataSourceName = dataSource.getName() + " (Id: " + dataSource.getId() + ")";
|
||||
if (dataSourcesDrawableDBStatus.get(dataSource.getId()) == DrawableDbBuildStatusEnum.UNKNOWN) {
|
||||
dataSourceName += " - Not Analyzed";
|
||||
shouldEnable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if it's analyzed, then make sure there aren't too many files
|
||||
if (shouldEnable) {
|
||||
if (dataSourcesTooManyFiles.getOrDefault(dataSource, false)) {
|
||||
dataSourceName += " - Too Many Files";
|
||||
shouldEnable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if item should be disabled
|
||||
if (shouldEnable) {
|
||||
setGraphic(null);
|
||||
setStyle("-fx-opacity : 1");
|
||||
}
|
||||
else {
|
||||
setDisable(true);
|
||||
setStyle("-fx-opacity : .5");
|
||||
}
|
||||
|
||||
setText(text);
|
||||
setText(dataSourceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<right>
|
||||
<HBox alignment="CENTER_RIGHT" prefHeight="-1.0" prefWidth="-1.0" spacing="5.0" BorderPane.alignment="CENTER_RIGHT">
|
||||
<children>
|
||||
<Label fx:id="staleLabel" text="Some data may be out of date. Enable listening to ingest to update.">
|
||||
<Label fx:id="staleLabel" text="Additional data may be available after rebuild or enabling listeners.">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
|
@ -333,7 +333,7 @@ class TskDbDiff(object):
|
||||
for line in postgreSQL_db:
|
||||
line = line.strip('\r\n ')
|
||||
# Deal with pg_dump result file
|
||||
if line.startswith('--') or line.lower().startswith('alter') or not line: # It's comment or alter statement or empty line
|
||||
if line.startswith('--') or line.lower().startswith('alter') or "pg_catalog" in line or not line: # It's comment or alter statement or catalog entry or empty line
|
||||
continue
|
||||
elif not line.endswith(';'): # Statement not finished
|
||||
dump_line += line
|
||||
|
Loading…
x
Reference in New Issue
Block a user