diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 3cba837bfb..659a5a04e9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -89,11 +89,10 @@ import org.sleuthkit.datamodel.TskData; public class DataContentViewerOtherCases extends JPanel implements DataContentViewer { private static final long serialVersionUID = -1L; - + private static final String UUID_PLACEHOLDER_STRING = "NoCorrelationAttributeInstance"; private static final Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName()); private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noArtifacts()); private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noResultsFound()); - private final OtherOccurrencesFilesTableModel filesTableModel; private final OtherOccurrencesCasesTableModel casesTableModel; private final OtherOccurrencesDataSourcesTableModel dataSourcesTableModel; @@ -129,6 +128,16 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi reset(); } + /** + * Get a placeholder string to use in place of case uuid when it isn't + * available + * + * @return UUID_PLACEHOLDER_STRING + */ + static String getPlaceholderUUID() { + return UUID_PLACEHOLDER_STRING; + } + private void customizeComponents() { ActionListener actList = (ActionEvent e) -> { JMenuItem jmi = (JMenuItem) e.getSource(); @@ -837,7 +846,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi * selection */ private void updateOnDataSourceSelection() { - int[] selectedCaseIndexes = casesTable.getSelectedRows(); int[] selectedDataSources = dataSourcesTable.getSelectedRows(); filesTableModel.clearTable(); for (CorrelationAttributeInstance corAttr : correlationAttributes) { @@ -846,23 +854,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // get correlation and reference set instances from DB correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { - for (int selectedCaseRow : selectedCaseIndexes) { - for (int selectedDataSourceRow : selectedDataSources) { - try { - if (nodeData.isCentralRepoNode()) { - if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)) != null - && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) - && dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { - filesTableModel.addNodeData(nodeData); - } - } else { - if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { - filesTableModel.addNodeData(nodeData); - } + for (int selectedDataSourceRow : selectedDataSources) { + try { + if (nodeData.isCentralRepoNode()) { + if (dataSourcesTableModel.getCaseUUIDForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) + && dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { + filesTableModel.addNodeData(nodeData); + } + } else { + if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { + filesTableModel.addNodeData(nodeData); } - } catch (EamDbException ex) { - LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); } + } catch (EamDbException ex) { + LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); } } } @@ -928,7 +933,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi if (EamDb.isEnabled()) { CorrelationCase partialCase; partialCase = casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(caseTableRowIdx)); - if (partialCase == null){ + if (partialCase == null) { return ""; } return EamDb.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); @@ -1141,6 +1146,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private final String dataSourceID; private final String filePath; private final String type; + private final String caseUUID; UniquePathKey(OtherOccurrenceNodeInstanceData nodeData) { super(); @@ -1151,6 +1157,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi filePath = null; } type = nodeData.getType(); + String tempCaseUUID; + try { + tempCaseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); + } catch (EamDbException ignored) { + tempCaseUUID = UUID_PLACEHOLDER_STRING; + //place holder value will be used since correlation attribute was unavailble + } + caseUUID = tempCaseUUID; } @Override @@ -1159,14 +1173,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi UniquePathKey otherKey = (UniquePathKey) (other); return (Objects.equals(otherKey.getDataSourceID(), this.getDataSourceID()) && Objects.equals(otherKey.getFilePath(), this.getFilePath()) - && Objects.equals(otherKey.getType(), this.getType())); + && Objects.equals(otherKey.getType(), this.getType()) + && Objects.equals(otherKey.getCaseUUID(), this.getCaseUUID())); } return false; } @Override public int hashCode() { - return Objects.hash(getDataSourceID(), getFilePath(), getType()); + return Objects.hash(getDataSourceID(), getFilePath(), getType(), getCaseUUID()); } /** @@ -1195,5 +1210,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi String getDataSourceID() { return dataSourceID; } + + /** + * Get the case uuid for the UniquePathKey + * + * @return the case UUID + */ + String getCaseUUID() { + return caseUUID; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java index 30b44ea5a6..240826b8b7 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.Set; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; /** * Model for cells in the data sources section of the other occurrences data @@ -86,6 +87,25 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getDeviceId(); } + /** + * Get the case uuid of the case the data source shown at the specified row + * index exists in + * + * @param rowIdx the row index of the data source you want the case uuid for + * + * @return the case uuid of the case the specified data source exists in or + * an empty string if a device id could not be retrieved + */ + String getCaseUUIDForRow(int rowIdx) { + //if anything would prevent this from working we will return an empty string + if (dataSourceSet.isEmpty() || rowIdx < 0 + || rowIdx >= dataSourceSet.size() + || !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) { + return ""; + } + return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getCaseUUID(); + } + /** * Get the case name of the data source shown at the specified row index * @@ -115,7 +135,15 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { * @param newNodeData data to add to the table */ void addNodeData(OtherOccurrenceNodeData newNodeData) { - dataSourceSet.add(new DataSourceColumnItem((OtherOccurrenceNodeInstanceData) newNodeData)); + OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) newNodeData; + String caseUUID; + try { + caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); + } catch (EamDbException ignored) { + caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); + //place holder value will be used since correlation attribute was unavailble + } + dataSourceSet.add(new DataSourceColumnItem(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName(), caseUUID)); fireTableDataChanged(); } @@ -136,30 +164,23 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { private final String caseName; private final String deviceId; private final String dataSourceName; - - /** - * Create a DataSourceColumnItem given an - * OtherOccurrenceNodeInstanceData object - * - * @param nodeData the OtherOccurrenceNodeInstanceData which contains - * the data source information - */ - private DataSourceColumnItem(OtherOccurrenceNodeInstanceData nodeData) { - this(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName()); - } + private final String caseUUID; /** * Create a DataSourceColumnItem given a case name, device id, and data * source name * * @param caseName the name of the case the data source exists in - * @param deviceId the name of the device id for the data source + * @param deviceId the device id for the data source * @param dataSourceName the name of the data source + * @param caseUUID the case uuid for the case the data source + * exists in */ - private DataSourceColumnItem(String caseName, String deviceId, String dataSourceName) { + private DataSourceColumnItem(String caseName, String deviceId, String dataSourceName, String caseUUID) { this.caseName = caseName; this.deviceId = deviceId; this.dataSourceName = dataSourceName; + this.caseUUID = caseUUID; } /** @@ -189,17 +210,27 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { return caseName; } + /** + * Get the case uuid of the case the data source exists in + * + * @return the case UUID + */ + private String getCaseUUID() { + return caseUUID; + } + @Override public boolean equals(Object other) { return other instanceof DataSourceColumnItem && caseName.equals(((DataSourceColumnItem) other).getCaseName()) && dataSourceName.equals(((DataSourceColumnItem) other).getDataSourceName()) - && deviceId.equals(((DataSourceColumnItem) other).getDeviceId()); + && deviceId.equals(((DataSourceColumnItem) other).getDeviceId()) + && caseUUID.equals(((DataSourceColumnItem) other).getCaseUUID()); } @Override public int hashCode() { - return Objects.hash(caseName, deviceId, dataSourceName); + return Objects.hash(caseName, deviceId, dataSourceName, caseUUID); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java index dd797a19d3..f759ecfd2a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java @@ -25,6 +25,7 @@ import java.util.Map; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; import org.apache.commons.io.FilenameUtils; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; /** * Model for cells in the files section of the other occurrences data content @@ -114,7 +115,14 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { } private String createNodeKey(OtherOccurrenceNodeInstanceData nodeData) { - return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath(); + String caseUUID; + try { + caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); + } catch (EamDbException ignored) { + caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); + //place holder value will be used since correlation attribute was unavailble + } + return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath() + caseUUID; } /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java index b7359a9088..25d7bf5519 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java @@ -72,7 +72,7 @@ final class AccountDeviceInstanceNode extends AbstractNode { } @Override - @NbBundle.Messages(value = {"AccountNode.device=Device", "AccountNode.accountName=Account", "AccountNode.accountType=Type", "AccountNode.messageCount=Messages"}) + @NbBundle.Messages(value = {"AccountNode.device=Device", "AccountNode.accountName=Account", "AccountNode.accountType=Type", "AccountNode.messageCount=Items"}) protected Sheet createSheet() { Sheet sheet = super.createSheet(); Sheet.Set properties = sheet.get(Sheet.PROPERTIES); diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED index 7a3ef27eec..b314af1cb9 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED @@ -1,7 +1,7 @@ AccountNode.accountName=Account AccountNode.accountType=Type AccountNode.device=Device -AccountNode.messageCount=Messages +AccountNode.messageCount=Items applyText=Apply CTL_OpenCVTAction=Communications CVTTopComponent.name=\ Communications Visualization diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java index 97a645da3c..8747d49795 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java @@ -507,7 +507,9 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private void bnTestDatabaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnTestDatabaseActionPerformed lbTestDatabase.setIcon(null); + lbTestDatabase.paintImmediately(lbTestDatabase.getVisibleRect()); lbTestDbWarning.setText(""); + lbTestDbWarning.paintImmediately(lbTestDbWarning.getVisibleRect()); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { CaseDbConnectionInfo info = new CaseDbConnectionInfo( @@ -530,7 +532,9 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private void bnTestMessageServiceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnTestMessageServiceActionPerformed lbTestMessageService.setIcon(null); + lbTestMessageService.paintImmediately(lbTestMessageService.getVisibleRect()); lbTestMessageWarning.setText(""); + lbTestMessageWarning.paintImmediately(lbTestMessageWarning.getVisibleRect()); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); int port; @@ -561,7 +565,9 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private void bnTestSolrActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnTestSolrActionPerformed lbTestSolr.setIcon(null); + lbTestSolr.paintImmediately(lbTestSolr.getVisibleRect()); lbTestSolrWarning.setText(""); + lbTestSolrWarning.paintImmediately(lbTestSolrWarning.getVisibleRect()); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties index 11fbe567a5..e6c8663dfa 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties @@ -43,31 +43,21 @@ ConfigVisualPanel2.jCheckBox2.text=jCheckBox2 ConfigVisualPanel2.jTextField1.text=jTextField1 ConfigVisualPanel1.jRadioButton1.text=Create new configuration ConfigVisualPanel1.jRadioButton2.text=Open existing configuration -ConfigVisualPanel1.jLabel1.text_1=Configuration file: -ConfigVisualPanel1.configFileTextField.text_1= ConfigVisualPanel2.modifiedDateLabel.text=Modified Within: ConfigVisualPanel2.folderNamesLabel.text=Folder names: -ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed ConfigVisualPanel2.filenamesLabel.text=File names: -ConfigVisualPanel2.extensionsTextField.text= ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console if rule matches ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file if it matches a rule ConfigVisualPanel2.deleteRuleButton.text=Delete Rule -ConfigVisualPanel2.descriptionEditTextField.text= ConfigVisualPanel2.editRuleButton.text=Edit Rule ConfigVisualPanel2.newRuleButton.text=New Rule -ConfigVisualPanel2.ruleNameEditTextField.text= -ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption programs are found ConfigVisualPanel2.fullPathsLabel.text=Full paths: ConfigVisualPanel2.daysIncludedLabel.text=day(s) -ConfigVisualPanel2.daysIncludedTextField.text= -ConfigVisualPanel2.configFileTextField.toolTipText= -ConfigVisualPanel2.configFileTextField.text= ConfigVisualPanel2.filenamesTable.columnModel.title0= ConfigVisualPanel2.fileSizeLabel.text=File size in bytes: ConfigVisualPanel2.extensionsLabel.text=Extensions: ConfigVisualPanel2.descriptionLabel.text=Description: -ConfigVisualPanel2.ruleNameLabel.text=Rule Name: +ConfigVisualPanel2.ruleNameLabel.text=Rule name: ConfigVisualPanel2.ruleSetFileLabel.text=Configuration rule file: EditRulePanel.ruleNameLabel.text=Rule Set: EditRulePanel.descriptionTextField.text= @@ -85,45 +75,48 @@ EditRulePanel.jTable1.columnModel.title3=Title 4 EditRulePanel.jTable1.columnModel.title2=Title 3 EditRulePanel.jTable1.columnModel.title1=Title 2 EditRulePanel.shouldAlertCheckBox.actionCommand= -EditFullPathsRulePanel.ruleNameLabel.text=Rule Name: -EditFullPathsRulePanel.descriptionLabel.text=Description: -EditFullPathsRulePanel.descriptionTextField.text= +EditFullPathsRulePanel.ruleNameLabel.text=Rule name: EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches -EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule -EditFullPathsRulePanel.ruleNameTextField.text= +EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a path matches +EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a path EditFullPathsRulePanel.fullPathsLabel.text=Full paths: EditFullPathsRulePanel.fullPathsLabel.toolTipText= -EditNonFullPathsRulePanel.ruleNameTextField.text= -EditNonFullPathsRulePanel.ruleNameLabel.text=Rule Name: -EditNonFullPathsRulePanel.descriptionLabel.text=Description: -EditNonFullPathsRulePanel.descriptionTextField.text= -EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditNonFullPathsRulePanel.ruleNameLabel.text=Rule name: +EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a condition EditNonFullPathsRulePanel.daysIncludedLabel.text=day(s) -EditNonFullPathsRulePanel.modifiedDateLabel.text=Modified Within: -EditNonFullPathsRulePanel.fileSizeLabel.text=File size (bytes): -EditNonFullPathsRulePanel.folderNamesLabel.text=Folder names: -EditNonFullPathsRulePanel.filenamesLabel.text=File names: -EditNonFullPathsRulePanel.extensionsTextField.text= -EditNonFullPathsRulePanel.extensionsLabel.text=Extensions: EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a condition matches ConfigVisualPanel1.browseButton.text=Browse ConfigVisualPanel2.fullPathsTable.columnModel.title0= ConfigVisualPanel2.folderNamesTable.columnModel.title0= ConfigVisualPanel2.shouldSaveCheckBox.toolTipText= -NewRuleSetPanel.chooseLabel.text=Choose the type of rule -EditNonFullPathsRulePanel.minSizeLabel.text=Minimum: -EditNonFullPathsRulePanel.maxSizeLabel.text=Maximum: -EditNonFullPathsRulePanel.minSizeTextField.text= -EditNonFullPathsRulePanel.maxSizeTextField.text= -ConfigVisualPanel2.maxSizeTextField.text= ConfigVisualPanel2.maxSizeLabel.text=Maximum: -ConfigVisualPanel2.minSizeTextField.text= ConfigVisualPanel2.minSizeLabel.text=Minimum: EditNonFullPathsRulePanel.minDaysTextField.text=jFormattedTextField1 ConfigVisualPanel1.browseButton.toolTipText= -EditNonFullPathsRulePanel.extensionsRadioButton.text= -EditNonFullPathsRulePanel.filenamesRadioButton.text= -EditNonFullPathsRulePanel.extensionsRadioButton.toolTipText=Extensions and File names are mutually exclusive -EditNonFullPathsRulePanel.filenamesRadioButton.toolTipText=Extensions and File names are mutually exclusive +EditNonFullPathsRulePanel.userFolderNote.text=Starting a folder name with the token [USER_FOLDER] will allow matches of all user folders in the file system. +EditNonFullPathsRulePanel.modifiedWithinCheckbox.text=Modified within: +EditNonFullPathsRulePanel.folderNamesCheckbox.text=Folder names: +EditNonFullPathsRulePanel.fileNamesCheckbox.text=File names: +EditNonFullPathsRulePanel.extensionsCheckbox.text=Extensions: +EditNonFullPathsRulePanel.filenamesScrollPane.toolTipText= +EditNonFullPathsRulePanel.maxSizeCheckbox.text=Maximum size: +EditNonFullPathsRulePanel.minSizeCheckbox.text=Minimum size: +NewRulePanel.chooseLabel.text=Choose the type of rule +ConfigVisualPanel1.configureDriveRadioButton.text_1=Configure selected external drive: +ConfigVisualPanel1.configureFolderRadioButton.text_1=Configure in a folder: +ConfigVisualPanel1.descriptionTextArea.text=Select a location for the Logical Imager. This location will contain the imaging program and a configuration file. If that location already contains a configuration file, it will be loaded to edit. Imaging results will be saved to this location, so ensure it has enough free space. +ConfigVisualPanel1.refreshButton.text=Refresh +ConfigVisualPanel3.saveButton.text=Save +ConfigVisualPanel3.configLabel.text=Logical Imager config file save status: +ConfigVisualPanel3.executableLabel.text=Logical Imager executable save status: +ConfigVisualPanel3.executableStatusLabel.text= +EditFullPathsRulePanel.jLabel1.text=Description (Optional): +EditNonFullPathsRulePanel.jLabel2.text=Folder name matches are case insensitive and occur anywhere in a path. +EditNonFullPathsRulePanel.descriptionLabel.text=Description (Optional): +EditNonFullPathsRulePanel.jLabel1.text=If file is found: +EditFullPathsRulePanel.jLabel2.text=If file is found: +ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption programs are found +ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed +EditNonFullPathsRulePanel.fileNamesInfoLabel.text=File names are case insensitive. +EditNonFullPathsRulePanel.extensionsInfoLabel.text=Extensions are case insensitive. diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED index 4d3e51b1c6..40890b33de 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED @@ -1,4 +1,3 @@ -ConfigureLogicalImager.title=Configure Logical Imager ConfigureLogicalImagerDialog.loadButton.text=Load ConfigureLogicalImagerDialog.newButton.text=New ConfigureLogicalImagerDialog.title=Configure Logical Imager @@ -29,22 +28,32 @@ ConfigVisualPanel1.configFileIsEmpty=Configuration file {0} is empty ConfigVisualPanel1.configurationError=Configuration error ConfigVisualPanel1.fileNameExtensionFilter=Configuration JSON File ConfigVisualPanel1.invalidConfigJson=Invalid config JSON: -ConfigVisualPanel1.selectConfigurationFile=Select configuration file +ConfigVisualPanel1.messageLabel.noExternalDriveFound=No drive found +ConfigVisualPanel1.selectConfigurationFile=Select location ConfigVisualPanel2.cancel=Cancel ConfigVisualPanel2.deleteRuleSet=Delete rule ConfigVisualPanel2.deleteRuleSetConfirmation=Delete rule confirmation -ConfigVisualPanel2.editConfiguration=Edit configuration +ConfigVisualPanel2.editConfiguration=Configure imager ConfigVisualPanel2.editRuleError=Edit rule error -ConfigVisualPanel2.editRuleSet=Edit rule +ConfigVisualPanel2.editRuleSet=Edit Rule +ConfigVisualPanel2.newRule.name=New Rule ConfigVisualPanel2.ok=OK ConfigVisualPanel2.rulesTable.columnModel.title0=Rule Name ConfigVisualPanel2.rulesTable.columnModel.title1=Description +ConfigVisualPanel3.copyStatus.error=Unable to save file +ConfigVisualPanel3.copyStatus.notSaved=File has not been saved +ConfigVisualPanel3.copyStatus.saved=Saved +ConfigVisualPanel3.copyStatus.savingInProgress=Saving file, please wait +# {0} - configurationLocation +ConfigVisualPanel3.description.text=Press Save to write the imaging tool and configuration file to the destination.\nDestination: {0} # {0} - configFilename -ConfigWizardPanel2.failedToSaveConfigMsg=Failed to save configuration file: {0} -ConfigWizardPanel2.failedToSaveExeMsg=Failed to save tsk_logical_imager.exe file +ConfigVisualPanel3.failedToSaveConfigMsg=Failed to save configuration file: {0} +ConfigVisualPanel3.failedToSaveExeMsg=Failed to save tsk_logical_imager.exe file # {0} - reason -ConfigWizardPanel2.reason=\nReason: -CTL_ConfigureLogicalImager=Create Logical Imager +ConfigVisualPanel3.reason=\nReason: +ConfigVisualPanel3.saveConfigurationFile=Save imager +CreateLogicalImagerAction.title=Create Logical Imager +CTL_CreateLogicalImagerAction=Create Logical Imager EditFullPathsRulePanel.example=Example: EditFullPathsRulePanel.fullPaths=Full paths EditNonFullPathsRulePanel.emptyExtensionException=Extensions cannot have an empty entry @@ -56,16 +65,22 @@ EditNonFullPathsRulePanel.maxFileSizeMustBeNumberException=Maximum file size mus EditNonFullPathsRulePanel.maxFileSizeNotPositiveException=Maximum file size must be a positive # {0} - maxFileSize # {1} - minFileSize -EditNonFullPathsRulePanel.maxFileSizeSmallerThanMinException=Maximum file size: {0} must be bigger than minimum file size: {1} +EditNonFullPathsRulePanel.maxFileSizeSmallerThanMinException=Maximum file size: {0} bytes must be bigger than minimum file size: {1} bytes # {0} - message EditNonFullPathsRulePanel.minFileSizeMustBeNumberException=Minimum file size must be a number: {0} EditNonFullPathsRulePanel.minFileSizeNotPositiveException=Minimum file size must be a positive # {0} - message EditNonFullPathsRulePanel.modifiedDaysMustBeNumberException=Modified days must be a number: {0} EditNonFullPathsRulePanel.modifiedDaysNotPositiveException=Modified days must be a positive -EditNonFullPathsRulePanel.note=NOTE: A special [USER_FOLDER] token at the the start of a folder name to allow matches of all user folders in the file system. +EditNonFullPathsRulePanel.units.bytes=Bytes +EditNonFullPathsRulePanel.units.gigabytes=Gigabytes +EditNonFullPathsRulePanel.units.kilobytes=Kilobytes +EditNonFullPathsRulePanel.units.megabytes=MegaBytes # {0} - fieldName EditRulePanel.blankLineException={0} cannot have a blank line +EditRulePanel.emptyRuleName.message=Rule name cannot be empty +# {0} - ruleName +EditRulePanel.reservedRuleName.message=Rule name "{0}" is reserved for use with a predefined rule EditRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches EditRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule EditRulePanel.fullPathsLabel.text=Full paths: @@ -87,31 +102,21 @@ ConfigVisualPanel2.jCheckBox2.text=jCheckBox2 ConfigVisualPanel2.jTextField1.text=jTextField1 ConfigVisualPanel1.jRadioButton1.text=Create new configuration ConfigVisualPanel1.jRadioButton2.text=Open existing configuration -ConfigVisualPanel1.jLabel1.text_1=Configuration file: -ConfigVisualPanel1.configFileTextField.text_1= ConfigVisualPanel2.modifiedDateLabel.text=Modified Within: ConfigVisualPanel2.folderNamesLabel.text=Folder names: -ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed ConfigVisualPanel2.filenamesLabel.text=File names: -ConfigVisualPanel2.extensionsTextField.text= ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console if rule matches ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file if it matches a rule ConfigVisualPanel2.deleteRuleButton.text=Delete Rule -ConfigVisualPanel2.descriptionEditTextField.text= ConfigVisualPanel2.editRuleButton.text=Edit Rule ConfigVisualPanel2.newRuleButton.text=New Rule -ConfigVisualPanel2.ruleNameEditTextField.text= -ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption programs are found ConfigVisualPanel2.fullPathsLabel.text=Full paths: ConfigVisualPanel2.daysIncludedLabel.text=day(s) -ConfigVisualPanel2.daysIncludedTextField.text= -ConfigVisualPanel2.configFileTextField.toolTipText= -ConfigVisualPanel2.configFileTextField.text= ConfigVisualPanel2.filenamesTable.columnModel.title0= ConfigVisualPanel2.fileSizeLabel.text=File size in bytes: ConfigVisualPanel2.extensionsLabel.text=Extensions: ConfigVisualPanel2.descriptionLabel.text=Description: -ConfigVisualPanel2.ruleNameLabel.text=Rule Name: +ConfigVisualPanel2.ruleNameLabel.text=Rule name: ConfigVisualPanel2.ruleSetFileLabel.text=Configuration rule file: EditRulePanel.ruleNameLabel.text=Rule Set: EditRulePanel.descriptionTextField.text= @@ -129,52 +134,58 @@ EditRulePanel.jTable1.columnModel.title3=Title 4 EditRulePanel.jTable1.columnModel.title2=Title 3 EditRulePanel.jTable1.columnModel.title1=Title 2 EditRulePanel.shouldAlertCheckBox.actionCommand= -EditFullPathsRulePanel.ruleNameLabel.text=Rule Name: -EditFullPathsRulePanel.descriptionLabel.text=Description: -EditFullPathsRulePanel.descriptionTextField.text= +EditFullPathsRulePanel.ruleNameLabel.text=Rule name: EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches -EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule -EditFullPathsRulePanel.ruleNameTextField.text= +EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a path matches +EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a path EditFullPathsRulePanel.fullPathsLabel.text=Full paths: EditFullPathsRulePanel.fullPathsLabel.toolTipText= -EditNonFullPathsRulePanel.ruleNameTextField.text= -EditNonFullPathsRulePanel.ruleNameLabel.text=Rule Name: -EditNonFullPathsRulePanel.descriptionLabel.text=Description: -EditNonFullPathsRulePanel.descriptionTextField.text= -EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a rule +EditNonFullPathsRulePanel.ruleNameLabel.text=Rule name: +EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a condition EditNonFullPathsRulePanel.daysIncludedLabel.text=day(s) -EditNonFullPathsRulePanel.modifiedDateLabel.text=Modified Within: -EditNonFullPathsRulePanel.fileSizeLabel.text=File size (bytes): -EditNonFullPathsRulePanel.folderNamesLabel.text=Folder names: -EditNonFullPathsRulePanel.filenamesLabel.text=File names: -EditNonFullPathsRulePanel.extensionsTextField.text= -EditNonFullPathsRulePanel.extensionsLabel.text=Extensions: EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if rule matches +EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a condition matches ConfigVisualPanel1.browseButton.text=Browse ConfigVisualPanel2.fullPathsTable.columnModel.title0= ConfigVisualPanel2.folderNamesTable.columnModel.title0= ConfigVisualPanel2.shouldSaveCheckBox.toolTipText= -EditRulePanel.validateRuleNameExceptionMsg=Rule name cannot be empty +ConfigVisualPanel2.maxSizeLabel.text=Maximum: +ConfigVisualPanel2.minSizeLabel.text=Minimum: +EditNonFullPathsRulePanel.minDaysTextField.text=jFormattedTextField1 +ConfigVisualPanel1.browseButton.toolTipText= +EditNonFullPathsRulePanel.userFolderNote.text=Starting a folder name with the token [USER_FOLDER] will allow matches of all user folders in the file system. +EditNonFullPathsRulePanel.modifiedWithinCheckbox.text=Modified within: +EditNonFullPathsRulePanel.folderNamesCheckbox.text=Folder names: +EditNonFullPathsRulePanel.fileNamesCheckbox.text=File names: +EditNonFullPathsRulePanel.extensionsCheckbox.text=Extensions: +EditNonFullPathsRulePanel.filenamesScrollPane.toolTipText= +EditNonFullPathsRulePanel.maxSizeCheckbox.text=Maximum size: +EditNonFullPathsRulePanel.minSizeCheckbox.text=Minimum size: EncryptionProgramsRule.encryptionProgramsRuleDescription=Find encryption programs EncryptionProgramsRule.encryptionProgramsRuleName=Encryption Programs LogicalImagerConfigDeserializer.fullPathsException=A rule with full-paths cannot have other rule definitions LogicalImagerConfigDeserializer.missingRuleSetException=Missing rule-set # {0} - key LogicalImagerConfigDeserializer.unsupportedKeyException=Unsupported key: {0} -NewRuleSetPanel.chooseLabel.text=Choose the type of rule -EditNonFullPathsRulePanel.minSizeLabel.text=Minimum: -EditNonFullPathsRulePanel.maxSizeLabel.text=Maximum: -EditNonFullPathsRulePanel.minSizeTextField.text= -EditNonFullPathsRulePanel.maxSizeTextField.text= -ConfigVisualPanel2.maxSizeTextField.text= -ConfigVisualPanel2.maxSizeLabel.text=Maximum: -ConfigVisualPanel2.minSizeTextField.text= -ConfigVisualPanel2.minSizeLabel.text=Minimum: -EditNonFullPathsRulePanel.minDaysTextField.text=jFormattedTextField1 -ConfigVisualPanel1.browseButton.toolTipText= -EditNonFullPathsRulePanel.extensionsRadioButton.text= -EditNonFullPathsRulePanel.filenamesRadioButton.text= -EditNonFullPathsRulePanel.extensionsRadioButton.toolTipText=Extensions and File names are mutually exclusive -EditNonFullPathsRulePanel.filenamesRadioButton.toolTipText=Extensions and File names are mutually exclusive +NewRulePanel.chooseLabel.text=Choose the type of rule +ConfigVisualPanel1.configureDriveRadioButton.text_1=Configure selected external drive: +ConfigVisualPanel1.configureFolderRadioButton.text_1=Configure in a folder: +ConfigVisualPanel1.descriptionTextArea.text=Select a location for the Logical Imager. This location will contain the imaging program and a configuration file. If that location already contains a configuration file, it will be loaded to edit. Imaging results will be saved to this location, so ensure it has enough free space. +ConfigVisualPanel1.refreshButton.text=Refresh +ConfigVisualPanel3.saveButton.text=Save +ConfigVisualPanel3.configLabel.text=Logical Imager config file save status: +ConfigVisualPanel3.executableLabel.text=Logical Imager executable save status: +ConfigVisualPanel3.executableStatusLabel.text= +EditFullPathsRulePanel.jLabel1.text=Description (Optional): +EditNonFullPathsRulePanel.jLabel2.text=Folder name matches are case insensitive and occur anywhere in a path. +EditNonFullPathsRulePanel.descriptionLabel.text=Description (Optional): +EditNonFullPathsRulePanel.jLabel1.text=If file is found: +EditFullPathsRulePanel.jLabel2.text=If file is found: +ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption programs are found +ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed +EditNonFullPathsRulePanel.fileNamesInfoLabel.text=File names are case insensitive. +EditNonFullPathsRulePanel.extensionsInfoLabel.text=Extensions are case insensitive. +NewRuleSetPanel.attributeRule.description=Search for files based on one or more attributes or metadata fields. +NewRuleSetPanel.attributeRule.name=Attribute +NewRuleSetPanel.fullPathRule.description=Search for files based on full exact match path. +NewRuleSetPanel.fullPathRule.name=Full Path diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.form index 6840aacf9f..9cdd08be3f 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.form @@ -2,7 +2,7 @@
- + @@ -20,14 +20,31 @@ - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + @@ -35,32 +52,37 @@ - - - - - + + + + + + + + + + - + + + + + + + + + + - - - - - - - - - - + @@ -71,10 +93,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java index 1b4bee1d36..1be96b52a2 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,14 +28,25 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.filechooser.FileSystemView; +import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.logicalimager.dsp.DriveListUtils; /** * Configuration Visual Panel 1 @@ -43,9 +54,11 @@ import org.openide.util.NbBundle; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ConfigVisualPanel1 extends JPanel { - private LogicalImagerConfig config; + private static final Logger logger = Logger.getLogger(ConfigVisualPanel1.class.getName()); + private static final long serialVersionUID = 1L; + private static final String DEFAULT_CONFIG_FILE_NAME = "logical-imager-config.json"; + private static final String UPDATE_UI_EVENT_NAME = "UPDATE_UI"; private String configFilename; - private boolean newFile = true; /** * Creates new form ConfigVisualPanel1 @@ -53,10 +66,14 @@ final class ConfigVisualPanel1 extends JPanel { ConfigVisualPanel1() { initComponents(); configFileTextField.getDocument().addDocumentListener(new MyDocumentListener(this)); + refreshDriveList(); + SwingUtilities.invokeLater(() -> { + updateControls(); + }); } @NbBundle.Messages({ - "ConfigVisualPanel1.selectConfigurationFile=Select configuration file" + "ConfigVisualPanel1.selectConfigurationFile=Select location" }) @Override public String getName() { @@ -71,47 +88,129 @@ final class ConfigVisualPanel1 extends JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - buttonGroup1 = new javax.swing.ButtonGroup(); - jLabel1 = new javax.swing.JLabel(); + configurationLocationButtonGroup = new javax.swing.ButtonGroup(); configFileTextField = new javax.swing.JTextField(); browseButton = new javax.swing.JButton(); - - org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.jLabel1.text_1")); // NOI18N + descriptionScrollPane = new javax.swing.JScrollPane(); + descriptionTextArea = new javax.swing.JTextArea(); + configureDriveRadioButton = new javax.swing.JRadioButton(); + configureFolderRadioButton = new javax.swing.JRadioButton(); + driveListScrollPane = new javax.swing.JScrollPane(); + driveList = new javax.swing.JList<>(); + refreshButton = new javax.swing.JButton(); + warningLabel = new javax.swing.JLabel(); configFileTextField.setEditable(false); - configFileTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.configFileTextField.text_1")); // NOI18N + configFileTextField.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.browseButton.text")); // NOI18N browseButton.setToolTipText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.browseButton.toolTipText")); // NOI18N + browseButton.setEnabled(false); browseButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { browseButtonActionPerformed(evt); } }); + descriptionTextArea.setEditable(false); + descriptionTextArea.setBackground(new java.awt.Color(240, 240, 240)); + descriptionTextArea.setColumns(20); + descriptionTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + descriptionTextArea.setLineWrap(true); + descriptionTextArea.setRows(4); + descriptionTextArea.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.descriptionTextArea.text")); // NOI18N + descriptionTextArea.setWrapStyleWord(true); + descriptionTextArea.setEnabled(false); + descriptionScrollPane.setViewportView(descriptionTextArea); + + configurationLocationButtonGroup.add(configureDriveRadioButton); + configureDriveRadioButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(configureDriveRadioButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.configureDriveRadioButton.text_1")); // NOI18N + configureDriveRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + configureDriveRadioButtonActionPerformed(evt); + } + }); + + configurationLocationButtonGroup.add(configureFolderRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(configureFolderRadioButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.configureFolderRadioButton.text_1")); // NOI18N + configureFolderRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + configureFolderRadioButtonActionPerformed(evt); + } + }); + + driveList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + driveList.setEnabled(false); + driveList.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseReleased(java.awt.event.MouseEvent evt) { + driveListMouseReleasedSelection(evt); + } + }); + driveList.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + driveListKeyReleasedSelection(evt); + } + }); + driveListScrollPane.setViewportView(driveList); + + org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel1.class, "ConfigVisualPanel1.refreshButton.text")); // NOI18N + refreshButton.setEnabled(false); + refreshButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + refreshButtonActionPerformed(evt); + } + }); + + warningLabel.setForeground(new java.awt.Color(255, 0, 0)); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(38, 38, 38) + .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel1) - .addComponent(configFileTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 279, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseButton) + .addComponent(warningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(descriptionScrollPane) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(driveListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 437, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(configFileTextField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 437, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 87, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 87, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(configureDriveRadioButton) + .addComponent(configureFolderRadioButton)) + .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(116, 116, 116) - .addComponent(jLabel1) + .addContainerGap() + .addComponent(descriptionScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(13, 13, 13) + .addComponent(configureDriveRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(driveListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(refreshButton) + .addGap(0, 95, Short.MAX_VALUE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(configureFolderRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(configFileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(browseButton)) - .addContainerGap(141, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(warningLabel) + .addContainerGap(60, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -119,23 +218,99 @@ final class ConfigVisualPanel1 extends JPanel { "ConfigVisualPanel1.chooseFileTitle=Select a Logical Imager configuration" }) private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed - chooseFile(Bundle.ConfigVisualPanel1_chooseFileTitle()); + chooseFile(Bundle.ConfigVisualPanel1_chooseFileTitle()); }//GEN-LAST:event_browseButtonActionPerformed + private void configureFolderRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_configureFolderRadioButtonActionPerformed + updateControls(); + }//GEN-LAST:event_configureFolderRadioButtonActionPerformed + + private void configureDriveRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_configureDriveRadioButtonActionPerformed + updateControls(); + }//GEN-LAST:event_configureDriveRadioButtonActionPerformed + + private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed + refreshDriveList(); + }//GEN-LAST:event_refreshButtonActionPerformed + + private void driveListKeyReleasedSelection(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_driveListKeyReleasedSelection + firePropertyChange(UPDATE_UI_EVENT_NAME, false, true); // NON-NLS + }//GEN-LAST:event_driveListKeyReleasedSelection + + private void driveListMouseReleasedSelection(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_driveListMouseReleasedSelection + firePropertyChange(UPDATE_UI_EVENT_NAME, false, true); // NON-NLS + }//GEN-LAST:event_driveListMouseReleasedSelection + + /** + * Refresh the list of local drives on the current machine + */ + @Messages({"ConfigVisualPanel1.messageLabel.noExternalDriveFound=No drive found"}) + private void refreshDriveList() { + List listData = new ArrayList<>(); + File[] roots = File.listRoots(); + int firstRemovableDrive = -1; + int i = 0; + for (File root : roots) { + String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root); + long spaceInBytes = root.getTotalSpace(); + String sizeWithUnit = DriveListUtils.humanReadableByteCount(spaceInBytes, false); + listData.add(root + " (" + description + ") (" + sizeWithUnit + ")"); + if (firstRemovableDrive == -1) { + try { + FileStore fileStore = Files.getFileStore(root.toPath()); + if ((boolean) fileStore.getAttribute("volume:isRemovable")) { //NON-NLS + firstRemovableDrive = i; + } + } catch (IOException ignored) { + //unable to get this removable drive for default selection will try and select next removable drive by default + logger.log(Level.INFO, "Unable to select first removable drive found", ignored); + } + } + i++; + } + driveList.setListData(listData.toArray(new String[listData.size()])); + if (!listData.isEmpty()) { + // auto-select the first external drive, if any + driveList.setSelectedIndex(firstRemovableDrive == -1 ? 0 : firstRemovableDrive); + firePropertyChange(UPDATE_UI_EVENT_NAME, false, true); // NON-NLS + driveList.requestFocusInWindow(); + warningLabel.setText(""); + } else { + warningLabel.setText(Bundle.ConfigVisualPanel1_messageLabel_noExternalDriveFound()); + } + } + + /** + * Update which controls are enabled to reflect the current radio button + * selection. + */ + private void updateControls() { + browseButton.setEnabled(configureFolderRadioButton.isSelected()); + refreshButton.setEnabled(configureDriveRadioButton.isSelected()); + driveList.setEnabled(configureDriveRadioButton.isSelected()); + driveListScrollPane.setEnabled(configureDriveRadioButton.isSelected()); + firePropertyChange(UPDATE_UI_EVENT_NAME, false, true); // NON-NLS + } + + /** + * Open a file chooser to allow users to choose a json configuration file in + * a folder. + * + * @param title the dialog title + */ @NbBundle.Messages({ "ConfigVisualPanel1.fileNameExtensionFilter=Configuration JSON File", "ConfigVisualPanel1.invalidConfigJson=Invalid config JSON: ", - "ConfigVisualPanel1.configurationError=Configuration error", - }) + "ConfigVisualPanel1.configurationError=Configuration error",}) private void chooseFile(String title) { final String jsonExt = ".json"; // NON-NLS JFileChooser fileChooser = new JFileChooser(); fileChooser.setDialogTitle(title); fileChooser.setDragEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - FileFilter filter = new FileNameExtensionFilter(Bundle.ConfigVisualPanel1_fileNameExtensionFilter(), new String[] {"json"}); // NON-NLS + FileFilter filter = new FileNameExtensionFilter(Bundle.ConfigVisualPanel1_fileNameExtensionFilter(), new String[]{"json"}); // NON-NLS fileChooser.setFileFilter(filter); - fileChooser.setSelectedFile(new File("logical-imager-config.json")); // default + fileChooser.setSelectedFile(new File(DEFAULT_CONFIG_FILE_NAME)); // default fileChooser.setMultiSelectionEnabled(false); if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { String path = fileChooser.getSelectedFile().getPath(); @@ -144,12 +319,11 @@ final class ConfigVisualPanel1 extends JPanel { loadConfigFile(path); configFilename = path; configFileTextField.setText(path); - newFile = false; } catch (JsonIOException | JsonSyntaxException | IOException ex) { - JOptionPane.showMessageDialog(this, - Bundle.ConfigVisualPanel1_invalidConfigJson() + ex.getMessage() , - Bundle.ConfigVisualPanel1_configurationError(), - JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, + Bundle.ConfigVisualPanel1_invalidConfigJson() + ex.getMessage(), + Bundle.ConfigVisualPanel1_configurationError(), + JOptionPane.ERROR_MESSAGE); } } else { if (!path.endsWith(jsonExt)) { @@ -157,55 +331,117 @@ final class ConfigVisualPanel1 extends JPanel { } configFilename = path; configFileTextField.setText(path); - config = new LogicalImagerConfig(); - newFile = true; } } } - + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton browseButton; - private javax.swing.ButtonGroup buttonGroup1; private javax.swing.JTextField configFileTextField; - private javax.swing.JLabel jLabel1; + private javax.swing.ButtonGroup configurationLocationButtonGroup; + private javax.swing.JRadioButton configureDriveRadioButton; + private javax.swing.JRadioButton configureFolderRadioButton; + private javax.swing.JScrollPane descriptionScrollPane; + private javax.swing.JTextArea descriptionTextArea; + private javax.swing.JList driveList; + private javax.swing.JScrollPane driveListScrollPane; + private javax.swing.JButton refreshButton; + private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables + /** + * Load a json config file specified by the path argument. + * + * + * @param path the path of the json config to load + * + * @return the LogicalImagerConfig which contains the rules from the loaded + * config. + * + * @throws FileNotFoundException + * @throws JsonIOException + * @throws JsonSyntaxException + * @throws IOException + */ @NbBundle.Messages({ "# {0} - filename", - "ConfigVisualPanel1.configFileIsEmpty=Configuration file {0} is empty", - }) - private void loadConfigFile(String path) throws FileNotFoundException, JsonIOException, JsonSyntaxException, IOException { + "ConfigVisualPanel1.configFileIsEmpty=Configuration file {0} is empty",}) + private LogicalImagerConfig loadConfigFile(String path) throws FileNotFoundException, JsonIOException, JsonSyntaxException, IOException { try (FileInputStream is = new FileInputStream(path)) { InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting(); gsonBuilder.registerTypeAdapter(LogicalImagerConfig.class, new LogicalImagerConfigDeserializer()); Gson gson = gsonBuilder.create(); - config = gson.fromJson(reader, LogicalImagerConfig.class); - if (config == null) { + LogicalImagerConfig loadedConfig = gson.fromJson(reader, LogicalImagerConfig.class); + if (loadedConfig == null) { // This happens if the file is empty. Gson doesn't call the deserializer and doesn't throw any exception. throw new IOException(Bundle.ConfigVisualPanel1_configFileIsEmpty(path)); } + return loadedConfig; } } + /** + * Get the LogicalImagerConfig for the currently selected config file. + * + * @return the LogicalImagerConfig which contains the rules from the loaded + * config. + */ LogicalImagerConfig getConfig() { - return config; + String configFileName = getConfigPath(); + if (configFileName != null && new File(configFileName).exists()) { + try { + return loadConfigFile(configFileName); + } catch (JsonIOException | JsonSyntaxException | IOException ex) { + return new LogicalImagerConfig(); + } + } else { + return new LogicalImagerConfig(); + } } - String getConfigFilename() { - return configFilename; + /** + * Get the path of the currently selected json config file. + * + * @return the path of the currently selected config file or null if invalid + * settings are selected + */ + String getConfigPath() { + if (configureFolderRadioButton.isSelected()) { + return configFilename; + } else { + String selectedStr = driveList.getSelectedValue(); + if (selectedStr == null) { + return null; + } + return selectedStr.substring(0, 3) + DEFAULT_CONFIG_FILE_NAME; + } } - boolean isNewFile() { - return newFile; + /** + * The name of the event which signifies an update to the settings reflected + * by ConfigVisualPanel1 + * + * @return UPDATE_UI_EVENT_NAME + */ + static String getUpdateEventName() { + return UPDATE_UI_EVENT_NAME; } - void setConfigFilename(String filename) { + void setConfigFilename(String filename + ) { configFileTextField.setText(filename); } + /** + * Checks if the current panel has valid settings selected. + * + * @return true if panel has valid settings selected, false otherwise + */ boolean isPanelValid() { - return (newFile || !configFileTextField.getText().isEmpty()); + return !StringUtils.isBlank(getConfigPath()) && ((configureDriveRadioButton.isSelected() && !StringUtils.isBlank(driveList.getSelectedValue())) + || (configureFolderRadioButton.isSelected() && (!configFileTextField.getText().isEmpty()))); + } /** @@ -214,7 +450,7 @@ final class ConfigVisualPanel1 extends JPanel { private static class MyDocumentListener implements DocumentListener { private final ConfigVisualPanel1 panel; - + MyDocumentListener(ConfigVisualPanel1 aThis) { this.panel = aThis; } @@ -235,7 +471,7 @@ final class ConfigVisualPanel1 extends JPanel { } private void fireChange() { - panel.firePropertyChange("UPDATE_UI", false, true); // NON-NLS + panel.firePropertyChange(UPDATE_UI_EVENT_NAME, false, true); // NON-NLS } } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form index c0a0c8d348..9fd42600c8 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form @@ -16,98 +16,86 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - @@ -122,7 +110,7 @@ - + @@ -151,7 +139,7 @@ - + @@ -159,7 +147,7 @@ - + @@ -172,7 +160,7 @@ - + @@ -186,14 +174,14 @@ - + - - - + + + @@ -214,13 +202,10 @@ - + - - - @@ -264,9 +249,6 @@ - - - @@ -305,9 +287,6 @@ - - - @@ -324,7 +303,7 @@ - + @@ -358,7 +337,7 @@ - + @@ -396,7 +375,6 @@ - @@ -408,14 +386,13 @@ - - + @@ -460,9 +437,6 @@ - - - @@ -475,12 +449,7 @@ - - - - - - + @@ -501,7 +470,7 @@ - + @@ -576,9 +545,6 @@ - - - @@ -594,9 +560,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java index 6f71e80d54..5b2468efdc 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import javax.swing.JTable; import javax.swing.table.AbstractTableModel; import org.apache.commons.lang3.tuple.ImmutablePair; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; /** * Configuration Visual Panel 2 @@ -41,13 +42,13 @@ import org.openide.util.NbBundle; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ConfigVisualPanel2 extends JPanel { - private static final List EMPTY_LIST = new ArrayList<>(); + private static final long serialVersionUID = 1L; private String configFilename; private LogicalImagerConfig config = null; private final JButton okButton = new JButton(Bundle.ConfigVisualPanel2_ok()); private final JButton cancelButton = new JButton(Bundle.ConfigVisualPanel2_cancel()); private boolean flagEncryptionPrograms = false; - + /** * Creates new form ConfigVisualPanel2 */ @@ -59,7 +60,7 @@ final class ConfigVisualPanel2 extends JPanel { } @NbBundle.Messages({ - "ConfigVisualPanel2.editConfiguration=Edit configuration" + "ConfigVisualPanel2.editConfiguration=Configure imager" }) @Override public String getName() { @@ -75,7 +76,7 @@ final class ConfigVisualPanel2 extends JPanel { private void initComponents() { modifiedDateLabel = new javax.swing.JLabel(); - daysIncludedTextField = new javax.swing.JTextField(); + modifiedWithinTextField = new javax.swing.JTextField(); daysIncludedLabel = new javax.swing.JLabel(); fullPathsLabel = new javax.swing.JLabel(); flagEncryptionProgramsCheckBox = new javax.swing.JCheckBox(); @@ -86,13 +87,13 @@ final class ConfigVisualPanel2 extends JPanel { editRuleButton = new javax.swing.JButton(); descriptionEditTextField = new javax.swing.JTextField(); deleteRuleButton = new javax.swing.JButton(); - jScrollPane5 = new javax.swing.JScrollPane(); + fullPathsScrollPane = new javax.swing.JScrollPane(); fullPathsTable = new javax.swing.JTable(); - jScrollPane6 = new javax.swing.JScrollPane(); + filenamesScrollPane = new javax.swing.JScrollPane(); filenamesTable = new javax.swing.JTable(); shouldSaveCheckBox = new javax.swing.JCheckBox(); shouldAlertCheckBox = new javax.swing.JCheckBox(); - jScrollPane7 = new javax.swing.JScrollPane(); + folderNamesScrollPane = new javax.swing.JScrollPane(); folderNamesTable = new javax.swing.JTable(); extensionsLabel = new javax.swing.JLabel(); extensionsTextField = new javax.swing.JTextField(); @@ -100,7 +101,7 @@ final class ConfigVisualPanel2 extends JPanel { configFileTextField = new javax.swing.JTextField(); ruleSetFileLabel = new javax.swing.JLabel(); finalizeImageWriter = new javax.swing.JCheckBox(); - jScrollPane1 = new javax.swing.JScrollPane(); + rulesScrollPane = new javax.swing.JScrollPane(); rulesTable = new javax.swing.JTable(); folderNamesLabel = new javax.swing.JLabel(); fileSizeLabel = new javax.swing.JLabel(); @@ -112,12 +113,11 @@ final class ConfigVisualPanel2 extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.modifiedDateLabel.text")); // NOI18N - daysIncludedTextField.setEditable(false); - daysIncludedTextField.setHorizontalAlignment(javax.swing.JTextField.TRAILING); - daysIncludedTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.daysIncludedTextField.text")); // NOI18N - daysIncludedTextField.setEnabled(false); - daysIncludedTextField.setMinimumSize(new java.awt.Dimension(60, 20)); - daysIncludedTextField.setPreferredSize(new java.awt.Dimension(60, 20)); + modifiedWithinTextField.setEditable(false); + modifiedWithinTextField.setHorizontalAlignment(javax.swing.JTextField.TRAILING); + modifiedWithinTextField.setEnabled(false); + modifiedWithinTextField.setMinimumSize(new java.awt.Dimension(60, 20)); + modifiedWithinTextField.setPreferredSize(new java.awt.Dimension(60, 20)); org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.daysIncludedLabel.text")); // NOI18N daysIncludedLabel.setEnabled(false); @@ -133,7 +133,6 @@ final class ConfigVisualPanel2 extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.ruleNameLabel.text")); // NOI18N - ruleNameEditTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.ruleNameEditTextField.text")); // NOI18N ruleNameEditTextField.setEnabled(false); newRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N @@ -154,7 +153,6 @@ final class ConfigVisualPanel2 extends JPanel { } }); - descriptionEditTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.descriptionEditTextField.text")); // NOI18N descriptionEditTextField.setEnabled(false); deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N @@ -171,7 +169,7 @@ final class ConfigVisualPanel2 extends JPanel { fullPathsTable.setShowHorizontalLines(false); fullPathsTable.setShowVerticalLines(false); fullPathsTable.getTableHeader().setReorderingAllowed(false); - jScrollPane5.setViewportView(fullPathsTable); + fullPathsScrollPane.setViewportView(fullPathsTable); fullPathsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); if (fullPathsTable.getColumnModel().getColumnCount() > 0) { fullPathsTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.fullPathsTable.columnModel.title0")); // NOI18N @@ -182,17 +180,15 @@ final class ConfigVisualPanel2 extends JPanel { filenamesTable.setShowHorizontalLines(false); filenamesTable.setShowVerticalLines(false); filenamesTable.getTableHeader().setReorderingAllowed(false); - jScrollPane6.setViewportView(filenamesTable); + filenamesScrollPane.setViewportView(filenamesTable); if (filenamesTable.getColumnModel().getColumnCount() > 0) { filenamesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.filenamesTable.columnModel.title0")); // NOI18N } - shouldSaveCheckBox.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(shouldSaveCheckBox, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.shouldSaveCheckBox.text")); // NOI18N shouldSaveCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.shouldSaveCheckBox.toolTipText")); // NOI18N shouldSaveCheckBox.setEnabled(false); - shouldAlertCheckBox.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(shouldAlertCheckBox, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.shouldAlertCheckBox.text")); // NOI18N shouldAlertCheckBox.setEnabled(false); @@ -201,20 +197,18 @@ final class ConfigVisualPanel2 extends JPanel { folderNamesTable.setShowHorizontalLines(false); folderNamesTable.setShowVerticalLines(false); folderNamesTable.getTableHeader().setReorderingAllowed(false); - jScrollPane7.setViewportView(folderNamesTable); + folderNamesScrollPane.setViewportView(folderNamesTable); if (folderNamesTable.getColumnModel().getColumnCount() > 0) { folderNamesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.folderNamesTable.columnModel.title0")); // NOI18N } org.openide.awt.Mnemonics.setLocalizedText(extensionsLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.extensionsLabel.text")); // NOI18N - extensionsTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.extensionsTextField.text")); // NOI18N extensionsTextField.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(filenamesLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.filenamesLabel.text")); // NOI18N - configFileTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.configFileTextField.text")); // NOI18N - configFileTextField.setToolTipText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.configFileTextField.toolTipText")); // NOI18N + configFileTextField.setToolTipText(""); configFileTextField.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(ruleSetFileLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.ruleSetFileLabel.text")); // NOI18N @@ -241,7 +235,7 @@ final class ConfigVisualPanel2 extends JPanel { rulesTableKeyReleased(evt); } }); - jScrollPane1.setViewportView(rulesTable); + rulesScrollPane.setViewportView(rulesTable); if (rulesTable.getColumnModel().getColumnCount() > 0) { rulesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.rulesTable.columnModel.title0")); // NOI18N rulesTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.rulesTable.columnModel.title1")); // NOI18N @@ -254,13 +248,11 @@ final class ConfigVisualPanel2 extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(minSizeLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.minSizeLabel.text")); // NOI18N minSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); - minSizeTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.minSizeTextField.text")); // NOI18N minSizeTextField.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(maxSizeLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.maxSizeLabel.text")); // NOI18N maxSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); - maxSizeTextField.setText(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.maxSizeTextField.text")); // NOI18N maxSizeTextField.setEnabled(false); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -268,78 +260,73 @@ final class ConfigVisualPanel2 extends JPanel { layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(480, 480, 480) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(daysIncludedLabel)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(ruleNameEditTextField, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(descriptionEditTextField, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(extensionsTextField, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane5, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane6, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane7, javax.swing.GroupLayout.Alignment.LEADING)) - .addContainerGap()))) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 377, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(layout.createSequentialGroup() - .addComponent(newRuleButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(editRuleButton) - .addGap(37, 37, 37) - .addComponent(deleteRuleButton))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(flagEncryptionProgramsCheckBox) - .addComponent(finalizeImageWriter))) - .addGroup(layout.createSequentialGroup() - .addGap(393, 393, 393) - .addComponent(shouldSaveCheckBox)) - .addGroup(layout.createSequentialGroup() - .addGap(397, 397, 397) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(extensionsLabel) - .addComponent(filenamesLabel) - .addComponent(descriptionLabel) - .addComponent(ruleNameLabel))) - .addGroup(layout.createSequentialGroup() - .addGap(397, 397, 397) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(modifiedDateLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(fileSizeLabel) - .addComponent(fullPathsLabel) - .addComponent(folderNamesLabel)) - .addGap(4, 4, 4) - .addComponent(minSizeLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(maxSizeLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addGap(393, 393, 393) - .addComponent(shouldAlertCheckBox))) + .addGap(17, 17, 17) + .addComponent(ruleSetFileLabel) + .addGap(18, 18, 18) + .addComponent(configFileTextField) .addContainerGap()) .addGroup(layout.createSequentialGroup() + .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(17, 17, 17) - .addComponent(ruleSetFileLabel) - .addGap(18, 18, 18) - .addComponent(configFileTextField)) + .addComponent(newRuleButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(editRuleButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(deleteRuleButton)) + .addComponent(rulesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 341, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(397, 397, 397) - .addComponent(jSeparator1))) - .addGap(10, 10, 10)) + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(flagEncryptionProgramsCheckBox) + .addComponent(finalizeImageWriter) + .addComponent(shouldSaveCheckBox) + .addComponent(shouldAlertCheckBox) + .addComponent(extensionsLabel) + .addComponent(filenamesLabel) + .addComponent(descriptionLabel) + .addComponent(ruleNameLabel) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(modifiedDateLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(fileSizeLabel) + .addComponent(fullPathsLabel) + .addComponent(folderNamesLabel)) + .addGap(4, 4, 4) + .addComponent(minSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(maxSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(107, 107, 107)) + .addGroup(layout.createSequentialGroup() + .addGap(129, 129, 129) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(modifiedWithinTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(daysIncludedLabel)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(ruleNameEditTextField, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(descriptionEditTextField, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(extensionsTextField, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(fullPathsScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(filenamesScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) + .addContainerGap()))))) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator1)))) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deleteRuleButton, editRuleButton, newRuleButton}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -350,7 +337,7 @@ final class ConfigVisualPanel2 extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 527, Short.MAX_VALUE) + .addComponent(rulesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 479, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(newRuleButton) @@ -372,13 +359,13 @@ final class ConfigVisualPanel2 extends JPanel { .addComponent(ruleNameLabel))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane6, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(filenamesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addComponent(filenamesLabel) .addGap(0, 0, Short.MAX_VALUE))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane7, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addComponent(folderNamesLabel) .addGap(0, 0, Short.MAX_VALUE))) @@ -388,7 +375,7 @@ final class ConfigVisualPanel2 extends JPanel { .addComponent(fullPathsLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(jScrollPane5, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(fullPathsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addGap(11, 11, 11))) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(minSizeLabel) @@ -399,13 +386,13 @@ final class ConfigVisualPanel2 extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(modifiedDateLabel) - .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(modifiedWithinTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(daysIncludedLabel)) - .addGap(3, 3, 3) - .addComponent(shouldAlertCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(shouldSaveCheckBox) - .addGap(18, 18, 18) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(shouldAlertCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(flagEncryptionProgramsCheckBox) @@ -415,31 +402,27 @@ final class ConfigVisualPanel2 extends JPanel { ); }// //GEN-END:initComponents - private void finalizeImageWriterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_finalizeImageWriterActionPerformed - config.setFinalizeImageWriter(finalizeImageWriter.isSelected()); - }//GEN-LAST:event_finalizeImageWriterActionPerformed - private void rulesTableKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_rulesTableKeyReleased - rulesTableSelect(); + updateForSelectedRule(); }//GEN-LAST:event_rulesTableKeyReleased @NbBundle.Messages({ - "ConfigVisualPanel2.editRuleSet=Edit rule", + "ConfigVisualPanel2.editRuleSet=Edit Rule", "ConfigVisualPanel2.editRuleError=Edit rule error" }) private void editRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleButtonActionPerformed int row = rulesTable.getSelectedRow(); if (row != -1) { String ruleName = (String) rulesTable.getModel().getValueAt(row, 0); - LogicalImagerRule rule = getFirstRuleSet().getRules().get(row); + LogicalImagerRule rule = getRuleSetFromCurrentConfig().getRules().get(row); EditRulePanel editPanel = new EditRulePanel(okButton, cancelButton, ruleName, rule); editPanel.setEnabled(true); editPanel.setVisible(true); while (true) { - int option = JOptionPane.showOptionDialog(this, editPanel.getPanel(), Bundle.ConfigVisualPanel2_editRuleSet(), - JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, - null, new Object[]{okButton, cancelButton}, okButton); + int option = JOptionPane.showOptionDialog(this, editPanel.getPanel(), Bundle.ConfigVisualPanel2_editRuleSet(), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, + null, new Object[]{okButton, cancelButton}, okButton); if (option == JOptionPane.OK_OPTION) { try { ImmutablePair ruleMap = editPanel.toRule(); @@ -447,28 +430,29 @@ final class ConfigVisualPanel2 extends JPanel { break; } catch (IOException | NumberFormatException ex) { JOptionPane.showMessageDialog(this, - ex.getMessage(), - Bundle.ConfigVisualPanel2_editRuleError(), - JOptionPane.ERROR_MESSAGE); + ex.getMessage(), + Bundle.ConfigVisualPanel2_editRuleError(), + JOptionPane.ERROR_MESSAGE); // let user fix the error } } else { break; } } - } + } }//GEN-LAST:event_editRuleButtonActionPerformed + @Messages({"ConfigVisualPanel2.newRule.name=New Rule"}) private void newRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newRuleButtonActionPerformed - NewRuleSetPanel panel; - panel = new NewRuleSetPanel(okButton, cancelButton); + NewRulePanel panel; + panel = new NewRulePanel(okButton, cancelButton); panel.setEnabled(true); panel.setVisible(true); while (true) { - int option = JOptionPane.showOptionDialog(this, panel, "New rule", - JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, - null, new Object[]{okButton, cancelButton}, okButton); + int option = JOptionPane.showOptionDialog(this, panel, Bundle.ConfigVisualPanel2_newRule_name(), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, + null, new Object[]{okButton, cancelButton}, okButton); if (option == JOptionPane.OK_OPTION) { try { // Save the new rule @@ -477,71 +461,81 @@ final class ConfigVisualPanel2 extends JPanel { break; } catch (IOException | NumberFormatException ex) { JOptionPane.showMessageDialog(this, - ex.getMessage(), - "New rule error", - JOptionPane.ERROR_MESSAGE); + ex.getMessage(), + "New rule error", + JOptionPane.ERROR_MESSAGE); // let user fix the error } } else { break; } - } + } }//GEN-LAST:event_newRuleButtonActionPerformed @NbBundle.Messages({ "ConfigVisualPanel2.deleteRuleSet=Delete rule ", - "ConfigVisualPanel2.deleteRuleSetConfirmation=Delete rule confirmation", - }) + "ConfigVisualPanel2.deleteRuleSetConfirmation=Delete rule confirmation",}) private void deleteRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteRuleButtonActionPerformed int index = rulesTable.getSelectedRow(); if (index != -1) { String ruleName = (String) rulesTable.getModel().getValueAt(index, 0); - int option = JOptionPane.showOptionDialog(this, - Bundle.ConfigVisualPanel2_deleteRuleSet() + ruleName, - Bundle.ConfigVisualPanel2_deleteRuleSetConfirmation(), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); + int option = JOptionPane.showOptionDialog(this, + Bundle.ConfigVisualPanel2_deleteRuleSet() + ruleName, + Bundle.ConfigVisualPanel2_deleteRuleSetConfirmation(), + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); if (option == JOptionPane.NO_OPTION) { return; } - getFirstRuleSet().getRules().remove(index); + getRuleSetFromCurrentConfig().getRules().remove(index); updatePanel(configFilename, config); if (rulesTable.getRowCount() > 0) { rulesTable.setRowSelectionInterval(0, 0); - rulesTableSelect(); + updateForSelectedRule(); } } }//GEN-LAST:event_deleteRuleButtonActionPerformed - private void flagEncryptionProgramsCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_flagEncryptionProgramsCheckBoxActionPerformed - flagEncryptionPrograms = flagEncryptionProgramsCheckBox.isSelected(); - toggleEncryptionProgramsRule(flagEncryptionPrograms); - }//GEN-LAST:event_flagEncryptionProgramsCheckBoxActionPerformed - private void rulesTableMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rulesTableMouseReleased - rulesTableSelect(); + updateForSelectedRule(); }//GEN-LAST:event_rulesTableMouseReleased - private void toggleEncryptionProgramsRule(boolean flagEncryptionPrograms) { + private void flagEncryptionProgramsCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_flagEncryptionProgramsCheckBoxActionPerformed + flagEncryptionPrograms = flagEncryptionProgramsCheckBox.isSelected(); + setEnabledEncryptionProgramsRule(flagEncryptionPrograms); + }//GEN-LAST:event_flagEncryptionProgramsCheckBoxActionPerformed + + private void finalizeImageWriterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_finalizeImageWriterActionPerformed + config.setFinalizeImageWriter(finalizeImageWriter.isSelected()); + }//GEN-LAST:event_finalizeImageWriterActionPerformed + + /** + * Set the whether the a rule for detecting encryption programs will be + * added to the rules in this config + * + * @param flagEncryptionPrograms true to have encryption programs rule + * added, false otherwise. + */ + private void setEnabledEncryptionProgramsRule(boolean flagEncryptionPrograms) { if (flagEncryptionPrograms) { // add the special rule ImmutablePair ruleMap = createEncryptionProgramsRule(); - appendRow(ruleMap); + appendRow(ruleMap); } else { // remove it int index = ((RulesTableModel) rulesTable.getModel()).findRow(EncryptionProgramsRule.getName()); if (index != -1) { - getFirstRuleSet().getRules().remove(index); + getRuleSetFromCurrentConfig().getRules().remove(index); updatePanel(configFilename, config); if (rulesTable.getRowCount() > 0) { rulesTable.setRowSelectionInterval(0, 0); - rulesTableSelect(); + updateForSelectedRule(); } } } } - + /* * Create an encryption programs rule */ @@ -559,7 +553,6 @@ final class ConfigVisualPanel2 extends JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField configFileTextField; private javax.swing.JLabel daysIncludedLabel; - private javax.swing.JTextField daysIncludedTextField; private javax.swing.JButton deleteRuleButton; private javax.swing.JTextField descriptionEditTextField; private javax.swing.JLabel descriptionLabel; @@ -568,33 +561,34 @@ final class ConfigVisualPanel2 extends JPanel { private javax.swing.JTextField extensionsTextField; private javax.swing.JLabel fileSizeLabel; private javax.swing.JLabel filenamesLabel; + private javax.swing.JScrollPane filenamesScrollPane; private javax.swing.JTable filenamesTable; private javax.swing.JCheckBox finalizeImageWriter; private javax.swing.JCheckBox flagEncryptionProgramsCheckBox; private javax.swing.JLabel folderNamesLabel; + private javax.swing.JScrollPane folderNamesScrollPane; private javax.swing.JTable folderNamesTable; private javax.swing.JLabel fullPathsLabel; + private javax.swing.JScrollPane fullPathsScrollPane; private javax.swing.JTable fullPathsTable; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JScrollPane jScrollPane5; - private javax.swing.JScrollPane jScrollPane6; - private javax.swing.JScrollPane jScrollPane7; private javax.swing.JSeparator jSeparator1; private javax.swing.JLabel maxSizeLabel; private javax.swing.JFormattedTextField maxSizeTextField; private javax.swing.JLabel minSizeLabel; private javax.swing.JFormattedTextField minSizeTextField; private javax.swing.JLabel modifiedDateLabel; + private javax.swing.JTextField modifiedWithinTextField; private javax.swing.JButton newRuleButton; private javax.swing.JTextField ruleNameEditTextField; private javax.swing.JLabel ruleNameLabel; private javax.swing.JLabel ruleSetFileLabel; + private javax.swing.JScrollPane rulesScrollPane; private javax.swing.JTable rulesTable; private javax.swing.JCheckBox shouldAlertCheckBox; private javax.swing.JCheckBox shouldSaveCheckBox; // End of variables declaration//GEN-END:variables - private LogicalImagerRuleSet getFirstRuleSet() { + private LogicalImagerRuleSet getRuleSetFromCurrentConfig() { if (config.getRuleSets().isEmpty()) { List ruleSets = new ArrayList<>(); ruleSets.add(new LogicalImagerRuleSet("no-set-name", new ArrayList<>())); // NON-NLS @@ -602,18 +596,25 @@ final class ConfigVisualPanel2 extends JPanel { } return config.getRuleSets().get(0); } - + + /** + * Update the panel to reflect the rules in the current config + * + * @param configFilePath path of the config file being modified + * @param config contents of the config file being modifed + * @param rowSelectionkey the name of the rule to select by default + */ private void updatePanel(String configFilePath, LogicalImagerConfig config, String rowSelectionkey) { configFileTextField.setText(configFilePath); finalizeImageWriter.setSelected(config.isFinalizeImageWriter()); - LogicalImagerRuleSet ruleSet = getFirstRuleSet(); + LogicalImagerRuleSet ruleSet = getRuleSetFromCurrentConfig(); flagEncryptionProgramsCheckBox.setSelected(ruleSet.find(EncryptionProgramsRule.getName()) != null); RulesTableModel rulesTableModel = new RulesTableModel(); int row = 0; int selectThisRow = 0; Collections.sort(ruleSet.getRules(), new SortRuleByName()); - + for (LogicalImagerRule rule : ruleSet.getRules()) { rulesTableModel.setValueAt(rule.getName(), row, 0); if (rowSelectionkey != null && rowSelectionkey.equals(rule.getName())) { @@ -628,31 +629,48 @@ final class ConfigVisualPanel2 extends JPanel { // If there are any rules, select the first one if (rulesTableModel.getRowCount() > 0) { rulesTable.setRowSelectionInterval(selectThisRow, selectThisRow); - rulesTableSelect(); + updateForSelectedRule(); } else { - updateRuleSetButtons(false); + clearRuleDetails(); + updateRuleButtons(false); } } - + + /** + * Private helper method to call updatePanel with no row to select specified + * + * @param configFilePath path of the config file being modified + * @param config contents of the config file being modifed + */ private void updatePanel(String configFilePath, LogicalImagerConfig config) { updatePanel(configFilePath, config, null); } - private void rulesTableSelect() { + /** + * Update the panel to reflect the selected rule + */ + private void updateForSelectedRule() { int index = rulesTable.getSelectedRow(); if (index != -1) { String ruleName = (String) rulesTable.getModel().getValueAt(index, 0); String description = (String) rulesTable.getModel().getValueAt(index, 1); - updateRuleDetails(ruleName, description, config); - updateRuleSetButtons(ruleName.equals(EncryptionProgramsRule.getName()) ? false : true); + updateRuleDetails(ruleName, description); + updateRuleButtons(!ruleName.equals(EncryptionProgramsRule.getName())); } else { - updateRuleSetButtons(false); + clearRuleDetails(); + updateRuleButtons(false); } } - private void updateRuleDetails(String ruleName, String description, LogicalImagerConfig config) { + /** + * Update the panel to display details of the specified rule + * + * @param ruleName the name of the rule to display + * @param description the description of the rule to display + */ + private void updateRuleDetails(String ruleName, String description) { clearRuleDetails(); - LogicalImagerRule rule = getFirstRuleSet().find(ruleName); + LogicalImagerRule rule = getRuleSetFromCurrentConfig().find(ruleName); shouldAlertCheckBox.setSelected(rule.isShouldAlert()); shouldSaveCheckBox.setSelected(rule.isShouldSave()); ruleNameEditTextField.setText(ruleName); @@ -672,18 +690,36 @@ final class ConfigVisualPanel2 extends JPanel { maxSizeTextField.setText(rule.getMaxFileSize().toString()); } if (rule.getMinDays() == null) { - daysIncludedTextField.setText(""); + modifiedWithinTextField.setText(""); } else { - daysIncludedTextField.setText(Integer.toString(rule.getMinDays())); + modifiedWithinTextField.setText(Integer.toString(rule.getMinDays())); } } + /** + * Reset rule details displayed to be blank or default + */ private void clearRuleDetails() { + ruleNameEditTextField.setText(""); + descriptionEditTextField.setText(""); extensionsTextField.setText(""); - shouldAlertCheckBox.setSelected(false); + updateExtensions(null); + updateList(filenamesTable, null); + updateList(folderNamesTable, null); + updateList(fullPathsTable, null); + minSizeTextField.setText(""); + maxSizeTextField.setText(""); + modifiedWithinTextField.setText(""); shouldSaveCheckBox.setSelected(true); + shouldAlertCheckBox.setSelected(false); } + /** + * Update the extensions displayed + * + * @param extensions the list of extensions to display, null to display + * nothing + */ private void updateExtensions(List extensions) { extensionsTextField.setText(""); if (extensions == null) { @@ -691,61 +727,58 @@ final class ConfigVisualPanel2 extends JPanel { } String content = ""; boolean first = true; - for (String ext : extensions) { + for (String ext : extensions) { content += (first ? "" : ",") + ext; first = false; } extensionsTextField.setText(content); } - private void updateList(javax.swing.JTable jTable, List set) { + /** + * Update a JTable to display a list of Strings + * + * @param jTable the JTable to update + * @param list the list of Strings to display, null to display nothing + */ + private void updateList(javax.swing.JTable jTable, List list) { SingleColumnTableModel tableModel = new SingleColumnTableModel(); jTable.setTableHeader(null); - if (set == null) { + if (list == null) { jTable.setModel(tableModel); return; } int row = 0; - for (String s : set) { + for (String s : list) { tableModel.setValueAt(s, row, 0); row++; } jTable.setModel(tableModel); } - void setConfiguration(String configFilename, LogicalImagerConfig config, boolean newFile) { + void setConfiguration(String configFilename, LogicalImagerConfig config) { this.configFilename = configFilename; this.config = config; - if (newFile) { - initPanel(); - } updatePanel(configFilename, config); } - private void initPanel() { - configFileTextField.setText(""); - rulesTable.setModel(new RulesTableModel()); - shouldAlertCheckBox.setSelected(false); - shouldSaveCheckBox.setSelected(true); - ruleNameEditTextField.setText(""); - descriptionEditTextField.setText(""); - extensionsTextField.setText(""); - updateList(filenamesTable, EMPTY_LIST); - updateList(folderNamesTable, EMPTY_LIST); - } - private void updateRow(int index, ImmutablePair ruleMap) { - getFirstRuleSet().getRules().remove(index); - getFirstRuleSet().getRules().add(ruleMap.getValue()); + getRuleSetFromCurrentConfig().getRules().remove(index); + getRuleSetFromCurrentConfig().getRules().add(ruleMap.getValue()); updatePanel(configFilename, config, ruleMap.getKey()); } private void appendRow(ImmutablePair ruleMap) { - getFirstRuleSet().getRules().add(ruleMap.getValue()); + getRuleSetFromCurrentConfig().getRules().add(ruleMap.getValue()); updatePanel(configFilename, config, ruleMap.getKey()); } - private void updateRuleSetButtons(boolean isRowSelected) { + /** + * Adjust the enabled status of the rule buttons to reflect wether there is + * a currently selected rule which can be modified + * + * @param isRowSelected true if a row is selected, false otherwise + */ + private void updateRuleButtons(boolean isRowSelected) { newRuleButton.setEnabled(true); editRuleButton.setEnabled(isRowSelected); deleteRuleButton.setEnabled(isRowSelected); @@ -756,6 +789,7 @@ final class ConfigVisualPanel2 extends JPanel { */ private class SortRuleByName implements Comparator { + @Override public int compare(LogicalImagerRule a, LogicalImagerRule b) { return a.getName().compareToIgnoreCase(b.getName()); } @@ -766,6 +800,7 @@ final class ConfigVisualPanel2 extends JPanel { */ private class RulesTableModel extends AbstractTableModel { + private static final long serialVersionUID = 1L; private final List ruleName = new ArrayList<>(); private final List ruleDescription = new ArrayList<>(); private final List rule = new ArrayList<>(); @@ -773,7 +808,7 @@ final class ConfigVisualPanel2 extends JPanel { int findRow(String name) { return ruleName.indexOf(name); } - + @Override public int getRowCount() { return ruleName.size(); @@ -855,6 +890,8 @@ final class ConfigVisualPanel2 extends JPanel { */ private class SingleColumnTableModel extends AbstractTableModel { + private static final long serialVersionUID = 1L; + private final List list = new ArrayList<>(); @Override diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel3.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel3.form new file mode 100644 index 0000000000..00c251301e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel3.form @@ -0,0 +1,142 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel3.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel3.java new file mode 100644 index 0000000000..f9d61edeff --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel3.java @@ -0,0 +1,275 @@ +/* + * Autopsy + * + * Copyright 2019 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.logicalimager.configuration; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import java.awt.Color; +import java.awt.Cursor; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import static org.sleuthkit.autopsy.logicalimager.configuration.CreateLogicalImagerAction.getTskLogicalImagerExe; + +/** + * Configuration visual panel 3 + */ +class ConfigVisualPanel3 extends javax.swing.JPanel { + + private static final Logger logger = Logger.getLogger(ConfigVisualPanel3.class.getName()); + private static final String SAVED_LOGICAL_IMAGER = "SAVED_LOGICAL_IMAGER"; + private static final long serialVersionUID = 1L; + private boolean hasBeenSaved = false; + private String configFilename; + private LogicalImagerConfig config; + + /** + * Creates new form ConfigVisualPanel3 + */ + @NbBundle.Messages({"ConfigVisualPanel3.copyStatus.notSaved=File has not been saved", + "ConfigVisualPanel3.copyStatus.savingInProgress=Saving file, please wait", + "ConfigVisualPanel3.copyStatus.saved=Saved", + "ConfigVisualPanel3.copyStatus.error=Unable to save file"}) + ConfigVisualPanel3() { + initComponents(); + } + + final void resetPanel() { + hasBeenSaved = false; + configStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_notSaved()); + executableStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_notSaved()); + } + + @NbBundle.Messages({ + "ConfigVisualPanel3.saveConfigurationFile=Save imager" + }) + @Override + public String getName() { + return Bundle.ConfigVisualPanel3_saveConfigurationFile(); + } + + /** + * Identifies whether the configuration has been saved + * + * @return true if it has been saved, false otherwise + */ + boolean isSaved() { + return hasBeenSaved; + } + + /** + * Save the current configuration file and copy the logical imager + * executable to the same location. + */ + @NbBundle.Messages({ + "# {0} - configFilename", + "ConfigVisualPanel3.failedToSaveConfigMsg=Failed to save configuration file: {0}", + "# {0} - reason", + "ConfigVisualPanel3.reason=\nReason: ", + "ConfigVisualPanel3.failedToSaveExeMsg=Failed to save tsk_logical_imager.exe file",}) + void saveConfigFile() { + boolean saveSuccess = true; + executableStatusLabel.setForeground(Color.BLACK); + configStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_savingInProgress()); + executableStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_savingInProgress()); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + GsonBuilder gsonBuilder = new GsonBuilder() + .setPrettyPrinting() + .excludeFieldsWithoutExposeAnnotation() + .disableHtmlEscaping(); + Gson gson = gsonBuilder.create(); + String toJson = gson.toJson(config); + try { + List lines = Arrays.asList(toJson.split("\\n")); + FileUtils.writeLines(new File(configFilename), "UTF-8", lines, System.getProperty("line.separator")); // NON-NLS + configStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_saved()); + } catch (IOException ex) { + saveSuccess = false; + executableStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_error()); + executableStatusLabel.setForeground(Color.RED); + JOptionPane.showMessageDialog(this, Bundle.ConfigVisualPanel3_failedToSaveConfigMsg(configFilename) + + Bundle.ConfigVisualPanel3_reason(ex.getMessage())); + } catch (JsonIOException jioe) { + saveSuccess = false; + executableStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_error()); + executableStatusLabel.setForeground(Color.RED); + logger.log(Level.SEVERE, "Failed to save configuration file: " + configFilename, jioe); // NON-NLS + JOptionPane.showMessageDialog(this, Bundle.ConfigVisualPanel3_failedToSaveConfigMsg(configFilename) + + Bundle.ConfigVisualPanel3_reason(jioe.getMessage())); + } + try { + writeTskLogicalImagerExe(Paths.get(configFilename).getParent()); + executableStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_saved()); + } catch (IOException ex) { + saveSuccess = false; + executableStatusLabel.setText(Bundle.ConfigVisualPanel3_copyStatus_error()); + executableStatusLabel.setForeground(Color.RED); + logger.log(Level.SEVERE, "Failed to save tsk_logical_imager.exe file", ex); // NON-NLS + JOptionPane.showMessageDialog(this, Bundle.ConfigVisualPanel3_failedToSaveExeMsg() + + Bundle.ConfigVisualPanel3_reason(ex.getMessage())); + } + if (saveSuccess) { + hasBeenSaved = true; + firePropertyChange(SAVED_LOGICAL_IMAGER, false, true); // NON-NLS + } + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + /** + * Write the logical imager executable to the specified location + * + * @param destDir the location to write the logical imager executable to + * + * @throws IOException + */ + private void writeTskLogicalImagerExe(Path destDir) throws IOException { + FileUtils.copyFileToDirectory(getTskLogicalImagerExe(), destDir.toFile()); + } + + /** + * The name of the event which is sent when the configuration and executable + * have been saved. + * + * @return SAVED_LOGICAL_IMAGER + */ + static String getSavedEventName() { + return SAVED_LOGICAL_IMAGER; + } + + /** + * 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() { + + javax.swing.JButton saveButton = new javax.swing.JButton(); + descriptionScrollPane = new javax.swing.JScrollPane(); + descriptionTextArea = new javax.swing.JTextArea(); + javax.swing.JLabel configLabel = new javax.swing.JLabel(); + configStatusLabel = new javax.swing.JLabel(); + javax.swing.JLabel executableLabel = new javax.swing.JLabel(); + executableStatusLabel = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(saveButton, org.openide.util.NbBundle.getMessage(ConfigVisualPanel3.class, "ConfigVisualPanel3.saveButton.text")); // NOI18N + saveButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + saveButtonActionPerformed(evt); + } + }); + + descriptionTextArea.setEditable(false); + descriptionTextArea.setBackground(new java.awt.Color(240, 240, 240)); + descriptionTextArea.setColumns(20); + descriptionTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + descriptionTextArea.setLineWrap(true); + descriptionTextArea.setRows(5); + descriptionTextArea.setWrapStyleWord(true); + descriptionTextArea.setEnabled(false); + descriptionScrollPane.setViewportView(descriptionTextArea); + + org.openide.awt.Mnemonics.setLocalizedText(configLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel3.class, "ConfigVisualPanel3.configLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(executableLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel3.class, "ConfigVisualPanel3.executableLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(executableStatusLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel3.class, "ConfigVisualPanel3.executableStatusLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(configLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(executableLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(configStatusLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 237, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(executableStatusLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 238, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 10, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(descriptionScrollPane) + .addGroup(layout.createSequentialGroup() + .addComponent(saveButton, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(descriptionScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(saveButton) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(configLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(configStatusLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(executableLabel) + .addComponent(executableStatusLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(120, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void saveButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveButtonActionPerformed + saveConfigFile(); + }//GEN-LAST:event_saveButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel configStatusLabel; + private javax.swing.JScrollPane descriptionScrollPane; + private javax.swing.JTextArea descriptionTextArea; + private javax.swing.JLabel executableStatusLabel; + // End of variables declaration//GEN-END:variables + + /** + * Set the information necessary to save the configuration + * + * @param configFile the path to the json configuration file + * @param config the configuration to save + */ + @NbBundle.Messages({ + "# {0} - configurationLocation", + "ConfigVisualPanel3.description.text=Press Save to write the imaging tool and configuration file to the destination.\nDestination: {0}" + }) + void setConfigInfoForSaving(String configFile, LogicalImagerConfig config) { + this.configFilename = configFile; + this.config = config; + descriptionTextArea.setText(Bundle.ConfigVisualPanel3_description_text(FilenameUtils.getFullPath(configFilename))); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigWizardPanel1.java index 3c5174f441..c84301018e 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigWizardPanel1.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,13 +52,13 @@ final class ConfigWizardPanel1 implements WizardDescriptor.ValidatingPanel listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 /** @@ -130,9 +130,8 @@ final class ConfigWizardPanel1 implements WizardDescriptor.ValidatingPanel sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,32 +18,15 @@ */ package org.sleuthkit.autopsy.logicalimager.configuration; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; -import java.util.logging.Level; -import javax.swing.JOptionPane; import javax.swing.event.ChangeListener; -import org.apache.commons.io.FileUtils; import org.openide.WizardDescriptor; import org.openide.util.HelpCtx; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; -import static org.sleuthkit.autopsy.logicalimager.configuration.ConfigureLogicalImager.getTskLogicalImagerExe; /** * Configuration Wizard Panel 2 */ final class ConfigWizardPanel2 implements WizardDescriptor.Panel { - private static final Logger LOGGER = Logger.getLogger(ConfigWizardPanel2.class.getName()); - /** * The visual component that displays this panel. If you need to access the * component from this class, just use getComponent(). @@ -87,51 +70,13 @@ final class ConfigWizardPanel2 implements WizardDescriptor.Panel lines = Arrays.asList(toJson.split("\\n")); - FileUtils.writeLines(new File(configFilename), "UTF-8", lines, System.getProperty("line.separator")); // NON-NLS - } catch (IOException ex) { - JOptionPane.showMessageDialog(component, Bundle.ConfigWizardPanel2_failedToSaveConfigMsg(configFilename) - + Bundle.ConfigWizardPanel2_reason(ex.getMessage())); - } catch (JsonIOException jioe) { - LOGGER.log(Level.SEVERE, "Failed to save configuration file: " + configFilename, jioe); // NON-NLS - JOptionPane.showMessageDialog(component, Bundle.ConfigWizardPanel2_failedToSaveConfigMsg(configFilename) - + Bundle.ConfigWizardPanel2_reason(jioe.getMessage())); - } - try { - writeTskLogicalImagerExe(Paths.get(configFilename).getParent()); - } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Failed to save tsk_logical_imager.exe file", ex); // NON-NLS - JOptionPane.showMessageDialog(component, Bundle.ConfigWizardPanel2_failedToSaveExeMsg() - + Bundle.ConfigWizardPanel2_reason(ex.getMessage())); - } - } - - private void writeTskLogicalImagerExe(Path destDir) throws IOException { - FileUtils.copyFileToDirectory(getTskLogicalImagerExe(), destDir.toFile()); - } @Override public void addChangeListener(ChangeListener cl) { diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigWizardPanel3.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigWizardPanel3.java new file mode 100644 index 0000000000..1719f71745 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigWizardPanel3.java @@ -0,0 +1,106 @@ +/* + * Autopsy + * + * Copyright 2019 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.logicalimager.configuration; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.WizardDescriptor; +import org.openide.util.HelpCtx; + +/** + * Configuration Wizard Panel 3 + */ +final class ConfigWizardPanel3 implements WizardDescriptor.Panel { + + private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 + private ConfigVisualPanel3 component; + + @Override + public ConfigVisualPanel3 getComponent() { + if (component == null) { + component = new ConfigVisualPanel3(); + component.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(ConfigVisualPanel3.getSavedEventName())) { // NON-NLS + fireChangeEvent(); + } + } + }); + } + return component; + } + + @Override + public HelpCtx getHelp() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public void readSettings(WizardDescriptor wiz) { + String configFilename = (String) wiz.getProperty("configFilename"); // NON-NLS + LogicalImagerConfig config = (LogicalImagerConfig) wiz.getProperty("config"); // NON-NLS + component.setConfigInfoForSaving(configFilename, config); + component.resetPanel(); + } + + @Override + public void storeSettings(WizardDescriptor data) { + //no settings to store + } + + @Override + public boolean isValid() { + return component.isSaved(); + } + + /** + * Fire an envent to indicate that state of the wizard panel has changed + */ + private void fireChangeEvent() { + Iterator it; + synchronized (listeners) { + it = new HashSet<>(listeners).iterator(); + } + ChangeEvent ev = new ChangeEvent(this); + while (it.hasNext()) { + it.next().stateChanged(ev); + } + } + + @Override + public void addChangeListener(ChangeListener cl) { + synchronized (listeners) { + listeners.add(cl); + } + } + + @Override + public void removeChangeListener(ChangeListener cl) { + synchronized (listeners) { + listeners.remove(cl); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigureLogicalImager.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/CreateLogicalImagerAction.java similarity index 79% rename from Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigureLogicalImager.java rename to Core/src/org/sleuthkit/autopsy/logicalimager/configuration/CreateLogicalImagerAction.java index 081599bdb9..ddb798512c 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigureLogicalImager.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/CreateLogicalImagerAction.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.logicalimager.configuration; import java.awt.Component; +import java.awt.Dialog; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; @@ -38,28 +39,25 @@ import static org.sleuthkit.autopsy.coreutils.PlatformUtil.getInstallModulesPath /** * Configuration Logical Imager */ - -@ActionID( - category = "Tools", - id = "org.sleuthkit.autopsy.configurelogicalimager.ConfigureLogicalImager" -) -@ActionRegistration(displayName = "#CTL_ConfigureLogicalImager", lazy = false) +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.logicalimager.configuration.CreateLogicalImagerAction") +@ActionRegistration(displayName = "#CTL_CreateLogicalImagerAction", lazy = false) @ActionReference(path = "Menu/Tools", position = 2000, separatorBefore = 1999) -@Messages("CTL_ConfigureLogicalImager=Create Logical Imager") -public final class ConfigureLogicalImager extends CallableSystemAction { +@Messages("CTL_CreateLogicalImagerAction=Create Logical Imager") +public final class CreateLogicalImagerAction extends CallableSystemAction { - private static final String DISPLAY_NAME = Bundle.CTL_ConfigureLogicalImager(); + private static final String DISPLAY_NAME = Bundle.CTL_CreateLogicalImagerAction(); private static final String TSK_LOGICAL_IMAGER_DIR = "tsk_logical_imager"; // NON-NLS private static final String TSK_LOGICAL_IMAGER_EXE = "tsk_logical_imager.exe"; // NON-NLS - + @NbBundle.Messages({ - "ConfigureLogicalImager.title=Configure Logical Imager" + "CreateLogicalImagerAction.title=Create Logical Imager" }) @Override public void performAction() { List> panels = new ArrayList<>(); panels.add(new ConfigWizardPanel1()); panels.add(new ConfigWizardPanel2()); + panels.add(new ConfigWizardPanel3()); String[] steps = new String[panels.size()]; for (int i = 0; i < panels.size(); i++) { Component c = panels.get(i).getComponent(); @@ -77,12 +75,9 @@ public final class ConfigureLogicalImager extends CallableSystemAction { WizardDescriptor wiz = new WizardDescriptor(new WizardDescriptor.ArrayIterator<>(panels)); // {0} will be replaced by WizardDesriptor.Panel.getComponent().getName() wiz.setTitleFormat(new MessageFormat("{0}")); // NON-NLS - wiz.setTitle(Bundle.ConfigureLogicalImager_title()); - if ((DialogDisplayer.getDefault().notify(wiz) == WizardDescriptor.FINISH_OPTION) && - (panels.get(1) instanceof ConfigWizardPanel2)) { - ConfigWizardPanel2 panel = (ConfigWizardPanel2) panels.get(1); - panel.saveConfigFile(); - } + wiz.setTitle(Bundle.CreateLogicalImagerAction_title()); + Dialog dialog = DialogDisplayer.getDefault().createDialog(wiz); + dialog.setVisible(true); } @Override @@ -100,7 +95,7 @@ public final class ConfigureLogicalImager extends CallableSystemAction { File tskLogicalImagerExe = getTskLogicalImagerExe(); return tskLogicalImagerExe.exists(); } - + static public File getTskLogicalImagerExe() { String installModulesPath = getInstallModulesPath(); File tskLogicalImagerExe = new File(installModulesPath + File.separator + TSK_LOGICAL_IMAGER_DIR + File.separator + TSK_LOGICAL_IMAGER_EXE); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/DefaultToEmptyNumberFormatter.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/DefaultToEmptyNumberFormatter.java new file mode 100644 index 0000000000..fbcf3a7e84 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/DefaultToEmptyNumberFormatter.java @@ -0,0 +1,40 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.logicalimager.configuration; + +import java.text.NumberFormat; +import java.text.ParseException; +import javax.swing.text.NumberFormatter; + +/** + * Number formatter which will reset to being a null value when an invalid value + * is entered + */ +final class DefaultToEmptyNumberFormatter extends NumberFormatter { + + private static final long serialVersionUID = 1L; + + /** + * Create a DefaultToEmptyNumberFormatter + * + * @param format the format for the numbers + */ + DefaultToEmptyNumberFormatter(NumberFormat format) { + super(format); + } + + @Override + public Object stringToValue(String string) + throws ParseException { + Object returnValue = null; + try { + returnValue = super.stringToValue(string); + } catch (ParseException ignored) { + //reset value to being empty since invalid value was entered + } + return returnValue; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.form index 5ca39dbd25..22cbb340e7 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.form @@ -16,23 +16,31 @@ - + - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -42,7 +50,7 @@ - + @@ -50,20 +58,26 @@ - + + + - + - - - + + + + + + + @@ -77,6 +91,9 @@ + + +
@@ -87,6 +104,9 @@ + + + @@ -96,39 +116,49 @@ + + + - - - - - - - - - - - - + + + - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java index 9f3d7dff99..af65e8eacb 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,10 @@ import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.openide.util.NbBundle; @@ -40,7 +44,7 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { private JButton okButton; private JButton cancelButton; private final JTextArea fullPathsTextArea; - + /** * Creates new form EditFullPathsRulePanel */ @@ -49,25 +53,60 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { }) EditFullPathsRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule, boolean editing) { initComponents(); - + if (editing) { ruleNameTextField.setEnabled(!editing); } - + this.setRule(ruleName, rule); this.setButtons(okButton, cancelButton); fullPathsTextArea = new JTextArea(); initTextArea(fullPathsScrollPane, fullPathsTextArea); setTextArea(fullPathsTextArea, rule.getFullPaths()); - - EditRulePanel.setTextFieldPrompts(fullPathsTextArea, + + EditRulePanel.setTextFieldPrompts(fullPathsTextArea, "" + Bundle.EditFullPathsRulePanel_example() + "
/Program Files/Common Files/system/wab32.dll
/Windows/System32/1033/VsGraphicsResources.dll"); // NON-NLS ruleNameTextField.requestFocus(); + addDocumentListeners(); validate(); - repaint(); + repaint(); } + /** + * Update the OK button when contents of a field change + */ + private void addDocumentListeners() { + SwingUtilities.invokeLater(() -> { + setOkButton(); //ensure initial state before listeners added is correct + }); + DocumentListener docListener; + docListener = new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + setOkButton(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + setOkButton(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + setOkButton(); + } + }; + ruleNameTextField.getDocument().addDocumentListener(docListener); + fullPathsTextArea.getDocument().addDocumentListener(docListener); + } + + /** + * Initialize the text area and the scroll pane viewing it + * + * @param pane the JScrollPane which will be viewing the JTextArea + * @param textArea the JTextArea being initialized + */ private void initTextArea(JScrollPane pane, JTextArea textArea) { textArea.setColumns(20); textArea.setRows(5); @@ -83,7 +122,7 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { } e.consume(); } - } + } }); } @@ -100,27 +139,41 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { shouldAlertCheckBox = new javax.swing.JCheckBox(); fullPathsLabel = new javax.swing.JLabel(); descriptionTextField = new javax.swing.JTextField(); - descriptionLabel = new javax.swing.JLabel(); ruleNameLabel = new javax.swing.JLabel(); ruleNameTextField = new javax.swing.JTextField(); fullPathsScrollPane = new javax.swing.JScrollPane(); + jSeparator1 = new javax.swing.JSeparator(); + jSeparator2 = new javax.swing.JSeparator(); + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); shouldSaveCheckBox.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(shouldSaveCheckBox, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.shouldSaveCheckBox.text")); // NOI18N + shouldSaveCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + shouldSaveCheckBoxActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(shouldAlertCheckBox, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.shouldAlertCheckBox.text")); // NOI18N shouldAlertCheckBox.setActionCommand(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand")); // NOI18N + shouldAlertCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + shouldAlertCheckBoxActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(fullPathsLabel, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.fullPathsLabel.text")); // NOI18N fullPathsLabel.setToolTipText(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.fullPathsLabel.toolTipText")); // NOI18N - - descriptionTextField.setText(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.descriptionTextField.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.descriptionLabel.text")); // NOI18N + fullPathsLabel.setPreferredSize(new java.awt.Dimension(112, 14)); org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.ruleNameLabel.text")); // NOI18N + ruleNameLabel.setPreferredSize(new java.awt.Dimension(112, 14)); - ruleNameTextField.setText(org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.ruleNameTextField.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.jLabel1.text")); // NOI18N + jLabel1.setPreferredSize(new java.awt.Dimension(112, 14)); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(EditFullPathsRulePanel.class, "EditFullPathsRulePanel.jLabel2.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -129,51 +182,79 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(shouldSaveCheckBox) - .addComponent(shouldAlertCheckBox) + .addComponent(jSeparator2) + .addComponent(jSeparator1) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(ruleNameLabel) - .addComponent(descriptionLabel) - .addComponent(fullPathsLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(ruleNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 519, Short.MAX_VALUE) - .addComponent(descriptionTextField) - .addComponent(fullPathsScrollPane)))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(ruleNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(fullPathsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 648, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(descriptionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 648, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(fullPathsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 648, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(shouldSaveCheckBox) + .addComponent(shouldAlertCheckBox) + .addComponent(jLabel2)) + .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {fullPathsLabel, jLabel1, ruleNameLabel}); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {descriptionTextField, fullPathsScrollPane, ruleNameTextField}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addContainerGap() + .addGap(8, 8, 8) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(ruleNameLabel) + .addComponent(ruleNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(descriptionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(descriptionLabel)) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(fullPathsLabel) - .addGap(0, 167, Short.MAX_VALUE)) + .addComponent(fullPathsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 115, Short.MAX_VALUE)) .addComponent(fullPathsScrollPane)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(shouldAlertCheckBox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(shouldSaveCheckBox) + .addGap(3, 3, 3) + .addComponent(shouldAlertCheckBox) .addContainerGap()) ); }// //GEN-END:initComponents + private void shouldSaveCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shouldSaveCheckBoxActionPerformed + setOkButton(); + }//GEN-LAST:event_shouldSaveCheckBoxActionPerformed + + private void shouldAlertCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shouldAlertCheckBoxActionPerformed + setOkButton(); + }//GEN-LAST:event_shouldAlertCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JLabel descriptionLabel; private javax.swing.JTextField descriptionTextField; private javax.swing.JLabel fullPathsLabel; private javax.swing.JScrollPane fullPathsScrollPane; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JSeparator jSeparator2; private javax.swing.JLabel ruleNameLabel; private javax.swing.JTextField ruleNameTextField; private javax.swing.JCheckBox shouldAlertCheckBox; @@ -184,9 +265,9 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { * Sets whether or not the OK button should be enabled based upon other UI * elements */ - private void setOkButton() { + void setOkButton() { if (this.okButton != null) { - this.okButton.setEnabled(true); + this.okButton.setEnabled(!StringUtils.isBlank(ruleNameTextField.getText()) && !StringUtils.isBlank(fullPathsTextArea.getText()) && (shouldAlertCheckBox.isSelected() || shouldSaveCheckBox.isSelected())); } } @@ -224,9 +305,14 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { JOptionPane pane = getOptionPane(cancelButton); pane.setValue(cancelButton); }); - this.setOkButton(); } + /** + * Set the name description and should alert / should save checkboxes + * + * @param ruleName the name of the rule + * @param rule the LogicalImagerRule + */ private void setRule(String ruleName, LogicalImagerRule rule) { ruleNameTextField.setText(ruleName); descriptionTextField.setText(rule.getDescription()); @@ -234,17 +320,31 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { shouldSaveCheckBox.setSelected(rule.isShouldSave()); } - private void setTextArea(JTextArea textArea, List set) { + /** + * Set the text of a text area to contain the contents of the list each on + * its own line + * + * @param textArea the text area to set the contents of + * @param list the list of strings to display in the text area + */ + private void setTextArea(JTextArea textArea, List list) { String text = ""; - for (String s : set) { + for (String s : list) { text += s + System.getProperty("line.separator"); // NON-NLS } textArea.setText(text); } + /** + * Convert the contents of this panel to a rule and return it as well as its + * name. + * + * @return an ImmutablePair containing the name of the rule and the rule + * + * @throws IOException + */ @NbBundle.Messages({ - "EditFullPathsRulePanel.fullPaths=Full paths", - }) + "EditFullPathsRulePanel.fullPaths=Full paths",}) ImmutablePair toRule() throws IOException { List fullPaths = EditRulePanel.validateTextList(fullPathsTextArea, Bundle.EditFullPathsRulePanel_fullPaths()); String ruleName = EditRulePanel.validRuleName(ruleNameTextField.getText()); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.form index c237d38b6d..4eca4bcbac 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.form @@ -1,10 +1,6 @@
- - - - @@ -21,61 +17,68 @@ + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - + - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - @@ -85,69 +88,79 @@ - + - - - + + + - - - - - + + + + + + - + + + - + - - - - + + + - - + + + + + - - - - - - - + + + + + + + + + - + + + + + + + - - + + - - - + + + + + + + - - - - - - - @@ -162,6 +175,9 @@ + + + @@ -172,151 +188,227 @@ - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + - - + + + + + + + + + + + + + + + + + + - + - + - - - - - - - - - - - - + - - + + - + - + - - - - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java index 50490fb743..a48db9b2c4 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,16 +26,23 @@ import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import javax.swing.JButton; +import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.apache.commons.lang.StringUtils; import static org.apache.commons.lang.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.strip; import org.apache.commons.lang3.tuple.ImmutablePair; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Edit non-full paths rule panel @@ -43,21 +50,27 @@ import org.openide.util.NbBundle; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class EditNonFullPathsRulePanel extends javax.swing.JPanel { + private static final Logger logger = Logger.getLogger(EditNonFullPathsRulePanel.class.getName()); + private static final long serialVersionUID = 1L; + private static final Color DISABLED_COLOR = new Color(240, 240, 240); + private static final int BYTE_UNIT_CONVERSION = 1000; private JButton okButton; private JButton cancelButton; - private final javax.swing.JTextArea filenamesTextArea; + private final javax.swing.JTextArea fileNamesTextArea; private final javax.swing.JTextArea folderNamesTextArea; - + /** * Creates new form EditRulePanel */ @NbBundle.Messages({ "EditNonFullPathsRulePanel.example=Example: ", - "EditNonFullPathsRulePanel.note=NOTE: A special [USER_FOLDER] token at the the start of a folder name to allow matches of all user folders in the file system." + "EditNonFullPathsRulePanel.units.bytes=Bytes", + "EditNonFullPathsRulePanel.units.kilobytes=Kilobytes", + "EditNonFullPathsRulePanel.units.megabytes=MegaBytes", + "EditNonFullPathsRulePanel.units.gigabytes=Gigabytes" }) EditNonFullPathsRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule, boolean editing) { initComponents(); - if (editing) { ruleNameTextField.setEnabled(!editing); } @@ -66,48 +79,106 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { this.setButtons(okButton, cancelButton); setExtensions(rule.getExtensions()); - - filenamesTextArea = new JTextArea(); - initTextArea(filenamesScrollPane, filenamesTextArea); - setTextArea(filenamesTextArea, rule.getFilenames()); - - if (rule.getExtensions() == null) { - extensionsRadioButton.setSelected(false); - filenamesRadioButton.setSelected(true); - } else { - extensionsRadioButton.setSelected(true); - filenamesRadioButton.setSelected(false); + fileNamesTextArea = new JTextArea(); + initTextArea(filenamesScrollPane, fileNamesTextArea); + setTextArea(fileNamesTextArea, rule.getFilenames()); + if (rule.getExtensions() != null && !rule.getExtensions().isEmpty()) { + extensionsCheckbox.setSelected(true); + extensionsTextField.setEnabled(extensionsCheckbox.isSelected()); + } else if (rule.getFilenames() != null && !rule.getFilenames().isEmpty()) { + fileNamesCheckbox.setSelected(true); + fileNamesTextArea.setEnabled(fileNamesCheckbox.isSelected()); } - + updateExclusiveConditions(); folderNamesTextArea = new JTextArea(); initTextArea(folderNamesScrollPane, folderNamesTextArea); setTextArea(folderNamesTextArea, rule.getPaths()); - - setMinDays(rule.getMinDays()); - - minSizeTextField.setText(rule.getMinFileSize() == null ? "" : rule.getMinFileSize().toString()); - maxSizeTextField.setText(rule.getMaxFileSize() == null ? "" : rule.getMaxFileSize().toString()); + folderNamesCheckbox.setSelected(!StringUtils.isBlank(folderNamesTextArea.getText())); + folderNamesTextArea.setEnabled(folderNamesCheckbox.isSelected()); + updateTextAreaBackgroundColor(folderNamesTextArea); + setModifiedWithin(rule.getMinDays()); + + setupMinMaxSizeOptions(rule); ruleNameTextField.requestFocus(); - + EditRulePanel.setTextFieldPrompts(extensionsTextField, Bundle.EditNonFullPathsRulePanel_example() + "gif,jpg,png"); // NON-NLS - EditRulePanel.setTextFieldPrompts(filenamesTextArea, "" - + Bundle.EditNonFullPathsRulePanel_example() + EditRulePanel.setTextFieldPrompts(fileNamesTextArea, "" + + Bundle.EditNonFullPathsRulePanel_example() + "
filename.txt
readme.txt"); // NON-NLS - EditRulePanel.setTextFieldPrompts(folderNamesTextArea, "" - + Bundle.EditNonFullPathsRulePanel_example() + EditRulePanel.setTextFieldPrompts(folderNamesTextArea, "" + + Bundle.EditNonFullPathsRulePanel_example() + "
[USER_FOLDER]/My Documents/Downloads" - + "
/Program Files/Common Files" - + "
" - + Bundle.EditNonFullPathsRulePanel_note() - + ""); // NON-NLS + + "
/Program Files/Common Files"); // NON-NLS validate(); repaint(); + addDocumentListeners(); } - + + /** + * Set the min and max size options + * + * @param rule the rule the min and max size options should reflect + */ + private void setupMinMaxSizeOptions(LogicalImagerRule rule) { + String savedMinSize = rule.getMinFileSize() == null ? "" : rule.getMinFileSize().toString(); + setSizeAndUnits(minSizeTextField, minSizeUnitsCombobox, savedMinSize); + minSizeCheckbox.setSelected(!StringUtils.isBlank(minSizeTextField.getText())); + minSizeTextField.setEnabled(minSizeCheckbox.isSelected()); + minSizeUnitsCombobox.setEnabled(minSizeCheckbox.isSelected()); + + String savedMaxSize = rule.getMaxFileSize() == null ? "" : rule.getMaxFileSize().toString(); + setSizeAndUnits(maxSizeTextField, maxSizeUnitsCombobox, savedMaxSize); + maxSizeCheckbox.setSelected(!StringUtils.isBlank(maxSizeTextField.getText())); + maxSizeTextField.setEnabled(maxSizeCheckbox.isSelected()); + maxSizeUnitsCombobox.setEnabled(maxSizeCheckbox.isSelected()); + } + + /** + * Update the OK button when contents of a field change + */ + private void addDocumentListeners() { + SwingUtilities.invokeLater(() -> { + setOkButton(); //ensure initial state before listeners added is correct + }); + DocumentListener docListener; + docListener = new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + setOkButton(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + setOkButton(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + setOkButton(); + } + }; + ruleNameTextField.getDocument().addDocumentListener(docListener); + extensionsTextField.getDocument().addDocumentListener(docListener); + fileNamesTextArea.getDocument().addDocumentListener(docListener); + folderNamesTextArea.getDocument().addDocumentListener(docListener); + minSizeTextField.getDocument().addDocumentListener(docListener); + maxSizeTextField.getDocument().addDocumentListener(docListener); + modifiedWithinTextField.getDocument().addDocumentListener(docListener); + } + + /** + * Initialize the text area and the scroll pane viewing it + * + * @param pane the JScrollPane which will be viewing the JTextArea + * @param textArea the JTextArea being initialized + */ private void initTextArea(JScrollPane pane, JTextArea textArea) { textArea.setColumns(20); - textArea.setRows(5); + textArea.setRows(4); pane.setViewportView(textArea); + textArea.setEnabled(false); + textArea.setEditable(false); + textArea.setBackground(DISABLED_COLOR); textArea.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { @@ -119,36 +190,113 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { } e.consume(); } - } + } }); } - - private void setMinDays(Integer minDays) { - minDaysTextField.setText(minDays == null ? "" : minDays.toString()); + + /** + * Convert a long value and a string representing its units to Bytes + * + * @param value the numerical value to convert + * @param units the name of the units to convert to Kilobytes, Megabytes, or + * Gigabytes + * + * @return + */ + private long convertToBytes(long value, String units) { + long convertedValue = value; + if (units.equals(Bundle.EditNonFullPathsRulePanel_units_gigabytes())) { + convertedValue = convertedValue * BYTE_UNIT_CONVERSION * BYTE_UNIT_CONVERSION * BYTE_UNIT_CONVERSION; + } else if (units.equals(Bundle.EditNonFullPathsRulePanel_units_megabytes())) { + convertedValue = convertedValue * BYTE_UNIT_CONVERSION * BYTE_UNIT_CONVERSION; + } else if (units.equals(Bundle.EditNonFullPathsRulePanel_units_kilobytes())) { + convertedValue *= BYTE_UNIT_CONVERSION; + } + return convertedValue; } - private void setTextArea(JTextArea textArea, List set) { + /** + * Set the size and units for a specified sizeField and unitsComboBox + * + * @param sizeField the size field to set + * @param unitsComboBox the units combo box to set + * @param value the value as a string representation of the number + * of Bytes + */ + private void setSizeAndUnits(JTextField sizeField, JComboBox unitsComboBox, String value) { + if (StringUtils.isBlank(value)) { + unitsComboBox.setSelectedItem(Bundle.EditNonFullPathsRulePanel_units_bytes()); + sizeField.setText(""); + return; + } + long longValue = Long.valueOf(value); + if (longValue % BYTE_UNIT_CONVERSION != 0) { + unitsComboBox.setSelectedItem(Bundle.EditNonFullPathsRulePanel_units_bytes()); + sizeField.setText(value); //value stored in bytes is correct value to display + return; + } + longValue /= BYTE_UNIT_CONVERSION; + if (longValue % BYTE_UNIT_CONVERSION != 0) { + unitsComboBox.setSelectedItem(Bundle.EditNonFullPathsRulePanel_units_kilobytes()); + sizeField.setText(String.valueOf(longValue)); + return; + } + longValue /= BYTE_UNIT_CONVERSION; + if (longValue % BYTE_UNIT_CONVERSION != 0) { + unitsComboBox.setSelectedItem(Bundle.EditNonFullPathsRulePanel_units_megabytes()); + sizeField.setText(String.valueOf(longValue)); + return; + } + longValue /= BYTE_UNIT_CONVERSION; + unitsComboBox.setSelectedItem(Bundle.EditNonFullPathsRulePanel_units_gigabytes()); + sizeField.setText(String.valueOf(longValue)); + + } + + /** + * Set the modified within X days field + * + * @param minDays the number of days to include + */ + private void setModifiedWithin(Integer minDays) { + modifiedWithinTextField.setText(minDays == null ? "" : minDays.toString()); + modifiedWithinCheckbox.setSelected(!StringUtils.isBlank(modifiedWithinTextField.getText())); + modifiedWithinTextField.setEnabled(modifiedWithinCheckbox.isSelected()); + } + + /** + * Set the contents of a text area to display a list of Strings + * + * @param textArea the text area to set the text of + * @param list the list of Strings to display in the text area + */ + private void setTextArea(JTextArea textArea, List list) { String text = ""; - if (set != null) { - for (String s : set) { - text += s + System.getProperty("line.separator"); // NON-NLS - } + if (list != null) { + text = list.stream().map((s) -> s + System.getProperty("line.separator")).reduce(text, String::concat); // NON-NLS } textArea.setText(text); } + /** + * Set the extensions textField to display a list of extensions + * + * @param extensions the list of extensions to display + */ private void setExtensions(List extensions) { extensionsTextField.setText(""); String content = ""; if (extensions != null) { boolean first = true; - for (String ext : extensions) { + for (String ext : extensions) { content += (first ? "" : ",") + ext; first = false; } } + extensionsCheckbox.setSelected(!StringUtils.isBlank(content)); extensionsTextField.setText(content); } + /** * 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 @@ -158,228 +306,382 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - buttonGroup = new javax.swing.ButtonGroup(); - modifiedDateLabel = new javax.swing.JLabel(); daysIncludedLabel = new javax.swing.JLabel(); shouldSaveCheckBox = new javax.swing.JCheckBox(); shouldAlertCheckBox = new javax.swing.JCheckBox(); - extensionsLabel = new javax.swing.JLabel(); extensionsTextField = new javax.swing.JTextField(); - filenamesLabel = new javax.swing.JLabel(); - folderNamesLabel = new javax.swing.JLabel(); - fileSizeLabel = new javax.swing.JLabel(); descriptionTextField = new javax.swing.JTextField(); - descriptionLabel = new javax.swing.JLabel(); ruleNameLabel = new javax.swing.JLabel(); ruleNameTextField = new javax.swing.JTextField(); filenamesScrollPane = new javax.swing.JScrollPane(); folderNamesScrollPane = new javax.swing.JScrollPane(); - minSizeLabel = new javax.swing.JLabel(); minSizeTextField = new javax.swing.JFormattedTextField(); - maxSizeLabel = new javax.swing.JLabel(); maxSizeTextField = new javax.swing.JFormattedTextField(); - minDaysTextField = new javax.swing.JFormattedTextField(); - extensionsRadioButton = new javax.swing.JRadioButton(); - filenamesRadioButton = new javax.swing.JRadioButton(); - - org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.modifiedDateLabel.text")); // NOI18N + modifiedWithinTextField = new javax.swing.JFormattedTextField(); + userFolderNote = new javax.swing.JLabel(); + minSizeCheckbox = new javax.swing.JCheckBox(); + maxSizeCheckbox = new javax.swing.JCheckBox(); + modifiedWithinCheckbox = new javax.swing.JCheckBox(); + folderNamesCheckbox = new javax.swing.JCheckBox(); + fileNamesCheckbox = new javax.swing.JCheckBox(); + extensionsCheckbox = new javax.swing.JCheckBox(); + minSizeUnitsCombobox = new javax.swing.JComboBox<>(); + maxSizeUnitsCombobox = new javax.swing.JComboBox<>(); + jSeparator1 = new javax.swing.JSeparator(); + jSeparator2 = new javax.swing.JSeparator(); + descriptionLabel = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jLabel1 = new javax.swing.JLabel(); + extensionsInfoLabel = new javax.swing.JLabel(); + fileNamesInfoLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.daysIncludedLabel.text")); // NOI18N shouldSaveCheckBox.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(shouldSaveCheckBox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.shouldSaveCheckBox.text")); // NOI18N + shouldSaveCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + shouldSaveCheckBoxActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(shouldAlertCheckBox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.shouldAlertCheckBox.text")); // NOI18N shouldAlertCheckBox.setActionCommand(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand")); // NOI18N + shouldAlertCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + shouldAlertCheckBoxActionPerformed(evt); + } + }); - org.openide.awt.Mnemonics.setLocalizedText(extensionsLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsLabel.text")); // NOI18N - - extensionsTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsTextField.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(filenamesLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.filenamesLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(folderNamesLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.folderNamesLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(fileSizeLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.fileSizeLabel.text")); // NOI18N - - descriptionTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.descriptionTextField.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.descriptionLabel.text")); // NOI18N + extensionsTextField.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.ruleNameLabel.text")); // NOI18N + ruleNameLabel.setPreferredSize(new java.awt.Dimension(112, 14)); - ruleNameTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.ruleNameTextField.text")); // NOI18N + filenamesScrollPane.setToolTipText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.filenamesScrollPane.toolTipText")); // NOI18N + filenamesScrollPane.setEnabled(false); - org.openide.awt.Mnemonics.setLocalizedText(minSizeLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.minSizeLabel.text")); // NOI18N + folderNamesScrollPane.setEnabled(false); - minSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); - minSizeTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.minSizeTextField.text")); // NOI18N + minSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new DefaultToEmptyNumberFormatter(new java.text.DecimalFormat("#,###; ")))); + minSizeTextField.setEnabled(false); - org.openide.awt.Mnemonics.setLocalizedText(maxSizeLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.maxSizeLabel.text")); // NOI18N + maxSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new DefaultToEmptyNumberFormatter(new java.text.DecimalFormat("#,###; ")))); + maxSizeTextField.setEnabled(false); - maxSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); - maxSizeTextField.setText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.maxSizeTextField.text")); // NOI18N + modifiedWithinTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new DefaultToEmptyNumberFormatter(new java.text.DecimalFormat("#,###; ")))); + modifiedWithinTextField.setEnabled(false); - minDaysTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("####; ")))); + userFolderNote.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/info-icon-16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(userFolderNote, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.userFolderNote.text")); // NOI18N - buttonGroup.add(extensionsRadioButton); - extensionsRadioButton.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(extensionsRadioButton, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsRadioButton.text")); // NOI18N - extensionsRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsRadioButton.toolTipText")); // NOI18N - extensionsRadioButton.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(minSizeCheckbox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.minSizeCheckbox.text")); // NOI18N + minSizeCheckbox.setPreferredSize(new java.awt.Dimension(112, 23)); + minSizeCheckbox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - extensionsRadioButtonActionPerformed(evt); + minSizeCheckboxActionPerformed(evt); } }); - buttonGroup.add(filenamesRadioButton); - org.openide.awt.Mnemonics.setLocalizedText(filenamesRadioButton, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.filenamesRadioButton.text")); // NOI18N - filenamesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.filenamesRadioButton.toolTipText")); // NOI18N - filenamesRadioButton.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(maxSizeCheckbox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.maxSizeCheckbox.text")); // NOI18N + maxSizeCheckbox.setPreferredSize(new java.awt.Dimension(112, 23)); + maxSizeCheckbox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - filenamesRadioButtonActionPerformed(evt); + maxSizeCheckboxActionPerformed(evt); } }); + org.openide.awt.Mnemonics.setLocalizedText(modifiedWithinCheckbox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.modifiedWithinCheckbox.text")); // NOI18N + modifiedWithinCheckbox.setPreferredSize(new java.awt.Dimension(112, 23)); + modifiedWithinCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + modifiedWithinCheckboxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(folderNamesCheckbox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.folderNamesCheckbox.text")); // NOI18N + folderNamesCheckbox.setPreferredSize(new java.awt.Dimension(112, 23)); + folderNamesCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + folderNamesCheckboxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(fileNamesCheckbox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.fileNamesCheckbox.text")); // NOI18N + fileNamesCheckbox.setPreferredSize(new java.awt.Dimension(112, 23)); + fileNamesCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + fileNamesCheckboxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(extensionsCheckbox, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsCheckbox.text")); // NOI18N + extensionsCheckbox.setPreferredSize(new java.awt.Dimension(112, 23)); + extensionsCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + extensionsCheckboxActionPerformed(evt); + } + }); + + minSizeUnitsCombobox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { Bundle.EditNonFullPathsRulePanel_units_bytes(), Bundle.EditNonFullPathsRulePanel_units_kilobytes(), Bundle.EditNonFullPathsRulePanel_units_megabytes(), Bundle.EditNonFullPathsRulePanel_units_gigabytes()})); + minSizeUnitsCombobox.setEnabled(false); + + maxSizeUnitsCombobox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { Bundle.EditNonFullPathsRulePanel_units_bytes(), Bundle.EditNonFullPathsRulePanel_units_kilobytes(), Bundle.EditNonFullPathsRulePanel_units_megabytes(), Bundle.EditNonFullPathsRulePanel_units_gigabytes()})); + maxSizeUnitsCombobox.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.descriptionLabel.text")); // NOI18N + descriptionLabel.setPreferredSize(new java.awt.Dimension(112, 14)); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.jLabel2.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.jLabel1.text")); // NOI18N + + extensionsInfoLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/info-icon-16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(extensionsInfoLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.extensionsInfoLabel.text")); // NOI18N + + fileNamesInfoLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/info-icon-16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(fileNamesInfoLabel, org.openide.util.NbBundle.getMessage(EditNonFullPathsRulePanel.class, "EditNonFullPathsRulePanel.fileNamesInfoLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() + .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator2) + .addComponent(jSeparator1) .addGroup(layout.createSequentialGroup() - .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(extensionsRadioButton) - .addComponent(filenamesRadioButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(shouldAlertCheckBox) + .addComponent(shouldSaveCheckBox) + .addComponent(fileNamesCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 112, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(modifiedWithinCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(maxSizeCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(ruleNameLabel) - .addComponent(descriptionLabel) - .addComponent(extensionsLabel) - .addComponent(filenamesLabel) - .addComponent(folderNamesLabel) - .addComponent(fileSizeLabel) - .addComponent(modifiedDateLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(extensionsCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(ruleNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(minSizeCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(folderNamesCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(filenamesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(extensionsTextField, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(descriptionTextField, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(ruleNameTextField, javax.swing.GroupLayout.Alignment.TRAILING))) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(extensionsTextField, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(folderNamesScrollPane) + .addComponent(filenamesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(layout.createSequentialGroup() - .addGap(23, 23, 23) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(shouldSaveCheckBox) - .addComponent(shouldAlertCheckBox))) - .addGroup(layout.createSequentialGroup() - .addGap(108, 108, 108) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(minDaysTextField) - .addComponent(minSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(maxSizeLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(daysIncludedLabel)))) - .addGap(0, 236, Short.MAX_VALUE))) + .addComponent(fileNamesInfoLabel) + .addComponent(extensionsInfoLabel) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 522, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(userFolderNote) + .addGroup(layout.createSequentialGroup() + .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(minSizeUnitsCombobox, javax.swing.GroupLayout.PREFERRED_SIZE, 110, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(modifiedWithinTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(maxSizeUnitsCombobox, javax.swing.GroupLayout.PREFERRED_SIZE, 110, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(daysIncludedLabel)))))) + .addGap(0, 11, Short.MAX_VALUE))))) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {maxSizeTextField, minSizeTextField, modifiedWithinTextField}); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {descriptionLabel, extensionsCheckbox, fileNamesCheckbox, folderNamesCheckbox, maxSizeCheckbox, minSizeCheckbox, modifiedWithinCheckbox, ruleNameLabel}); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {maxSizeUnitsCombobox, minSizeUnitsCombobox}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addContainerGap() + .addGap(8, 8, 8) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) - .addComponent(ruleNameLabel) + .addComponent(ruleNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) - .addComponent(descriptionLabel) - .addComponent(descriptionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(descriptionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(extensionsTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(extensionsLabel) - .addComponent(extensionsRadioButton)) + .addComponent(extensionsCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(extensionsInfoLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(filenamesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 70, Short.MAX_VALUE) + .addComponent(filenamesScrollPane) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) - .addComponent(filenamesLabel) - .addComponent(filenamesRadioButton)) + .addComponent(fileNamesCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, Short.MAX_VALUE))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(fileNamesInfoLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(folderNamesLabel) - .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) - .addComponent(fileSizeLabel) - .addComponent(minSizeLabel) + .addComponent(folderNamesScrollPane) + .addGroup(layout.createSequentialGroup() + .addComponent(folderNamesCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(userFolderNote, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(maxSizeLabel) - .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(6, 6, 6) + .addComponent(minSizeCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(minSizeUnitsCombobox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(maxSizeCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(maxSizeUnitsCombobox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) - .addComponent(modifiedDateLabel) .addComponent(daysIncludedLabel) - .addComponent(minDaysTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(14, 14, 14) - .addComponent(shouldAlertCheckBox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(modifiedWithinTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(modifiedWithinCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(shouldSaveCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(shouldAlertCheckBox) .addContainerGap()) ); }// //GEN-END:initComponents - private void extensionsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_extensionsRadioButtonActionPerformed - filenamesTextArea.setEnabled(false); - filenamesTextArea.setForeground(Color.LIGHT_GRAY); - extensionsTextField.setEnabled(true); - extensionsTextField.setForeground(Color.BLACK); - }//GEN-LAST:event_extensionsRadioButtonActionPerformed + private void extensionsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_extensionsCheckboxActionPerformed + if (fileNamesCheckbox.isSelected() && extensionsCheckbox.isSelected()) { + fileNamesCheckbox.setSelected(false); + } + updateExclusiveConditions(); + setOkButton(); + }//GEN-LAST:event_extensionsCheckboxActionPerformed - private void filenamesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filenamesRadioButtonActionPerformed - filenamesTextArea.setEnabled(true); - filenamesTextArea.setForeground(Color.BLACK); - extensionsTextField.setEnabled(false); - extensionsTextField.setForeground(Color.LIGHT_GRAY); - }//GEN-LAST:event_filenamesRadioButtonActionPerformed + private void fileNamesCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileNamesCheckboxActionPerformed + if (fileNamesCheckbox.isSelected() && extensionsCheckbox.isSelected()) { + extensionsCheckbox.setSelected(false); + } + updateExclusiveConditions(); + setOkButton(); + }//GEN-LAST:event_fileNamesCheckboxActionPerformed + + private void folderNamesCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_folderNamesCheckboxActionPerformed + folderNamesScrollPane.setEnabled(folderNamesCheckbox.isSelected()); + folderNamesTextArea.setEditable(folderNamesCheckbox.isSelected()); + folderNamesTextArea.setEnabled(folderNamesCheckbox.isSelected()); + updateTextAreaBackgroundColor(folderNamesTextArea); + setOkButton(); + + }//GEN-LAST:event_folderNamesCheckboxActionPerformed + + private void minSizeCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_minSizeCheckboxActionPerformed + minSizeTextField.setEnabled(minSizeCheckbox.isSelected()); + minSizeUnitsCombobox.setEnabled(minSizeCheckbox.isSelected()); + setOkButton(); + }//GEN-LAST:event_minSizeCheckboxActionPerformed + + private void maxSizeCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_maxSizeCheckboxActionPerformed + maxSizeTextField.setEnabled(maxSizeCheckbox.isSelected()); + maxSizeUnitsCombobox.setEnabled(maxSizeCheckbox.isSelected()); + setOkButton(); + }//GEN-LAST:event_maxSizeCheckboxActionPerformed + + private void modifiedWithinCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_modifiedWithinCheckboxActionPerformed + modifiedWithinTextField.setEnabled(modifiedWithinCheckbox.isSelected()); + setOkButton(); + }//GEN-LAST:event_modifiedWithinCheckboxActionPerformed + + private void shouldSaveCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shouldSaveCheckBoxActionPerformed + setOkButton(); + }//GEN-LAST:event_shouldSaveCheckBoxActionPerformed + + private void shouldAlertCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shouldAlertCheckBoxActionPerformed + setOkButton(); + }//GEN-LAST:event_shouldAlertCheckBoxActionPerformed + + /** + * Update the background area of a JTextArea to reflect whether it is + * enabled or not + * + * @param textArea the textArea to update the background color of + */ + private static void updateTextAreaBackgroundColor(JTextArea textArea) { + if (textArea.isEnabled()) { + textArea.setBackground(Color.WHITE); + } else { + textArea.setBackground(DISABLED_COLOR); + } + } + + /** + * Update the enabled status of conditions which are exclusive of each other + * when either one is changed + */ + private void updateExclusiveConditions() { + extensionsTextField.setEnabled(extensionsCheckbox.isSelected()); + filenamesScrollPane.setEnabled(fileNamesCheckbox.isSelected()); + fileNamesTextArea.setEditable(fileNamesCheckbox.isSelected()); + fileNamesTextArea.setEnabled(fileNamesCheckbox.isSelected()); + updateTextAreaBackgroundColor(fileNamesTextArea); + } // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.ButtonGroup buttonGroup; private javax.swing.JLabel daysIncludedLabel; private javax.swing.JLabel descriptionLabel; private javax.swing.JTextField descriptionTextField; - private javax.swing.JLabel extensionsLabel; - private javax.swing.JRadioButton extensionsRadioButton; + private javax.swing.JCheckBox extensionsCheckbox; + private javax.swing.JLabel extensionsInfoLabel; private javax.swing.JTextField extensionsTextField; - private javax.swing.JLabel fileSizeLabel; - private javax.swing.JLabel filenamesLabel; - private javax.swing.JRadioButton filenamesRadioButton; + private javax.swing.JCheckBox fileNamesCheckbox; + private javax.swing.JLabel fileNamesInfoLabel; private javax.swing.JScrollPane filenamesScrollPane; - private javax.swing.JLabel folderNamesLabel; + private javax.swing.JCheckBox folderNamesCheckbox; private javax.swing.JScrollPane folderNamesScrollPane; - private javax.swing.JLabel maxSizeLabel; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JSeparator jSeparator2; + private javax.swing.JCheckBox maxSizeCheckbox; private javax.swing.JFormattedTextField maxSizeTextField; - private javax.swing.JFormattedTextField minDaysTextField; - private javax.swing.JLabel minSizeLabel; + private javax.swing.JComboBox maxSizeUnitsCombobox; + private javax.swing.JCheckBox minSizeCheckbox; private javax.swing.JFormattedTextField minSizeTextField; - private javax.swing.JLabel modifiedDateLabel; + private javax.swing.JComboBox minSizeUnitsCombobox; + private javax.swing.JCheckBox modifiedWithinCheckbox; + private javax.swing.JFormattedTextField modifiedWithinTextField; private javax.swing.JLabel ruleNameLabel; private javax.swing.JTextField ruleNameTextField; private javax.swing.JCheckBox shouldAlertCheckBox; private javax.swing.JCheckBox shouldSaveCheckBox; + private javax.swing.JLabel userFolderNote; // End of variables declaration//GEN-END:variables + /** + * Set the name description and should alert / should save checkboxes + * + * @param ruleName the name of the rule + * @param rule the LogicalImagerRule + */ private void setRule(String ruleName, LogicalImagerRule rule) { ruleNameTextField.setText(ruleName); descriptionTextField.setText(rule.getDescription()); @@ -391,12 +693,49 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { * Sets whether or not the OK button should be enabled based upon other UI * elements */ - private void setOkButton() { + void setOkButton() { if (this.okButton != null) { - this.okButton.setEnabled(true); + this.okButton.setEnabled(!StringUtils.isBlank(ruleNameTextField.getText()) && atLeastOneConditionSet() && (shouldAlertCheckBox.isSelected() || shouldSaveCheckBox.isSelected())); } } + /** + * Checks that at least one condition has been selected and set to have a + * value + * + * @return true if at least one condition is set, false otherwise + */ + private boolean atLeastOneConditionSet() { + try { + return (extensionsCheckbox.isSelected() && !StringUtils.isBlank(extensionsTextField.getText()) && !validateExtensions(extensionsTextField).isEmpty()) + || (fileNamesCheckbox.isSelected() && !StringUtils.isBlank(fileNamesTextArea.getText())) + || (folderNamesCheckbox.isSelected() && !StringUtils.isBlank(folderNamesTextArea.getText())) + || (minSizeCheckbox.isSelected() && !StringUtils.isBlank(minSizeTextField.getText()) && isNonZeroLong(minSizeTextField.getText())) + || (maxSizeCheckbox.isSelected() && !StringUtils.isBlank(maxSizeTextField.getText()) && isNonZeroLong(maxSizeTextField.getText())) + || (modifiedWithinCheckbox.isSelected() && !StringUtils.isBlank(modifiedWithinTextField.getText())); + } catch (IOException ex) { + logger.log(Level.WARNING, "Invalid contents of extensionsTextField", ex); + return false; + } + } + + /** + * Check that value could be a non zero long + * + * @param numberString the string to check + * + * @return true if the value is a non-zero long + */ + private boolean isNonZeroLong(String numberString) { + Long value = 0L; + try { + value = Long.parseLong(numberString); + } catch (NumberFormatException ignored) { + //The string was not a number, this method will return false becaue the value is still 0L + } + return (value != 0); + } + /** * Gets the JOptionPane that is used to contain this panel if there is one * @@ -434,6 +773,14 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { this.setOkButton(); } + /** + * Convert the contents of this panel to a rule and return it as well as its + * name. + * + * @return an ImmutablePair containing the name of the rule and the rule + * + * @throws IOException + */ @NbBundle.Messages({ "EditNonFullPathsRulePanel.modifiedDaysNotPositiveException=Modified days must be a positive", "# {0} - message", @@ -446,34 +793,31 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { "EditNonFullPathsRulePanel.maxFileSizeMustBeNumberException=Maximum file size must be a number: {0}", "# {0} - maxFileSize", "# {1} - minFileSize", - "EditNonFullPathsRulePanel.maxFileSizeSmallerThanMinException=Maximum file size: {0} must be bigger than minimum file size: {1}", + "EditNonFullPathsRulePanel.maxFileSizeSmallerThanMinException=Maximum file size: {0} bytes must be bigger than minimum file size: {1} bytes", "EditNonFullPathsRulePanel.fileNames=File names", - "EditNonFullPathsRulePanel.folderNames=Folder names", - }) + "EditNonFullPathsRulePanel.folderNames=Folder names",}) ImmutablePair toRule() throws IOException { String ruleName = EditRulePanel.validRuleName(ruleNameTextField.getText()); - List extensions = validateExtensions(extensionsTextField); - List filenames = EditRulePanel.validateTextList(filenamesTextArea, Bundle.EditNonFullPathsRulePanel_fileNames()); - List folderNames = EditRulePanel.validateTextList(folderNamesTextArea, Bundle.EditNonFullPathsRulePanel_folderNames()); - + List folderNames = folderNamesCheckbox.isSelected() ? EditRulePanel.validateTextList(folderNamesTextArea, Bundle.EditNonFullPathsRulePanel_folderNames()) : null; + LogicalImagerRule.Builder builder = new LogicalImagerRule.Builder(); builder.getName(ruleName) .getDescription(descriptionTextField.getText()) .getShouldAlert(shouldAlertCheckBox.isSelected()) .getShouldSave(shouldSaveCheckBox.isSelected()) .getPaths(folderNames); - - if (extensionsRadioButton.isSelected()) { - builder.getExtensions(extensions); - } else { - builder.getFilenames(filenames); + + if (extensionsCheckbox.isSelected()) { + builder.getExtensions(validateExtensions(extensionsTextField)); + } else if (fileNamesCheckbox.isSelected()) { + builder.getFilenames(EditRulePanel.validateTextList(fileNamesTextArea, Bundle.EditNonFullPathsRulePanel_fileNames())); } - + int minDays; - if (!isBlank(minDaysTextField.getText())) { + if (modifiedWithinCheckbox.isSelected() && !isBlank(modifiedWithinTextField.getText())) { try { - minDaysTextField.commitEdit(); - minDays = ((Number)minDaysTextField.getValue()).intValue(); + modifiedWithinTextField.commitEdit(); + minDays = ((Number) modifiedWithinTextField.getValue()).intValue(); if (minDays < 0) { throw new IOException(Bundle.EditNonFullPathsRulePanel_modifiedDaysNotPositiveException()); } @@ -482,61 +826,72 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { throw new IOException(Bundle.EditNonFullPathsRulePanel_modifiedDaysMustBeNumberException(ex.getMessage()), ex); } } - - int minFileSize = 0; - if (!isBlank(minSizeTextField.getText())) { + + long minFileSize = 0; + if (minSizeCheckbox.isSelected() && !isBlank(minSizeTextField.getText())) { try { minSizeTextField.commitEdit(); - minFileSize = ((Number)minSizeTextField.getValue()).intValue(); + minFileSize = ((Number) minSizeTextField.getValue()).longValue(); if (minFileSize < 0) { throw new IOException(Bundle.EditNonFullPathsRulePanel_minFileSizeNotPositiveException()); } + minFileSize = convertToBytes(minFileSize, minSizeUnitsCombobox.getItemAt(minSizeUnitsCombobox.getSelectedIndex())); } catch (NumberFormatException | ParseException ex) { throw new IOException(Bundle.EditNonFullPathsRulePanel_minFileSizeMustBeNumberException(ex.getMessage()), ex); } } - - int maxFileSize = 0; - if (!isBlank(maxSizeTextField.getText())) { + + long maxFileSize = 0; + if (maxSizeCheckbox.isSelected() && !isBlank(maxSizeTextField.getText())) { try { maxSizeTextField.commitEdit(); - maxFileSize = ((Number)maxSizeTextField.getValue()).intValue(); + maxFileSize = ((Number) maxSizeTextField.getValue()).longValue(); if (maxFileSize < 0) { throw new IOException(Bundle.EditNonFullPathsRulePanel_maxFileSizeNotPositiveException()); } + maxFileSize = convertToBytes(maxFileSize, maxSizeUnitsCombobox.getItemAt(maxSizeUnitsCombobox.getSelectedIndex())); } catch (NumberFormatException | ParseException ex) { throw new IOException(Bundle.EditNonFullPathsRulePanel_maxFileSizeMustBeNumberException(ex.getMessage()), ex); } } - + if (maxFileSize != 0 && (maxFileSize < minFileSize)) { throw new IOException(Bundle.EditNonFullPathsRulePanel_maxFileSizeSmallerThanMinException(maxFileSize, minFileSize)); } - if (minFileSize != 0) { + if (minSizeCheckbox.isSelected() && minFileSize != 0) { builder.getMinFileSize(minFileSize); } - if (maxFileSize != 0) { + if (maxSizeCheckbox.isSelected() && maxFileSize != 0) { builder.getMaxFileSize(maxFileSize); } - + LogicalImagerRule rule = builder.build(); return new ImmutablePair<>(ruleName, rule); } + /** + * Validate the contents of the extensions textField contain a list of comma + * seperated strings which could be file extensions + * + * @param textField the JTextField which contains the extensions + * + * @return a List containing a string for each possible extension specified. + * + * @throws IOException + */ @NbBundle.Messages({ - "EditNonFullPathsRulePanel.emptyExtensionException=Extensions cannot have an empty entry", - }) + "EditNonFullPathsRulePanel.emptyExtensionException=Extensions cannot have an empty entry",}) private List validateExtensions(JTextField textField) throws IOException { if (isBlank(textField.getText())) { return null; } List extensions = new ArrayList<>(); for (String extension : textField.getText().split(",")) { - extension = strip(extension); - if (extension.isEmpty()) { + String strippedExtension = strip(extension); + if (strippedExtension.isEmpty()) { throw new IOException(Bundle.EditNonFullPathsRulePanel_emptyExtensionException()); } - extensions.add(extension); + extensions.add(strippedExtension); } if (extensions.isEmpty()) { return null; diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditRulePanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditRulePanel.java index c54740f5e3..5512af2b58 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditRulePanel.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,17 +38,20 @@ import org.sleuthkit.autopsy.corecomponents.TextPrompt; */ final class EditRulePanel extends JPanel { - private EditFullPathsRulePanel editFullPathsRulePanel = null; - private EditNonFullPathsRulePanel editNonFullPathsRulePanel = null; + private static final long serialVersionUID = 1L; + private final EditFullPathsRulePanel editFullPathsRulePanel; + private final EditNonFullPathsRulePanel editNonFullPathsRulePanel; /** * Creates new form EditRulePanel */ - EditRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule) { + EditRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule) { if (rule.getFullPaths() != null && rule.getFullPaths().size() > 0) { editFullPathsRulePanel = new EditFullPathsRulePanel(okButton, cancelButton, ruleName, rule, true); + editNonFullPathsRulePanel = null; } else { editNonFullPathsRulePanel = new EditNonFullPathsRulePanel(okButton, cancelButton, ruleName, rule, true); + editFullPathsRulePanel = null; } } @@ -66,10 +69,10 @@ final class EditRulePanel extends JPanel { ruleMap = editFullPathsRulePanel.toRule(); } else { ruleMap = editNonFullPathsRulePanel.toRule(); - } + } return ruleMap; } - + static void setTextFieldPrompts(JTextComponent textField, String text) { /** * Add text prompt to the text field. @@ -78,41 +81,45 @@ final class EditRulePanel extends JPanel { if (textField instanceof JTextArea) { textPrompt = new TextPrompt(text, textField, BorderLayout.NORTH); } else { - textPrompt = new TextPrompt(text, textField); + textPrompt = new TextPrompt(text, textField); } - + /** * Sets the foreground color and transparency of the text prompt. */ textPrompt.setForeground(Color.LIGHT_GRAY); textPrompt.changeAlpha(0.9f); // Mostly opaque } - + @NbBundle.Messages({ - "EditRulePanel.validateRuleNameExceptionMsg=Rule name cannot be empty" - }) + "EditRulePanel.emptyRuleName.message=Rule name cannot be empty", + "# {0} - ruleName", + "EditRulePanel.reservedRuleName.message=Rule name \"{0}\" is reserved for use with a predefined rule"}) static String validRuleName(String name) throws IOException { if (name.isEmpty()) { - throw new IOException(Bundle.EditRulePanel_validateRuleNameExceptionMsg()); + throw new IOException(Bundle.EditRulePanel_emptyRuleName_message()); } + if (name.equals(EncryptionProgramsRule.getName())) { + throw new IOException(Bundle.EditRulePanel_reservedRuleName_message(name)); + } + //TODO JIRA-5239 check if rule name exists already return name; } - + @NbBundle.Messages({ "# {0} - fieldName", - "EditRulePanel.blankLineException={0} cannot have a blank line", - }) + "EditRulePanel.blankLineException={0} cannot have a blank line",}) static List validateTextList(JTextArea textArea, String fieldName) throws IOException { if (isBlank(textArea.getText())) { return null; } List list = new ArrayList<>(); for (String line : textArea.getText().split("\\n")) { // NON-NLS - line = strip(line); - if (line.isEmpty()) { + String strippedLine = strip(line); + if (strippedLine.isEmpty()) { throw new IOException(Bundle.EditRulePanel_blankLineException(fieldName)); } - list.add(line); + list.add(strippedLine); } if (list.isEmpty()) { return null; diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EncryptionProgramsRule.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EncryptionProgramsRule.java index 3d5d6422bd..f94a958add 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EncryptionProgramsRule.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EncryptionProgramsRule.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ import java.util.List; import org.openide.util.NbBundle; /** - * Encryption programs rule + * Encryption programs rule */ @NbBundle.Messages({ "EncryptionProgramsRule.encryptionProgramsRuleName=Encryption Programs", @@ -34,14 +34,15 @@ final class EncryptionProgramsRule { private static final String ENCRYPTION_PROGRAMS_RULE_NAME = Bundle.EncryptionProgramsRule_encryptionProgramsRuleName(); private static final String ENCRYPTION_PROGRAMS_RULE_DESCRIPTION = Bundle.EncryptionProgramsRule_encryptionProgramsRuleDescription(); private static final List FILENAMES = new ArrayList<>(); - - private EncryptionProgramsRule() {} - - // TODO: Add more files here + + private EncryptionProgramsRule() { + //private no arg constructor intentionally blank + } + static { // Truecrypt FILENAMES.add("truecrypt.exe"); // NON-NLS - + // AxCrypt FILENAMES.add("AxCrypt.exe"); // NON-NLS @@ -62,7 +63,7 @@ final class EncryptionProgramsRule { FILENAMES.add("gpgsm.exe"); // NON-NLS FILENAMES.add("gpgtar.exe"); // NON-NLS FILENAMES.add("gpgv.exe"); // NON-NLS - + // Symantec Encryption Desktop aka PGP FILENAMES.add("PGP Viewer.exe"); // NON-NLS FILENAMES.add("PGPcbt64.exe"); // NON-NLS @@ -87,7 +88,7 @@ final class EncryptionProgramsRule { static String getDescription() { return ENCRYPTION_PROGRAMS_RULE_DESCRIPTION; } - + static List getFilenames() { return FILENAMES; } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java index e0dd24a787..685a2c8d35 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,19 +35,19 @@ class LogicalImagerConfig { private String version; @SerializedName("finalize-image-writer") - @Expose(serialize = true) + @Expose(serialize = true) private boolean finalizeImageWriter; @SerializedName("rule-sets") - @Expose(serialize = true) + @Expose(serialize = true) private List ruleSets; - + LogicalImagerConfig() { this.version = CURRENT_VERSION; this.finalizeImageWriter = false; this.ruleSets = new ArrayList<>(); } - + LogicalImagerConfig( boolean finalizeImageWriter, List ruleSets @@ -93,5 +93,5 @@ class LogicalImagerConfig { void setRuleSet(List ruleSets) { this.ruleSets = ruleSets; - } + } } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java index 3624f3d81c..a9c049ce08 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,8 +38,7 @@ import org.openide.util.NbBundle; "LogicalImagerConfigDeserializer.missingRuleSetException=Missing rule-set", "# {0} - key", "LogicalImagerConfigDeserializer.unsupportedKeyException=Unsupported key: {0}", - "LogicalImagerConfigDeserializer.fullPathsException=A rule with full-paths cannot have other rule definitions", -}) + "LogicalImagerConfigDeserializer.fullPathsException=A rule with full-paths cannot have other rule definitions",}) class LogicalImagerConfigDeserializer implements JsonDeserializer { @Override @@ -64,7 +63,7 @@ class LogicalImagerConfigDeserializer implements JsonDeserializer ruleSets = new ArrayList<>(); - for (JsonElement element: asJsonArray) { + for (JsonElement element : asJsonArray) { String setName = null; List rules = null; JsonObject asJsonObject = element.getAsJsonObject(); @@ -77,11 +76,11 @@ class LogicalImagerConfigDeserializer implements JsonDeserializer parseRules(JsonArray asJsonArray) { List rules = new ArrayList<>(); - for (JsonElement element: asJsonArray) { + for (JsonElement element : asJsonArray) { String key1; Boolean shouldSave = false; Boolean shouldAlert = true; @@ -91,12 +90,12 @@ class LogicalImagerConfigDeserializer implements JsonDeserializer paths = null; List fullPaths = null; List filenames = null; - Integer minFileSize = null; - Integer maxFileSize = null; + Long minFileSize = null; + Long maxFileSize = null; Integer minDays = null; Integer minDate = null; Integer maxDate = null; - + Set> entrySet = element.getAsJsonObject().entrySet(); for (Map.Entry entry1 : entrySet) { @@ -149,11 +148,11 @@ class LogicalImagerConfigDeserializer implements JsonDeserializer sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,51 +31,51 @@ import java.util.Map; */ class LogicalImagerRule { - @Expose(serialize = true) + @Expose(serialize = true) private final Boolean shouldAlert; - @Expose(serialize = true) + @Expose(serialize = true) private final Boolean shouldSave; - @Expose(serialize = true) + @Expose(serialize = true) private final String name; - @Expose(serialize = true) + @Expose(serialize = true) private final String description; - @Expose(serialize = true) + @Expose(serialize = true) private List extensions = new ArrayList<>(); @SerializedName("file-names") - @Expose(serialize = true) + @Expose(serialize = true) private List filenames = new ArrayList<>(); @SerializedName("folder-names") - @Expose(serialize = true) + @Expose(serialize = true) private List paths = new ArrayList<>(); @SerializedName("full-paths") - @Expose(serialize = true) + @Expose(serialize = true) private List fullPaths = new ArrayList<>(); - @SerializedName("size-range") - @Expose(serialize = true) - final private Map sizeRange = new HashMap<>(); - @SerializedName("date-range") - @Expose(serialize = true) + @SerializedName("size-range") + @Expose(serialize = true) + final private Map sizeRange = new HashMap<>(); + @SerializedName("date-range") + @Expose(serialize = true) final private Map dateRange = new HashMap<>(); - + // The following fields should not be serialized, internal use only - @Expose(serialize = false) - private Integer minFileSize; - @Expose(serialize = false) - private Integer maxFileSize; - @Expose(serialize = false) + @Expose(serialize = false) + private Long minFileSize; + @Expose(serialize = false) + private Long maxFileSize; + @Expose(serialize = false) private Integer minDays; - @Expose(serialize = false) + @Expose(serialize = false) private Integer minDate; - @Expose(serialize = false) + @Expose(serialize = false) private Integer maxDate; - + private LogicalImagerRule(Boolean shouldAlert, Boolean shouldSave, String name, String description, List extensions, List filenames, List paths, List fullPaths, - Integer minFileSize, - Integer maxFileSize, + Long minFileSize, + Long maxFileSize, Integer minDays, Integer minDate, Integer maxDate @@ -88,7 +88,7 @@ class LogicalImagerRule { this.filenames = filenames; this.paths = paths; this.fullPaths = fullPaths; - + this.sizeRange.put("min", minFileSize); // NON-NLS this.minFileSize = minFileSize; this.sizeRange.put("max", maxFileSize); // NON-NLS @@ -107,7 +107,7 @@ class LogicalImagerRule { this.description = null; this.name = null; } - + Boolean isShouldAlert() { return shouldAlert; } @@ -140,11 +140,11 @@ class LogicalImagerRule { return fullPaths; } - Integer getMinFileSize() { + Long getMinFileSize() { return minFileSize; } - Integer getMaxFileSize() { + Long getMaxFileSize() { return maxFileSize; } @@ -164,87 +164,88 @@ class LogicalImagerRule { * Builder class */ static class Builder { + private Boolean shouldAlert = null; private Boolean shouldSave = null; private String name = null; private String description = null; private List extensions = null; - private List filenames = null; - private List paths = null; - private List fullPaths = null; - private Integer minFileSize = null; - private Integer maxFileSize = null; + private List filenames = null; + private List paths = null; + private List fullPaths = null; + private Long minFileSize = null; + private Long maxFileSize = null; private Integer minDays = null; private Integer minDate = null; private Integer maxDate = null; - + Builder getShouldAlert(boolean shouldAlert) { this.shouldAlert = shouldAlert; return this; } - + Builder getShouldSave(boolean shouldSave) { this.shouldSave = shouldSave; return this; } - + Builder getName(String name) { this.name = name; return this; } - + Builder getDescription(String description) { this.description = description; return this; } - + Builder getExtensions(List extensions) { this.extensions = extensions; return this; } - + Builder getFilenames(List filenames) { this.filenames = filenames; return this; } - + Builder getPaths(List paths) { this.paths = paths; return this; } - + Builder getFullPaths(List fullPaths) { this.fullPaths = fullPaths; return this; } - - Builder getMinFileSize(Integer minFileSize) { + + Builder getMinFileSize(Long minFileSize) { this.minFileSize = minFileSize; return this; } - - Builder getMaxFileSize(Integer maxFileSize) { + + Builder getMaxFileSize(Long maxFileSize) { this.maxFileSize = maxFileSize; return this; } - + Builder getMinDays(Integer minDays) { this.minDays = minDays; return this; } - + Builder getMinDate(Integer minDate) { this.minDate = minDate; return this; } - + Builder getMaxDate(Integer maxDate) { this.maxDate = maxDate; return this; } - + LogicalImagerRule build() { - return new LogicalImagerRule(shouldAlert, shouldSave, name, description, + return new LogicalImagerRule(shouldAlert, shouldSave, name, description, extensions, filenames, paths, fullPaths, minFileSize, maxFileSize, minDays, minDate, maxDate diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerRuleSet.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerRuleSet.java index c431674a56..c4fbb921bb 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerRuleSet.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerRuleSet.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,11 +28,11 @@ import java.util.List; class LogicalImagerRuleSet { @SerializedName("set-name") - @Expose(serialize = true) + @Expose(serialize = true) final private String setName; @SerializedName("rules") - @Expose(serialize = true) + @Expose(serialize = true) private final List rules; LogicalImagerRuleSet(String setName, List rules) { @@ -49,8 +49,8 @@ class LogicalImagerRuleSet { } /* - * Find a rule with the given name. Return null if not found. - */ + * Find a rule with the given name. Return null if not found. + */ LogicalImagerRule find(String name) { for (LogicalImagerRule rule : rules) { if (rule.getName().equals(name)) { diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRuleSetPanel.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRulePanel.form similarity index 77% rename from Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRuleSetPanel.form rename to Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRulePanel.form index 83b7e76cea..71b01a4884 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRuleSetPanel.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRulePanel.form @@ -22,7 +22,9 @@
- + + +
@@ -37,7 +39,10 @@ - + + + + @@ -49,18 +54,15 @@ - + - - - - - + + @@ -80,10 +82,12 @@
- +
+ + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRuleSetPanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRulePanel.java similarity index 69% rename from Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRuleSetPanel.java rename to Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRulePanel.java index 1e54b35763..3b3da6dc5a 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRuleSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/NewRulePanel.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,16 +20,20 @@ package org.sleuthkit.autopsy.logicalimager.configuration; import java.awt.BorderLayout; import java.io.IOException; +import java.util.logging.Level; import javax.swing.JButton; import javax.swing.JPanel; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.Logger; /** * New rule set panel */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -class NewRuleSetPanel extends javax.swing.JPanel { +class NewRulePanel extends javax.swing.JPanel { + private static final Logger logger = Logger.getLogger(NewRulePanel.class.getName()); private final JPanel nonFullPathsJPanel; private final EditNonFullPathsRulePanel editNonFullPathsRulePanel; private final JPanel fullPathsPanel; @@ -38,13 +42,13 @@ class NewRuleSetPanel extends javax.swing.JPanel { /** * Creates new form NewRuleSetPanel */ - NewRuleSetPanel(JButton okButton, JButton cancelButton) { + NewRulePanel(JButton okButton, JButton cancelButton) { initComponents(); - + nonFullPathsJPanel = createPanel(); editNonFullPathsRulePanel = new EditNonFullPathsRulePanel(okButton, cancelButton, "", new LogicalImagerRule(), false); nonFullPathsJPanel.add(editNonFullPathsRulePanel, BorderLayout.NORTH); - + fullPathsPanel = createPanel(); editFullPathsRulePanel = new EditFullPathsRulePanel(okButton, cancelButton, "", new LogicalImagerRule(), false); fullPathsPanel.add(editFullPathsRulePanel, BorderLayout.NORTH); @@ -52,12 +56,13 @@ class NewRuleSetPanel extends javax.swing.JPanel { sharedLayeredPane.add(nonFullPathsJPanel, Integer.valueOf(0)); sharedLayeredPane.add(fullPathsPanel, Integer.valueOf(1)); nonFullPathsJPanel.setVisible(true); + ruleDescription.setText(Bundle.NewRuleSetPanel_attributeRule_description()); fullPathsPanel.setVisible(false); } private JPanel createPanel() { JPanel panel = new JPanel(new BorderLayout()); - panel.setSize(800, 640); + panel.setSize(800, 650); return panel; } @@ -66,6 +71,10 @@ class NewRuleSetPanel extends javax.swing.JPanel { * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ + @Messages({"NewRuleSetPanel.attributeRule.name=Attribute", + "NewRuleSetPanel.fullPathRule.name=Full Path", + "NewRuleSetPanel.attributeRule.description=Search for files based on one or more attributes or metadata fields.", + "NewRuleSetPanel.fullPathRule.description=Search for files based on full exact match path."}) @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { @@ -73,11 +82,12 @@ class NewRuleSetPanel extends javax.swing.JPanel { chooseLabel = new javax.swing.JLabel(); chooseComboBox = new javax.swing.JComboBox<>(); sharedLayeredPane = new javax.swing.JLayeredPane(); + ruleDescription = new javax.swing.JLabel(); - org.openide.awt.Mnemonics.setLocalizedText(chooseLabel, org.openide.util.NbBundle.getMessage(NewRuleSetPanel.class, "NewRuleSetPanel.chooseLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(chooseLabel, org.openide.util.NbBundle.getMessage(NewRulePanel.class, "NewRulePanel.chooseLabel.text")); // NOI18N chooseComboBox.setMaximumRowCount(2); - chooseComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "By Attribute", "By Full Path" })); + chooseComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] {Bundle.NewRuleSetPanel_attributeRule_name(), Bundle.NewRuleSetPanel_fullPathRule_name()})); chooseComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { chooseComboBoxActionPerformed(evt); @@ -92,7 +102,7 @@ class NewRuleSetPanel extends javax.swing.JPanel { ); sharedLayeredPaneLayout.setVerticalGroup( sharedLayeredPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 373, Short.MAX_VALUE) + .addGap(0, 467, Short.MAX_VALUE) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -104,7 +114,9 @@ class NewRuleSetPanel extends javax.swing.JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(chooseLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(chooseComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap(716, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ruleDescription, javax.swing.GroupLayout.PREFERRED_SIZE, 562, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(114, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(sharedLayeredPane) @@ -116,7 +128,9 @@ class NewRuleSetPanel extends javax.swing.JPanel { .addContainerGap() .addComponent(chooseLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(chooseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chooseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(ruleDescription, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(sharedLayeredPane) .addContainerGap()) @@ -125,18 +139,29 @@ class NewRuleSetPanel extends javax.swing.JPanel { private void chooseComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chooseComboBoxActionPerformed int index = chooseComboBox.getSelectedIndex(); - if (index == 0) { + if (chooseComboBox.getItemAt(index).equals(Bundle.NewRuleSetPanel_attributeRule_name())) { nonFullPathsJPanel.setVisible(true); + editNonFullPathsRulePanel.setOkButton(); + ruleDescription.setText(Bundle.NewRuleSetPanel_attributeRule_description()); fullPathsPanel.setVisible(false); - } else { - nonFullPathsJPanel.setVisible(false); + } else if (chooseComboBox.getItemAt(index).equals(Bundle.NewRuleSetPanel_fullPathRule_name())) { + nonFullPathsJPanel.setVisible(false); + ruleDescription.setText(Bundle.NewRuleSetPanel_fullPathRule_description()); fullPathsPanel.setVisible(true); + editFullPathsRulePanel.setOkButton(); + } else { + logger.log(Level.WARNING, "Rule type selected was not one of the expected rule types"); + nonFullPathsJPanel.setVisible(false); + fullPathsPanel.setVisible(false); + ruleDescription.setText(""); } + }//GEN-LAST:event_chooseComboBoxActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JComboBox chooseComboBox; private javax.swing.JLabel chooseLabel; + private javax.swing.JLabel ruleDescription; private javax.swing.JLayeredPane sharedLayeredPane; // End of variables declaration//GEN-END:variables @@ -146,7 +171,7 @@ class NewRuleSetPanel extends javax.swing.JPanel { ruleMap = editNonFullPathsRulePanel.toRule(); } else { ruleMap = editFullPathsRulePanel.toRule(); - } + } return ruleMap; } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java index a76b889980..19ff3079e1 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2013-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,26 +36,25 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** - * A runnable that - * - copy the logical image folder to a destination folder - * - add alert.txt and users.txt files to report - * - add an image data source to the case database. + * A runnable that - copy the logical image folder to a destination folder - add + * alert.txt and users.txt files to report - add an image data source to the + * case database. */ final class AddLogicalImageTask extends AddMultipleImageTask { - private final static Logger logger = Logger.getLogger(AddLogicalImageTask.class.getName()); + private final static Logger logger = Logger.getLogger(AddLogicalImageTask.class.getName()); private final static String ALERT_TXT = "alert.txt"; //NON-NLS private final static String USERS_TXT = "users.txt"; //NON-NLS private final File src; private final File dest; private final DataSourceProcessorCallback callback; private final DataSourceProcessorProgressMonitor progressMonitor; - - AddLogicalImageTask(String deviceId, - List imagePaths, - String timeZone, + + AddLogicalImageTask(String deviceId, + List imagePaths, + String timeZone, File src, File dest, - DataSourceProcessorProgressMonitor progressMonitor, + DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback ) throws NoCurrentCaseException { super(deviceId, imagePaths, timeZone, progressMonitor, callback); @@ -64,11 +63,10 @@ final class AddLogicalImageTask extends AddMultipleImageTask { this.progressMonitor = progressMonitor; this.callback = callback; } - + /** - * Copy the src directory to dest. - * Add alert.txt and users.txt to the case report - * Adds the image to the case database. + * Copy the src directory to dest. Add alert.txt and users.txt to the case + * report Adds the image to the case database. */ @Messages({ "# {0} - src", "# {1} - dest", "AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1}", @@ -81,7 +79,7 @@ final class AddLogicalImageTask extends AddMultipleImageTask { public void run() { List errorList = new ArrayList<>(); List emptyDataSources = new ArrayList<>(); - + try { progressMonitor.setProgressText(Bundle.AddLogicalImageTask_copyingImageFromTo(src.toString(), dest.toString())); FileUtils.copyDirectory(src, dest); @@ -94,7 +92,7 @@ final class AddLogicalImageTask extends AddMultipleImageTask { callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); return; } - + // Add the alert.txt and users.txt to the case report progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(ALERT_TXT)); String status = addReport(Paths.get(dest.toString(), ALERT_TXT), ALERT_TXT + " " + src.getName()); @@ -113,15 +111,16 @@ final class AddLogicalImageTask extends AddMultipleImageTask { return; } progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(USERS_TXT)); - + super.run(); } - - /** + + /** * Add a file specified by the reportPath to the case report. - * + * * @param reportPath Path to the report to be added * @param reportName Name associated the report + * * @returns null if success, or exception message if failure * */ @@ -139,6 +138,6 @@ final class AddLogicalImageTask extends AddMultipleImageTask { String msg = Bundle.AddLogicalImageTask_failedToAddReport(reportPath.toString(), ex.getMessage()); logger.log(Level.SEVERE, String.format("Failed to add report {0}. Reason= {1}", reportPath.toString(), ex.getMessage())); return msg; - } + } } } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java index ef7f547952..60f2e43a4f 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2013-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,9 +40,9 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; /** - * + * * A runnable that adds multiple images to the case database - * + * */ @Messages({ "AddMultipleImageTask.fsTypeUnknownErr=Cannot determine file system type" @@ -59,12 +59,11 @@ class AddMultipleImageTask implements Runnable { private final Case currentCase; private boolean criticalErrorOccurred; private volatile boolean cancelled; - + /** - * Constructs a runnable that adds multiple image files - * to a case database. If Sleuth Kit fails to find a filesystem - * in any of input image files, the file is added to the case as a - * local/logical file instead. + * Constructs a runnable that adds multiple image files to a case database. + * If Sleuth Kit fails to find a filesystem in any of input image files, the + * file is added to the case as a local/logical file instead. * * @param deviceId An ASCII-printable identifier for the device * associated with the data source that is intended @@ -76,14 +75,14 @@ class AddMultipleImageTask implements Runnable { * @param progressMonitor Progress monitor for reporting progress during * processing. * @param callback Callback to call when processing is done. - * @throws NoCurrentCaseException The exception if there is no open case. + * + * @throws NoCurrentCaseException The exception if there is no open case. */ @Messages({ "# {0} - file", "AddMultipleImageTask.addingFileAsLogicalFile=Adding: {0} as logical file", - "# {0} - deviceId", "# {1} - exceptionMessage", - "AddMultipleImageTask.errorAddingImgWithoutFileSystem=Error adding images without file systems for device %s: %s", - }) - AddMultipleImageTask(String deviceId, List imageFilePaths, String timeZone, + "# {0} - deviceId", "# {1} - exceptionMessage", + "AddMultipleImageTask.errorAddingImgWithoutFileSystem=Error adding images without file systems for device %s: %s",}) + AddMultipleImageTask(String deviceId, List imageFilePaths, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) throws NoCurrentCaseException { this.deviceId = deviceId; this.imageFilePaths = imageFilePaths; @@ -171,9 +170,9 @@ class AddMultipleImageTask implements Runnable { * added to this list for eventual return to * the caller via the callback. * @param localFileDataSourcePaths If the image cannot be added because - * Sleuth Kit cannot detect a filesystem, the - * image file path is added to this list for - * later addition as a part of a + * Sleuth Kit cannot detect a filesystem, + * the image file path is added to this list + * for later addition as a part of a * local/logical files data source. * @param errorMessages If there are any error messages, the * error messages are added to this list for @@ -184,8 +183,7 @@ class AddMultipleImageTask implements Runnable { "# {0} - imageFilePath", "AddMultipleImageTask.adding=Adding: {0}", "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.criticalErrorAdding=Critical error adding {0} for device {1}: {2}", "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.criticalErrorReverting=Critical error reverting add image process for {0} for device {1}: {2}", - "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.nonCriticalErrorAdding=Non-critical error adding {0} for device {1}: {2}", - }) + "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.nonCriticalErrorAdding=Non-critical error adding {0} for device {1}: {2}",}) private void addImageToCase(String imageFilePath, List newDataSources, List localFileDataSourcePaths, List errorMessages) { /* * Try to add the image to the case database as a data source. @@ -251,4 +249,4 @@ class AddMultipleImageTask implements Runnable { } } -} \ No newline at end of file +} diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/DriveListUtils.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/DriveListUtils.java new file mode 100644 index 0000000000..12b10d3000 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/DriveListUtils.java @@ -0,0 +1,51 @@ +/* + * Autopsy + * + * Copyright 2019 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.logicalimager.dsp; + +/** + * Utility class for displaying a list of drives + */ +public final class DriveListUtils { + + /** + * Convert a number of bytes to a human readable string + * + * @param bytes the number of bytes to convert + * @param si whether it takes 1000 or 1024 of a unit to reach the next + * unit + * + * @return a human readable string representing the number of bytes + */ + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) { + return bytes + " B"; //NON-NLS + } + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); //NON-NLS + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); //NON-NLS + } + + /** + * Empty private constructor for util class + */ + private DriveListUtils() { + //empty private constructor for util class + } +} diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java index 55649edfdf..da9d5f7aa6 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,20 +38,20 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress import org.sleuthkit.datamodel.Content; /** - * A Logical Imager data source processor that implements the DataSourceProcessor service - * provider interface to allow integration with the add data source wizard. It - * also provides a run method overload to allow it to be used independently of - * the wizard. + * A Logical Imager data source processor that implements the + * DataSourceProcessor service provider interface to allow integration with the + * add data source wizard. It also provides a run method overload to allow it to + * be used independently of the wizard. */ -@ServiceProviders(value={ - @ServiceProvider(service=DataSourceProcessor.class)} +@ServiceProviders(value = { + @ServiceProvider(service = DataSourceProcessor.class)} ) public final class LogicalImagerDSProcessor implements DataSourceProcessor { private static final String LOGICAL_IMAGER_DIR = "LogicalImager"; //NON-NLS private final LogicalImagerPanel configPanel; private AddLogicalImageTask addLogicalImageTask; - + /* * Constructs a Logical Imager data source processor that implements the * DataSourceProcessor service provider interface to allow integration with @@ -131,16 +131,15 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { "# {0} - directory", "LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0}", "# {0} - directory", "LogicalImagerDSProcessor.directoryAlreadyExists=Directory {0} already exists", "# {0} - file", "LogicalImagerDSProcessor.failToGetCanonicalPath=Fail to get canonical path for {0}", - "LogicalImagerDSProcessor.noCurrentCase=No current case", - }) + "LogicalImagerDSProcessor.noCurrentCase=No current case",}) @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { configPanel.storeSettings(); - + Path imageDirPath = configPanel.getImageDirPath(); List errorList = new ArrayList<>(); List emptyDataSources = new ArrayList<>(); - + if (!imageDirPath.toFile().exists()) { // This can happen if the USB drive was selected in the panel, but // was ejected before pressing the NEXT button @@ -150,7 +149,7 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); return; } - + // Create the LogicalImager directory under ModuleDirectory String moduleDirectory = Case.getCurrentCase().getModuleDirectory(); File logicalImagerDir = Paths.get(moduleDirectory, LOGICAL_IMAGER_DIR).toFile(); @@ -170,7 +169,7 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { return; } File src = imageDirPath.toFile(); - + // Get all VHD files in the src directory List imagePaths = new ArrayList<>(); for (File f : src.listFiles()) { @@ -189,8 +188,8 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { String deviceId = UUID.randomUUID().toString(); String timeZone = Calendar.getInstance().getTimeZone().getID(); run(deviceId, imagePaths, - timeZone, src, dest, - progressMonitor, callback); + timeZone, src, dest, + progressMonitor, callback); } catch (NoCurrentCaseException ex) { String msg = Bundle.LogicalImagerDSProcessor_noCurrentCase(); errorList.add(msg); @@ -198,29 +197,28 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { return; } } - + /** - * Adds a "Logical Imager" data source to the case database using a background task in - * a separate thread and the given settings instead of those provided by the - * selection and configuration panel. Returns as soon as the background task - * is started and uses the callback object to signal task completion and - * return results. + * Adds a "Logical Imager" data source to the case database using a + * background task in a separate thread and the given settings instead of + * those provided by the selection and configuration panel. Returns as soon + * as the background task is started and uses the callback object to signal + * task completion and return results. * - * @param deviceId An ASCII-printable identifier for the device - * associated with the data source that is - * intended to be unique across multiple cases - * (e.g., a UUID). - * @param imagePaths Paths to the image files. - * @param timeZone The time zone to use when processing dates - * and times for the image, obtained from - * java.util.TimeZone.getID. - * @param src The source directory of image. - * @param dest The destination directory to copy the source. - * @param progressMonitor Progress monitor for reporting progress - * during processing. - * @param callback Callback to call when processing is done. + * @param deviceId An ASCII-printable identifier for the device + * associated with the data source that is intended + * to be unique across multiple cases (e.g., a UUID). + * @param imagePaths Paths to the image files. + * @param timeZone The time zone to use when processing dates and + * times for the image, obtained from + * java.util.TimeZone.getID. + * @param src The source directory of image. + * @param dest The destination directory to copy the source. + * @param progressMonitor Progress monitor for reporting progress during + * processing. + * @param callback Callback to call when processing is done. */ - private void run(String deviceId, List imagePaths, String timeZone, + private void run(String deviceId, List imagePaths, String timeZone, File src, File dest, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback ) throws NoCurrentCaseException { diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form index a52c4aedce..43a8e0f1ca 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form @@ -72,7 +72,7 @@ - + @@ -89,7 +89,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java index a30c842ae0..880259f95a 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java @@ -1,7 +1,7 @@ /* - * Autopsy Forensic Browser + * Autopsy * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +29,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JFileChooser; @@ -42,6 +43,7 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.TableModel; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Panel for adding an logical image file from drive letters. Allows the user to @@ -54,6 +56,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LogicalImagerPanel extends JPanel implements DocumentListener { + private static final Logger logger = Logger.getLogger(LogicalImagerPanel.class.getName()); private static final long serialVersionUID = 1L; private static final String NO_IMAGE_SELECTED = Bundle.LogicalImagerPanel_messageLabel_noImageSelected(); private static final String DRIVE_HAS_NO_IMAGES = Bundle.LogicalImagerPanel_messageLabel_driveHasNoImages(); @@ -255,7 +258,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 568, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addContainerGap(338, Short.MAX_VALUE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -269,7 +272,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(imageScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(driveListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 461, Short.MAX_VALUE)) + .addComponent(driveListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 106, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(refreshButton) .addGap(18, 18, 18) @@ -288,16 +291,6 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { ); }// //GEN-END:initComponents - private static String humanReadableByteCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) { - return bytes + " B"; //NON-NLS - } - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); //NON-NLS - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); //NON-NLS - } - @Messages({ "# {0} - sparseImageDirectory", "LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain any images", @@ -490,7 +483,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { for (File root : roots) { String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root); long spaceInBytes = root.getTotalSpace(); - String sizeWithUnit = humanReadableByteCount(spaceInBytes, false); + String sizeWithUnit = DriveListUtils.humanReadableByteCount(spaceInBytes, false); listData.add(root + " (" + description + ") (" + sizeWithUnit + ")"); if (firstRemovableDrive == -1) { try { @@ -498,9 +491,9 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { if ((boolean) fileStore.getAttribute("volume:isRemovable")) { //NON-NLS firstRemovableDrive = i; } - } catch (IOException ex) { - i++; - continue; + } catch (IOException ignored) { + //unable to get this removable drive for default selection will try and select next removable drive by default + logger.log(Level.INFO, "Unable to select first removable drive found", ignored); } } i++; diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form index 124f268c20..1b66a2ae6b 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.form @@ -27,7 +27,7 @@
- + @@ -36,13 +36,16 @@ + + + - + - - + + @@ -65,7 +68,7 @@ - + @@ -84,7 +87,7 @@ - + @@ -97,7 +100,7 @@ - + @@ -109,7 +112,7 @@ - + @@ -121,7 +124,7 @@ - + @@ -131,19 +134,19 @@ - + - + - - + + - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java index 6d3f00151e..340dc830d8 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslationContentPanel.java @@ -299,18 +299,18 @@ public class TranslationContentPanel extends javax.swing.JPanel { setName(""); // NOI18N setPreferredSize(new java.awt.Dimension(100, 58)); setVerifyInputWhenFocusTarget(false); - setLayout(new java.awt.GridBagLayout()); + setLayout(new java.awt.BorderLayout()); jPanel1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - jPanel1.setPreferredSize(new java.awt.Dimension(250, 81)); + jPanel1.setMaximumSize(new java.awt.Dimension(182, 24)); + jPanel1.setPreferredSize(new java.awt.Dimension(182, 24)); jPanel1.setLayout(new java.awt.GridBagLayout()); displayTextComboBox.setMinimumSize(new java.awt.Dimension(43, 20)); displayTextComboBox.setPreferredSize(new java.awt.Dimension(43, 20)); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 3; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridheight = 3; + gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; gridBagConstraints.weightx = 0.1; @@ -322,9 +322,8 @@ public class TranslationContentPanel extends javax.swing.JPanel { ocrDropdown.setName(""); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 5; - gridBagConstraints.gridy = 1; + gridBagConstraints.gridy = 0; gridBagConstraints.gridwidth = 2; - gridBagConstraints.gridheight = 3; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; gridBagConstraints.weightx = 0.1; @@ -335,9 +334,7 @@ public class TranslationContentPanel extends javax.swing.JPanel { ocrLabel.setEnabled(false); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 4; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridheight = 3; - gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; + gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; gridBagConstraints.insets = new java.awt.Insets(0, 10, 0, 0); jPanel1.add(ocrLabel, gridBagConstraints); @@ -345,9 +342,8 @@ public class TranslationContentPanel extends javax.swing.JPanel { warningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/warning16.png"))); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridheight = 3; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.weightx = 0.25; jPanel1.add(warningLabel, gridBagConstraints); @@ -355,29 +351,15 @@ public class TranslationContentPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(showLabel, org.openide.util.NbBundle.getMessage(TranslationContentPanel.class, "TranslationContentPanel.showLabel.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 2; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridheight = 3; + gridBagConstraints.gridy = 0; jPanel1.add(showLabel, gridBagConstraints); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - add(jPanel1, gridBagConstraints); + add(jPanel1, java.awt.BorderLayout.NORTH); textScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - textScrollPane.setMaximumSize(new java.awt.Dimension(2000, 2000)); - textScrollPane.setPreferredSize(new java.awt.Dimension(660, 344)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new java.awt.Insets(6, 0, 0, 0); - add(textScrollPane, gridBagConstraints); + textScrollPane.setMaximumSize(new java.awt.Dimension(20000, 20000)); + textScrollPane.setPreferredSize(new java.awt.Dimension(640, 250)); + add(textScrollPane, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables @@ -389,4 +371,4 @@ public class TranslationContentPanel extends javax.swing.JPanel { private javax.swing.JScrollPane textScrollPane; private javax.swing.JLabel warningLabel; // End of variables declaration//GEN-END:variables -} +} \ No newline at end of file diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.form index 9bb5abd0e1..8e3c4e53d3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.form @@ -25,7 +25,7 @@ - + @@ -49,8 +49,8 @@ - - + + @@ -117,8 +117,11 @@ - - + + + + + @@ -187,7 +190,9 @@ - + + + @@ -460,6 +465,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java index 7e4608ae2e..e2f63df513 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.experimental.configuration; import java.awt.BorderLayout; import java.awt.Cursor; +import java.awt.Color; import java.io.File; import java.nio.file.Files; import java.util.List; @@ -35,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestJobSettingsPanel; import java.awt.Dimension; import java.nio.file.Paths; +import javax.swing.ImageIcon; import org.openide.util.ImageUtilities; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; @@ -47,8 +49,6 @@ import org.sleuthkit.autopsy.experimental.autoingest.FileExporterSettingsPanel; /** * Configuration panel for auto ingest settings. */ -@Messages({"AutoIngestSettingsPanel.examinerModeRadioButton.text=Examiner mode", - "AutoIngestSettingsPanel.autoIngestModeRadioButton.text=Auto Ingest mode"}) @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AutoIngestSettingsPanel extends javax.swing.JPanel { @@ -58,7 +58,9 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(AutoIngestSettingsPanel.class.getName()); private final Integer oldIngestThreads; private static final String MULTI_USER_SETTINGS_MUST_BE_ENABLED = NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.validationErrMsg.MUdisabled"); - + private final ImageIcon goodIcon; + private final ImageIcon badIcon; + enum OptionsUiMode { STANDALONE, AIM, DOWNLOADING_CONFIGURATION @@ -92,6 +94,9 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { jLabelTaskDescription.setEnabled(false); this.oldIngestThreads = UserPreferences.numberOfFileIngestThreads(); + + goodIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/experimental/images/good.png", false)); + badIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/experimental/images/bad.png", false)); } private class MyDocumentListener implements DocumentListener { @@ -121,6 +126,9 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { * @param inStartup True if we're doing the initial population of the UI */ final void load(boolean inStartup) { + + lbMultiUserResult.setIcon(null); + lbTestResultText.setText(""); // multi user mode must be enabled if (!UserPreferences.getIsMultiUserModeEnabled()) { @@ -448,7 +456,7 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { jLabelInvalidImageFolder.setVisible(true); jLabelInvalidImageFolder.setText(NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CannotAccess") + " " + inputPath + " " - + NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CheckPermissions")); + + NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CheckPermissions", System.getProperty("user.name"))); return false; } @@ -479,7 +487,7 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { jLabelInvalidResultsFolder.setVisible(true); jLabelInvalidResultsFolder.setText(NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CannotAccess") + " " + outputPath + " " - + NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CheckPermissions")); + + NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CheckPermissions", System.getProperty("user.name"))); return false; } @@ -514,7 +522,7 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { sharedSettingsErrorTextField.setVisible(true); sharedSettingsErrorTextField.setText(NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CannotAccess") + " " + sharedSettingsPath + " " - + NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CheckPermissions")); + + NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.CheckPermissions", System.getProperty("user.name"))); return false; } @@ -572,6 +580,11 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { downloadButton.setEnabled(mode == OptionsUiMode.AIM && sharedConfigCheckbox.isSelected()); browseSharedSettingsButton.setEnabled(mode == OptionsUiMode.AIM && sharedConfigCheckbox.isSelected()); uploadButton.setEnabled(mode == OptionsUiMode.AIM && sharedConfigCheckbox.isSelected() && masterNodeCheckBox.isSelected()); + + lbTestMultiUserText.setEnabled(mode == OptionsUiMode.AIM); + bnTestMultiUser.setEnabled(mode == OptionsUiMode.AIM); + lbMultiUserResult.setEnabled(mode == OptionsUiMode.AIM); + lbTestResultText.setEnabled(mode == OptionsUiMode.AIM); } else { setEnabledState(false); } @@ -623,6 +636,11 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { masterNodeCheckBox = new javax.swing.JCheckBox(); examinerModeRadioButton = new javax.swing.JRadioButton(); autoIngestModeRadioButton = new javax.swing.JRadioButton(); + pnTestMultiUser = new javax.swing.JPanel(); + lbTestMultiUserText = new javax.swing.JLabel(); + bnTestMultiUser = new javax.swing.JButton(); + lbMultiUserResult = new javax.swing.JLabel(); + lbTestResultText = new javax.swing.JLabel(); nodeScrollPane.setMinimumSize(new java.awt.Dimension(0, 0)); @@ -761,6 +779,55 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { } }); + pnTestMultiUser.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + lbTestMultiUserText.setFont(lbTestMultiUserText.getFont().deriveFont(lbTestMultiUserText.getFont().getStyle() & ~java.awt.Font.BOLD, 12)); + org.openide.awt.Mnemonics.setLocalizedText(lbTestMultiUserText, org.openide.util.NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.lbTestMultiUserText.text")); // NOI18N + + bnTestMultiUser.setFont(bnTestMultiUser.getFont().deriveFont(bnTestMultiUser.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + org.openide.awt.Mnemonics.setLocalizedText(bnTestMultiUser, org.openide.util.NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.bnTestMultiUser.text")); // NOI18N + bnTestMultiUser.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnTestMultiUserActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(lbMultiUserResult, org.openide.util.NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.lbMultiUserResult.text")); // NOI18N + + lbTestResultText.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(lbTestResultText, org.openide.util.NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.lbTestResultText.text")); // NOI18N + + javax.swing.GroupLayout pnTestMultiUserLayout = new javax.swing.GroupLayout(pnTestMultiUser); + pnTestMultiUser.setLayout(pnTestMultiUserLayout); + pnTestMultiUserLayout.setHorizontalGroup( + pnTestMultiUserLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnTestMultiUserLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnTestMultiUserLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbTestResultText, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(pnTestMultiUserLayout.createSequentialGroup() + .addComponent(lbTestMultiUserText) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 236, Short.MAX_VALUE) + .addComponent(bnTestMultiUser, javax.swing.GroupLayout.PREFERRED_SIZE, 83, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(lbMultiUserResult, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(8, 8, 8))) + .addContainerGap()) + ); + pnTestMultiUserLayout.setVerticalGroup( + pnTestMultiUserLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnTestMultiUserLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnTestMultiUserLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(lbMultiUserResult, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(pnTestMultiUserLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(bnTestMultiUser) + .addComponent(lbTestMultiUserText))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(lbTestResultText, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 16, Short.MAX_VALUE)) + ); + javax.swing.GroupLayout nodePanelLayout = new javax.swing.GroupLayout(nodePanel); nodePanel.setLayout(nodePanelLayout); nodePanelLayout.setHorizontalGroup( @@ -820,7 +887,9 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { .addGap(0, 0, Short.MAX_VALUE))) .addGap(10, 10, 10)) .addGroup(nodePanelLayout.createSequentialGroup() - .addComponent(autoIngestModeRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 145, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(autoIngestModeRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 145, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(pnTestMultiUser, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, Short.MAX_VALUE)))) ); nodePanelLayout.setVerticalGroup( @@ -875,7 +944,9 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { .addComponent(jLabelTaskDescription)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(pbTaskInProgress, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(pnTestMultiUser, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(82, Short.MAX_VALUE)) ); nodeScrollPane.setViewportView(nodePanel); @@ -888,7 +959,7 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(nodeScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(nodeScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 656, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -1075,6 +1146,37 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { controller.changed(); }//GEN-LAST:event_examinerModeRadioButtonActionPerformed + private void bnTestMultiUserActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnTestMultiUserActionPerformed + + lbTestResultText.setForeground(Color.BLACK); + lbTestResultText.setText(NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.TestRunning")); + lbTestResultText.paintImmediately(lbTestResultText.getVisibleRect()); + lbMultiUserResult.setIcon(null); + lbMultiUserResult.paintImmediately(lbMultiUserResult.getVisibleRect()); + + if (!validateResultsPath()) { + lbMultiUserResult.setIcon(badIcon); + lbTestResultText.setForeground(Color.RED); + lbTestResultText.setText(NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.PathInvalid")); + return; + } + + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + String resultsFolderPath = getNormalizedFolderPath(outputPathTextField.getText().trim()); + String testResult = MultiUserTestTool.runTest(resultsFolderPath); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (testResult.equals(MultiUserTestTool.MULTI_USER_TEST_SUCCESSFUL)) { + // test successful + lbMultiUserResult.setIcon(goodIcon); + lbTestResultText.setText(""); + } else { + // test failed + lbMultiUserResult.setIcon(badIcon); + lbTestResultText.setText(testResult); + lbTestResultText.setForeground(Color.RED); + } + }//GEN-LAST:event_bnTestMultiUserActionPerformed + private void enableUI(boolean state) { enableOptionsBasedOnMode(OptionsUiMode.DOWNLOADING_CONFIGURATION); downloadButton.setEnabled(state); @@ -1198,6 +1300,7 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { private javax.swing.JButton bnAdvancedSettings; private javax.swing.JButton bnEditIngestSettings; private javax.swing.JButton bnFileExport; + private javax.swing.JButton bnTestMultiUser; private javax.swing.JButton browseInputFolderButton; private javax.swing.JButton browseOutputFolderButton; private javax.swing.JButton browseSharedSettingsButton; @@ -1211,12 +1314,16 @@ public class AutoIngestSettingsPanel extends javax.swing.JPanel { private javax.swing.JLabel jLabelSelectInputFolder; private javax.swing.JLabel jLabelSelectOutputFolder; private javax.swing.JLabel jLabelTaskDescription; + private javax.swing.JLabel lbMultiUserResult; + private javax.swing.JLabel lbTestMultiUserText; + private javax.swing.JLabel lbTestResultText; private javax.swing.JCheckBox masterNodeCheckBox; private javax.swing.ButtonGroup modeSelectionButtonGroup; private javax.swing.JPanel nodePanel; private javax.swing.JScrollPane nodeScrollPane; private javax.swing.JTextField outputPathTextField; private javax.swing.JProgressBar pbTaskInProgress; + private javax.swing.JPanel pnTestMultiUser; private javax.swing.JCheckBox sharedConfigCheckbox; private javax.swing.JTextField sharedSettingsErrorTextField; private javax.swing.JTextField sharedSettingsTextField; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java index 72a06bbdab..0c05710e29 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java @@ -19,10 +19,7 @@ package org.sleuthkit.autopsy.experimental.configuration; import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.coreutils.ModuleSettings; -import org.sleuthkit.autopsy.coreutils.TextConverter; -import org.sleuthkit.autopsy.coreutils.TextConverterException; /** * Provides convenient access to a Preferences node for auto ingest user @@ -42,12 +39,6 @@ public final class AutoIngestUserPreferences { private static final String MAX_NUM_TIMES_TO_PROCESS_IMAGE = "MaxNumTimesToAttemptToProcessImage"; // NON-NLS private static final int DEFAULT_MAX_TIMES_TO_PROCESS_IMAGE = 0; private static final String MAX_CONCURRENT_NODES_FOR_ONE_CASE = "MaxConcurrentNodesForOneCase"; // NON-NLS - private static final String STATUS_DATABASE_LOGGING_ENABLED = "StatusDatabaseLoggingEnabled"; // NON-NLS - private static final String LOGGING_DB_HOSTNAME_OR_IP = "LoggingHostnameOrIP"; // NON-NLS - private static final String LOGGING_PORT = "LoggingPort"; // NON-NLS - private static final String LOGGING_USERNAME = "LoggingUsername"; // NON-NLS - private static final String LOGGING_PASSWORD = "LoggingPassword"; // NON-NLS - private static final String LOGGING_DATABASE_NAME = "LoggingDatabaseName"; // NON-NLS private static final String INPUT_SCAN_INTERVAL_TIME = "IntervalBetweenInputScan"; // NON-NLS // Prevent instantiation. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties index be60569400..becf3c5a2b 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties @@ -21,10 +21,11 @@ AIMIngestSettingsPanel.jLabel1.text=Download the current shared setting (highly AIMIngestSettingsPanel.lbSecondsBetweenJobs.text=Number of seconds to wait between jobs: AIMIngestSettingsPanel.lbSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. AIMIngestSettingsPanel.spSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. +AutoIngestSettingsPanel.examinerModeRadioButton.text=Examiner mode +AutoIngestSettingsPanel.autoIngestModeRadioButton.text=Auto Ingest mode AutoIngestSettingsPanel.AdvancedAutoIngestSettingsPanel.Title=Advanced Settings AutoIngestSettingsPanel.browseGlobalSettingsButton.text=Browse AutoIngestSettingsPanel.CannotAccess=Cannot access -AutoIngestSettingsPanel.CheckPermissions=Check permissions. AutoIngestSettingsPanel.EmptySettingsDirectory=Enter path to settings directory AutoIngestSettingsPanel.ErrorSettingDefaultFolder=Error creating default folder AutoIngestSettingsPanel.FileExportRules.text=File Export Rules @@ -37,7 +38,6 @@ AutoIngestSettingsPanel.KeywordSearchNull=Cannot find Keyword Search service AutoIngestSettingsPanel.MustRestart=Autopsy must be restarted for new configuration to take effect AutoIngestSettingsPanel.nodePanel.TabConstraints.tabTitle=Node Configuration AutoIngestSettingsPanel.NodeStatusLogging.text=Node Status Logging Settings -AutoIngestSettingsPanel.PathInvalid=Path is not valid AutoIngestSettingsPanel.restartRequiredLabel.text=Application restart required to take effect. AutoIngestSettingsPanel.restartRequiredLabel.text=Application restart required AutoIngestSettingsPanel.ResultsDirectoryUnspecified=Shared cases folder must be set @@ -47,6 +47,16 @@ AutoIngestSettingsPanel.validationErrMsg.invalidDatabasePort=Invalid database po AutoIngestSettingsPanel.validationErrMsg.invalidIndexingServerPort=Invalid Solr server port number AutoIngestSettingsPanel.validationErrMsg.invalidMessageServicePort=Invalid message service port number AutoIngestSettingsPanel.validationErrMsg.MUdisabled=Multi user settings must be enabled and saved +AutoIngestSettingsPanel.bnTestMultiUser.text=Test +AutoIngestSettingsPanel.lbTestMultiUserText.text=Test Multi-User Case Creation and Ingest Settings +AutoIngestSettingsPanel.lbMultiUserResult.text= +AutoIngestSettingsPanel.lbTestResultText.text= +AutoIngestSettingsPanel.validationErrMsg.outputPathNotSpecified=Output folder must be set +AutoIngestSettingsPanel.PathInvalid=Case output directory path is not valid +AutoIngestSettingsPanel.CheckPermissions=Ensure that the user account {0} has write permissions in this folder +AutoIngestSettingsPanel.Success=Success +AutoIngestSettingsPanel.TestRunning=Test in progress... +AutoIngestSettingsPanel.servicesDown=Some of the Multi User services are down GeneralOptionsPanelController.moduleErr.msg=A module caused an error listening to GeneralOptionsPanelController updates. See log to determine which module. Some data could be incomplete. GeneralOptionsPanelController.moduleErr=Module Error NodeStatusLogPanel.bnCancel.text=Cancel diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties-MERGED index bb4910edee..91e2bebd08 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties-MERGED @@ -21,14 +21,13 @@ AIMIngestSettingsPanel.jLabel1.text=Download the current shared setting (highly AIMIngestSettingsPanel.lbSecondsBetweenJobs.text=Number of seconds to wait between jobs: AIMIngestSettingsPanel.lbSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. AIMIngestSettingsPanel.spSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. -AutoIngestSettingsPanel.AdvancedAutoIngestSettingsPanel.Title=Advanced Settings +AutoIngestSettingsPanel.examinerModeRadioButton.text=Examiner mode AutoIngestSettingsPanel.autoIngestModeRadioButton.text=Auto Ingest mode +AutoIngestSettingsPanel.AdvancedAutoIngestSettingsPanel.Title=Advanced Settings AutoIngestSettingsPanel.browseGlobalSettingsButton.text=Browse AutoIngestSettingsPanel.CannotAccess=Cannot access -AutoIngestSettingsPanel.CheckPermissions=Check permissions. AutoIngestSettingsPanel.EmptySettingsDirectory=Enter path to settings directory AutoIngestSettingsPanel.ErrorSettingDefaultFolder=Error creating default folder -AutoIngestSettingsPanel.examinerModeRadioButton.text=Examiner mode AutoIngestSettingsPanel.FileExportRules.text=File Export Rules AutoIngestSettingsPanel.globalSettingsErrorTextField.text= AutoIngestSettingsPanel.globalSettingsTextField.text= @@ -39,7 +38,6 @@ AutoIngestSettingsPanel.KeywordSearchNull=Cannot find Keyword Search service AutoIngestSettingsPanel.MustRestart=Autopsy must be restarted for new configuration to take effect AutoIngestSettingsPanel.nodePanel.TabConstraints.tabTitle=Node Configuration AutoIngestSettingsPanel.NodeStatusLogging.text=Node Status Logging Settings -AutoIngestSettingsPanel.PathInvalid=Path is not valid AutoIngestSettingsPanel.restartRequiredLabel.text=Application restart required to take effect. AutoIngestSettingsPanel.restartRequiredLabel.text=Application restart required AutoIngestSettingsPanel.ResultsDirectoryUnspecified=Shared cases folder must be set @@ -49,8 +47,39 @@ AutoIngestSettingsPanel.validationErrMsg.invalidDatabasePort=Invalid database po AutoIngestSettingsPanel.validationErrMsg.invalidIndexingServerPort=Invalid Solr server port number AutoIngestSettingsPanel.validationErrMsg.invalidMessageServicePort=Invalid message service port number AutoIngestSettingsPanel.validationErrMsg.MUdisabled=Multi user settings must be enabled and saved +AutoIngestSettingsPanel.bnTestMultiUser.text=Test +AutoIngestSettingsPanel.lbTestMultiUserText.text=Test Multi-User Case Creation and Ingest Settings +AutoIngestSettingsPanel.lbMultiUserResult.text= +AutoIngestSettingsPanel.lbTestResultText.text= +AutoIngestSettingsPanel.validationErrMsg.outputPathNotSpecified=Output folder must be set +AutoIngestSettingsPanel.PathInvalid=Case output directory path is not valid +AutoIngestSettingsPanel.CheckPermissions=Ensure that the user account {0} has write permissions in this folder +AutoIngestSettingsPanel.Success=Success +AutoIngestSettingsPanel.TestRunning=Test in progress... +AutoIngestSettingsPanel.servicesDown=Some of the Multi User services are down GeneralOptionsPanelController.moduleErr.msg=A module caused an error listening to GeneralOptionsPanelController updates. See log to determine which module. Some data could be incomplete. GeneralOptionsPanelController.moduleErr=Module Error +# {0} - errorMessage +MultiUserTestTool.criticalError=Critical error running data source processor on test data source: {0} +MultiUserTestTool.errorStartingIngestJob=Ingest manager error while starting ingest job +# {0} - cancellationReason +MultiUserTestTool.ingestCancelled=Ingest cancelled due to {0} +MultiUserTestTool.ingestSettingsError=Failed to analyze data source due to ingest settings errors +MultiUserTestTool.noContent=Test data source failed to produce content +# {0} - serviceName +MultiUserTestTool.serviceDown=Multi User service is down: {0} +MultiUserTestTool.startupError=Failed to analyze data source due to ingest job startup error +MultiUserTestTool.unableAddFileAsDataSource=Unable to add test file as data source to case +MultiUserTestTool.unableCreatFile=Unable to create a file in case output directory +# {0} - serviceName +MultiUserTestTool.unableToCheckService=Unable to check Multi User service state: {0} +MultiUserTestTool.unableToCreateCase=Unable to create case +MultiUserTestTool.unableToInitializeDatabase=Case database was not successfully initialized +MultiUserTestTool.unableToReadDatabase=Unable to read from case database +MultiUserTestTool.unableToReadTestFileFromDatabase=Unable to read test file info from case database +MultiUserTestTool.unableToRunIngest=Unable to run ingest on test data source +MultiUserTestTool.unableToUpdateKWSIndex=Unable to write to Keyword Search index +MultiUserTestTool.unexpectedError=Unexpected error while performing Multi User test NodeStatusLogPanel.bnCancel.text=Cancel NodeStatusLogPanel.bnOk.text=OK NodeStatusLogPanel.bnTestDatabase.text=Test diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle_ja.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle_ja.properties deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/MultiUserTestTool.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/MultiUserTestTool.java new file mode 100755 index 0000000000..f37c7cefa0 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/MultiUserTestTool.java @@ -0,0 +1,455 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.experimental.configuration; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Paths; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import org.apache.commons.io.FileUtils; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; +import org.sleuthkit.autopsy.datasourceprocessors.AddDataSourceCallback; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSource; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.core.ServicesMonitor; +import org.sleuthkit.autopsy.events.AutopsyEvent; +import org.sleuthkit.autopsy.ingest.IngestJob; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobStartResult; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Test tool that creates a multi user case, database, KWS index, runs ingest, + * etc. If errors are encountered during this process, provides a message + * regarding the problem and possible causes. + */ +class MultiUserTestTool { + + private static final String CASE_NAME = "Test_Multi_User_Settings"; + private static final Logger LOGGER = Logger.getLogger(MultiUserTestTool.class.getName()); + private static final String TEST_FILE_NAME = "AutopsyTempFile"; + private static final Object INGEST_LOCK = new Object(); + static final String MULTI_USER_TEST_SUCCESSFUL = NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.Success"); + + private MultiUserTestTool() { + } + + @NbBundle.Messages({ + "MultiUserTestTool.unableToCreateCase=Unable to create case", + "MultiUserTestTool.unableToInitializeDatabase=Case database was not successfully initialized", + "MultiUserTestTool.unableToReadDatabase=Unable to read from case database", + "MultiUserTestTool.unableCreatFile=Unable to create a file in case output directory", + "MultiUserTestTool.unableAddFileAsDataSource=Unable to add test file as data source to case", + "MultiUserTestTool.unableToReadTestFileFromDatabase=Unable to read test file info from case database", + "MultiUserTestTool.unableToUpdateKWSIndex=Unable to write to Keyword Search index", + "MultiUserTestTool.unableToRunIngest=Unable to run ingest on test data source", + "MultiUserTestTool.unexpectedError=Unexpected error while performing Multi User test", + "# {0} - serviceName", + "MultiUserTestTool.serviceDown=Multi User service is down: {0}", + "# {0} - serviceName", + "MultiUserTestTool.unableToCheckService=Unable to check Multi User service state: {0}" + }) + static String runTest(String rootOutputDirectory) { + + // run standard tests for all services. this detects many problems sooner. + try { + if (!isServiceUp(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString())) { + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.serviceDown", ServicesMonitor.Service.REMOTE_CASE_DATABASE.getDisplayName()); + } + } catch (ServicesMonitor.ServicesMonitorException ex) { + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.unableToCheckService", + ServicesMonitor.Service.REMOTE_CASE_DATABASE.getDisplayName() + ". " + ex.getMessage()); + } + + try { + if (!isServiceUp(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString())) { + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.serviceDown", ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.getDisplayName()); + } + } catch (ServicesMonitor.ServicesMonitorException ex) { + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.unableToCheckService", + ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.getDisplayName() + ". " + ex.getMessage()); + } + + try { + if (!isServiceUp(ServicesMonitor.Service.MESSAGING.toString())) { + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.serviceDown", ServicesMonitor.Service.MESSAGING.getDisplayName()); + } + } catch (ServicesMonitor.ServicesMonitorException ex) { + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.unableToCheckService", + ServicesMonitor.Service.MESSAGING.getDisplayName() + ". " + ex.getMessage()); + } + + // Create a case in the output folder. + Case caseForJob; + try { + caseForJob = createCase(CASE_NAME, rootOutputDirectory); + } catch (CaseActionException ex) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableToCreateCase(), ex); + return Bundle.MultiUserTestTool_unableToCreateCase() + ". " + ex.getMessage(); + } + + if (caseForJob == null) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableToCreateCase()); + return Bundle.MultiUserTestTool_unableToCreateCase(); + } + + try { + // Verify that DB was created. etc + String getDatabaseInfoQuery = "select * from tsk_db_info"; + try (SleuthkitCase.CaseDbQuery queryResult = caseForJob.getSleuthkitCase().executeQuery(getDatabaseInfoQuery)) { + ResultSet resultSet = queryResult.getResultSet(); + // check if we got a result + if (resultSet.next() == false) { + // we got a result so we are able to read from the database + return Bundle.MultiUserTestTool_unableToInitializeDatabase(); + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableToReadDatabase(), ex); + return Bundle.MultiUserTestTool_unableToReadDatabase() + ". " + ex.getMessage(); + } + + // Make a text file in Windows TEMP folder + String tempFilePath = System.getProperty("java.io.tmpdir") + TEST_FILE_NAME + "_" + TimeStampUtils.createTimeStamp() + ".txt"; + try { + FileUtils.writeStringToFile(new File(tempFilePath), "Test", Charset.forName("UTF-8")); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableCreatFile(), ex); + return Bundle.MultiUserTestTool_unableCreatFile() + ". " + ex.getMessage(); + } + + // Add it as a logical file set data source. + AutoIngestDataSource dataSource = new AutoIngestDataSource("", Paths.get(tempFilePath)); + try { + String error = runLogicalFilesDSP(caseForJob, dataSource); + if (!error.isEmpty()) { + LOGGER.log(Level.SEVERE, error); + return error; + } + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableAddFileAsDataSource(), ex); + return Bundle.MultiUserTestTool_unableAddFileAsDataSource() + ". " + ex.getMessage(); + } + + // Verify that Solr was able to create the core and is able to write to it + FileManager fileManager = caseForJob.getServices().getFileManager(); + List listOfFiles = null; + try { + listOfFiles = fileManager.findFiles(new File(tempFilePath).getName()); + if (listOfFiles == null || listOfFiles.isEmpty()) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableToReadTestFileFromDatabase()); + return Bundle.MultiUserTestTool_unableToReadTestFileFromDatabase(); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableToReadTestFileFromDatabase(), ex); + return Bundle.MultiUserTestTool_unableToReadTestFileFromDatabase() + ". " + ex.getMessage(); + } + + AbstractFile file = listOfFiles.get(0); + + // write to KWS index + KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class); + try { + kwsService.index(file); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableToUpdateKWSIndex(), ex); + return Bundle.MultiUserTestTool_unableToUpdateKWSIndex() + ". " + ex.getMessage(); + } + + // Run ingest on that data source and report errors if the modules could not start. + try { + String error = analyze(dataSource); + if (!error.isEmpty()) { + LOGGER.log(Level.SEVERE, error); + return error; + } + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, Bundle.MultiUserTestTool_unableToRunIngest(), ex); + return Bundle.MultiUserTestTool_unableToRunIngest() + ". " + ex.getMessage(); + } + } catch (Exception ex) { + // unexpected exception firewall + LOGGER.log(Level.SEVERE, "Unexpected error while performing Multi User test", ex); + return Bundle.MultiUserTestTool_unexpectedError() + ". " + ex.getMessage(); + } finally { + // Close and delete the case. + try { + Case.deleteCurrentCase(); + } catch (CaseActionException ex) { + // I don't think this should result in the test being marked as "failed" if everyhitng else went well + LOGGER.log(Level.WARNING, "Unable to delete test case", ex); + } + } + + return MULTI_USER_TEST_SUCCESSFUL; + } + + /** + * Creates a new multi user case. + * + * @param baseCaseName Case name (will get time stamp appended to it) + * @param rootOutputDirectory Full path to directory in which the case will + * be created + * @return Case object + * @throws CaseActionException + */ + private static Case createCase(String baseCaseName, String rootOutputDirectory) throws CaseActionException { + + String caseDirectoryPath = rootOutputDirectory + File.separator + baseCaseName + "_" + TimeStampUtils.createTimeStamp(); + + // Create the case directory + Case.createCaseDirectory(caseDirectoryPath, Case.CaseType.MULTI_USER_CASE); + + CaseDetails caseDetails = new CaseDetails(baseCaseName); + Case.createAsCurrentCase(Case.CaseType.MULTI_USER_CASE, caseDirectoryPath, caseDetails); + return Case.getCurrentCase(); + } + + /** + * Passes the data source for the current job Logical Files data source + * processor that adds it to the case database. + * + * @param caseForJob The case + * @param dataSource The data source. + * + * @return Error String if there was an error, empty string if the data + * source was added successfully + * + * @throws InterruptedException if the thread running the job processing + * task is interrupted while blocked, i.e., if ingest is shutting down. + */ + @NbBundle.Messages({ + "MultiUserTestTool.noContent=Test data source failed to produce content", + "# {0} - errorMessage", + "MultiUserTestTool.criticalError=Critical error running data source processor on test data source: {0}" + }) + private static String runLogicalFilesDSP(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException { + + AutoIngestDataSourceProcessor selectedProcessor = new LocalFilesDSProcessor(); + DataSourceProcessorProgressMonitor progressMonitor = new DoNothingDSPProgressMonitor(); + synchronized (INGEST_LOCK) { + UUID taskId = UUID.randomUUID(); + caseForJob.notifyAddingDataSource(taskId); + DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId, INGEST_LOCK); + caseForJob.notifyAddingDataSource(taskId); + selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), progressMonitor, callBack); + INGEST_LOCK.wait(); + + // at this point we got the content object(s) from the DSP. + // check whether the data source was processed successfully + if (dataSource.getContent().isEmpty()) { + return Bundle.MultiUserTestTool_noContent(); + } + + if ((dataSource.getResultDataSourceProcessorResultCode() == CRITICAL_ERRORS)) { + for (String errorMessage : dataSource.getDataSourceProcessorErrorMessages()) { + LOGGER.log(Level.SEVERE, "Critical error running data source processor on test data source: {0}", errorMessage); + } + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.criticalError", dataSource.getDataSourceProcessorErrorMessages().get(0)); + } + + return ""; + } + } + + /** + * Analyzes the data source content returned by the data source processor + * using the configured set of data source level and file level analysis + * modules. + * + * @param dataSource The data source to analyze. + * + * @return Error String if there was an error, empty string if the data + * source was analyzed successfully + * + * @throws InterruptedException if the thread running the job processing + * task is interrupted while blocked, i.e., if auto ingest is shutting down. + */ + @NbBundle.Messages({ + "# {0} - cancellationReason", + "MultiUserTestTool.ingestCancelled=Ingest cancelled due to {0}", + "MultiUserTestTool.startupError=Failed to analyze data source due to ingest job startup error", + "MultiUserTestTool.errorStartingIngestJob=Ingest manager error while starting ingest job", + "MultiUserTestTool.ingestSettingsError=Failed to analyze data source due to ingest settings errors" + }) + private static String analyze(AutoIngestDataSource dataSource) throws InterruptedException { + + LOGGER.log(Level.INFO, "Starting ingest modules analysis for {0} ", dataSource.getPath()); + IngestJobEventListener ingestJobEventListener = new IngestJobEventListener(); + IngestManager.getInstance().addIngestJobEventListener(ingestJobEventListener); + try { + synchronized (INGEST_LOCK) { + IngestJobSettings ingestJobSettings = new IngestJobSettings(AutoIngestUserPreferences.getAutoModeIngestModuleContextString()); + List settingsWarnings = ingestJobSettings.getWarnings(); + if (settingsWarnings.isEmpty()) { + IngestJobStartResult ingestJobStartResult = IngestManager.getInstance().beginIngestJob(dataSource.getContent(), ingestJobSettings); + IngestJob ingestJob = ingestJobStartResult.getJob(); + if (null != ingestJob) { + /* + * Block until notified by the ingest job event + * listener or until interrupted because auto ingest + * is shutting down. + */ + INGEST_LOCK.wait(); + LOGGER.log(Level.INFO, "Finished ingest modules analysis for {0} ", dataSource.getPath()); + IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot(); + for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { + if (!snapshot.isCancelled()) { + List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); + if (!cancelledModules.isEmpty()) { + LOGGER.log(Level.WARNING, String.format("Ingest module(s) cancelled for %s", dataSource.getPath())); + for (String module : snapshot.getCancelledDataSourceIngestModules()) { + LOGGER.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, dataSource.getPath())); + } + } + LOGGER.log(Level.INFO, "Analysis of data source completed"); + } else { + LOGGER.log(Level.WARNING, "Analysis of data source cancelled"); + IngestJob.CancellationReason cancellationReason = snapshot.getCancellationReason(); + if (IngestJob.CancellationReason.NOT_CANCELLED != cancellationReason && IngestJob.CancellationReason.USER_CANCELLED != cancellationReason) { + return NbBundle.getMessage(MultiUserTestTool.class, "MultiUserTestTool.ingestCancelled", cancellationReason.getDisplayName()); + } + } + } + } else if (!ingestJobStartResult.getModuleErrors().isEmpty()) { + for (IngestModuleError error : ingestJobStartResult.getModuleErrors()) { + LOGGER.log(Level.SEVERE, String.format("%s ingest module startup error for %s", error.getModuleDisplayName(), dataSource.getPath()), error.getThrowable()); + } + LOGGER.log(Level.SEVERE, "Failed to analyze data source due to ingest job startup error"); + return Bundle.MultiUserTestTool_startupError(); + } else { + LOGGER.log(Level.SEVERE, String.format("Ingest manager ingest job start error for %s", dataSource.getPath()), ingestJobStartResult.getStartupException()); + return Bundle.MultiUserTestTool_errorStartingIngestJob(); + } + } else { + for (String warning : settingsWarnings) { + LOGGER.log(Level.SEVERE, "Ingest job settings error for {0}: {1}", new Object[]{dataSource.getPath(), warning}); + } + return Bundle.MultiUserTestTool_ingestSettingsError(); + } + } + } finally { + IngestManager.getInstance().removeIngestJobEventListener(ingestJobEventListener); + } + // ingest completed successfully + return ""; + } + + /** + * Tests service of interest to verify that it is running. + * + * @param serviceName Name of the service. + * + * @return True if the service is running, false otherwise. + * + * @throws ServicesMonitorException if there is an error querying the + * services monitor. + */ + private static boolean isServiceUp(String serviceName) throws ServicesMonitor.ServicesMonitorException { + return (ServicesMonitor.getInstance().getServiceStatus(serviceName).equals(ServicesMonitor.ServiceStatus.UP.toString())); + } + + /** + * A data source processor progress monitor does nothing. There is currently + * no mechanism for showing or recording data source processor progress + * during an ingest job. + */ + private static class DoNothingDSPProgressMonitor implements DataSourceProcessorProgressMonitor { + + /** + * Does nothing. + * + * @param indeterminate + */ + @Override + public void setIndeterminate(final boolean indeterminate) { + } + + /** + * Does nothing. + * + * @param progress + */ + @Override + public void setProgress(final int progress) { + } + + /** + * Does nothing. + * + * @param text + */ + @Override + public void setProgressText(final String text) { + } + } + + /** + * An ingest job event listener that allows the job processing task to block + * until the analysis of a data source by the data source level and file + * level ingest modules is completed. + *

+ * Note that the ingest job can spawn "child" ingest jobs (e.g., if an + * embedded virtual machine is found), so the job processing task must + * remain blocked until ingest is no longer running. + */ + private static class IngestJobEventListener implements PropertyChangeListener { + + /** + * Listens for local ingest job completed or cancelled events and + * notifies the job processing thread when such an event occurs and + * there are no "child" ingest jobs running. + * + * @param event + */ + @Override + public void propertyChange(PropertyChangeEvent event) { + if (AutopsyEvent.SourceType.LOCAL == ((AutopsyEvent) event).getSourceType()) { + String eventType = event.getPropertyName(); + if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { + synchronized (INGEST_LOCK) { + INGEST_LOCK.notifyAll(); + } + } + } + } + }; +} diff --git a/docs/doxygen-user/content_viewer.dox b/docs/doxygen-user/content_viewer.dox index 3a06fa89fa..d7f3420fbc 100644 --- a/docs/doxygen-user/content_viewer.dox +++ b/docs/doxygen-user/content_viewer.dox @@ -1,20 +1,20 @@ /*! \page content_viewer_page Content Viewer -The Content Viewer lives in the lower right-hand side of the Autopsy main screen and shows pictures, video, hex, text, extracted strings, metadata, etc. The Content Viewer is enabled when you select an entry in the \ref ui_results. +The Content Viewer lives in the lower right-hand side of the Autopsy main screen and shows pictures, video, hex, text, extracted strings, metadata, etc. The Content Viewer is populated when you select an entry in the \ref ui_results. The Content Viewer is context-aware, meaning different tabs will be enabled depending on the type of content selected and which ingest modules have been run. It will default to what it considers the "most specific" tab. For example, selecting a JPG will cause the Content Viewer to automatically select the "Application" tab and will display the image there. If you instead would like the Content Viewer to stay on the previously selected tab when you change to a different content object, go to the \ref view_options_page panel through Tools->Options->Application Tab and select the "Stay on the same file viewer" option. \image html content_viewer_options_panel.png -When a Result type is selected in the Result Viewer (as opposed to a file), most of the tabs will correspond to the file associated with the result and not the result itself. For example, when selecting a Keyword Hit, the "Hex", "Strings", and "File Metadata" tabs will show data from the file where the keyword was found. The descriptions below will generally assume a file has been selected, but most also apply when we have a file associated with a selected result. +When a result item is selected in the Result Viewer (as opposed to a file), most of the tabs will correspond to the file associated with the result and not the result itself. For example, when selecting a keyword hit, the "Hex", "Strings", and "File Metadata" tabs will show data from the file where the keyword was found. The descriptions below will generally assume a file has been selected, but most also apply when we have a file associated with a selected result. \section cv_hex Hex -The Hex Content Viewer is nearly always available and shows you the raw and exact contents of a file. In this content viewer, the data of the file is represented as hexadecimal values grouped in 2 groups of 8 bytes, followed by one group of 16 ASCII characters which are derived from each pair of hex values (each byte). Non-printable ASCII characters and characters that would take more than one character space are typically represented by a dot (".") in the following ASCII field. +The Hex tab is nearly always available and shows you the raw and exact contents of a file. In this tab, the data of the file is represented as hexadecimal values grouped in 2 groups of 8 bytes, followed by one group of 16 ASCII characters which are derived from each pair of hex values (each byte). Non-printable ASCII characters and characters that would take more than one character space are typically represented by a dot (".") in the following ASCII field. \image html content_viewer_hex.png -If desired, you can open the file in an external hex editor. This is configured through the "External Viewer" tab on the options panel. HxD has been tested to work, but alternate hex editors may also be compatible. +If desired, you can open the file in an external hex editor. This is configured through the "External Viewer" tab on the options panel. HxD has been verified to work with Autopsy, but alternate hex editors may also be compatible. \image html content_viewer_hex_editor_setup.png @@ -22,12 +22,30 @@ Note that this process saves the file to disk before launching the hex editor. A \image html content_viewer_hxd_progress.png -\section cv_strings Strings +\section cv_text Text -The Strings tab shows all text strings found in the file. Different scripts can be chosen from the drop-down menu to display results for non-Latin alphabets. +The Text tab has three sub tabs for displaying the text contained in the selected item. + +\subsection cv_strings Strings + +The Strings tab shows all text strings found in the file for the given script selected in the upper right. By default Latin text is used. + +\image html content_viewer_strings_latin.png + +Different scripts can be chosen from the drop-down menu to display results for non-Latin alphabets. \image html content_viewer_strings_cyrillic.png +\subsection cv_indexed_text Indexed Text + +The Indexed Text tab shows the text that has been indexed by the \ref keyword_search_page. You can switch the "Text Source" field to "Result Text" to see the text that has been indexed for the results associated with a file. + +\image html content_viewer_indexed_text.png + +\subsection cv_translation Translation + +If you have a translation service enabled, the Translation tab allows you to translate the text. See the \ref machine_translation_page page for more information. + \section cv_app Application For certain file types, the Application tab can display the contents in a user friendly format. The following screenshots show some examples of what the Application tab will display. @@ -48,11 +66,9 @@ HTML files can be displayed closer to their original form: \image html content_viewer_html.png -\section cv_indexed_text Indexed Text +Registry hive files can be viewed in a format similar to a registry editor. -The Indexed Text tab shows the text that has been indexed by the Keyword Search module. You can switch the "Text Source" Field to "Result Text" to see which text has been indexed for associated results. - -\image html content_viewer_indexed_text.png +\image html content_viewer_registry.png \section cv_message Message @@ -68,7 +84,7 @@ The File Metadata tab displays basic information about the file, such as type, s \section cv_results Results -The Results tab is active when selecting entries that are part of the Results tree, such as keyword hits, call logs, and messages. It is also active when looking at a file that has results associated with it. The exact fields displayed depend on the type of entry. The two images below show the Results tab for a call log and a web bookmark. +The Results tab is active when selecting items with associated results such as keyword hits, call logs, and messages. The exact fields displayed depend on the type of result. The two images below show the Results tab for a call log and a web bookmark. \image html content_viewer_results_call.png
diff --git a/docs/doxygen-user/images/content_viewer_annotations.png b/docs/doxygen-user/images/content_viewer_annotations.png index fed1346e5c..671a842099 100644 Binary files a/docs/doxygen-user/images/content_viewer_annotations.png and b/docs/doxygen-user/images/content_viewer_annotations.png differ diff --git a/docs/doxygen-user/images/content_viewer_app_image.png b/docs/doxygen-user/images/content_viewer_app_image.png index 8ceff5cb0a..42b57d27c6 100644 Binary files a/docs/doxygen-user/images/content_viewer_app_image.png and b/docs/doxygen-user/images/content_viewer_app_image.png differ diff --git a/docs/doxygen-user/images/content_viewer_app_plist.png b/docs/doxygen-user/images/content_viewer_app_plist.png index 13a53d08ea..29da91b7ac 100644 Binary files a/docs/doxygen-user/images/content_viewer_app_plist.png and b/docs/doxygen-user/images/content_viewer_app_plist.png differ diff --git a/docs/doxygen-user/images/content_viewer_app_sqlite.png b/docs/doxygen-user/images/content_viewer_app_sqlite.png index bd652913a7..0166cb32e3 100644 Binary files a/docs/doxygen-user/images/content_viewer_app_sqlite.png and b/docs/doxygen-user/images/content_viewer_app_sqlite.png differ diff --git a/docs/doxygen-user/images/content_viewer_hex.png b/docs/doxygen-user/images/content_viewer_hex.png index a0d6242651..330c0468a7 100644 Binary files a/docs/doxygen-user/images/content_viewer_hex.png and b/docs/doxygen-user/images/content_viewer_hex.png differ diff --git a/docs/doxygen-user/images/content_viewer_html.png b/docs/doxygen-user/images/content_viewer_html.png index 7baae5b4fd..29f2f86ea4 100644 Binary files a/docs/doxygen-user/images/content_viewer_html.png and b/docs/doxygen-user/images/content_viewer_html.png differ diff --git a/docs/doxygen-user/images/content_viewer_indexed_text.png b/docs/doxygen-user/images/content_viewer_indexed_text.png index d4e7d32ddf..83c0076eda 100644 Binary files a/docs/doxygen-user/images/content_viewer_indexed_text.png and b/docs/doxygen-user/images/content_viewer_indexed_text.png differ diff --git a/docs/doxygen-user/images/content_viewer_message.png b/docs/doxygen-user/images/content_viewer_message.png index 032f1e4e4a..f927289531 100644 Binary files a/docs/doxygen-user/images/content_viewer_message.png and b/docs/doxygen-user/images/content_viewer_message.png differ diff --git a/docs/doxygen-user/images/content_viewer_metadata.png b/docs/doxygen-user/images/content_viewer_metadata.png index d9f7922b60..1c25115398 100644 Binary files a/docs/doxygen-user/images/content_viewer_metadata.png and b/docs/doxygen-user/images/content_viewer_metadata.png differ diff --git a/docs/doxygen-user/images/content_viewer_other_occurrences.png b/docs/doxygen-user/images/content_viewer_other_occurrences.png index 64010b2886..3bdac5b67b 100644 Binary files a/docs/doxygen-user/images/content_viewer_other_occurrences.png and b/docs/doxygen-user/images/content_viewer_other_occurrences.png differ diff --git a/docs/doxygen-user/images/content_viewer_registry.png b/docs/doxygen-user/images/content_viewer_registry.png new file mode 100644 index 0000000000..43149fcb7f Binary files /dev/null and b/docs/doxygen-user/images/content_viewer_registry.png differ diff --git a/docs/doxygen-user/images/content_viewer_results_bookmark.png b/docs/doxygen-user/images/content_viewer_results_bookmark.png index caf2853b5f..6baa7f7383 100644 Binary files a/docs/doxygen-user/images/content_viewer_results_bookmark.png and b/docs/doxygen-user/images/content_viewer_results_bookmark.png differ diff --git a/docs/doxygen-user/images/content_viewer_results_call.png b/docs/doxygen-user/images/content_viewer_results_call.png index ed09b4e814..03b79bf1f0 100644 Binary files a/docs/doxygen-user/images/content_viewer_results_call.png and b/docs/doxygen-user/images/content_viewer_results_call.png differ diff --git a/docs/doxygen-user/images/content_viewer_strings_cyrillic.png b/docs/doxygen-user/images/content_viewer_strings_cyrillic.png index 1629b74ed7..f70ba0c130 100644 Binary files a/docs/doxygen-user/images/content_viewer_strings_cyrillic.png and b/docs/doxygen-user/images/content_viewer_strings_cyrillic.png differ diff --git a/docs/doxygen-user/images/content_viewer_strings_latin.png b/docs/doxygen-user/images/content_viewer_strings_latin.png new file mode 100644 index 0000000000..6968326d65 Binary files /dev/null and b/docs/doxygen-user/images/content_viewer_strings_latin.png differ diff --git a/docs/doxygen-user/images/directory-tree.PNG b/docs/doxygen-user/images/directory-tree.PNG deleted file mode 100644 index 5477b17dba..0000000000 Binary files a/docs/doxygen-user/images/directory-tree.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/mt_config.png b/docs/doxygen-user/images/mt_config.png new file mode 100644 index 0000000000..e3022aec0d Binary files /dev/null and b/docs/doxygen-user/images/mt_config.png differ diff --git a/docs/doxygen-user/images/mt_content_viewer_translated.png b/docs/doxygen-user/images/mt_content_viewer_translated.png new file mode 100644 index 0000000000..7ba3804415 Binary files /dev/null and b/docs/doxygen-user/images/mt_content_viewer_translated.png differ diff --git a/docs/doxygen-user/images/mt_content_viewer_untranslated_text.png b/docs/doxygen-user/images/mt_content_viewer_untranslated_text.png new file mode 100644 index 0000000000..488254a11b Binary files /dev/null and b/docs/doxygen-user/images/mt_content_viewer_untranslated_text.png differ diff --git a/docs/doxygen-user/images/mt_file_name_enable.png b/docs/doxygen-user/images/mt_file_name_enable.png new file mode 100644 index 0000000000..8f2ee50ae0 Binary files /dev/null and b/docs/doxygen-user/images/mt_file_name_enable.png differ diff --git a/docs/doxygen-user/images/mt_file_name_original.png b/docs/doxygen-user/images/mt_file_name_original.png new file mode 100644 index 0000000000..93d9aaa449 Binary files /dev/null and b/docs/doxygen-user/images/mt_file_name_original.png differ diff --git a/docs/doxygen-user/images/mt_file_names_translated.png b/docs/doxygen-user/images/mt_file_names_translated.png new file mode 100644 index 0000000000..38904efa38 Binary files /dev/null and b/docs/doxygen-user/images/mt_file_names_translated.png differ diff --git a/docs/doxygen-user/images/portable_case_chunks.png b/docs/doxygen-user/images/portable_case_chunks.png new file mode 100644 index 0000000000..de21370434 Binary files /dev/null and b/docs/doxygen-user/images/portable_case_chunks.png differ diff --git a/docs/doxygen-user/images/portable_case_interesting_items.png b/docs/doxygen-user/images/portable_case_interesting_items.png new file mode 100644 index 0000000000..3a8358c4c6 Binary files /dev/null and b/docs/doxygen-user/images/portable_case_interesting_items.png differ diff --git a/docs/doxygen-user/images/portable_case_report_panel.png b/docs/doxygen-user/images/portable_case_report_panel.png index 91b9c0c722..9b46e610c4 100644 Binary files a/docs/doxygen-user/images/portable_case_report_panel.png and b/docs/doxygen-user/images/portable_case_report_panel.png differ diff --git a/docs/doxygen-user/images/portable_case_unpackage.png b/docs/doxygen-user/images/portable_case_unpackage.png new file mode 100644 index 0000000000..1d4b20b7ec Binary files /dev/null and b/docs/doxygen-user/images/portable_case_unpackage.png differ diff --git a/docs/doxygen-user/images/reports_files_delimiter.png b/docs/doxygen-user/images/reports_files_delimiter.png new file mode 100644 index 0000000000..6726897cc6 Binary files /dev/null and b/docs/doxygen-user/images/reports_files_delimiter.png differ diff --git a/docs/doxygen-user/images/result-viewer-example-1.PNG b/docs/doxygen-user/images/result-viewer-example-1.PNG index fd813be852..409cea78be 100644 Binary files a/docs/doxygen-user/images/result-viewer-example-1.PNG and b/docs/doxygen-user/images/result-viewer-example-1.PNG differ diff --git a/docs/doxygen-user/images/result-viewer-example-3.PNG b/docs/doxygen-user/images/result-viewer-example-3.PNG index fbdda31e8a..f9cd06056b 100644 Binary files a/docs/doxygen-user/images/result-viewer-example-3.PNG and b/docs/doxygen-user/images/result-viewer-example-3.PNG differ diff --git a/docs/doxygen-user/images/result_viewer_csv.PNG b/docs/doxygen-user/images/result_viewer_csv.PNG new file mode 100644 index 0000000000..c02c182b89 Binary files /dev/null and b/docs/doxygen-user/images/result_viewer_csv.PNG differ diff --git a/docs/doxygen-user/images/result_viewer_paging.PNG b/docs/doxygen-user/images/result_viewer_paging.PNG new file mode 100644 index 0000000000..2604b53741 Binary files /dev/null and b/docs/doxygen-user/images/result_viewer_paging.PNG differ diff --git a/docs/doxygen-user/images/tagging_image_create_tag.png b/docs/doxygen-user/images/tagging_image_create_tag.png new file mode 100644 index 0000000000..067cc278dd Binary files /dev/null and b/docs/doxygen-user/images/tagging_image_create_tag.png differ diff --git a/docs/doxygen-user/images/tagging_image_edit_tag.png b/docs/doxygen-user/images/tagging_image_edit_tag.png new file mode 100644 index 0000000000..ce9a629526 Binary files /dev/null and b/docs/doxygen-user/images/tagging_image_edit_tag.png differ diff --git a/docs/doxygen-user/images/tagging_image_menu.png b/docs/doxygen-user/images/tagging_image_menu.png new file mode 100644 index 0000000000..9ca8f06099 Binary files /dev/null and b/docs/doxygen-user/images/tagging_image_menu.png differ diff --git a/docs/doxygen-user/images/tagging_image_multiple.png b/docs/doxygen-user/images/tagging_image_multiple.png new file mode 100644 index 0000000000..d41bba0f9a Binary files /dev/null and b/docs/doxygen-user/images/tagging_image_multiple.png differ diff --git a/docs/doxygen-user/images/tagging_image_report.png b/docs/doxygen-user/images/tagging_image_report.png new file mode 100644 index 0000000000..6a053c9af5 Binary files /dev/null and b/docs/doxygen-user/images/tagging_image_report.png differ diff --git a/docs/doxygen-user/images/tagging_image_select.png b/docs/doxygen-user/images/tagging_image_select.png new file mode 100644 index 0000000000..d44e2552aa Binary files /dev/null and b/docs/doxygen-user/images/tagging_image_select.png differ diff --git a/docs/doxygen-user/images/thumbnail-result-viewer-tab.PNG b/docs/doxygen-user/images/thumbnail-result-viewer-tab.PNG index c6f5921690..b53c14dc52 100644 Binary files a/docs/doxygen-user/images/thumbnail-result-viewer-tab.PNG and b/docs/doxygen-user/images/thumbnail-result-viewer-tab.PNG differ diff --git a/docs/doxygen-user/images/ui-layout-1.PNG b/docs/doxygen-user/images/ui-layout-1.PNG index 96816f578f..7f9485888d 100644 Binary files a/docs/doxygen-user/images/ui-layout-1.PNG and b/docs/doxygen-user/images/ui-layout-1.PNG differ diff --git a/docs/doxygen-user/images/view_options_options_panel.png b/docs/doxygen-user/images/view_options_options_panel.png index 88267355ca..3a917e166e 100644 Binary files a/docs/doxygen-user/images/view_options_options_panel.png and b/docs/doxygen-user/images/view_options_options_panel.png differ diff --git a/docs/doxygen-user/installSolr.dox b/docs/doxygen-user/installSolr.dox index e8cc660baa..494e24588d 100644 --- a/docs/doxygen-user/installSolr.dox +++ b/docs/doxygen-user/installSolr.dox @@ -4,10 +4,10 @@ A central Solr server is needed to store keyword indexes, and its embedded Zooke \section install_solr_prereq Prerequisites You will need: -- 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. -- Download the Apache Solr 4.10.3-0 installation package from https://sourceforge.net/projects/autopsy/files/CollaborativeServices/Solr or Direct Download Link +- A 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. +- The Apache Solr 4.10.3-0 installation package from https://sourceforge.net/projects/autopsy/files/CollaborativeServices/Solr or Direct Download Link - Access to an installed version of Autopsy so that you can copy files from it. -- A network-accessible machine to install Solr upon. Note that the Solr process will need to write data out to the main shared storage drive, and needs adequate permissions to write to this location, which may be across a network. +- A network-accessible machine to install Solr on. Note that the Solr process will need to write data out to the main shared storage drive, and needs adequate permissions to write to this location, which may be across a network. \section install_solr_install Installation @@ -29,7 +29,7 @@ The following steps will configure Solr to run using an account that will have a \subsection install_solr_config Solr Configuration 1. Stop the _solrJetty_ service by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrJetty_ Windows service. Select the service and press _Stop the service_. If the service is already stopped and there is no _Stop the service_ available, this is okay. -2. Edit the "C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat" script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file: +2. Edit the "C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat" script. You need administrator rights to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:

- Add the following options in the line that begins with "C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe" : diff --git a/docs/doxygen-user/machine_translation.dox b/docs/doxygen-user/machine_translation.dox new file mode 100644 index 0000000000..ee3fbd07da --- /dev/null +++ b/docs/doxygen-user/machine_translation.dox @@ -0,0 +1,37 @@ +/*! \page machine_translation_page Machine Translation + +You can configure a machine translation service to use with Autopsy. If one is configured, you will be able to translate text in the \ref content_viewer_page and file names in the \ref result_viewer_page and \ref tree_viewer_page. + +\section mt_config Configuration + +To set up a machine translation service, go to Options->Tools and then select the Machine Translation tab. Select the service you want to use from the drop down menu at the top. + +\image html mt_config.png + +Each service will require slightly different configuration steps. After setting everything up, you can run a quick check that the service is set up correctly using the "Test" button. + +\section mt_file_names Translating File Names + +You can use machine translation to automatically translate file and folder names, such as the ones seen below: + +\image html mt_file_name_original.png + +To enable file name translation, go to the \ref view_options_page and check the box under "Translate Text". + +\image html mt_file_name_enable.png + +Once enabled, the translated versions of the file and folder names will be shown in the \ref tree_viewer_page and in the first column of the \ref result_viewer_page. The original name will be displayed in the new "Original Name" column. + +\image html mt_file_names_translated.png + +\section mt_content_viewer Translating File Content + +After you set up a machine translation service, the Translation tab under the Text tab in the Content Viewer will be active. The Translation tab allows you to use your service to translate the beginning of a file. For example, you might see the following in the default Indexed Text tab: + +\image html mt_content_viewer_untranslated_text.png + +Switching to the Translation tab will display the results of running the text through the machine translation service. + +\image html mt_content_viewer_translated.png + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index e148279aa6..3a4c6d96fc 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -49,6 +49,7 @@ The following topics are available here: - \subpage common_properties_page - \subpage search_all_cases_page - \subpage logs_and_output_page + - \subpage machine_translation_page - Reporting - \subpage tagging_page - \subpage reporting_page diff --git a/docs/doxygen-user/portable_case.dox b/docs/doxygen-user/portable_case.dox index 4f4e809ae8..b476206a38 100644 --- a/docs/doxygen-user/portable_case.dox +++ b/docs/doxygen-user/portable_case.dox @@ -9,7 +9,7 @@ The general use case is as follows:

  1. Alice is analyzing one or more data sources using Autopsy. She tags files and results that are of particular interest.
  2. Alice wants to share her findings with Bob but is unable to send him the original data sources because he shouldn't see them or the originals are too big. -
  3. Alice creates a portable case which will contain only her tagged files and results, plus any files associated with those results, and sends it to Bob. +
  4. Alice creates a portable case which will contain only her tagged files and results, plus any files associated with those results, and sends it to Bob. She could also choose to include \ref interesting_files_identifier_page "interesting files" or results.
  5. Bob can open the portable case in Autopsy and view all files and results Alice tagged, and run any of the normal Autopsy features.
@@ -21,25 +21,38 @@ The portable version could like this: \image html portable_case_portable_version.png -Alice only tagged eight files and results, so most of the original content is no longer in the case. Some of the data sources had no tagged items so they're not included at all. The file structure of any tagged files is preserved - you can see that the tagged image in the screenshot is still in the same location, but the non-tagged files are gone. Note that although the original images (such as "image1.vhd") appear in the tree, their contents are not included in the portable case. +Alice only tagged eight files and results and her case had no interesting items, so most of the original content is no longer in the case. Some of the data sources had no tagged items so they're not included at all. The file structure of any tagged files is preserved - you can see that the tagged image in the screenshot is still in the same location, but the non-tagged files are gone. Note that although the original images (such as "image1.vhd") appear in the tree, their contents are not included in the portable case. \section portable_case_creation Creating a Portable Case -First you'll want to make sure that all the files and results you want included in the portable case are tagged - see the \ref tagging_page page for more details. -You can see what tags you've added in the \ref tree_viewer_page. +A portable case can contain tagged files and results and data from the Interesting Items section of the \ref tree_viewer_page. You'll be able to choose which of the Interesting Item sets you want to include in the portable case. + +\image html portable_case_interesting_items.png + +You can tag any additional files you want to include in the portable case. See the \ref tagging_page page for details on how to create tags. You can see what tags you've added in the \ref tree_viewer_page. \image html portable_case_tags.png -Portable cases are created through the \ref reporting_page feature. The Generate Report dialog will display a list of all tags that are in use in the current case and you can choose which ones you would like to include. At the bottom you can select the output folder for the new case. By default it will be placed in the "Reports" folder in the current case. +Portable cases are created through the \ref reporting_page feature. The Generate Report dialog will display a list of all tags and interesting file sets that are in use in the current case and you can choose which ones you would like to include. At the bottom you can choose to optionally package the case. Choosing to package the case without chunking will simply compress the portable case in a single archive that can be extracted with common compression programs. If you choose split the packaged case into multiple files, you will need to use the "Unpackage Portable Case" option before loading it. This will be discussed in the next section. + +The portable case will be placed in the "Reports" folder in the current case. \image html portable_case_report_panel.png -Here you can see the new portable case. It will be named with the original case name plus "(Portable)". The portable case is initially missing many of the normal Autopsy folders - these will be created the first time a user opens it. The portable case folder can be zipped and sent to a different user. +Here you can see an unpackaged portable case. It will be named with the original case name plus "(Portable)". The portable case is initially missing many of the normal Autopsy folders - these will be created the first time a user opens it. \image html portable_case_folder.png +If you packaged the portable case but did not choose to split it into chunks, you'll have a single .zip file. If you chose to split the packaged case, you'll have one or more files starting with extension .zip.001. + +\image html portable_case_chunks.png + \section portable_case_usage Using a Portable Case +If your portable case was packaged, you'll first need to unpackage it. Open the "Case" menu and then select "Unpackage Portable Case". This will bring up a dialog where you can browse to your packaged case and select where to extract it to. Once unpackaged you can open it normally. + +\image html portable_case_unpackage.png + Portable cases generally behave like any other Autopsy case. You can run ingest, do keyword searches, use the timeline viewer, etc. One point to note is that while the original data source names appear in the case, the data sources themselves were not copied into the portable case. \image html portable_case_empty_image.png diff --git a/docs/doxygen-user/reporting.dox b/docs/doxygen-user/reporting.dox index 63c1917036..c1065983e7 100644 --- a/docs/doxygen-user/reporting.dox +++ b/docs/doxygen-user/reporting.dox @@ -64,13 +64,17 @@ show up as Hashset Hits. \subsection report_case_uco CASE-UCO -This module creates an JSON output file in CASE-UCO format from a single data source. +This module creates a JSON output file in CASE-UCO format for a single data source. \image html reports_case.png \subsection report_files Files - Text -This report module allows you create a tab delimited text file from all files in the current case. You can select which fields should be exported. +This report module allows you create a tab or comma delimited text file report of all of the files in the current case. Start by selecting which delimiter you would like to use. + +\image html reports_files_delimiter.png + +You can then select which fields should be reported. \image html reports_files_config.png
@@ -84,7 +88,7 @@ This report module generates a KML file from any GPS data in the case. This file \subsection report_portable_case Portable Case -This report module generates a new Autopsy case from any tagged files and results. See the \ref portable_case_page page for additional information. +This report module generates a new Autopsy case that includes tagged and/or interesting items. See the \ref portable_case_page page for additional information. \subsection report_stix STIX diff --git a/docs/doxygen-user/result_viewer.dox b/docs/doxygen-user/result_viewer.dox index 1c813d9f81..b5b349682b 100644 --- a/docs/doxygen-user/result_viewer.dox +++ b/docs/doxygen-user/result_viewer.dox @@ -1,37 +1,61 @@ /*! \page result_viewer_page Result Viewer -The Result Viewer is located on the top right of the Autopsy screen. It shows lists of files and their corresponding attributes such as time, path, size, checksum, etc. +The Result Viewer is located on the top right of the Autopsy screen and shows the the contents of what was selected in the \ref tree_viewer_page. -
+\section result_viewer_table Table Viewers + +The main table viewer in the "Listing" tab displays the contents of the current selection as a table with selected details (properties) of each item. For files, some examples of the properties that this viewer shows are: name, time (modified, changed, accessed, and created), size, flags (directory and meta), mode, user ID, group ID, metadata address, attribute address, and type (directory and meta). For other data types, the columns will be different. Click the "Table" tab to select this view. + +The following shows the main table viewer when a folder is selected in the Data Source section of the \ref tree_viewer_page. \image html result-viewer-example-1.PNG -
-By default, the first three columns after the file name in the results viewer are named "S", "C" and "O". +As mentioned above, a table viewer is context-aware which means it will show applicable columns for the data type selected. The following shows the data in the "Web Bookmarks" node in the \ref tree_viewer_page. + +\image html result-viewer-example-3.PNG + +\subsection result_viewer_sco SCO Columns +By default, the first three columns after the file name in a table viewer are named "S", "C" and "O". \image html view_options_sco.png -These columns display the following: +These columns display the following information:
    -
  • (S)core column - indicates whether the item is interesting or notable +
  • (S)core column - indicates whether the item is interesting or notable.
      -
    • Displays a red icon if the file is a match for a notable hash set or has been tagged with a notable tag -
    • Displays a yellow icon if the file has an interesting item match or has been tagged with a non-notable tag +
    • Displays a red icon if the file is a match for a notable hashset or has been tagged with a notable tag. +
    • Displays a yellow icon if the file has an interesting item match or has been tagged with a non-notable tag.
    -
  • (C)omment column - indicates whether the item has a comment in the Central Repository or has a comment associated with a tag +
  • (C)omment column - indicates whether the item has a comment in the Central Repository or has a comment associated with a tag.
  • (O)ther occurrences column - indicates how many data sources in the Central Repository contain this item. The count will include the selected item.
To display more information about why an icon has appeared, you can hover over it. The Comment and Other occurrences columns query the Central Repository. If this seems to be having a performance impact, it can be disabled through the \ref view_options_page. This will remove the Other occurrences column entirely and the Comment column will be based only on tags. -You can also switch it to Thumbnail view to see thumbnails of the content in the selected folder. +\subsection export_csv Exporting to CSV -
-\image html result-viewer-example-2.PNG -
+You can export the contents of a table viewer to a CSV file in two ways. The "Save table as CSV" button in the upper left will save the entire contents of the table viewer to a CSV file. You can also select rows in the table viewer and then right-click and select "Export selected rows to CSV" to save only a subset of the rows: -The Result Viewer is context-aware, meaning it will show applicable columns for the data type selected. -
-\image html result-viewer-example-3.PNG -
+\image html result_viewer_csv.PNG + +\subsection right_click_functions Right Click Functions +Table viewers in the Results Viewer have certain right-click functions built-in into them that can be accessed when a row of a particular type is selected (a file, a directory, or a result). +Here are some examples that you may see: +\li Open File in External Viewer: Opens the selected file in an "external" application as defined by the local OS or through the External Viewer tab that you can navigate to by selecting the Options menu item from the Tools menu. For example, HTML files may be opened by Chrome or Firefox or some other browser, depending on what the local system is configured to use. +\li View in New Window: Opens the selected item in a new content viewer (instead of in the default location in the lower right area of the main window). +\li Extract: Makes a local copy of the selected file or directory for further analysis. + + +\section thumbnail_result_viewer Thumbnail Viewers +Thumbnail viewers display items selected in the \ref tree_viewer_page as a table of thumbnail images in adjustable sizes. This viewer only supports "picture" files (it currently only supports the JPG, GIF, and PNG formats). Click on the Thumbnail tab in the Listing tab to select this view. Note that for a large number of images in a directory selected in the Data Sources area of the \ref tree_viewer_page, or for a selection in the Views area of the \ref tree_viewer_page that contains a large number of images, it might take a while to populate the thumbnail viewer for the first time, i.e., before the thumbnails are cached. + +\image html thumbnail-result-viewer-tab.PNG + +\section result_viewer_paging Paging + +A table viewer can perform slowly when displaying a large numbers of rows. To address this, when there are over a certain numer of rows (10,000 by default), the results will be split into pages. The paging controls at the top right of the table view allow you to browse the different pages. + +\image html result_viewer_paging.PNG + +You can adust the page sizes through \ref view_options_page or turn paging off entirely. */ diff --git a/docs/doxygen-user/tagging.dox b/docs/doxygen-user/tagging.dox index 107dd7700a..b6cba720ae 100644 --- a/docs/doxygen-user/tagging.dox +++ b/docs/doxygen-user/tagging.dox @@ -1,6 +1,6 @@ /*! \page tagging_page Tagging -Tagging (or Bookmarking) allows you to create a reference to a file or object and easily find it later. Tagging is also used by the \ref central_repo_page "central repository" to mark items as notable. +Tagging (or Bookmarking) allows you to create a reference to a file or object and easily find it later or include it in a \ref reporting_page "report". For images, you can select a portion of the image to tag. Tagging is also used by the \ref central_repo_page "central repository" to mark items as notable. \section tagging_items Tagging items @@ -41,6 +41,40 @@ Tagged results are shown in the "Results" portion of the tree under "Tags". Tagg \image html tagging-4.PNG +\section image_tagging Image tagging + +When you have an image selected in the \ref result_viewer_page, you'll see a "Tags Menu" option in the upper right of the "Application" \ref content_viewer_page "Content Viewer". This allows you to tag only a selected area of the image. + +\image html tagging_image_menu.png + +\subsection image_tagging_creation Creating and editing an image tag + +To start, select the "Create" option on the tags menu. Then left-click and hold to draw a box around part of the image. When you release the mouse, you'll be able to choose which tag name to use. + +\image html tagging_image_select.png + +You can add a new tag name using the "New Tag" button. + +\image html tagging_image_create_tag.png + +Once you choose the tag name you'll see a red outline in the image around the section you chose. If you need to edit the tag, right click on the red border to select it. From here you can drag the edges to resize the box, or delete it by selecting "Delete" from the tags menu. + +\image html tagging_image_edit_tag.png + +You can create multiple tags in the same image. + +\image html tagging_image_multiple.png + +If you want to temporarily hide the tag outlines, select "Hide" from the tags menu. You can then select "Show" to see them again. The outlines will also reappear if you switch to a different item in the Result Viewer and come back. + +\subsection image_tagging_report Exporting and reporting on image tags + +If you want to save the image with the tag outline, select "Export" from the tags menu. The result will always be a PNG file. Note that using the "Extract File(s)" option from the right-click menu in the Result Viewer will export the original image. + +You'll also be able to see the image tags in an \ref report_html "HTML report". + +\image html tagging_image_report.png + \section managing_tags Managing tags The list of tags can be edited through the Tags tab on the Options menu. diff --git a/docs/doxygen-user/tree_viewer.dox b/docs/doxygen-user/tree_viewer.dox index 9e309476d0..b163018f4f 100644 --- a/docs/doxygen-user/tree_viewer.dox +++ b/docs/doxygen-user/tree_viewer.dox @@ -1,8 +1,47 @@ /*! \page tree_viewer_page Tree Viewer -The Tree Viewer shows the discovered folders by the data sources they come from, as well as a list of files in the folders. It is located on the left side of the Autopsy screen. The "Group by Data Source" option on the top left moves all views, results, and tags under their corresponding data source. +The tree on the left-hand side of the main window is where you can browse the files in the data sources in the case and find saved results from automated analyis (ingest). The tree has five main areas: +- Data Sources: This shows the directory tree hierarchy of the data sources. You can navigate to a specific file or directory here. Each data source added to the case is represented as a distinct sub tree. If you add a data source multiple times, it shows up multiple times. +- Views: Specific types of files from the data sources are shown here, aggregated by type or other properties. Files here can come from more than one data source. +- Results: This is where you can see the results from both the automated analysis (ingest) running in the background and your search results. +- Tags: This is where files and results that have been \ref tagging_page "tagged" are shown. +- Reports: Reports that you have generated, or that ingest modules have created, show up here. -Each folder in the tree on the left shows how many items are contained within it in parentheses after the directory name. See the picture below. +You can also use the "Group by data source" option available through the \ref view_options_page to move the Views, Results, and Tags tree nodes under their corresponding data sources. This can be helpful on very large cases to reduce the size of each sub tree. For example: + +\image html ui_layout_group_tree.PNG + +\section ui_tree_ds Data Sources + +The Data Sources area shows each data source that has been added to the case, in order added (top one is first). +Right clicking on the various nodes in the Data Sources area of the tree will allow you to get more options for each data source and its contents. + +Unallocated space is the chunks of a file system that are currently not being used for anything. Unallocated space can hold deleted files and other interesting artifacts. In an image data source, unallocated space is stored in blocks with distinct locations in the file system. However, because of the way carving tools work, it is better to feed these tools a single, large unallocated space file. Autopsy provides access to both methods of looking at unallocated space. +\li Individual blocks in a volume For each volume, there is a "virtual" folder named "$Unalloc". This folder contains all the individual unallocated blocks in contiguous runs (unallocated space files) as the image is storing them. You can right click and extract any unallocated space file the same way you can extract any other type of file in the Data Sources area. +\li Single files Right click on a volume and select "Extract Unallocated Space as Single File" to concatenate all of the unallocated space files in the volume into a single, continuous file. (If desired, you can right click on an image, and select "Extract Unallocated Space to Single Files" which will do the same thing, but once for each volume in the image). + +An example of the single file extraction option is shown below. +\image html extracting-unallocated-space.PNG + +\section ui_tree_views Views + +Views filter all the files in the case by some property of the file. +- File Types Sorts files by file extension or by MIME type, and shows them in the appropriate group. For example, files with .mp3 and .wav extensions end up in the "Audio" group. +- Deleted Files Displays files that have been deleted, but the names have been recovered. +- File Size Sorts files based on size. + + +\section ui_tree_results Results +- Extracted Content: Many ingest modules will place results here; EXIF metadata, GPS locations, or Web history for example. +- Keyword Hits: Keyword search hits show up here. +- Hashset Hits: Hashset hits show up here. +- E-Mail Messages: Email messages show up here. +- Interesting Items: Things deemed interesting show up here. +- Accounts: Credit card accounts show up here. +- Tags: Any item you tag shows up here so you can find it again easily. + +\section ui_tree_reports Reports + +Reports can be added by \subpage ingest_page or created using the \subpage reporting_page tool. -\image html directory-tree.PNG */ diff --git a/docs/doxygen-user/uilayout.dox b/docs/doxygen-user/uilayout.dox index c0906dd0cc..0f39386afe 100644 --- a/docs/doxygen-user/uilayout.dox +++ b/docs/doxygen-user/uilayout.dox @@ -5,115 +5,37 @@ \section ui_overview Overview The major areas in the Autopsy User Interface (UI) are: -- \ref ui_tree, shown outlined in green below -- \ref ui_results, shown outlined in blue below -- \ref ui_content, shown outlined in red below -- \ref ui_keyword, shown outlined in yellow below -- \ref ui_status, shown in solid purple below +- \ref ui_tree, shown in green on the left side +- \ref ui_results, shown in blue on the upper right side +- \ref ui_content, shown in red on the lower right side +- \ref ui_keyword, shown in magenta in the upper right corner +- \ref ui_status, shown in purple in the lower right corner You can customize how data is shown in the UI through the \ref view_options_page panel. \image html ui-layout-1.PNG -
-
-
\section ui_tree Tree Viewer -\subpage tree_viewer_page "More..." -
+The Tree Viewer on the left-hand side is the top level of the UI. Selecting items in the Tree Viewer will cause their contents to be displayed in the Result Viewer to the right. You can browse the files in the image, find saved results generated by the \ref ingest_page "ingest modules", and see the results from \ref tagging_page and \ref reporting_page. See the \subpage tree_viewer_page page for additional information. -The tree on the left-hand side is where you can browse the files in the image and find saved results from automated procedures (ingest). The tree has five main areas: -- Data Sources: This shows the directory tree hierarchy of the file systems in the images. You can navigate to a specific file or directory here. Each data source added is represented as a drive. If you add a data source multiple times, it shows up multiple times. -- Views: Specific types of files from the data sources are shown here, aggregated by type or other properties. Files here can come from more than one data source. Look here for files of a specific type or property. -- Results: Where you can see the results from the background ingest tasks and you can see your previous search results. Go here to see what was found by the ingest modules and to find your previous search results. -- Tags: Where files and results that have been \ref tagging_page "tagged" are shown -- Reports: References to reports that you have generated or that ingest modules have created show up here - -You can also use the "Group by data source" option available through the \ref view_options_page to move the views, results, and tags subtrees under their corresponding data sources. This can be helpful on very large cases to reduce the size of each node. - -\image html ui_layout_group_tree.PNG - -\subsection ui_tree_ds Data Sources - -The Data Sources section shows each data source that has been added to the case, in order added (top one is first). -Right clicking on the various nodes in the Data Sources section of the tree will allow you to get more options for each data source and its contents. - -Unallocated space is chunks of the file system that is currently not being used for anything. Unallocated space can store deleted files and other interesting artifacts. On the actual image, Unallocated space is stored in blocks with distinct locations on the system. However, because of the way various carving tools work, it is more ideal to feed them a single, large unallocated file. Autopsy provides access to both methods of looking at unallocated space. -\li Individual blocks in a volume There is a folder named "Unalloc". This folder contains all the individual unallocated blocks as the image is storing them. You can right click and extract them the same way you can extract any other type of file in the Directory Tree. -\li Single files Right click on a volume and select "Extract Unallocated Space as Single File" to concatenate all the unallocated files in the volume into a single, continuous file. (If desired, you can right click on an image, and select "Extract Unallocated Space to Single Files" which will do the same thing, but once for each volume in the image). - -An example of the single file extraction option is shown below. -\image html extracting-unallocated-space.PNG - -\subsection ui_tree_views Views - -Views filter all the files in the case by some external property of the file, not by any internal analysis of the file. -- File Type Sorts files by file extension or MIME type, and shows them in the appropriate group. For example, .mp3 and .wav both end up in the "Audio" group. -- Recent Files Displays files that are accessed within the last seven days the user had the device. -- Deleted Files Displays files that have been deleted but the names have been recovered. -- File Size Sorts files based upon size. This can give you an idea where to look for files you are interested in. - - -\subsection ui_tree_results Results -- Extracted Content: Many ingest modules will place results here; EXIF data, GPS locations, or Web History for example -- Keyword Hits: Keyword search hits show up here -- Hashset Hits: Hashset hits show up here -- E-Mail Messages: Email messages show up here -- Interesting Items: Things deemed interesting show up here -- Accounts: Credit card accounts show up here -- Tags: Any item you tag shows up here so you can find it again easily - -\subsection ui_tree_reports Reports - -Reports can be added by \subpage ingest_page or created using the \subpage reporting_page tool. - -
-
-
\section ui_results Result Viewer -\subpage result_viewer_page "More..." -
- -The Result Viewer windows are in the upper right area of the interface and display the results from selecting something in the tree. You will have the option to display the results in a variety of formats. - -\subsection right_click_functions Right Click Functions -Viewers in Result Viewers have certain right-click functions built-in into them that can be accessed when a node a certain type is selected (a file, directory or a result). -Here are some examples that you may see: -\li Open File in External Viewer: Opens the selected file in an "external" application as defined by the local OS or through the External Viewer tab on the Options menu. For example, HTML files may be opened by IE or Firefox, depending on what the local system is configured to use. -\li View in New Window: Opens the content in a new internal Content Viewer (instead of in the default location in the lower right). -\li Extract: Make a local copy of the file or directory for further analysis. -\li Search for files with the same MD5 Hash: Searches the entire file-system for any files with the same MD5 Hash as the one selected. - -\subsection thumbnail_result_viewer Thumbnail Result Viewers -Thumbnail Results Viewer displays the data catalog as a table of thumbnail images in adjustable sizes. This viewer only supports picture files (Currently, only supports JPG, GIF, and PNG formats). Click the Thumbnail tab to select this view. Note that for a large number of images in a directory selected in the Data Explorer, or for a View selected that contains a large number of images, it might take a while to populate this view for the first time before the images are cached. - -Example\n -Below is an example of "Thumbnail Results Viewer" window: -\image html thumbnail-result-viewer-tab.PNG - -\subsection table_result_viewer Table Result Viewers -Table Results Viewer (Directory Listing) displays the data catalog as a table with some details (properties) of each file. The properties that it shows are: name, time (modified, changed, accessed, and created), size, flags (directory and meta), mode, user ID, group ID, metadata address, attribute address, and type (directory and meta). Click the Table Viewer tab to select this view. - -The Results Viewer can be also activated for saved results and it can show a high level results grouped, or a results at a file level, depending on which node on the Directory Tree is selected to populate the Table Results Viewer. - -Example\n -Below is an example of a "Table Results Viewer" window: -\image html table-result-viewer-tab.PNG +The Result Viewer windows are in the upper right area of the interface and display the results from selecting something in the Tree Viewer. The columns displayed will depend on what was selected in the Tree Viewer - files will show things like path, size, and creation date while a contact would show its name, phone number, and email. Selecting an item in the Result Viewer will show details about the item in the Content Viewer below. See the \ref result_viewer_page for additional information. \section ui_content Content Viewer -The \ref content_viewer_page area is in the lower right area of the interface. This area is used to view a specific file in a variety of formats. There are different tabs for different viewers. Not all tabs support all file types, so only some of them will be enabled. To display data in this area, a file must be selected from the Result Viewer window. +The Content Viewer area is in the lower right area of the interface. This area is used to view a specific file in a variety of formats. There are different tabs for different viewers. Not all tabs support all file types, so only some of them will be enabled. To display data in this area, a file must be selected from the Result Viewer window. -The Content Viewer area is part of a plug-in framework. You can install modules that will add more viewer types. For additional information on the built-in content viewers, see the \ref content_viewer_page page. +The Content Viewer area is part of a plug-in framework, meaning you can install modules that will add more viewer types. +For additional information see the \ref content_viewer_page page. \section ui_keyword Keyword Search -Keyword Search allows the user to search for keywords in the data source. It is covered in more detail here: \subpage keyword_search_page +The Keyword Search feature allows the user to search for keywords in one or more data sources contained in the current case. It is covered in more detail here: \ref keyword_search_page \section ui_status Status Area -The Status area will show progress bars while ingest is occuring. This visually indicates to the user what portion of the processing is already complete. The user can click on the progress bars to see further detail or to cancel ingest jobs. +The Status area will show progress bars while ingest is occurring. This visually indicates what portion of the processing is already complete. You can click on the progress bars to see further detail or to cancel ingest jobs.
*/ diff --git a/docs/doxygen-user/view_options.dox b/docs/doxygen-user/view_options.dox index 67cb5853d4..4516a035ef 100644 --- a/docs/doxygen-user/view_options.dox +++ b/docs/doxygen-user/view_options.dox @@ -39,6 +39,10 @@ This option allows you to hide tags from other users in the Tagging section of t By default, the first three columns in the result viewer after the file name in the results viewer are named "S", "C" and "O". These are described in more detail on the \ref result_viewer_page page. The Comment and Other occurrences columns query the Central Repository. If this seems to be having a performance impact, it can be disabled using the checkbox. This will remove the Other occurrences column entirely and the Comment column will be based only on tags. +\subsection view_options_paging Paging + +By default, only 10,000 results will be show in the Result Viewer. You can change that threshold here. Setting it to zero will disable paging. + \subsection view_options_content_viewer Content viewer selection By default, the \ref content_viewer_page attempts to select the most relevant tab to display when choosing a node. If you would like to change this behavior to instead stay on the same content viewer when changing nodes, switch to the "Stay on the same file viewer" option. @@ -53,7 +57,7 @@ Timestamps can be viewed in either local time or in a time zone selected in the \subsection view_options_translate Translate text -If you have a custom translation module installed, this option will add a column to the \ref result_viewer_page to show the translated name of files and folders. +If you have a \ref machine_translation_page module installed, this option will add a column to the \ref result_viewer_page to show the translated name of files and folders. \section view_options_case Current Case Settings