Merge remote-tracking branch 'upstream/develop' into merge-develop-into-timeline

# Conflicts:
#	Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java
#	RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java
This commit is contained in:
millmanorama 2018-11-21 21:11:45 +01:00
commit 0131d3b15d
63 changed files with 3818 additions and 2739 deletions

View File

@ -338,7 +338,7 @@
<package>org.sleuthkit.autopsy.modules.vmextractor</package>
<package>org.sleuthkit.autopsy.progress</package>
<package>org.sleuthkit.autopsy.report</package>
<package>org.sleuthkit.autopsy.tabulardatareader</package>
<package>org.sleuthkit.autopsy.texttranslation</package>
<package>org.sleuthkit.datamodel</package>
</public-packages>
<class-path-extension>

View File

@ -419,7 +419,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
}
// we can correlate based on the MD5 if it is enabled
if (this.file != null && EamDb.isEnabled()) {
if (this.file != null && EamDb.isEnabled() && this.file.getSize() > 0) {
try {
List<CorrelationAttributeInstance.Type> artifactTypes = EamDb.getInstance().getDefinedCorrelationTypes();
@ -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);
}
@ -447,27 +448,23 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
} catch (EamDbException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
}
} else {
// If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled.
if (this.file != null) {
String md5 = this.file.getMd5Hash();
if (md5 != null && !md5.isEmpty()) {
try {
final CorrelationAttributeInstance.Type fileAttributeType
= CorrelationAttributeInstance.getDefaultCorrelationTypes()
.stream()
.filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.findAny()
.get();
ret.add(new CorrelationAttributeInstance(fileAttributeType, md5));
} catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, String.format("Unable to create CorrelationAttributeInstance for value %s", md5), ex); // NON-NLS
}
} else if (this.file != null && this.file.getSize() > 0) {
String md5 = this.file.getMd5Hash();
if (md5 != null && !md5.isEmpty()) {
try {
final CorrelationAttributeInstance.Type fileAttributeType
= CorrelationAttributeInstance.getDefaultCorrelationTypes()
.stream()
.filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.findAny()
.get();
//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) {
LOGGER.log(Level.INFO, String.format("Unable to create CorrelationAttributeInstance for value %s", md5), ex); // NON-NLS
}
}
}
@ -515,9 +512,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
* artifact. If the central repo is not enabled, this will only return files
* from the current case with matching MD5 hashes.
*
* @param corAttr CorrelationAttribute to query for
* @param corAttr CorrelationAttribute to query for
* @param dataSourceName Data source to filter results
* @param deviceId Device Id to filter results
* @param deviceId Device Id to filter results
*
* @return A collection of correlated artifact instances
*/
@ -580,7 +577,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
* Get all other abstract files in the current case with the same MD5 as the
* selected node.
*
* @param corAttr The CorrelationAttribute containing the MD5 to search for
* @param corAttr The CorrelationAttribute containing the MD5 to search for
* @param openCase The current case
*
* @return List of matching AbstractFile objects
@ -657,11 +654,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
// - The central repo is disabled and the backing file has a valid MD5 hash
this.file = this.getAbstractFileFromNode(node);
if (EamDb.isEnabled()) {
return this.file != null
&& this.file.getSize() > 0
&& !getCorrelationAttributesFromNode(node).isEmpty();
return !getCorrelationAttributesFromNode(node).isEmpty();
} else {
return this.file != null
return this.file != null
&& this.file.getSize() > 0
&& ((this.file.getMd5Hash() != null) && (!this.file.getMd5Hash().isEmpty()));
}
@ -733,8 +728,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
* Adjust a given column for the text provided.
*
* @param columnIndex The index of the column to adjust.
* @param text The text whose length will be used to adjust the column
* width.
* @param text The text whose length will be used to adjust the
* column width.
*/
private void setColumnWidthToText(int columnIndex, String text) {
TableColumn column = otherCasesTable.getColumnModel().getColumn(columnIndex);

View File

@ -808,9 +808,9 @@ abstract class AbstractSqlEamDb implements EamDb {
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 device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?, ?) "
+ getConflictClause();
try {
@ -824,11 +824,13 @@ abstract class AbstractSqlEamDb implements EamDb {
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);
} else {
preparedStatement.setString(7, eamArtifact.getComment());
}
preparedStatement.setLong(8, eamArtifact.getFileObjectId());
preparedStatement.executeUpdate();
}
@ -900,6 +902,8 @@ abstract class AbstractSqlEamDb implements EamDb {
+ ".id,"
+ tableName
+ ".value,"
+ 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 FROM "
+ tableName
+ " LEFT JOIN cases ON "
@ -963,6 +967,8 @@ abstract class AbstractSqlEamDb implements EamDb {
+ ".id, "
+ tableName
+ ".value,"
+ 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 FROM "
+ tableName
+ " LEFT JOIN cases ON "
@ -1229,9 +1235,9 @@ abstract class AbstractSqlEamDb implements EamDb {
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 device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?, ?) "
+ getConflictClause();
bulkPs = conn.prepareStatement(sql);
@ -1275,6 +1281,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."
@ -1439,6 +1446,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 +1564,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
@ -1637,6 +1706,8 @@ abstract class AbstractSqlEamDb implements EamDb {
+ ".id, "
+ tableName
+ ".value, "
+ 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 FROM "
+ tableName
+ " LEFT JOIN cases ON "
@ -1694,7 +1765,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 FROM "
+ tableName
+ " LEFT JOIN cases ON "
+ tableName
@ -2964,7 +3035,6 @@ abstract class AbstractSqlEamDb implements EamDb {
resultSet.getString("creation_date"), resultSet.getString("case_number"), resultSet.getString("examiner_name"),
resultSet.getString("examiner_email"), resultSet.getString("examiner_phone"), resultSet.getString("notes"));
return eamCase;
}
@ -3021,8 +3091,8 @@ abstract class AbstractSqlEamDb implements EamDb {
new CorrelationDataSource(resultSet.getInt("case_id"), resultSet.getInt("data_source_id"), resultSet.getString("device_id"), resultSet.getString("name")),
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 {
@ -3070,6 +3140,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)
*
@ -3080,6 +3162,7 @@ abstract class AbstractSqlEamDb implements EamDb {
ResultSet resultSet = null;
Statement statement = null;
PreparedStatement preparedStatement = null;
Connection conn = null;
try {
@ -3114,6 +3197,10 @@ abstract class AbstractSqlEamDb implements EamDb {
logger.log(Level.INFO, "Central Repository is up to date");
return;
}
if (dbSchemaVersion.compareTo(CURRENT_DB_SCHEMA_VERSION) > 0) {
logger.log(Level.INFO, "Central Repository is of newer version than software creates");
return;
}
// Update from 1.0 to 1.1
if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) {
@ -3126,7 +3213,71 @@ abstract class AbstractSqlEamDb implements EamDb {
// regardless of whether this succeeds.
EamDbUtil.insertDefaultOrganization(conn);
}
//Update to 1.2
if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 2)) < 0) {
EamDbPlatformEnum selectedPlatform = EamDbPlatformEnum.getSelectedPlatform();
final String addObjectIdColumnTemplate = "ALTER TABLE %s ADD COLUMN file_obj_id 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(); //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 (?, ?, ?, ?, ?)"; //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.");
}
//update central repository to be able to store new correlation attributes
final String wirelessNetworsDbTableName = "wireless_networks";
final String wirelessNetworksTableInstanceName = wirelessNetworsDbTableName + "_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.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 file_obj_id column to _instances table which do not already have it
String instance_type_dbname;
for (CorrelationAttributeInstance.Type type : CorrelationAttributeInstance.getDefaultCorrelationTypes()) {
instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
if (!doesColumnExist(conn, instance_type_dbname, "file_obj_id")) {
statement.execute(String.format(addObjectIdColumnTemplate, instance_type_dbname)); //NON-NLS
}
statement.execute(String.format(addObjectIdIndexTemplate, instance_type_dbname, instance_type_dbname));
}
}
if (!updateSchemaVersion(conn)) {
throw new EamDbException("Error updating schema version");
}
@ -3144,6 +3295,7 @@ abstract class AbstractSqlEamDb implements EamDb {
throw ex;
} finally {
EamDbUtil.closeResultSet(resultSet);
EamDbUtil.closeStatement(preparedStatement);
EamDbUtil.closeStatement(statement);
EamDbUtil.closeConnection(conn);
}

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,18 +126,11 @@ 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?
*
* @return True if the instance ID is greater or equal to zero; otherwise
* false.
* false.
*/
public boolean isDatabaseInstance() {
return (ID >= 0);
@ -234,30 +193,42 @@ public class CorrelationAttributeInstance implements Serializable {
* as notable and should never be set to KNOWN.
*
* @param knownStatus Should be BAD if the item is tagged as notable,
* UNKNOWN otherwise
* UNKNOWN otherwise
*/
public void setKnownStatus(TskData.FileKnown knownStatus) {
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;
public static final int EMAIL_TYPE_ID = 2;
public static final int PHONE_TYPE_ID = 3;
public static final int USBID_TYPE_ID = 4;
public static final int SSID_TYPE_ID = 5;
/**
* Load the default correlation types
*
* @throws EamDbException if the Type's dbTableName has invalid
* characters/format
* characters/format
*/
@Messages({"CorrelationType.FILES.displayName=Files",
"CorrelationType.DOMAIN.displayName=Domains",
"CorrelationType.EMAIL.displayName=Email Addresses",
"CorrelationType.PHONE.displayName=Phone Numbers",
"CorrelationType.USBID.displayName=USB Devices"})
"CorrelationType.USBID.displayName=USB Devices",
"CorrelationType.SSID.displayName=Wireless Networks"})
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
@ -265,6 +236,7 @@ public class CorrelationAttributeInstance implements Serializable {
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(EMAIL_TYPE_ID, Bundle.CorrelationType_EMAIL_displayName(), "email_address", true, true)); // NON-NLS
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
return DEFAULT_CORRELATION_TYPES;
}
@ -283,13 +255,14 @@ public class CorrelationAttributeInstance implements Serializable {
/**
*
* @param typeId Unique ID for this Correlation Type
* @param typeId Unique ID for this Correlation Type
* @param displayName Name of this type displayed in the UI.
* @param dbTableName Central repository db table where data of this
* type is stored. Must start with a lowercase letter and only contain
* lowercase letters, numbers, and '_' characters.
* @param supported Is this Type currently supported
* @param enabled Is this Type currently enabled.
* type is stored. Must start with a lowercase letter
* and only contain lowercase letters, numbers, and
* '_' characters.
* @param supported Is this Type currently supported
* @param enabled Is this Type currently enabled.
*/
public Type(int typeId, String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException {
if (dbTableName == null) {
@ -312,10 +285,11 @@ public class CorrelationAttributeInstance implements Serializable {
*
* @param displayName Name of this type displayed in the UI.
* @param dbTableName Central repository db table where data of this
* type is stored Must start with a lowercase letter and only contain
* lowercase letters, numbers, and '_' characters.
* @param supported Is this Type currently supported
* @param enabled Is this Type currently enabled.
* type is stored Must start with a lowercase letter
* and only contain lowercase letters, numbers, and
* '_' characters.
* @param supported Is this Type currently supported
* @param enabled Is this Type currently enabled.
*/
public Type(String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException {
this(-1, displayName, dbTableName, supported, enabled);
@ -477,8 +451,8 @@ public class CorrelationAttributeInstance implements Serializable {
* custom_instances)
*
* @param dbTableName the dbTableName to set. Must start with lowercase
* letter and can only contain lowercase letters, numbers, and '_'
* characters.
* letter and can only contain lowercase letters,
* numbers, and '_' characters.
*
* @throws EamDbException if dbTableName contains invalid characters
*/

View File

@ -63,6 +63,8 @@ final public class CorrelationAttributeNormalizer {
return normalizePhone(data);
case CorrelationAttributeInstance.USBID_TYPE_ID:
return normalizeUsbId(data);
case CorrelationAttributeInstance.SSID_TYPE_ID:
return data;
default:
final String errorMessage = String.format(
"Validator function not found for attribute type: %s",

View File

@ -54,9 +54,9 @@ public class EamArtifactUtil {
* EamArtifact with a single EamArtifactInstance within. If not, return
* null.
*
* @param bbArtifact BlackboardArtifact to examine
* @param bbArtifact BlackboardArtifact to examine
* @param checkEnabled If true, only create a CorrelationAttribute if it is
* enabled
* enabled
*
* @return List of EamArtifacts
*/
@ -93,10 +93,10 @@ public class EamArtifactUtil {
* 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
* @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
* bbArtifact did not contain the needed data
*/
private static CorrelationAttributeInstance makeInstanceFromBlackboardArtifact(CorrelationAttributeInstance.Type correlationType,
BlackboardArtifact bbArtifact) throws EamDbException {
@ -159,13 +159,14 @@ public class EamArtifactUtil {
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;
@ -185,9 +186,10 @@ public class EamArtifactUtil {
* Uses the determined type and vallue, then looks up instance details to
* create proper CorrelationAttributeInstance.
*
* @param bbArtifact the blackboard artifatc
* @param bbArtifact the blackboard artifact
* @param correlationType the given type
* @param value the artifact value
* @param value the artifact value
*
* @return CorrelationAttributeInstance from details
*/
private static CorrelationAttributeInstance makeCorrelationAttributeInstanceUsingTypeValue(BlackboardArtifact bbArtifact, CorrelationAttributeInstance.Type correlationType, String value) {
@ -205,14 +207,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
@ -245,8 +247,6 @@ public class EamArtifactUtil {
CorrelationAttributeInstance.Type type;
CorrelationCase correlationCase;
CorrelationDataSource correlationDataSource;
String value;
String filePath;
try {
type = EamDb.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
@ -256,8 +256,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;
@ -268,13 +266,26 @@ 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;
}
@ -317,12 +328,16 @@ public class EamArtifactUtil {
if (null == correlationCase) {
correlationCase = EamDb.getInstance().newCase(Case.getCurrentCaseThrows());
}
return new CorrelationAttributeInstance(
filesType,
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);
@ -340,7 +355,7 @@ public class EamArtifactUtil {
* @param file The file to test
*
* @return true if the file should be added to the central repo, false
* otherwise
* otherwise
*/
public static boolean isSupportedAbstractFileType(AbstractFile file) {
if (file == null) {

View File

@ -31,10 +31,9 @@ import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
*/
public interface EamDb {
public static final int SCHEMA_VERSION = 1;
public static final int SCHEMA_VERSION = 2;
public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
= new CaseDbSchemaVersionNumber(1, 1);
= new CaseDbSchemaVersionNumber(1, 2);
/**
* Get the instance
@ -183,6 +182,7 @@ public interface EamDb {
* @return The retrieved case
*/
CorrelationCase getCaseById(int caseId) throws EamDbException;
/**
* Retrieves cases that are in DB.
*
@ -208,13 +208,12 @@ public interface EamDb {
*/
CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) 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
*/
@ -314,8 +313,8 @@ public interface EamDb {
/**
* 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 +322,7 @@ public interface EamDb {
/**
* Executes a bulk insert of the eamArtifacts added from the
addAttributeInstanceBulk() method
* addAttributeInstanceBulk() method
*/
void commitAttributeInstancesBulk() throws EamDbException;
@ -347,6 +346,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 +362,22 @@ 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
@ -385,10 +403,13 @@ public interface EamDb {
* "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".
*
@ -700,8 +721,9 @@ 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;
@ -709,9 +731,10 @@ 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
* @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 {
@ -50,7 +50,8 @@ final class PostgresEamDb extends AbstractSqlEamDb {
*
* @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) {
@ -62,8 +63,9 @@ 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()
}
@ -191,29 +193,32 @@ final class PostgresEamDb extends AbstractSqlEamDb {
}
/**
* 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

@ -36,7 +36,8 @@ import org.sleuthkit.autopsy.coreutils.TextConverterException;
/**
* Settings for the Postgres implementation of the Central Repository database
*
* NOTE: This is public scope because the options panel calls it directly to set/get
* NOTE: This is public scope because the options panel calls it directly to
* set/get
*/
public final class PostgresEamDbSettings {
@ -391,26 +392,13 @@ public final class PostgresEamDbSettings {
createCorrelationTypesTable.append("CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
createCorrelationTypesTable.append(")");
// Each "%s" will be replaced with the relevant TYPE_instances table name.
StringBuilder createArtifactInstancesTableTemplate = new StringBuilder();
createArtifactInstancesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
createArtifactInstancesTableTemplate.append("id SERIAL PRIMARY KEY,");
createArtifactInstancesTableTemplate.append("case_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("data_source_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("value text NOT NULL,");
createArtifactInstancesTableTemplate.append("file_path text NOT NULL,");
createArtifactInstancesTableTemplate.append("known_status integer NOT NULL,");
createArtifactInstancesTableTemplate.append("comment text,");
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");
createArtifactInstancesTableTemplate.append(")");
String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
// Each "%s" will be replaced with the relevant TYPE_instances table name.
String instancesIdx1 = "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
String instancesIdx2 = "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
String instancesIdx3 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
String instancesIdx4 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
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 (");
@ -454,11 +442,12 @@ public final class PostgresEamDbSettings {
reference_type_dbname = EamDbUtil.correlationTypeToReferenceTableName(type);
instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
stmt.execute(String.format(createArtifactInstancesTableTemplate.toString(), 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(createArtifactInstancesTableTemplate, 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) {
@ -480,6 +469,97 @@ public final class PostgresEamDbSettings {
return true;
}
/**
* Get the template String for creating a new _instances table in a Postgres
* central repository. %s will exist in the template where the name of the
* new table will be addedd.
*
* @return a String which is a template for cretating a new _instances table
*/
static String getCreateArtifactInstancesTableTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
StringBuilder createArtifactInstancesTableTemplate = new StringBuilder();
createArtifactInstancesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
createArtifactInstancesTableTemplate.append("id SERIAL PRIMARY KEY,");
createArtifactInstancesTableTemplate.append("case_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("data_source_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("value text NOT NULL,");
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");
createArtifactInstancesTableTemplate.append(")");
return createArtifactInstancesTableTemplate.toString();
}
/**
* Get the template for creating an index on the case_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 case_id
* column of a _instances table
*/
static String getAddCaseIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
}
/**
* Get the template for creating an index on the data_source_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
* data_source_id column of a _instances table
*/
static String getAddDataSourceIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
}
/**
* Get the template for creating an index on the value 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 value
* column of a _instances table
*/
static String getAddValueIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
}
/**
* Get the template for creating an index on the known_status 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
* known_status column of a _instances table
*/
static String getAddKnownStatusIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
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
@ -372,8 +374,8 @@ 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
@ -391,9 +393,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
/**
* 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 +463,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 +488,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 +521,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @param value The value to search for
*
* @return Number of unique tuples
*
* @throws EamDbException
*/
@Override
@ -545,11 +549,11 @@ 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 {
@ -563,7 +567,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 +600,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 +637,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 +678,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 +696,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* Remove a reference set and all values contained in it.
*
* @param referenceSetID
*
* @throws EamDbException
*/
@Override
@ -708,6 +715,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 +731,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
@ -740,8 +749,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
@ -761,7 +771,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*
* @param referenceSetName
* @param version
*
* @return true if a matching set is found
*
* @throws EamDbException
*/
@Override
@ -928,7 +940,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 +973,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 +1014,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 +1033,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 +1052,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 +1124,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 +1170,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

@ -36,7 +36,8 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
/**
* Settings for the sqlite implementation of the Central Repository database
*
* NOTE: This is public scope because the options panel calls it directly to set/get
* NOTE: This is public scope because the options panel calls it directly to
* set/get
*/
public final class SqliteEamDbSettings {
@ -103,11 +104,11 @@ public final class SqliteEamDbSettings {
*/
public boolean dbFileExists() {
File dbFile = new File(getFileNameWithPath());
if(! dbFile.exists()){
if (!dbFile.exists()) {
return false;
}
// It's unlikely, but make sure the file isn't actually a directory
return ( ! dbFile.isDirectory());
return (!dbFile.isDirectory());
}
/**
@ -151,6 +152,7 @@ public final class SqliteEamDbSettings {
/**
* Delete the database
*
* @return
*/
public boolean deleteDatabase() {
@ -333,26 +335,13 @@ public final class SqliteEamDbSettings {
createCorrelationTypesTable.append("CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
createCorrelationTypesTable.append(")");
// Each "%s" will be replaced with the relevant TYPE_instances table name.
StringBuilder createArtifactInstancesTableTemplate = new StringBuilder();
createArtifactInstancesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
createArtifactInstancesTableTemplate.append("id integer primary key autoincrement NOT NULL,");
createArtifactInstancesTableTemplate.append("case_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("data_source_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("value text NOT NULL,");
createArtifactInstancesTableTemplate.append("file_path text NOT NULL,");
createArtifactInstancesTableTemplate.append("known_status integer NOT NULL,");
createArtifactInstancesTableTemplate.append("comment text,");
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");
createArtifactInstancesTableTemplate.append(")");
String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
// Each "%s" will be replaced with the relevant TYPE_instances table name.
String instancesIdx1 = "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
String instancesIdx2 = "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
String instancesIdx3 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
String instancesIdx4 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
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 (");
@ -402,11 +391,12 @@ public final class SqliteEamDbSettings {
reference_type_dbname = EamDbUtil.correlationTypeToReferenceTableName(type);
instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
stmt.execute(String.format(createArtifactInstancesTableTemplate.toString(), 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(createArtifactInstancesTableTemplate, 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) {
@ -427,6 +417,97 @@ public final class SqliteEamDbSettings {
return true;
}
/**
* Get the template String for creating a new _instances table in a Sqlite
* central repository. %s will exist in the template where the name of the
* new table will be addedd.
*
* @return a String which is a template for cretating a new _instances table
*/
static String getCreateArtifactInstancesTableTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
StringBuilder createArtifactInstancesTableTemplate = new StringBuilder();
createArtifactInstancesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
createArtifactInstancesTableTemplate.append("id integer primary key autoincrement NOT NULL,");
createArtifactInstancesTableTemplate.append("case_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("data_source_id integer NOT NULL,");
createArtifactInstancesTableTemplate.append("value text NOT NULL,");
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");
createArtifactInstancesTableTemplate.append(")");
return createArtifactInstancesTableTemplate.toString();
}
/**
* Get the template for creating an index on the case_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 case_id
* column of a _instances table
*/
static String getAddCaseIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
}
/**
* Get the template for creating an index on the data_source_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
* data_source_id column of a _instances table
*/
static String getAddDataSourceIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
}
/**
* Get the template for creating an index on the value 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 value
* column of a _instances table
*/
static String getAddValueIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
}
/**
* Get the template for creating an index on the known_status 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
* known_status column of a _instances table
*/
static String getAddKnownStatusIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
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) {
@ -490,8 +571,6 @@ public final class SqliteEamDbSettings {
}
}
/**
* @return the dbDirectory
*/

View File

@ -183,7 +183,10 @@ public class IngestEventsListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (getCeModuleInstanceCount() > 0) {
//if ingest is running we want there to check if there is a Correlation Engine module running
//sometimes artifacts are generated by DSPs or other sources while ingest is not running
//in these cases we still want to create correlation attributes for those artifacts when appropriate
if (!IngestManager.getInstance().isIngestRunning() || getCeModuleInstanceCount() > 0) {
EamDb dbManager;
try {
dbManager = EamDb.getInstance();
@ -193,7 +196,9 @@ public class IngestEventsListener {
}
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
case DATA_ADDED: {
jobProcessingExecutor.submit(new DataAddedTask(dbManager, evt, isFlagNotableItems()));
//if ingest isn't running create the interesting items otherwise use the ingest module setting to determine if we create interesting items
boolean flagNotable = !IngestManager.getInstance().isIngestRunning() || isFlagNotableItems();
jobProcessingExecutor.submit(new DataAddedTask(dbManager, evt, flagNotable));
break;
}
}

View File

@ -70,7 +70,6 @@ final class IngestModule implements FileIngestModule {
private CorrelationDataSource eamDataSource;
private Blackboard blackboard;
private CorrelationAttributeInstance.Type filesType;
private final boolean flagTaggedNotableItems;
/**
@ -152,14 +151,14 @@ final class IngestModule implements FileIngestModule {
// insert this file into the central repository
try {
CorrelationAttributeInstance cefi = new CorrelationAttributeInstance(
md5,
filesType,
md5,
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

View File

@ -34,7 +34,7 @@ AddNewOrganizationDialog.bnOK.text=OK
AddNewOrganizationDialog.tfName.tooltip=POC Name
ManageTagsDialog.okButton.text=OK
ManageTagsDialog.cancelButton.text=Cancel
ManageArtifactTypesDialog.taInstructionsMsg.text=Enable one or more correlation properties to use for correlation during ingest. Note, these properties are global and impact all users of the central repository.
ManageArtifactTypesDialog.taInstructionsMsg.text=Enable one or more correlation properties to use for correlation during ingest. Note, these properties are global and impact all users of the Central Repository.
EamSqliteSettingsDialog.bnOk.text=OK
EamPostgresSettingsDialog.bnSave.text=Save
EamDbSettingsDialog.bnDatabasePathFileOpen.text=Browse...
@ -58,11 +58,10 @@ ManageCorrelationPropertiesDialog.okButton.text=OK
GlobalSettingsPanel.bnManageProperties.text=Manage Correlation Properties
EamDbSettingsDialog.lbDatabaseDesc.text=Database File:
EamDbSettingsDialog.lbFullDbPath.text=
GlobalSettingsPanel.cbUseCentralRepo.text=Use a central repository
GlobalSettingsPanel.correlationPropertiesTextArea.text=Choose which file and result properties to store in the central repository for later correlation.\n
GlobalSettingsPanel.organizationTextArea.text=Organization information can be tracked in the central repository.
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.
GlobalSettingsPanel.pnCorrelationProperties.border.title=Correlation Properties
GlobalSettingsPanel.organizationPanel.border.title=Organizations
GlobalSettingsPanel.casesPanel.border.title=Case Details
@ -74,8 +73,9 @@ ShowCasesDialog.caseDetailsTable.toolTipText=Click column name to sort. Right-cl
ShowCasesDialog.title=Case Details
GlobalSettingsPanel.Case\ Details.AccessibleContext.accessibleName=Cases Details
ShowCasesDialog.caseDetailsTable.AccessibleContext.accessibleDescription=Click column name to sort.
GlobalSettingsPanel.casesTextArea.text=Display table that lists central repository case details.
GlobalSettingsPanel.ingestRunningWarningLabel.text=Cannot make changes to central repository settings when ingest is running!
GlobalSettingsPanel.casesTextArea.text=Display table that lists Central Repository case details.
GlobalSettingsPanel.ingestRunningWarningLabel.text=Cannot make changes to Central Repository settings when ingest is running!
GlobalSettingsPanel.correlationPropertiesTextArea.text=Choose which file and result properties to store in the Central Repository for later correlation.\n
ManageCasesDialog.examinerPhoneLabel.text=Examiner Phone:
ManageCasesDialog.examinerNameLabel.text=Examiner Name:
ManageCasesDialog.examinerEmailLabel.text=Examiner Email:

View File

@ -102,7 +102,7 @@ public class EamDbSettingsDialog extends JDialog {
@Override
public String getDescription() {
return "Directories and central repository databases";
return "Directories and Central Repository databases";
}
});
cbDatabaseType.setSelectedItem(selectedPlatform);

View File

@ -57,7 +57,7 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="lbCentralRepository" pref="1022" max="32767" attributes="0"/>
<Component id="lbCentralRepository" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="pnDatabaseConfiguration" alignment="0" max="32767" attributes="0"/>
@ -67,7 +67,7 @@
<Group type="102" attributes="0">
<Component id="cbUseCentralRepo" min="-2" pref="162" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="ingestRunningWarningLabel" max="32767" attributes="0"/>
<Component id="ingestRunningWarningLabel" pref="844" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
@ -89,13 +89,13 @@
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pnDatabaseConfiguration" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="pnCorrelationProperties" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="organizationPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="casesPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="tbOops" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
@ -242,14 +242,14 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="correlationPropertiesScrollPane" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="bnManageTypes" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Component id="correlationPropertiesScrollPane" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
@ -258,8 +258,7 @@
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="-2" pref="7" max="-2" attributes="0"/>
<Component id="correlationPropertiesScrollPane" min="-2" max="-2" attributes="0"/>
<Component id="correlationPropertiesScrollPane" pref="32" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="bnManageTypes" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
@ -301,7 +300,7 @@
<Font name="Tahoma" size="11" style="0"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="2"/>
<Property name="rows" type="int" value="1"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties" key="GlobalSettingsPanel.correlationPropertiesTextArea.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -334,7 +333,7 @@
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="organizationScrollPane" pref="992" max="32767" attributes="0"/>
<Component id="organizationScrollPane" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Component id="manageOrganizationButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>

View File

@ -222,7 +222,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
correlationPropertiesTextArea.setColumns(20);
correlationPropertiesTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N
correlationPropertiesTextArea.setLineWrap(true);
correlationPropertiesTextArea.setRows(2);
correlationPropertiesTextArea.setRows(1);
correlationPropertiesTextArea.setText(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.correlationPropertiesTextArea.text")); // NOI18N
correlationPropertiesTextArea.setToolTipText("");
correlationPropertiesTextArea.setWrapStyleWord(true);
@ -236,17 +236,16 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
.addGroup(pnCorrelationPropertiesLayout.createSequentialGroup()
.addContainerGap()
.addGroup(pnCorrelationPropertiesLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(correlationPropertiesScrollPane)
.addGroup(pnCorrelationPropertiesLayout.createSequentialGroup()
.addComponent(bnManageTypes)
.addGap(0, 0, Short.MAX_VALUE)))
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(correlationPropertiesScrollPane))
.addContainerGap())
);
pnCorrelationPropertiesLayout.setVerticalGroup(
pnCorrelationPropertiesLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnCorrelationPropertiesLayout.createSequentialGroup()
.addGap(7, 7, 7)
.addComponent(correlationPropertiesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(correlationPropertiesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 32, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(bnManageTypes)
.addGap(8, 8, 8))
@ -281,7 +280,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
.addGroup(organizationPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(organizationPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(organizationScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 992, Short.MAX_VALUE)
.addComponent(organizationScrollPane)
.addGroup(organizationPanelLayout.createSequentialGroup()
.addComponent(manageOrganizationButton)
.addGap(0, 0, Short.MAX_VALUE)))
@ -356,7 +355,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbCentralRepository, javax.swing.GroupLayout.DEFAULT_SIZE, 1022, Short.MAX_VALUE)
.addComponent(lbCentralRepository, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pnDatabaseConfiguration, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
@ -366,7 +365,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(cbUseCentralRepo, javax.swing.GroupLayout.PREFERRED_SIZE, 162, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(ingestRunningWarningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(ingestRunningWarningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 844, Short.MAX_VALUE))
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
.addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, 974, javax.swing.GroupLayout.PREFERRED_SIZE)))
@ -382,13 +381,13 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
.addComponent(ingestRunningWarningLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pnDatabaseConfiguration, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGap(0, 0, 0)
.addComponent(pnCorrelationProperties, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGap(0, 0, 0)
.addComponent(organizationPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGap(0, 0, 0)
.addComponent(casesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGap(0, 0, 0)
.addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);

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);
}
List<ContentTag> tags = getContentTagsFromDatabase();
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());
}
}
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

@ -208,13 +208,14 @@ 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;
}
}
@ -223,8 +224,8 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
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;
@ -367,6 +368,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
* 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) {

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

@ -74,7 +74,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer
private final Outline outline;
private ExplorerManager explorerManager;
private NSDictionary rootDict;
private NSObject rootDict;
/**
* Creates new form PListViewer
@ -415,22 +415,35 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer
}
/**
* Parses given binary stream and extracts Plist key/value
* Parses given binary stream and extracts Plist key/value.
*
* @param plistbytes
* @param plistbytes The byte array containing the Plist data.
*
* @return list of PropKeyValue
*/
private List<PropKeyValue> parsePList(final byte[] plistbytes) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
final List<PropKeyValue> plist = new ArrayList<>();
rootDict = (NSDictionary) PropertyListParser.parse(plistbytes);
rootDict = PropertyListParser.parse(plistbytes);
final String[] keys = rootDict.allKeys();
for (final String key : keys) {
final PropKeyValue pkv = parseProperty(key, rootDict.objectForKey(key));
if (null != pkv) {
plist.add(pkv);
/*
* Parse the data if the root is an NSArray or NSDictionary. Anything
* else is unexpected and will be ignored.
*/
if (rootDict instanceof NSArray) {
for (int i=0; i < ((NSArray)rootDict).count(); i++) {
final PropKeyValue pkv = parseProperty("", ((NSArray)rootDict).objectAtIndex(i));
if (null != pkv) {
plist.add(pkv);
}
}
} else if (rootDict instanceof NSDictionary) {
final String[] keys = ((NSDictionary)rootDict).allKeys();
for (final String key : keys) {
final PropKeyValue pkv = parseProperty(key, ((NSDictionary)rootDict).objectForKey(key));
if (null != pkv) {
plist.add(pkv);
}
}
}

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,15 +264,15 @@ 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());
@ -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;
@ -443,24 +410,18 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
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,6 +74,7 @@ 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() {
@ -255,6 +256,14 @@ public final class UserPreferences {
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

@ -90,73 +90,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 +180,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 +201,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 +346,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 +353,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 +403,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

@ -29,6 +29,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.
@ -70,9 +71,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
viewsHideSlackCheckbox.setSelected(UserPreferences.hideSlackFilesInViewsTree());
commentsOccurencesColumnsCheckbox.setEnabled(EamDbUtil.useCentralRepo());
commentsOccurencesColumnWrapAroundText.setEnabled(EamDbUtil.useCentralRepo());
commentsOccurencesColumnsCheckbox.setSelected(UserPreferences.hideCentralRepoCommentsAndOccurrences());
deletedFilesLimitCheckbox.setSelected(DeletedFilePreferences.getDefault().getShouldLimitDeletedFiles());
translateNamesInTableRadioButton.setSelected(UserPreferences.displayTranslatedFileNames());
TextTranslationService tts = TextTranslationService.getInstance();
translateNamesInTableRadioButton.setEnabled(tts.hasProvider());
// Current Case Settings
boolean caseIsOpen = Case.isCaseOpen();
@ -99,6 +105,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,12 +154,15 @@ 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();
@ -240,15 +250,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 +276,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 +295,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 +365,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 +384,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 +481,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 +497,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 +513,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 +634,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;

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>() {
* @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 id 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,51 @@ 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)));
}
/*
* Data that was being computed in the background task. Kicked off by a
* call to createSheet().
*/
} 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 +232,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 +319,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()),
@ -235,12 +356,270 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
}
}
/**
* Creates and populates a list of properties for this nodes property sheet.
*/
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<>(MODE.toString(), MODE.toString(), NO_DESCR, content.getModesAsString()));
properties.add(new NodeProperty<>(USER_ID.toString(), USER_ID.toString(), NO_DESCR, content.getUid()));
properties.add(new NodeProperty<>(GROUP_ID.toString(), GROUP_ID.toString(), NO_DESCR, content.getGid()));
properties.add(new NodeProperty<>(META_ADDR.toString(), META_ADDR.toString(), NO_DESCR, content.getMetaAddr()));
properties.add(new NodeProperty<>(ATTR_ADDR.toString(), ATTR_ADDR.toString(), NO_DESCR, content.getAttrType().getValue() + "-" + content.getAttributeId()));
properties.add(new NodeProperty<>(TYPE_DIR.toString(), TYPE_DIR.toString(), NO_DESCR, content.getDirType().getLabel()));
properties.add(new NodeProperty<>(TYPE_META.toString(), TYPE_META.toString(), NO_DESCR, content.getMetaType().toString()));
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<>(ObjectID.toString(), ObjectID.toString(), NO_DESCR, content.getId()));
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;
}
/**
* 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
*/
@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(", "))));
}
/**
* 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"})
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 {
//don't perform the query if there is no correlation value
if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
} else if (attribute != null) {
description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
}
} catch (EamDbException ex) {
logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
}
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;
}
/**
* Translates this nodes content name. Doesn't attempt translation if
* the name is in english or if there is now translation service available.
*/
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);
}
return tags;
}
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.getName(), ex); //NON-NLS
return ""; //NON-NLS
}
}
static String getContentDisplayName(AbstractFile file) {
String name = file.getName();
switch (name) {
case "..":
return DirectoryNode.DOTDOTDIR;
case ".":
return DirectoryNode.DOTDIR;
default:
return name;
}
}
/**
* 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.
*
* TODO JIRA-4421: Deprecate this method and resolve warnings that appear
* in other locations.
*/
static public void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
map.put(NAME.toString(), getContentDisplayName(content));
@ -265,196 +644,4 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
map.put(EXTENSION.toString(), content.getNameExtension());
}
/**
* 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
* 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
*/
@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.createSheet.count.name=O",
"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) {
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 {
//don't perform the query if there is no correlation value
if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
} else if (attribute != null) {
description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
}
} catch (EamDbException ex) {
logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
} 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));
}
/**
* 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
*/
@NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags")
@Deprecated
protected void addTagProperty(Sheet.Set sheetSet) {
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(", "))));
}
private 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
return ""; //NON-NLS
}
}
static String getContentDisplayName(AbstractFile file) {
String name = file.getName();
switch (name) {
case "..":
return DirectoryNode.DOTDOTDIR;
case ".":
return DirectoryNode.DOTDIR;
default:
return name;
}
}
/**
* 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 "";
}
}
}

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

@ -157,6 +157,8 @@ public class ExtractedContent implements AutopsyVisitableItem {
return filePath + "drive_network.png"; //NON-NLS
} else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED.getTypeID()) {
return filePath + "face.png"; //NON-NLS
} else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID()) {
return filePath + "network-wifi.png"; //NON-NLS
}
return filePath + "artifact-icon.png"; //NON-NLS
}

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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

View File

@ -21,9 +21,6 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -801,138 +798,55 @@ class SevenZipExtractor {
}
/**
* Stream used to unpack the archive to local file
* UnpackStream used by the SevenZipBindings to do archive extraction. A memory
* leak exists in the SevenZip library that will not let go of the streams until
* the entire archive extraction is complete. Instead of creating a new UnpackStream
* for every file in the archive, instead we just rebase our EncodedFileOutputStream pointer
* for every new file.
*/
private abstract static class UnpackStream implements ISequentialOutStream {
private final static class UnpackStream implements ISequentialOutStream {
private OutputStream output;
private EncodedFileOutputStream output;
private String localAbsPath;
private int bytesWritten;
UnpackStream(String localAbsPath) {
UnpackStream(String localAbsPath) throws IOException {
this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
this.localAbsPath = localAbsPath;
try {
output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error writing extracted file: " + localAbsPath, ex); //NON-NLS
}
this.bytesWritten = 0;
}
public abstract long getSize();
OutputStream getOutput() {
return output;
public void setNewOutputStream(String localAbsPath) throws IOException {
this.output.close();
this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
this.localAbsPath = localAbsPath;
this.bytesWritten = 0;
}
String getLocalAbsPath() {
return localAbsPath;
}
public void close() {
if (output != null) {
try {
output.flush();
output.close();
output = null;
} catch (IOException e) {
logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
}
}
}
}
/**
* Stream used to unpack the archive of unknown size to local file
*/
private static class UnknownSizeUnpackStream extends UnpackStream {
private long freeDiskSpace;
private boolean outOfSpace = false;
private long bytesWritten = 0;
UnknownSizeUnpackStream(String localAbsPath, long freeDiskSpace) {
super(localAbsPath);
this.freeDiskSpace = freeDiskSpace;
}
@Override
public long getSize() {
return this.bytesWritten;
public int getSize() {
return bytesWritten;
}
@Override
public int write(byte[] bytes) throws SevenZipException {
try {
// If the content size is unknown, cautiously write to disk.
// Write only if byte array is less than 80% of the current
// free disk space.
if (freeDiskSpace == IngestMonitor.DISK_FREE_SPACE_UNKNOWN || bytes.length < 0.8 * freeDiskSpace) {
getOutput().write(bytes);
// NOTE: this method is called multiple times for a
// single extractSlow() call. Update bytesWritten and
// freeDiskSpace after every write operation.
this.bytesWritten += bytes.length;
this.freeDiskSpace -= bytes.length;
} else {
this.outOfSpace = true;
logger.log(Level.INFO, NbBundle.getMessage(
SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
throw new SevenZipException(
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
}
output.write(bytes);
this.bytesWritten += bytes.length;
} catch (IOException ex) {
throw new SevenZipException(
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
getLocalAbsPath()), ex);
NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
localAbsPath), ex);
}
return bytes.length;
}
@Override
public void close() {
if (getOutput() != null) {
try {
getOutput().flush();
getOutput().close();
if (this.outOfSpace) {
Files.delete(Paths.get(getLocalAbsPath()));
}
} catch (IOException e) {
logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", getLocalAbsPath()); //NON-NLS
}
}
}
}
/**
* Stream used to unpack the archive of known size to local file
*/
private static class KnownSizeUnpackStream extends UnpackStream {
private long size;
KnownSizeUnpackStream(String localAbsPath, long size) {
super(localAbsPath);
this.size = size;
public void close() throws IOException {
try(EncodedFileOutputStream out = output) {
out.flush();
}
}
@Override
public long getSize() {
return this.size;
}
@Override
public int write(byte[] bytes) throws SevenZipException {
try {
getOutput().write(bytes);
} catch (IOException ex) {
throw new SevenZipException(
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
getLocalAbsPath()), ex);
}
return bytes.length;
}
}
/**
@ -979,7 +893,6 @@ class SevenZipExtractor {
private final ProgressHandle progressHandle;
private int inArchiveItemIndex;
private final long freeDiskSpace;
private long createTimeInSeconds;
private long modTimeInSeconds;
@ -996,7 +909,6 @@ class SevenZipExtractor {
String password, long freeDiskSpace) {
this.inArchive = inArchive;
this.freeDiskSpace = freeDiskSpace;
this.progressHandle = progressHandle;
this.archiveFile = archiveFile;
this.archiveDetailsMap = archiveDetailsMap;
@ -1029,17 +941,24 @@ class SevenZipExtractor {
return null;
}
final Long archiveItemSize = (Long) inArchive.getProperty(
inArchiveItemIndex, PropID.SIZE);
final String localAbsPath = archiveDetailsMap.get(
inArchiveItemIndex).getLocalAbsPath();
if (archiveItemSize != null) {
unpackStream = new SevenZipExtractor.KnownSizeUnpackStream(
localAbsPath, archiveItemSize);
} else {
unpackStream = new SevenZipExtractor.UnknownSizeUnpackStream(
localAbsPath, freeDiskSpace);
//If the Unpackstream has been allocated, then set the Outputstream
//to another file rather than creating a new unpack stream. The 7Zip
//binding has a memory leak, so creating new unpack streams will not be
//dereferenced. As a fix, we create one UnpackStream, and mutate its state,
//so that there only exists one 8192 byte buffer in memory per archive.
try {
if (unpackStream != null) {
unpackStream.setNewOutputStream(localAbsPath);
} else {
unpackStream = new UnpackStream(localAbsPath);
}
} catch (IOException ex) {
logger.log(Level.WARNING, String.format("Error opening or setting new stream " //NON-NLS
+ "for archive file at %s", localAbsPath), ex.getMessage()); //NON-NLS
return null;
}
return unpackStream;
@ -1111,7 +1030,11 @@ class SevenZipExtractor {
!(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
0L, createTimeInSeconds, accessTimeInSeconds, modTimeInSeconds, localRelPath);
unpackStream.close();
try {
unpackStream.close();
} catch (IOException e) {
logger.log(Level.WARNING, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
}
}
@Override
@ -1222,9 +1145,9 @@ class SevenZipExtractor {
*/
List<AbstractFile> getRootFileObjects() {
List<AbstractFile> ret = new ArrayList<>();
for (UnpackedNode child : rootNode.getChildren()) {
rootNode.getChildren().forEach((child) -> {
ret.add(child.getFile());
}
});
return ret;
}
@ -1236,17 +1159,17 @@ class SevenZipExtractor {
*/
List<AbstractFile> getAllFileObjects() {
List<AbstractFile> ret = new ArrayList<>();
for (UnpackedNode child : rootNode.getChildren()) {
rootNode.getChildren().forEach((child) -> {
getAllFileObjectsRec(ret, child);
}
});
return ret;
}
private void getAllFileObjectsRec(List<AbstractFile> list, UnpackedNode parent) {
list.add(parent.getFile());
for (UnpackedNode child : parent.getChildren()) {
parent.getChildren().forEach((child) -> {
getAllFileObjectsRec(list, child);
}
});
}
/**

View File

@ -271,6 +271,9 @@ class ReportHTML implements TableReportModule {
case TSK_ACCOUNT:
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
break;
case TSK_WIFI_NETWORK:
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/network-wifi.png"); //NON-NLS
break;
default:
logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS

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

@ -22,6 +22,8 @@ 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;
import java.util.Map;
@ -68,6 +70,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
@ -204,8 +207,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();
}
@ -324,6 +327,7 @@ public final class ImageGalleryController {
groupManager.reset();
shutDownDBExecutor();
drawableDB.close();
dbExecutor = getNewDBExecutor();
}
@ -384,6 +388,51 @@ public final class ImageGalleryController {
}
/**
* 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.
*
* @return map of data source object ids and their Db build status.
*/
public Map<Long, DrawableDbBuildStatusEnum> getAllDataSourcesDrawableDBStatus() {
Map<Long, DrawableDbBuildStatusEnum> dataSourceStatusMap = new HashMap<>();
// no current case open to check
if ((null == getDatabase()) || (null == getSleuthKitCase())) {
return dataSourceStatusMap;
}
try {
Map<Long, DrawableDbBuildStatusEnum> knownDataSourceIds = getDatabase().getDataSourceDbBuildStatus();
List<DataSource> dataSources = getSleuthKitCase().getDataSources();
Set<Long> caseDataSourceIds = new HashSet<>();
dataSources.stream().map(DataSource::getId).forEach(caseDataSourceIds::add);
// collect all data sources already in the table
knownDataSourceIds.entrySet().stream().forEach((Map.Entry<Long, DrawableDbBuildStatusEnum> t) -> {
dataSourceStatusMap.put(t.getKey(), t.getValue());
});
// collect any new data sources in the case.
caseDataSourceIds.forEach((Long id) -> {
if (!knownDataSourceIds.containsKey(id)) {
dataSourceStatusMap.put(id, DrawableDbBuildStatusEnum.UNKNOWN);
}
});
return dataSourceStatusMap;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Image Gallery failed to get data source DB status.", ex);
return dataSourceStatusMap;
}
}
public boolean hasTooManyFiles(DataSource datasource) throws TskCoreException {
String whereClause = (datasource == null)
? "1 = 1"
@ -393,6 +442,30 @@ public final class ImageGalleryController {
}
/**
* 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
*/
public boolean hasFilesWithNoMimetype(Content datasource) 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 '$%:%' )";
return sleuthKitCase.countFilesWhere(whereClause) > 0;
}
synchronized private void shutDownDBExecutor() {
if (dbExecutor != null) {
dbExecutor.shutdownNow();
@ -526,10 +599,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;
@ -542,21 +616,11 @@ public final class ImageGalleryController {
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
@ -566,41 +630,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
@ -613,13 +648,13 @@ public final class ImageGalleryController {
static private final String FILE_EXTENSION_CLAUSE
= "(extension LIKE '" //NON-NLS
+ String.join("' OR extension LIKE '", FileTypeUtils.getAllSupportedExtensions()) //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;
@ -642,14 +677,14 @@ 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
+ " 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
//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
}
/**
@ -695,7 +730,8 @@ public final class ImageGalleryController {
//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.
*/
@ -744,27 +780,34 @@ 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
}
}
progressHandle.finish();
if (taskCompletionStatus) {
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.COMPLETE);
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());
cleanup(false);
} finally {
progressHandle.finish();
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus
= (taskCompletionStatus)
? DrawableDB.DrawableDbBuildStatusEnum.COMPLETE
: DrawableDB.DrawableDbBuildStatusEnum.DEFAULT;
taskDB.insertOrUpdateDataSource(dataSourceObjId, datasourceDrawableDBStatus);
updateMessage("");
updateProgress(-1.0);
}
@ -831,36 +874,4 @@ public final class ImageGalleryController {
}
}
/**
* Copy files from a newly added data source into the DB. Get all "drawable"
* files, based on extension and mime-type. After ingest we use file type id
* module and if necessary jpeg/png signature matching to add/remove files
*/
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"})
static class PrePopulateDataSourceFiles extends BulkTransferTask {
/**
* @param dataSourceObjId The object ID of the DataSource that is being
* pre-populated into the DrawableDB.
* @param controller The controller for this task.
*/
PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller) {
super(dataSourceObjId, controller);
}
@Override
protected void cleanup(boolean success) {
}
@Override
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) {
taskDB.insertBasicFileData(DrawableFile.create(f, false, false), tr, caseDBTransaction);
}
@Override
@NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",})
ProgressHandle getInitialProgressHandle() {
return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
}
}
}

View File

@ -43,6 +43,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.FILE_DONE;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisStartedEvent;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
@ -187,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",
@ -282,7 +277,7 @@ public class ImageGalleryModule {
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
Content newDataSource = (Content) evt.getNewValue();
if (con.isListeningEnabled()) {
con.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller));
controller.getDatabase().insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.DEFAULT);
}
}
break;
@ -326,40 +321,68 @@ public class ImageGalleryModule {
@Override
public void propertyChange(PropertyChangeEvent evt) {
IngestJobEvent eventType = IngestJobEvent.valueOf(evt.getPropertyName());
if (eventType != IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED
|| ((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.REMOTE) {
return;
}
// 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
try {
ImageGalleryController con = getController();
con.setStale(true);
if (con.isListeningEnabled()) {
SwingUtilities.invokeLater(() -> {
if (ImageGalleryTopComponent.isImageGalleryOpen()) {
int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(),
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(),
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(),
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
ImageGalleryController controller = getController();
switch (showAnswer) {
case JOptionPane.YES_OPTION:
con.rebuildDB();
break;
case JOptionPane.NO_OPTION:
case JOptionPane.CANCEL_OPTION:
default:
break; //do nothing
}
if (eventType == IngestJobEvent.DATA_SOURCE_ANALYSIS_STARTED) {
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
if (controller.isListeningEnabled()) {
DataSourceAnalysisStartedEvent dataSourceAnalysisStartedEvent = (DataSourceAnalysisStartedEvent) evt;
Content dataSource = dataSourceAnalysisStartedEvent.getDataSource();
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
}
});
}
} else if (eventType == IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED) {
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
if (controller.isListeningEnabled()) {
DataSourceAnalysisCompletedEvent dataSourceAnalysisCompletedEvent = (DataSourceAnalysisCompletedEvent) evt;
Content dataSource = dataSourceAnalysisCompletedEvent.getDataSource();
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus =
controller.hasFilesWithNoMimetype(dataSource) ?
DrawableDB.DrawableDbBuildStatusEnum.DEFAULT :
DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), datasourceDrawableDBStatus);
}
return;
}
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);
if (controller.isListeningEnabled()) {
SwingUtilities.invokeLater(() -> {
if (ImageGalleryTopComponent.isImageGalleryOpen()) {
int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(),
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(),
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(),
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
switch (showAnswer) {
case JOptionPane.YES_OPTION:
controller.rebuildDB();
break;
case JOptionPane.NO_OPTION:
case JOptionPane.CANCEL_OPTION:
default:
break; //do nothing
}
}
});
}
}
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
}
catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
}
}
}

View File

@ -18,13 +18,10 @@
*/
package org.sleuthkit.autopsy.imagegallery;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
@ -55,10 +52,8 @@ import javafx.stage.Modality;
import javax.swing.SwingUtilities;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.lang3.ObjectUtils.notEqual;
import org.apache.commons.lang3.StringUtils;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
@ -232,8 +227,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
@SuppressWarnings(value = "unchecked")
ComboBox<Optional<DataSource>> comboBox = (ComboBox<Optional<DataSource>>) datasourceDialog.getDialogPane().lookup(".combo-box");
//set custom cell renderer
comboBox.setCellFactory((ListView<Optional<DataSource>> param) -> new DataSourceCell(dataSourcesTooManyFiles));
comboBox.setButtonCell(new DataSourceCell(dataSourcesTooManyFiles));
comboBox.setCellFactory((ListView<Optional<DataSource>> param) -> new DataSourceCell(dataSourcesTooManyFiles, controller.getAllDataSourcesDrawableDBStatus()));
comboBox.setButtonCell(new DataSourceCell(dataSourcesTooManyFiles, controller.getAllDataSourcesDrawableDBStatus()));
DataSource dataSource = datasourceDialog.showAndWait().orElse(Optional.empty()).orElse(null);
try {

View File

@ -19,11 +19,10 @@
package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.util.concurrent.ListenableFuture;
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import java.util.Map;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.scene.control.Alert;
@ -32,7 +31,6 @@ import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javax.annotation.concurrent.ThreadSafe;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
@ -50,11 +48,12 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB.DrawableDbBuildStatusEnum;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
@ -70,6 +69,8 @@ import org.sleuthkit.datamodel.TskCoreException;
"OpenAction.stale.confDlg.msg=The image / video database may be out of date. "
+ "Do you want to update and listen for further ingest results?\n"
+ "Choosing 'yes' will update the database and enable listening to future ingests.",
"OpenAction.notAnalyzedDlg.msg=No image/video files available to display yet.\n"
+ "Please run FileType and EXIF ingest modules.",
"OpenAction.stale.confDlg.title=Image Gallery"})
public final class OpenAction extends CallableSystemAction {
@ -184,17 +185,43 @@ public final class OpenAction extends CallableSystemAction {
}
private void checkDBStale(ImageGalleryController controller) {
//check if db is stale on throw away bg thread and then react back on jfx thread.
ListenableFuture<Boolean> staleFuture = TaskUtils.getExecutorForClass(OpenAction.class)
.submit(controller::isDataSourcesTableStale);
addFXCallback(staleFuture,
dbIsStale -> {
ListenableFuture<Map<Long, DrawableDB.DrawableDbBuildStatusEnum>> dataSourceStatusMapFuture = TaskUtils.getExecutorForClass(OpenAction.class)
.submit(controller::getAllDataSourcesDrawableDBStatus);
addFXCallback(dataSourceStatusMapFuture,
dataSourceStatusMap -> {
boolean dbIsStale = false;
for (Map.Entry<Long, DrawableDbBuildStatusEnum> entry : dataSourceStatusMap.entrySet()) {
DrawableDbBuildStatusEnum status = entry.getValue();
if (DrawableDbBuildStatusEnum.COMPLETE != status) {
dbIsStale = true;
}
}
//back on fx thread.
if (false == dbIsStale) {
//drawable db is not stale, just open it
openTopComponent();
} else {
//drawable db is stale, ask what to do
// 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);
alert.showAndWait();
return;
}
}
//drawable db is stale,
//ask what to do
Alert alert = new Alert(Alert.AlertType.WARNING,
Bundle.OpenAction_stale_confDlg_msg(),
ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);

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

@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.imagegallery.gui;
import java.util.Map;
import java.util.Optional;
import javafx.scene.control.ListCell;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB.DrawableDbBuildStatusEnum;
import org.sleuthkit.datamodel.DataSource;
/**
@ -29,9 +31,23 @@ import org.sleuthkit.datamodel.DataSource;
public class DataSourceCell extends ListCell<Optional<DataSource>> {
private final Map<DataSource, Boolean> dataSourcesTooManyFiles;
private final Map<Long, DrawableDB.DrawableDbBuildStatusEnum> dataSourcesDrawableDBStatus;
/**
*
* @param dataSourcesTooManyFiles: a map of too many files indicator for
* each data source.
* Data sources with too many files may substantially slow down
* the system and hence are disabled for selection.
* @param dataSourcesDrawableDBStatus a map of drawable DB status for
* each data sources.
* Data sources in DEFAULT state are not fully analyzed yet and are
* disabled for selection.
*/
public DataSourceCell(Map<DataSource, Boolean> dataSourcesTooManyFiles, Map<Long, DrawableDB.DrawableDbBuildStatusEnum> dataSourcesDrawableDBStatus) {
this.dataSourcesTooManyFiles = dataSourcesTooManyFiles;
this.dataSourcesDrawableDBStatus = dataSourcesDrawableDBStatus;
public DataSourceCell(Map<DataSource, Boolean> dataSourcesViewable) {
this.dataSourcesTooManyFiles = dataSourcesViewable;
}
@Override
@ -43,14 +59,28 @@ public class DataSourceCell extends ListCell<Optional<DataSource>> {
DataSource dataSource = item.orElse(null);
String text = (dataSource == null) ? "All" : dataSource.getName() + " (Id: " + dataSource.getId() + ")";
Boolean tooManyFilesInDataSource = dataSourcesTooManyFiles.getOrDefault(dataSource, false);
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");
} else {
}
else {
setGraphic(null);
setStyle("-fx-opacity : 1");
}
setDisable(tooManyFilesInDataSource);
setText(text);
}
}

View File

@ -240,8 +240,8 @@ public class Toolbar extends ToolBar {
}
private void initDataSourceComboBox() {
dataSourceComboBox.setCellFactory(param -> new DataSourceCell(dataSourcesViewable));
dataSourceComboBox.setButtonCell(new DataSourceCell(dataSourcesViewable));
dataSourceComboBox.setCellFactory(param -> new DataSourceCell(dataSourcesViewable, controller.getAllDataSourcesDrawableDBStatus()));
dataSourceComboBox.setButtonCell(new DataSourceCell(dataSourcesViewable, controller.getAllDataSourcesDrawableDBStatus()));
dataSourceComboBox.setConverter(new StringConverter<Optional<DataSource>>() {
@Override
public String toString(Optional<DataSource> object) {

View File

@ -21,21 +21,15 @@ package org.sleuthkit.autopsy.keywordsearch;
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 @@ 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
boolean isContentTypeSpecific() {
@ -80,7 +73,7 @@ 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,267 +84,250 @@ 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);
}
/**
* 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.keywordsearch;
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

@ -810,6 +810,7 @@ class ExtractRegistry extends Extract {
installtime = Long.valueOf(Tempdate) / 1000;
} catch (ParseException e) {
logger.log(Level.SEVERE, "RegRipper::Conversion on DateTime -> ", e); //NON-NLS
wifiBBartifacts.add(bbart);
}
break;
default:
@ -817,6 +818,9 @@ class ExtractRegistry extends Extract {
}
}
}//for
}
if (!wifiBBartifacts.isEmpty()){
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_WIFI_NETWORK, wifiBBartifacts));
try {
List<BlackboardAttribute> bbattributes = Lists.newArrayList(
new BlackboardAttribute(

View File

@ -1,5 +1,5 @@
#Updated by build script
#Sat, 13 Oct 2018 21:02:18 -0400
#Tue, 13 Nov 2018 17:30:09 -0500
LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314
SPLASH_WIDTH=538
@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18
SplashRunningTextColor=0x0
SplashRunningTextFontSize=19
currentVersion=Autopsy 4.9.0
currentVersion=Autopsy 4.9.1

View File

@ -1,4 +1,4 @@
#Updated by build script
#Sat, 13 Oct 2018 21:02:18 -0400
CTL_MainWindow_Title=Autopsy 4.9.0
CTL_MainWindow_Title_No_Project=Autopsy 4.9.0
#Tue, 13 Nov 2018 17:30:09 -0500
CTL_MainWindow_Title=Autopsy 4.9.1
CTL_MainWindow_Title_No_Project=Autopsy 4.9.1

View File

@ -6,8 +6,8 @@ app.name=${branding.token}
### if left unset, version will default to today's date
app.version=4.9.1
### build.type must be one of: DEVELOPMENT, RELEASE
build.type=RELEASE
#build.type=DEVELOPMENT
#build.type=RELEASE
build.type=DEVELOPMENT
project.org.netbeans.progress=org-netbeans-api-progress
project.org.sleuthkit.autopsy.experimental=Experimental

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