diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index 87e4532806..c392408465 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -28,6 +28,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.TskCoreException; @@ -54,7 +55,7 @@ public class EamArtifactUtil { * EamArtifact with a single EamArtifactInstance within. If not, return * null. * - * @param bbArtifact BlackboardArtifact to examine + * @param artifact BlackboardArtifact to examine * @param checkEnabled If true, only create a CorrelationAttribute if it is * enabled * @@ -62,128 +63,100 @@ public class EamArtifactUtil { */ public static List makeInstancesFromBlackboardArtifact(BlackboardArtifact bbArtifact, boolean checkEnabled) { - List eamArtifacts = new ArrayList<>(); - try { - // Cycle through the types and see if there is a correlation attribute that works - // for the given blackboard artifact - // - // @@@ This seems ineffecient. Instead of cycling based on correlation type, we should just - // have switch based on artifact type - for (CorrelationAttributeInstance.Type aType : EamDb.getInstance().getDefinedCorrelationTypes()) { - if ((checkEnabled && aType.isEnabled()) || !checkEnabled) { - // Now always adds the instance details associated with this occurance. - CorrelationAttributeInstance correlationAttribute = EamArtifactUtil.makeInstanceFromBlackboardArtifact(aType, bbArtifact); - if (correlationAttribute != null) { - eamArtifacts.add(correlationAttribute); + BlackboardArtifact artifact = null; + if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == bbArtifact.getArtifactTypeID()) { + // Get the associated artifact + BlackboardAttribute attribute = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); + if (attribute != null) { + artifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong()); + } + } else { + artifact = bbArtifact; + } + if (artifact != null) { + switch (BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID())) { + case TSK_KEYWORD_HIT: { + BlackboardAttribute setNameAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); + if (setNameAttr != null + && EamArtifactUtil.getEmailAddressAttrString().equals(setNameAttr.getValueString())) { + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID); + } + break; } + case TSK_WEB_BOOKMARK: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID); + break; + case TSK_WEB_COOKIE: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID); + break; + case TSK_WEB_DOWNLOAD: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID); + break; + case TSK_WEB_HISTORY: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID); + break; + case TSK_CONTACT: + //generates the same correlation attrs as tsk_message + case TSK_CALLLOG: + //generates the same correlation attrs as tsk_message + case TSK_MESSAGE: { + String value = null; + if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) { + value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString(); + } else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) { + value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString(); + } else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) { + value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString(); + } + // Remove all non-numeric symbols to semi-normalize phone numbers, preserving leading "+" character + if (value != null) { + String newValue = value.replaceAll("\\D", ""); + if (value.startsWith("+")) { + newValue = "+" + newValue; + } + value = newValue; + // Only add the correlation attribute if the resulting phone number large enough to be of use + // (these 3-5 digit numbers can be valid, but are not useful for correlation) + if (value.length() > 5) { + eamArtifacts.add(makeCorrelationAttributeInstanceUsingTypeValue(artifact, EamDb.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value)); + } + } + break; + } + case TSK_DEVICE_ATTACHED: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID); + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID); + break; + case TSK_WIFI_NETWORK: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID); + break; + case TSK_WIFI_NETWORK_ADAPTER: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID); + break; + case TSK_BLUETOOTH_PAIRING: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID); + break; + case TSK_BLUETOOTH_ADAPTER: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID); + break; + case TSK_DEVICE_INFO: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID); + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID); + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID); + break; + case TSK_SIM_ATTACHED: + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID); + addCorrelationAttributeToList(eamArtifacts, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID); + break; + default: + break; } } } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error getting defined correlation types.", ex); // NON-NLS return eamArtifacts; - } - - return eamArtifacts; - } - - /** - * Create an EamArtifact of type correlationType if one can be generated - * based on the data in the blackboard artifact. - * - * @param correlationType The Central Repository artifact type to create - * @param bbArtifact The blackboard artifact to pull data from - * - * @return the new EamArtifact, or null if one was not created because - * bbArtifact did not contain the needed data - */ - private static CorrelationAttributeInstance makeInstanceFromBlackboardArtifact(CorrelationAttributeInstance.Type correlationType, - BlackboardArtifact bbArtifact) throws EamDbException { - String value = null; - int artifactTypeID = bbArtifact.getArtifactTypeID(); - - try { - if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifactTypeID) { - // Get the associated artifact - BlackboardAttribute attribute = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); - if (attribute != null) { - BlackboardArtifact associatedArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong()); - return EamArtifactUtil.makeInstanceFromBlackboardArtifact(correlationType, associatedArtifact); - } - - } else if (correlationType.getId() == CorrelationAttributeInstance.EMAIL_TYPE_ID - && BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() == artifactTypeID) { - - BlackboardAttribute setNameAttr = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); - if (setNameAttr != null - && EamArtifactUtil.getEmailAddressAttrString().equals(setNameAttr.getValueString())) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD)).getValueString(); - } - } else if (correlationType.getId() == CorrelationAttributeInstance.DOMAIN_TYPE_ID - && (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() == artifactTypeID)) { - - // Lower-case this to normalize domains - BlackboardAttribute attribute = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN)); - if (attribute != null) { - value = attribute.getValueString(); - } - } else if (correlationType.getId() == CorrelationAttributeInstance.PHONE_TYPE_ID - && (BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == artifactTypeID)) { - - if (null != bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString(); - } else if (null != bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString(); - } else if (null != bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString(); - } - - // Remove all non-numeric symbols to semi-normalize phone numbers, preserving leading "+" character - if (value != null) { - String newValue = value.replaceAll("\\D", ""); - if (value.startsWith("+")) { - newValue = "+" + newValue; - } - - value = newValue; - - // If the resulting phone number is too small to be of use, return null - // (these 3-5 digit numbers can be valid, but are not useful for correlation) - if (value.length() <= 5) { - return null; - } - } - } else if (correlationType.getId() == CorrelationAttributeInstance.USBID_TYPE_ID - && BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID() == artifactTypeID) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID)).getValueString(); - } else if (correlationType.getId() == CorrelationAttributeInstance.SSID_TYPE_ID - && BlackboardArtifact.ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID() == artifactTypeID) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID)).getValueString(); - } else if (correlationType.getId() == CorrelationAttributeInstance.MAC_TYPE_ID - && (BlackboardArtifact.ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_BLUETOOTH_ADAPTER.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID() == artifactTypeID)) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS)).getValueString(); - } else if (correlationType.getId() == CorrelationAttributeInstance.IMEI_TYPE_ID - && BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID() == artifactTypeID) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI)).getValueString(); - } else if (correlationType.getId() == CorrelationAttributeInstance.IMSI_TYPE_ID - && (BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID() == artifactTypeID)) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI)).getValueString(); - } else if (correlationType.getId() == CorrelationAttributeInstance.ICCID_TYPE_ID - && (BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID() == artifactTypeID - || BlackboardArtifact.ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID() == artifactTypeID)) { - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID)).getValueString(); - } - } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting attribute while getting type from BlackboardArtifact.", ex); // NON-NLS return null; @@ -191,11 +164,34 @@ public class EamArtifactUtil { logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS return null; } + return eamArtifacts; + } - if ((null != value) && (value.isEmpty() == false)) { - return makeCorrelationAttributeInstanceUsingTypeValue(bbArtifact, correlationType, value); - } else { - return null; + /** + * Add a CorrelationAttributeInstance of the specified type to the provided + * list if the artifact has an Attribute of the given type with a non empty + * value. + * + * @param eamArtifacts the list of CorrelationAttributeInstance objects + * which should be added to + * @param artifact the blackboard artifact which we are creating a + * CorrelationAttributeInstance for + * @param bbAttributeType the type of BlackboardAttribute we expect to exist + * for a CorrelationAttributeInstance of this type + * generated from this Blackboard Artifact + * @param typeId the integer type id of the + * CorrelationAttributeInstance type + * + * @throws EamDbException + * @throws TskCoreException + */ + private static void addCorrelationAttributeToList(List eamArtifacts, BlackboardArtifact artifact, ATTRIBUTE_TYPE bbAttributeType, int typeId) throws EamDbException, TskCoreException { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(bbAttributeType)); + if (attribute != null) { + String value = attribute.getValueString(); + if ((null != value) && (value.isEmpty() == false)) { + eamArtifacts.add(makeCorrelationAttributeInstanceUsingTypeValue(artifact, EamDb.getInstance().getCorrelationTypeById(typeId), value)); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index f2be8c90c4..f2db7c2532 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -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 row = new LinkedHashMap<>(); + private List> pageOfTableRows = new ArrayList<>(); + private List currentTableHeader = new ArrayList<>(); + private String prevTableName; + private int numRows; // num of rows in the selected table private int currPage = 0; // curr page of rows being displayed @@ -264,18 +264,18 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { }//GEN-LAST:event_tablesDropdownListActionPerformed /** - * The action when the Export Csv button is pressed. The file chooser window will pop - * up to choose where the user wants to save the csv file. The default location is case export directory. + * The action when the Export Csv button is pressed. The file chooser window + * will pop up to choose where the user wants to save the csv file. The + * default location is case export directory. * * @param evt the action event */ - @NbBundle.Messages({"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.", - "SQLiteViewer.csvExport.title=Export to csv file", - "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"}) + "SQLiteViewer.csvExport.title=Export to csv file", + "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"}) private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCsvButtonActionPerformed Case openCase = Case.getCurrentCase(); - File caseDirectory = new File(openCase.getExportDirectory()); + File caseDirectory = new File(openCase.getExportDirectory()); JFileChooser fileChooser = new JFileChooser(); fileChooser.setDragEnabled(false); fileChooser.setCurrentDirectory(caseDirectory); @@ -292,14 +292,14 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { File file = fileChooser.getSelectedFile(); if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("csv")) { if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, - Bundle.SQLiteViewer_csvExport_confirm_msg(), - Bundle.SQLiteViewer_csvExport_title(), + Bundle.SQLiteViewer_csvExport_confirm_msg(), + Bundle.SQLiteViewer_csvExport_title(), JOptionPane.YES_NO_OPTION)) { } else { return; - } + } } - + exportTableToCsv(file); } }//GEN-LAST:event_exportCsvButtonActionPerformed @@ -328,6 +328,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { public void setFile(AbstractFile file) { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); sqliteDbFile = file; + initReader(); processSQLiteFile(); WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } @@ -343,16 +344,15 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { tablesDropdownList.removeAllItems(); numEntriesField.setText(""); - // close DB connection to file - if (null != connection) { - try { - connection.close(); - connection = null; - } catch (SQLException ex) { - logger.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS - } + try { + viewReader.close(); + } catch (SQLiteTableReaderException ex) { + //Could not successfully close the reader, nothing we can do to recover. } - + row = new LinkedHashMap<>(); + pageOfTableRows = new ArrayList<>(); + currentTableHeader = new ArrayList<>(); + viewReader = null; sqliteDbFile = null; } @@ -368,17 +368,10 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.", "# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",}) private void processSQLiteFile() { - - tablesDropdownList.removeAllItems(); - try { - String localDiskPath = SqliteUtil.writeAbstractFileToLocalDisk(sqliteDbFile); - SqliteUtil.findAndCopySQLiteMetaFile(sqliteDbFile); - // Load the SQLite JDBC driver, if necessary. - Class.forName("org.sqlite.JDBC"); //NON-NLS - connection = DriverManager.getConnection("jdbc:sqlite:" + localDiskPath); //NON-NLS + tablesDropdownList.removeAllItems(); - Collection dbTablesMap = getTables(); + Collection 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 getTables() throws SQLException { - Collection tableNames = new LinkedList<>(); - try (Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery( - "SELECT name FROM sqlite_master " - + " WHERE type= 'table' ")){ - while (resultSet.next()) { - tableNames.add(resultSet.getString("name")); //NON-NLS - } - } - return tableNames; - } - @NbBundle.Messages({"# {0} - tableName", "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}" }) private void selectTable(String tableName) { - - try (Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery( - "SELECT count (*) as count FROM " + "\"" + tableName + "\"")) { //NON-NLS - - numRows = resultSet.getInt("count"); + try { + numRows = viewReader.getRowCount(tableName); numEntriesField.setText(numRows + " entries"); currPage = 1; @@ -442,25 +409,19 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } else { exportCsvButton.setEnabled(false); nextPageButton.setEnabled(false); - - //Execute a dummy SELECT * statement so that the metadata - //contains all column names - Map 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 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> 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> resultSetToArrayList(ResultSet resultSet) throws SQLException { - ResultSetMetaData metaData = resultSet.getMetaData(); - int columns = metaData.getColumnCount(); - ArrayList> rowlist = new ArrayList<>(); - while (resultSet.next()) { - Map 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 getForAllStrategy() { + return new Consumer() { + 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> 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 row = currentTableRows.get(0); - StringBuffer header = new StringBuffer(); - for (Map.Entry 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 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 getColumnNameCSVStrategy(FileOutputStream out) { + return new Consumer() { + 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 getForAllCSVStrategy(FileOutputStream out) { + return new Consumer() { + 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; + } + }; + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SqliteUtil.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SqliteUtil.java deleted file mode 100755 index 4fc220cf0d..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SqliteUtil.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018-2018 Basis Technology Corp. - * Contact: carrier sleuthkit 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 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(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReader.java b/Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReader.java index fdfcffd589..2d950b90f4 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReader.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/SQLiteTableReader.java @@ -55,13 +55,13 @@ import org.sleuthkit.datamodel.TskCoreException; * * or * - * SQLiteTableReader reader = new SQLiteTableReader.Builder(file) .onInteger(new - * Consumer() { - * @Override public void accept(Integer i) { - * System.out.println(i); - * } - * }).build(); - * + * SQLiteTableReader reader = new SQLiteTableReader.Builder(file) + * .onInteger(new Consumer() { + * @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 @@ -83,10 +83,9 @@ public class SQLiteTableReader implements AutoCloseable { private Consumer onFloatAction; private Consumer onBlobAction; private Consumer forAllAction; - + static Consumer doNothing() { - return NOOP -> { - }; + return NOOP -> {}; } /** @@ -211,7 +210,7 @@ public class SQLiteTableReader implements AutoCloseable { 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()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 002f87acba..df1358833f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -29,6 +29,7 @@ import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNod import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; +import org.sleuthkit.autopsy.othercasessearch.CorrelationAttributeInstanceNode; /** * Visitor pattern that goes over all nodes in the directory tree. This includes @@ -125,6 +126,8 @@ public interface DisplayableItemNodeVisitor { T visit(CentralRepoCommonAttributeInstanceNode crfin); T visit(InstanceCountNode icn); + + T visit(CorrelationAttributeInstanceNode cain); /* * Tags @@ -214,6 +217,11 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(icn); } + @Override + public T visit(CorrelationAttributeInstanceNode cain) { + return defaultVisit(cain); + } + @Override public T visit(CentralRepoCommonAttributeInstanceNode crfin){ return defaultVisit(crfin); diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/othercasessearch/Bundle.properties new file mode 100755 index 0000000000..17ea13ee0b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/Bundle.properties @@ -0,0 +1,10 @@ + +OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription= +OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleName=Search +OtherCasesSearchDialog.searchButton.text=Search +OtherCasesSearchDialog.correlationValueTextField.text= +OtherCasesSearchDialog.correlationValueLabel.text=Correlation Property Value: +OtherCasesSearchDialog.descriptionLabel.text=Search data in the Central Repository from other cases. +OtherCasesSearchDialog.errorLabel.text=\ +OtherCasesSearchDialog.correlationTypeLabel.text=Correlation Property Type: +OtherCasesSearchDialog.casesLabel.text=\ diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/CorrelationAttributeInstanceNode.java b/Core/src/org/sleuthkit/autopsy/othercasessearch/CorrelationAttributeInstanceNode.java new file mode 100755 index 0000000000..f3248d293b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/CorrelationAttributeInstanceNode.java @@ -0,0 +1,141 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.othercasessearch; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.swing.Action; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; +import org.sleuthkit.autopsy.othercasessearch.Bundle; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; + +/** + * Used by the Other Cases Search feature to encapsulate instances of a given + * search match. + */ +public final class CorrelationAttributeInstanceNode extends DisplayableItemNode { + + private final CorrelationAttributeInstance instance; + + CorrelationAttributeInstanceNode(CorrelationAttributeInstance content) { + super(Children.LEAF, Lookups.fixed(content)); + this.instance = content; + this.setDisplayName(new File(this.instance.getFilePath()).getName()); + } + + /** + * Get the CorrelationAttributeInstance attached to the node. + * + * @return The CorrelationAttributeInstance object. + */ + public CorrelationAttributeInstance getCorrelationAttributeInstance(){ + return this.instance; + } + + @Override + public Action[] getActions(boolean context){ + List actionsList = new ArrayList<>(); + + actionsList.addAll(Arrays.asList(super.getActions(true))); + + return actionsList.toArray(new Action[actionsList.size()]); + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + return CorrelationAttributeInstanceNode.class.getName(); + } + + @NbBundle.Messages({ + "CorrelationAttributeInstanceNode.columnName.name=Name", + "CorrelationAttributeInstanceNode.columnName.case=Case", + "CorrelationAttributeInstanceNode.columnName.dataSource=Data Source", + "CorrelationAttributeInstanceNode.columnName.known=Known", + "CorrelationAttributeInstanceNode.columnName.path=Path", + "CorrelationAttributeInstanceNode.columnName.comment=Comment", + "CorrelationAttributeInstanceNode.columnName.device=Device" + }) + @Override + protected Sheet createSheet(){ + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + + if(sheetSet == null){ + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + final CorrelationAttributeInstance centralRepoFile = this.getCorrelationAttributeInstance(); + + final String path = centralRepoFile.getFilePath(); + final File file = new File(path); + final String name = file.getName(); + final String caseName = centralRepoFile.getCorrelationCase().getDisplayName(); + final CorrelationDataSource dataSource = centralRepoFile.getCorrelationDataSource(); + final String dataSourceName = dataSource.getName(); + final String known = centralRepoFile.getKnownStatus().getName(); + final String comment = centralRepoFile.getComment(); + final String device = dataSource.getDeviceID(); + + final String NO_DESCR = ""; + + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_name(), + Bundle.CorrelationAttributeInstanceNode_columnName_name(), NO_DESCR, name)); + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_case(), + Bundle.CorrelationAttributeInstanceNode_columnName_case(), NO_DESCR, caseName)); + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_dataSource(), + Bundle.CorrelationAttributeInstanceNode_columnName_dataSource(), NO_DESCR, dataSourceName)); + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_known(), + Bundle.CorrelationAttributeInstanceNode_columnName_known(), NO_DESCR, known)); + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_path(), + Bundle.CorrelationAttributeInstanceNode_columnName_path(), NO_DESCR, path)); + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_comment(), + Bundle.CorrelationAttributeInstanceNode_columnName_comment(), NO_DESCR, comment)); + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_device(), + Bundle.CorrelationAttributeInstanceNode_columnName_device(), NO_DESCR, device)); + + return sheet; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesFilterChildren.java b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesFilterChildren.java new file mode 100755 index 0000000000..abe37bb15e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesFilterChildren.java @@ -0,0 +1,85 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.othercasessearch; + +import org.openide.nodes.Children; +import org.openide.nodes.FilterNode; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; + +/** + * A Children implementation for a + * CorrelationPropertyFilterNode. + */ +final class OtherCasesFilterChildren extends FilterNode.Children { + + /** + * Create a new Children instance. + * + * @param wrappedNode The node to be wrapped. + * @param createChildren If false, return LEAF. Otherwise, return a new + * CorrelationPropertyFilterChildren instance. + * + * @return A Children instance. + */ + static Children createInstance(Node wrappedNode, boolean createChildren) { + + if (createChildren) { + return new OtherCasesFilterChildren(wrappedNode); + } else { + return Children.LEAF; + } + } + + /** + * Constructs a children (child factory) implementation for a + * CorrelationPropertyFilterNode. + * + * @param wrappedNode The node wrapped by CorrelationPropertyFilterNode. + */ + OtherCasesFilterChildren(Node wrappedNode) { + super(wrappedNode); + } + + /** + * Copies a CorrelationPropertyFilterNode, with the children (child factory) + * flag set to false. + * + * @param nodeToCopy The CorrelationPropertyFilterNode to copy. + * + * @return A copy of a CorrelationPropertyFilterNode. + */ + @Override + protected Node copyNode(Node nodeToCopy) { + return new TableFilterNode(nodeToCopy, false); + } + + /** + * Creates the child nodes represented by this children (child factory) + * object. + * + * @param key The key, i.e., the node, for which to create the child nodes. + * + * @return A single-element node array. + */ + @Override + protected Node[] createNodes(Node key) { + return new Node[]{this.copyNode(key)}; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchAction.java b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchAction.java new file mode 100755 index 0000000000..55dcda0a24 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchAction.java @@ -0,0 +1,69 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.othercasessearch; + +import java.awt.event.ActionEvent; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.othercasessearch.Bundle; + +/** + * Action for accessing the Search Other Cases dialog. + */ +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.othercasessearch.OtherCasesSearchAction") +@ActionRegistration(displayName = "#CTL_OtherCasesSearchAction=Search Other Cases", lazy = false) +@ActionReference(path = "Menu/Tools", position = 104) +@NbBundle.Messages({"CTL_OtherCasesSearchAction=Search Other Cases"}) +public class OtherCasesSearchAction extends CallableSystemAction { + + @Override + public boolean isEnabled() { + return EamDb.isEnabled() && Case.isCaseOpen(); + } + + @Override + public void actionPerformed(ActionEvent event) { + performAction(); + } + + @Override + public void performAction() { + OtherCasesSearchDialog dialog = new OtherCasesSearchDialog(); + dialog.display(); + } + + @NbBundle.Messages({ + "OtherCasesSearchAction.getName.text=Search Other Cases"}) + @Override + public String getName() { + return Bundle.OtherCasesSearchAction_getName_text(); + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchChildren.java b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchChildren.java new file mode 100755 index 0000000000..eea55a6518 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchChildren.java @@ -0,0 +1,49 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.othercasessearch; + +import java.util.List; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; + +/** + * Creates CorrelationAttributeInstanceNodes from a collection of + * CorrelationAttributeInstances. + */ +class OtherCasesSearchChildren extends Children.Keys { + + /** + * Create an instance of OtherCasesSearchChildren. + * + * @param lazy Lazy load? + * @param fileList List of CorrelationAttributeInstances. + */ + OtherCasesSearchChildren(boolean lazy, List instances) { + super(lazy); + this.setKeys(instances); + } + + @Override + protected Node[] createNodes(CorrelationAttributeInstance t) { + Node[] node = new Node[1]; + node[0] = new CorrelationAttributeInstanceNode(t); + return node; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.form b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.form new file mode 100755 index 0000000000..00eb862b4a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.form @@ -0,0 +1,159 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.java b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.java new file mode 100755 index 0000000000..51e21631d2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchDialog.java @@ -0,0 +1,381 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.othercasessearch; + +import java.awt.Color; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JFrame; +import javax.swing.SwingWorker; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.corecomponents.TextPrompt; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.EmptyNode; + +@Messages({ + "OtherCasesSearchDialog.dialogTitle.text=Search Other Cases", + "OtherCasesSearchDialog.resultsTitle.text=Other Cases", + "OtherCasesSearchDialog.resultsDescription.text=Other Cases Search", + "OtherCasesSearchDialog.emptyNode.text=No results found.", + "OtherCasesSearchDialog.validation.invalidHash=The supplied value is not a valid MD5 hash.", + "# {0} - number of cases", + "OtherCasesSearchDialog.caseLabel.text=The current Central Repository contains {0} case(s)." +}) +/** + * The Search Other Cases dialog allows users to search for specific + * types of correlation properties in the Central Repository. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +final class OtherCasesSearchDialog extends javax.swing.JDialog { + private static final Logger logger = Logger.getLogger(OtherCasesSearchDialog.class.getName()); + private static final long serialVersionUID = 1L; + + private static final String FILES_CORRELATION_TYPE = "Files"; + + private final List correlationTypes; + private TextPrompt correlationValueTextFieldPrompt; + + /** + * Creates a new instance of the Search Other Cases dialog. + */ + OtherCasesSearchDialog() { + super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.OtherCasesSearchDialog_dialogTitle_text(), true); + this.correlationTypes = new ArrayList<>(); + initComponents(); + customizeComponents(); + } + + /** + * Perform the other cases search. + */ + private void search() { + new SwingWorker, Void>() { + + @Override + protected List doInBackground() { + List correlationTypes; + List correlationInstances = new ArrayList<>(); + + try { + correlationTypes = EamDb.getInstance().getDefinedCorrelationTypes(); + for (CorrelationAttributeInstance.Type type : correlationTypes) { + if (type.getDisplayName().equals((String) correlationTypeComboBox.getSelectedItem())) { + correlationInstances = EamDb.getInstance().getArtifactInstancesByTypeValue(type, correlationValueTextField.getText()); + break; + } + } + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex); + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.SEVERE, "Unable to retrieve data from the Central Repository.", ex); + } + + return correlationInstances; + } + + @Override + protected void done() { + try { + super.done(); + List correlationInstances = this.get(); + DataResultViewerTable table = new DataResultViewerTable(); + Collection viewers = new ArrayList<>(1); + viewers.add(table); + + OtherCasesSearchNode searchNode = new OtherCasesSearchNode(correlationInstances); + TableFilterNode tableFilterNode = new TableFilterNode(searchNode, true, searchNode.getName()); + + String resultsText = String.format("%s (%s; \"%s\")", + Bundle.OtherCasesSearchDialog_resultsTitle_text(), + (String) correlationTypeComboBox.getSelectedItem(), + correlationValueTextField.getText()); + final TopComponent searchResultWin; + if (correlationInstances.isEmpty()) { + Node emptyNode = new TableFilterNode( + new EmptyNode(Bundle.OtherCasesSearchDialog_emptyNode_text()), true); + searchResultWin = DataResultTopComponent.createInstance( + resultsText, Bundle.OtherCasesSearchDialog_resultsDescription_text(), emptyNode, 0); + } else { + searchResultWin = DataResultTopComponent.createInstance( + resultsText, Bundle.OtherCasesSearchDialog_resultsDescription_text(), tableFilterNode, correlationInstances.size(), viewers); + } + searchResultWin.requestActive(); // make it the active top component + } catch (ExecutionException | InterruptedException ex) { + logger.log(Level.SEVERE, "Unable to get CorrelationAttributeInstances.", ex); + } + } + }.execute(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + correlationValueLabel = new javax.swing.JLabel(); + correlationValueTextField = new javax.swing.JTextField(); + searchButton = new javax.swing.JButton(); + correlationTypeComboBox = new javax.swing.JComboBox<>(); + correlationTypeLabel = new javax.swing.JLabel(); + errorLabel = new javax.swing.JLabel(); + descriptionLabel = new javax.swing.JLabel(); + casesLabel = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setResizable(false); + + org.openide.awt.Mnemonics.setLocalizedText(correlationValueLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.correlationValueLabel.text")); // NOI18N + + correlationValueTextField.setText(org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.correlationValueTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.searchButton.text")); // NOI18N + searchButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(correlationTypeLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.correlationTypeLabel.text")); // NOI18N + + errorLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.errorLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.descriptionLabel.text")); // NOI18N + + casesLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + org.openide.awt.Mnemonics.setLocalizedText(casesLabel, org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.casesLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(casesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addComponent(searchButton)) + .addGroup(layout.createSequentialGroup() + .addComponent(descriptionLabel) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(correlationValueLabel) + .addComponent(correlationTypeLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(correlationTypeComboBox, 0, 289, Short.MAX_VALUE) + .addComponent(correlationValueTextField) + .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(descriptionLabel) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(correlationTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(correlationTypeLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(correlationValueLabel) + .addComponent(correlationValueTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorLabel) + .addGap(11, 11, 11) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(searchButton) + .addComponent(casesLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + searchButton.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleName")); // NOI18N + searchButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(OtherCasesSearchDialog.class, "OtherCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription")); // NOI18N + + pack(); + }// //GEN-END:initComponents + + private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed + if (validateInputs()) { + /* + * Just in case, we'll lock down the type and value components to + * avoid the possibly of a race condition. + */ + correlationTypeComboBox.setEnabled(false); + correlationValueTextField.setEnabled(false); + + search(); + dispose(); + } else { + searchButton.setEnabled(false); + errorLabel.setText(Bundle.OtherCasesSearchDialog_validation_invalidHash()); + correlationValueTextField.grabFocus(); + } + }//GEN-LAST:event_searchButtonActionPerformed + + /** + * Further customize the components beyond the standard initialization. + */ + private void customizeComponents() { + searchButton.setEnabled(false); + + /* + * Add correlation types to the combo-box. + */ + try { + EamDb dbManager = EamDb.getInstance(); + correlationTypes.clear(); + correlationTypes.addAll(dbManager.getDefinedCorrelationTypes()); + int numberOfCases = dbManager.getCases().size(); + casesLabel.setText(Bundle.OtherCasesSearchDialog_caseLabel_text(numberOfCases)); + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex); + } + + for (CorrelationAttributeInstance.Type type : correlationTypes) { + // We only support the "Files" type for now. + if (type.getDisplayName().equals(FILES_CORRELATION_TYPE)) { + correlationTypeComboBox.addItem(type.getDisplayName()); + } + } + correlationTypeComboBox.setSelectedIndex(0); + + correlationTypeComboBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + updateSearchButton(); + } + }); + + /* + * Create listener for text input. + */ + correlationValueTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + updateSearchButton(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + updateSearchButton(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updateSearchButton(); + } + }); + + updateCorrelationValueTextFieldPrompt(); + } + + @Messages({ + "OtherCasesSearchDialog.correlationValueTextField.filesExample=Example: \"f0e1d2c3b4a5968778695a4b3c2d1e0f\"" + }) + /** + * Update the text prompt of the name text field based on the input type + * selection. + */ + private void updateCorrelationValueTextFieldPrompt() { + /** + * Add text prompt to the text field. + */ + String text = Bundle.OtherCasesSearchDialog_correlationValueTextField_filesExample(); + correlationValueTextFieldPrompt = new TextPrompt(text, correlationValueTextField); + + /** + * Sets the foreground color and transparency of the text prompt. + */ + correlationValueTextFieldPrompt.setForeground(Color.LIGHT_GRAY); + correlationValueTextFieldPrompt.changeAlpha(0.9f); // Mostly opaque + + validate(); + repaint(); + } + + /** + * Enable or disable the Search button depending on whether or not text has + * been provided for the correlation property value. + */ + private void updateSearchButton() { + searchButton.setEnabled(correlationValueTextField.getText().isEmpty() == false); + } + + /** + * Validate the value input. + * + * @return True if the input is valid for the selected type; otherwise false. + */ + private boolean validateInputs() { + Pattern md5Pattern = Pattern.compile("^[a-fA-F0-9]{32}$"); // NON-NLS + Matcher matcher = md5Pattern.matcher(correlationValueTextField.getText().trim()); + if (matcher.find()) { + return true; + } + return false; + } + + /** + * Display the Search Other Cases dialog. + */ + public void display() { + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + setVisible(true); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel casesLabel; + private javax.swing.JComboBox correlationTypeComboBox; + private javax.swing.JLabel correlationTypeLabel; + private javax.swing.JLabel correlationValueLabel; + private javax.swing.JTextField correlationValueTextField; + private javax.swing.JLabel descriptionLabel; + private javax.swing.JLabel errorLabel; + private javax.swing.JButton searchButton; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchNode.java b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchNode.java new file mode 100755 index 0000000000..ff9c3a6d80 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/othercasessearch/OtherCasesSearchNode.java @@ -0,0 +1,47 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.othercasessearch; + +import java.util.List; +import org.openide.nodes.AbstractNode; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; + +/** + * Parent node to OtherCasesSearchChildren. + */ +class OtherCasesSearchNode extends AbstractNode { + + /** + * Create an instance of OtherCasesSearchNode. + * + * @param keys The list of CorrelationAttributeInstances. + */ + OtherCasesSearchNode(List keys) { + super(new OtherCasesSearchChildren(true, keys)); + } + + @Messages({ + "OtherCasesSearchNode.getName.text=Other Cases Search" + }) + @Override + public String getName() { + return Bundle.OtherCasesSearchNode_getName_text(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 618cba2a38..8ed4c46111 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -50,8 +50,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.report.ReportWizardAction") @ActionRegistration(displayName = "#CTL_ReportWizardAction", lazy = false) @ActionReferences(value = { - @ActionReference(path = "Menu/Tools", position = 103), - @ActionReference(path = "Toolbars/Case", position = 103)}) + @ActionReference(path = "Menu/Tools", position = 105), + @ActionReference(path = "Toolbars/Case", position = 105)}) public final class ReportWizardAction extends CallableSystemAction implements Presenter.Toolbar, ActionListener { private final JButton toolbarButton = new JButton(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java index 45563a4f4b..56f13e172a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboardOpenAction") -@ActionReference(path = "Menu/Tools", position = 104) +@ActionReference(path = "Menu/Tools", position = 106) @ActionRegistration(displayName = "#CTL_AutoIngestDashboardOpenAction", lazy = false) @Messages({"CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard"}) public final class AutoIngestDashboardOpenAction extends CallableSystemAction {