Merged in develop

This commit is contained in:
U-BASIS\dsmyda 2018-11-29 09:00:37 -05:00
commit 4082ffebe9
69 changed files with 4785 additions and 2749 deletions

View File

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

View File

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

View File

@ -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");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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">

View File

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

View 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();
}
}

View 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View 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));
}
}
}

View File

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

View File

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

View File

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

View 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=\

View File

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

View File

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

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

View File

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

View 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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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="&lt;String&gt;"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View 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
}

View 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();
}
}

View File

@ -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();

View File

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

View File

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

View File

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

View 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();
}
}

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

View 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);
}
}

View File

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

View File

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

View File

@ -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()) {

View File

@ -114,9 +114,6 @@
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.enabledByDefaultBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="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, &quot;{key}&quot;)"/>
</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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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