diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 0ba92c7bce..25789939b3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * The action associated with the Case/Open Case menu item via the layer.xml @@ -64,6 +65,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action private static final Logger LOGGER = Logger.getLogger(CaseOpenAction.class.getName()); private final FileFilter caseMetadataFileFilter; + private final JFileChooserFactory fileChooserHelper; + /** * Constructs the action associated with the Case/Open Case menu item via * the layer.xml file, a toolbar button, and the Open Case button of the @@ -72,6 +75,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action */ public CaseOpenAction() { caseMetadataFileFilter = new FileNameExtensionFilter(NbBundle.getMessage(CaseOpenAction.class, "CaseOpenAction.autFilter.title", Version.getName(), CaseMetadata.getFileExtension()), CaseMetadata.getFileExtension().substring(1)); + fileChooserHelper = new JFileChooserFactory(); } /** @@ -80,7 +84,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * to open the case described by the file. */ void openCaseSelectionWindow() { - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = fileChooserHelper.getChooser(); fileChooser.setDragEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); fileChooser.setMultiSelectionEnabled(false); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index 4c6126fcb7..eda7149f66 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.coreutils.DriveUtils; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.HashUtility; /** @@ -48,8 +49,10 @@ public class ImageFilePanel extends JPanel implements DocumentListener { private static final long serialVersionUID = 1L; private static final String PROP_LASTIMAGE_PATH = "LBL_LastImage_PATH"; //NON-NLS private static final String[] SECTOR_SIZE_CHOICES = {"Auto Detect", "512", "1024", "2048", "4096"}; - private final JFileChooser fileChooser = new JFileChooser(); + private final JFileChooserFactory fileChooserHelper = new JFileChooserFactory(); + private JFileChooser fileChooser; private final String contextName; + private final List fileChooserFilters; /** * Creates new form ImageFilePanel @@ -73,14 +76,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener { sectorSizeComboBox.setSelectedIndex(0); errorLabel.setVisible(false); - - fileChooser.setDragEnabled(false); - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - fileChooser.setMultiSelectionEnabled(false); - fileChooserFilters.forEach(fileChooser::addChoosableFileFilter); - if (fileChooserFilters.isEmpty() == false) { - fileChooser.setFileFilter(fileChooserFilters.get(0)); - } + this.fileChooserFilters = fileChooserFilters; } /** @@ -132,6 +128,21 @@ public class ImageFilePanel extends JPanel implements DocumentListener { private JTextField getSha256TextField() { return sha256HashTextField; } + + private JFileChooser getChooser() { + if(fileChooser == null) { + fileChooser = fileChooserHelper.getChooser(); + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(false); + fileChooserFilters.forEach(fileChooser::addChoosableFileFilter); + if (fileChooserFilters.isEmpty() == false) { + fileChooser.setFileFilter(fileChooserFilters.get(0)); + } + } + + return fileChooser; + } /** * This method is called from within the constructor to initialize the form. @@ -298,12 +309,13 @@ public class ImageFilePanel extends JPanel implements DocumentListener { String oldText = getContentPaths(); // set the current directory of the FileChooser if the ImagePath Field is valid File currentDir = new File(oldText); + JFileChooser chooser = getChooser(); if (currentDir.exists()) { - fileChooser.setCurrentDirectory(currentDir); + chooser.setCurrentDirectory(currentDir); } - if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - String path = fileChooser.getSelectedFile().getPath(); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + String path = chooser.getSelectedFile().getPath(); if (path.endsWith(".001")) { String zeroX3_path = StringUtils.removeEnd(path, ".001") + ".000"; if (new File(zeroX3_path).exists()) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 469b93da66..b0031ed1c7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; /** @@ -58,7 +59,8 @@ final class LocalDiskPanel extends JPanel { private static final long serialVersionUID = 1L; private LocalDisk localDisk; private boolean enableNext = false; - private final JFileChooser fc = new JFileChooser(); + private JFileChooser fc; + private final JFileChooserFactory chooserHelper; /** * Creates new form LocalDiskPanel @@ -68,6 +70,7 @@ final class LocalDiskPanel extends JPanel { customInit(); createTimeZoneList(); createSectorSizeList(); + chooserHelper = new JFileChooserFactory(); } /** @@ -261,6 +264,7 @@ final class LocalDiskPanel extends JPanel { }//GEN-LAST:event_pathTextFieldKeyReleased private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + fc = chooserHelper.getChooser(); String oldText = pathTextField.getText(); // set the current directory of the FileChooser if the ImagePath Field is valid File currentFile = new File(oldText); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java index 201d25ecef..e4edc6e7bb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2018 Basis Technology Corp. + * Copyright 2012-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.DriveUtils; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -45,7 +46,8 @@ class MissingImageDialog extends javax.swing.JDialog { long obj_id; SleuthkitCase db; - private final JFileChooser fileChooser = new JFileChooser(); + private JFileChooser fileChooser; + private final JFileChooserFactory chooserHelper; /** * Instantiate a MissingImageDialog. @@ -58,17 +60,8 @@ class MissingImageDialog extends javax.swing.JDialog { this.obj_id = obj_id; this.db = db; initComponents(); - - fileChooser.setDragEnabled(false); - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - fileChooser.setMultiSelectionEnabled(false); - - List fileFiltersList = ImageDSProcessor.getFileFiltersList(); - for (FileFilter fileFilter : fileFiltersList) { - fileChooser.addChoosableFileFilter(fileFilter); - } - fileChooser.setFileFilter(fileFiltersList.get(0)); - + + chooserHelper = new JFileChooserFactory(); selectButton.setEnabled(false); } @@ -270,6 +263,19 @@ class MissingImageDialog extends javax.swing.JDialog { private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + if(fileChooser == null) { + fileChooser = chooserHelper.getChooser(); + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(false); + + List fileFiltersList = ImageDSProcessor.getFileFiltersList(); + for (FileFilter fileFilter : fileFiltersList) { + fileChooser.addChoosableFileFilter(fileFilter); + } + fileChooser.setFileFilter(fileFiltersList.get(0)); + } + String oldText = pathNameTextField.getText(); lbWarning.setText(""); // set the current directory of the FileChooser if the ImagePath Field is valid diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index 32812b56ce..64def92cc2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import javax.swing.event.DocumentListener; import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.coreutils.PathValidator; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * The JPanel for the first page of the new case wizard. @@ -37,7 +38,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { - private final JFileChooser fileChooser = new JFileChooser(); + private final JFileChooserFactory fileChooserHelper = new JFileChooserFactory(); private final NewCaseWizardPanel1 wizPanel; /** @@ -353,8 +354,9 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { * @param evt the action event */ private void caseDirBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_caseDirBrowseButtonActionPerformed + JFileChooser fileChooser = fileChooserHelper.getChooser(); fileChooser.setDragEnabled(false); - if (!caseParentDirTextField.getText().trim().equals("")) { + if (!caseParentDirTextField.getText().trim().isEmpty()) { fileChooser.setCurrentDirectory(new File(caseParentDirTextField.getText())); } fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java index 3b86909953..94c708fdbb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED index 447b4e3038..84598e3756 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED @@ -1,8 +1,9 @@ CommandLineIngestSettingPanel_empty_report_name_mgs=Report profile name was empty, no profile created. CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created. +CommandLineIngestSettingPanel_invalid_report_name_mgs=Report profile name contained illegal characters, no profile created. CommandListIngestSettingsPanel_Default_Report_DisplayName=Default CommandListIngestSettingsPanel_Make_Config=Make new profile... -CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (commas not allowed): +CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (letters, digits, and underscore characters only): OpenIDE-Module-Name=CommandLineAutopsy OptionsCategory_Keywords_Command_Line_Ingest_Settings=Command Line Ingest Settings OptionsCategory_Keywords_General=Options diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java index ca11d03f13..531f96a878 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java @@ -280,18 +280,15 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { add(nodePanel, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents @Messages({ - "CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (commas not allowed):", + "CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (letters, digits, and underscore characters only):", "CommandLineIngestSettingPanel_empty_report_name_mgs=Report profile name was empty, no profile created.", - "CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created." + "CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created.", + "CommandLineIngestSettingPanel_invalid_report_name_mgs=Report profile name contained illegal characters, no profile created." }) private void bnEditReportSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnEditReportSettingsActionPerformed String reportName = getReportName(); if (reportName.equals(Bundle.CommandListIngestSettingsPanel_Make_Config())) { reportName = JOptionPane.showInputDialog(this, Bundle.CommandListIngestSettingsPanel_Report_Name_Msg()); - - // sanitize report name. Remove all commas because in CommandLineOptionProcessor we use commas - // to separate multiple report names - reportName = reportName.replaceAll(",", ""); // User hit cancel if (reportName == null) { @@ -302,6 +299,15 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { } else if (doesReportProfileNameExist(reportName)) { JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.CommandLineIngestSettingPanel_existing_report_name_mgs()); return; + } else { + // sanitize report name + String originalReportName = reportName; + reportName = reportName.replaceAll("[^A-Za-z0-9_]", ""); + if (reportName.isEmpty() || (!(originalReportName.equals(reportName)))) { + // report name contained only invalid characters, display error + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.CommandLineIngestSettingPanel_invalid_report_name_mgs()); + return; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java index 1473e7cd67..67a9ee5482 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java @@ -24,6 +24,7 @@ import java.awt.KeyboardFocusManager; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; @@ -38,6 +39,7 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; @@ -46,8 +48,10 @@ import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; import org.sleuthkit.datamodel.AbstractContent; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.SleuthkitCase; /** * A Panel that shows the media (thumbnails) for the selected account. @@ -65,6 +69,7 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM private final MessageDataContent contentViewer; private MediaViewerWorker worker; + private SelectionWorker selectionWorker; @Messages({ "MediaViewer_Name=Media Attachments" @@ -106,11 +111,15 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM @Override public void setSelectionInfo(SelectionInfo info) { contentViewer.setNode(null); - thumbnailViewer.resetComponent(); + thumbnailViewer.setNode(null); if (worker != null) { worker.cancel(true); } + + if(selectionWorker != null) { + selectionWorker.cancel(true); + } worker = new MediaViewerWorker(info); @@ -181,21 +190,66 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM */ private void handleNodeSelectionChange() { final Node[] nodes = tableEM.getSelectedNodes(); + contentViewer.setNode(null); + + if(selectionWorker != null) { + selectionWorker.cancel(true); + } if (nodes != null && nodes.length == 1) { AbstractContent thumbnail = nodes[0].getLookup().lookup(AbstractContent.class); if (thumbnail != null) { - try { - Content parentContent = thumbnail.getParent(); - if (parentContent != null && parentContent instanceof BlackboardArtifact) { - contentViewer.setNode(new BlackboardArtifactNode((BlackboardArtifact) parentContent)); - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get parent Content from AbstraceContent instance.", ex); //NON-NLS + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + selectionWorker = new SelectionWorker(thumbnail); + selectionWorker.execute(); + } + } + } + + /** + * A SwingWorker to get the artifact associated with the selected thumbnail. + */ + private class SelectionWorker extends SwingWorker { + + private final AbstractContent thumbnail; + + // Construct a SelectionWorker. + SelectionWorker(AbstractContent thumbnail) { + this.thumbnail = thumbnail; + } + + @Override + protected BlackboardArtifact doInBackground() throws Exception { + SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); + List artifactsList = skCase.getBlackboardArtifacts(TSK_ASSOCIATED_OBJECT, thumbnail.getId()); + for (BlackboardArtifact contextArtifact : artifactsList) { + BlackboardAttribute associatedArtifactAttribute = contextArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); + if (associatedArtifactAttribute != null) { + long artifactId = associatedArtifactAttribute.getValueLong(); + return contextArtifact.getSleuthkitCase().getBlackboardArtifact(artifactId); } } - } else { - contentViewer.setNode(null); + return null; + } + + @Override + protected void done() { + if (isCancelled()) { + return; + } + + try { + BlackboardArtifact artifact = get(); + if (artifact != null) { + contentViewer.setNode(new BlackboardArtifactNode(artifact)); + } else { + contentViewer.setNode(null); + } + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Failed message viewer based on thumbnail selection. thumbnailID = " + thumbnail.getId(), ex); + } finally { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java index f73cb6350f..51e9a549ee 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018-2019 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,6 +56,7 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.TskCoreException; import org.xml.sax.SAXException; @@ -75,6 +76,8 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer private ExplorerManager explorerManager; private NSObject rootDict; + + private final JFileChooserFactory fileChooserHelper = new JFileChooserFactory(); /** * Creates new form PListViewer @@ -203,7 +206,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer return; } - final JFileChooser fileChooser = new JFileChooser(); + final JFileChooser fileChooser = fileChooserHelper.getChooser(); fileChooser.setCurrentDirectory(new File(openCase.getExportDirectory())); fileChooser.setFileFilter(new FileNameExtensionFilter("XML file", "xml")); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 7772be7d29..38630b4aa4 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018-2019 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,6 +49,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.autopsy.coreutils.SQLiteTableReader; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * A file content viewer for SQLite database files. @@ -74,6 +75,8 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private int currPage = 0; // curr page of rows being displayed SwingWorker worker; + + private final JFileChooserFactory chooserHelper = new JFileChooserFactory(); /** * Constructs a file content viewer for SQLite database files. @@ -280,7 +283,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCsvButtonActionPerformed Case openCase = Case.getCurrentCase(); File caseDirectory = new File(openCase.getExportDirectory()); - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = chooserHelper.getChooser(); fileChooser.setDragEnabled(false); fileChooser.setCurrentDirectory(caseDirectory); //Set a filter to let the filechooser only work for csv files diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentViewer.java index 634d5c61db..ef623f44cf 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentViewer.java @@ -40,18 +40,17 @@ import org.sleuthkit.datamodel.TskCoreException; */ @ServiceProvider(service = DataContentViewer.class, position = 7) public class AnalysisResultsContentViewer implements DataContentViewer { + private static final Logger logger = Logger.getLogger(AnalysisResultsContentPanel.class.getName()); - + // isPreferred value private static final int PREFERRED_VALUE = 3; - + private final AnalysisResultsViewModel viewModel = new AnalysisResultsViewModel(); private final AnalysisResultsContentPanel panel = new AnalysisResultsContentPanel(); - + private SwingWorker worker = null; - - @NbBundle.Messages({ "AnalysisResultsContentViewer_title=Analysis Results" }) @@ -135,11 +134,11 @@ public class AnalysisResultsContentViewer implements DataContentViewer { if (content instanceof AnalysisResult) { return true; } - + if (content == null || content instanceof BlackboardArtifact) { continue; } - + try { if (Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().hasAnalysisResults(content.getId())) { return true; @@ -148,7 +147,7 @@ public class AnalysisResultsContentViewer implements DataContentViewer { logger.log(Level.SEVERE, "Unable to get analysis results for file with obj id " + content.getId(), ex); } } - + return false; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java index 00cc170b14..03720e0cbe 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java @@ -252,7 +252,7 @@ public class AnalysisResultsViewModel { try { nodeContent = Optional.of(content); - + // get the aggregate score of that content aggregateScore = Optional.ofNullable(content.getAggregateScore()); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 56ff4a0993..0a52ae749c 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -55,6 +55,7 @@ import org.sleuthkit.autopsy.machinesettings.UserMachinePreferencesException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.autopsy.machinesettings.UserMachinePreferences.TempDirChoice; import org.sleuthkit.autopsy.report.ReportBranding; @@ -82,8 +83,8 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private static final String DEFAULT_HEAP_DUMP_FILE_FIELD = ""; - private final JFileChooser logoFileChooser; - private final JFileChooser tempDirChooser; + private JFileChooser logoFileChooser; + private JFileChooser tempDirChooser; private static final String ETC_FOLDER_NAME = "etc"; private static final String CONFIG_FILE_EXTENSION = ".conf"; private static final long ONE_BILLION = 1000000000L; //used to roughly convert system memory from bytes to gigabytes @@ -94,27 +95,17 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private String initialMemValue = Long.toString(Runtime.getRuntime().maxMemory() / ONE_BILLION); private final ReportBranding reportBranding; - private final JFileChooser heapFileChooser; + private JFileChooser heapFileChooser; + + private final JFileChooserFactory logoChooserHelper = new JFileChooserFactory(); + private final JFileChooserFactory heapChooserHelper = new JFileChooserFactory(); + private final JFileChooserFactory tempChooserHelper = new JFileChooserFactory(); /** * Instantiate the Autopsy options panel. */ AutopsyOptionsPanel() { initComponents(); - logoFileChooser = new JFileChooser(); - logoFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - logoFileChooser.setMultiSelectionEnabled(false); - logoFileChooser.setAcceptAllFileFilterUsed(false); - logoFileChooser.setFileFilter(new GeneralFilter(GeneralFilter.GRAPHIC_IMAGE_EXTS, GeneralFilter.GRAPHIC_IMG_DECR)); - - tempDirChooser = new JFileChooser(); - tempDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - tempDirChooser.setMultiSelectionEnabled(false); - - heapFileChooser = new JFileChooser(); - heapFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - heapFileChooser.setMultiSelectionEnabled(false); - if (!isJVMHeapSettingsCapable()) { //32 bit JVM has a max heap size of 1.4 gb to 4 gb depending on OS //So disabling the setting of heap size when the JVM is not 64 bit @@ -1242,6 +1233,11 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { "# {0} - path", "AutopsyOptionsPanel_tempDirectoryBrowseButtonActionPerformed_onInvalidPath_description=Unable to create temporary directory within specified path: {0}",}) private void tempDirectoryBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tempDirectoryBrowseButtonActionPerformed + if(tempDirChooser == null) { + tempDirChooser = tempChooserHelper.getChooser(); + tempDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + tempDirChooser.setMultiSelectionEnabled(false); + } int returnState = tempDirChooser.showOpenDialog(this); if (returnState == JFileChooser.APPROVE_OPTION) { String specifiedPath = tempDirChooser.getSelectedFile().getPath(); @@ -1318,6 +1314,13 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { }//GEN-LAST:event_defaultLogoRBActionPerformed private void browseLogosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseLogosButtonActionPerformed + if(logoFileChooser == null) { + logoFileChooser = logoChooserHelper.getChooser(); + logoFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + logoFileChooser.setMultiSelectionEnabled(false); + logoFileChooser.setAcceptAllFileFilterUsed(false); + logoFileChooser.setFileFilter(new GeneralFilter(GeneralFilter.GRAPHIC_IMAGE_EXTS, GeneralFilter.GRAPHIC_IMG_DECR)); + } String oldLogoPath = agencyLogoPathField.getText(); int returnState = logoFileChooser.showOpenDialog(this); if (returnState == JFileChooser.APPROVE_OPTION) { @@ -1360,6 +1363,11 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { "AutopsyOptionsPanel_heapDumpBrowseButtonActionPerformed_fileAlreadyExistsMessage=File already exists. Please select a new location." }) private void heapDumpBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_heapDumpBrowseButtonActionPerformed + if(heapFileChooser == null) { + heapFileChooser = heapChooserHelper.getChooser(); + heapFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + heapFileChooser.setMultiSelectionEnabled(false); + } String oldHeapPath = heapDumpFileField.getText(); if (!StringUtils.isBlank(oldHeapPath)) { heapFileChooser.setCurrentDirectory(new File(oldHeapPath)); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java index 1a02f65060..dbb571d4ba 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Allows examiner to supply a raw data source. @@ -41,7 +42,8 @@ final class RawDSInputPanel extends JPanel implements DocumentListener { private static final long TWO_GB = 2000000000L; private static final long serialVersionUID = 1L; //default private final String PROP_LASTINPUT_PATH = "LBL_LastInputFile_PATH"; - private final JFileChooser fc = new JFileChooser(); + private JFileChooser fc; + private JFileChooserFactory chooserHelper = new JFileChooserFactory(); // Externally supplied name is used to store settings private final String contextName; /** @@ -51,11 +53,6 @@ final class RawDSInputPanel extends JPanel implements DocumentListener { initComponents(); errorLabel.setVisible(false); - - fc.setDragEnabled(false); - fc.setFileSelectionMode(JFileChooser.FILES_ONLY); - fc.setMultiSelectionEnabled(false); - this.contextName = context; } @@ -200,18 +197,25 @@ final class RawDSInputPanel extends JPanel implements DocumentListener { }// //GEN-END:initComponents @SuppressWarnings("deprecation") private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed - String oldText = pathTextField.getText(); - // set the current directory of the FileChooser if the ImagePath Field is valid - File currentDir = new File(oldText); - if (currentDir.exists()) { - fc.setCurrentDirectory(currentDir); - } + if (fc == null) { + fc = chooserHelper.getChooser(); + fc.setDragEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(false); + } - int retval = fc.showOpenDialog(this); - if (retval == JFileChooser.APPROVE_OPTION) { - String path = fc.getSelectedFile().getPath(); - pathTextField.setText(path); - } + String oldText = pathTextField.getText(); + // set the current directory of the FileChooser if the ImagePath Field is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + pathTextField.setText(path); + } }//GEN-LAST:event_browseButtonActionPerformed private void j2GBBreakupRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_j2GBBreakupRadioButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java index a641ea1f73..d7a78ea389 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import javax.swing.JPanel; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Allows an examiner to configure the XRY Data source processor. @@ -49,6 +50,8 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { //panel will indicate when it is ready for an update. private final PropertyChangeSupport pcs; + private final JFileChooserFactory chooserHelper = new JFileChooserFactory(); + /** * Creates new form XRYDataSourceConfigPanel. * Prevent direct instantiation. @@ -191,7 +194,7 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { * report folder. */ private void fileBrowserButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileBrowserButtonActionPerformed - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = chooserHelper.getChooser(); fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); Optional lastUsedPath = getLastUsedPath(); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java index 3cf1d2445a..beafb6ef5d 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import javax.swing.event.DocumentListener; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.GeneralFilter; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; /** @@ -35,7 +36,9 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; class AddExternalViewerRulePanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(AddExternalViewerRulePanel.class.getName()); - private final JFileChooser fc = new JFileChooser(); + private static final long serialVersionUID = 1L; + private JFileChooser fc; + private final JFileChooserFactory chooserHelper = new JFileChooserFactory(); private static final GeneralFilter exeFilter = new GeneralFilter(GeneralFilter.EXECUTABLE_EXTS, GeneralFilter.EXECUTABLE_DESC); enum EVENT { @@ -47,10 +50,6 @@ class AddExternalViewerRulePanel extends javax.swing.JPanel { */ AddExternalViewerRulePanel() { initComponents(); - fc.setDragEnabled(false); - fc.setFileSelectionMode(JFileChooser.FILES_ONLY); - fc.setMultiSelectionEnabled(false); - fc.setFileFilter(exeFilter); customize(); } @@ -260,6 +259,13 @@ class AddExternalViewerRulePanel extends javax.swing.JPanel { }// //GEN-END:initComponents private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + if(fc == null) { + fc = chooserHelper.getChooser(); + fc.setDragEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(false); + fc.setFileFilter(exeFilter); + } int returnState = fc.showOpenDialog(this); if (returnState == JFileChooser.APPROVE_OPTION) { String path = fc.getSelectedFile().getPath(); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index 7b312211c4..27b7a11a84 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,6 +51,7 @@ import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFileProp import org.openide.nodes.Node; import org.openide.nodes.Node.PropertySet; import org.openide.nodes.Node.Property; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Exports CSV version of result nodes to a location selected by the user. @@ -68,6 +69,8 @@ public final class ExportCSVAction extends AbstractAction { // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every // node in the array returns a reference to the same action object from Node.getActions(boolean). private static ExportCSVAction instance; + + private static final JFileChooserFactory chooserHelper = new JFileChooserFactory(); /** * Get an instance of the Action. See above for why @@ -125,7 +128,7 @@ public final class ExportCSVAction extends AbstractAction { // Set up the file chooser with a default name and either the Export // folder or the last used folder. String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode()); - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = chooserHelper.getChooser(); fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows()))); fileChooser.setSelectedFile(new File(fileName)); fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java index 0cfbd7d5e5..0235c849bd 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import org.netbeans.spi.options.OptionsPanelController; import org.sleuthkit.autopsy.casemodule.GeneralFilter; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * An options panel for the user to create, edit, and delete associations for @@ -42,9 +43,13 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel implements OptionsPanel { - private ExternalViewerGlobalSettingsTableModel tableModel; + private static final long serialVersionUID = 1L; - public ExternalViewerGlobalSettingsPanel() { + private ExternalViewerGlobalSettingsTableModel tableModel; + + private final JFileChooserFactory chooserHelper = new JFileChooserFactory(); + + ExternalViewerGlobalSettingsPanel() { this(new ExternalViewerGlobalSettingsTableModel(new String[] { "Mime type/Extension", "Application"})); } @@ -52,7 +57,7 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme /** * Creates new form ExternalViewerGlobalSettingsPanel */ - public ExternalViewerGlobalSettingsPanel(ExternalViewerGlobalSettingsTableModel tableModel) { + ExternalViewerGlobalSettingsPanel(ExternalViewerGlobalSettingsTableModel tableModel) { initComponents(); this.tableModel = tableModel; customizeComponents(tableModel); @@ -335,7 +340,7 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme }//GEN-LAST:event_deleteRuleButtonActionPerformed private void browseHxDDirectoryActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseHxDDirectoryActionPerformed - JFileChooser fileWindow = new JFileChooser(); + JFileChooser fileWindow = chooserHelper.getChooser(); fileWindow.setFileSelectionMode(JFileChooser.FILES_ONLY); GeneralFilter exeFilter = new GeneralFilter(GeneralFilter.EXECUTABLE_EXTS, GeneralFilter.EXECUTABLE_DESC); File HxDPathFile = new File(HxDPath.getText()); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index dcb55e5655..a29f5c39ef 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -33,9 +33,6 @@ import java.util.List; import java.util.Set; import java.util.HashSet; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JFileChooser; @@ -50,6 +47,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.AbstractContent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -78,9 +76,7 @@ final class ExtractUnallocAction extends AbstractAction { private final Volume volume; private final Image image; - private final FutureTask futureFileChooser = new FutureTask<>(CustomFileChooser::new); - - private JFileChooser fileChooser = null; + private final JFileChooserFactory chooserFactory; /** * Create an instance of ExtractUnallocAction with a volume. @@ -90,7 +86,7 @@ final class ExtractUnallocAction extends AbstractAction { */ ExtractUnallocAction(String title, Volume volume) { this(title, null, volume); - + } /** @@ -110,9 +106,8 @@ final class ExtractUnallocAction extends AbstractAction { this.volume = null; this.image = image; - - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.execute(futureFileChooser); + + chooserFactory = new JFileChooserFactory(CustomFileChooser.class); } /** @@ -138,13 +133,7 @@ final class ExtractUnallocAction extends AbstractAction { return; } - if (fileChooser == null) { - try { - fileChooser = futureFileChooser.get(); - } catch (InterruptedException | ExecutionException ex) { - fileChooser = new CustomFileChooser(); - } - } + JFileChooser fileChooser = chooserFactory.getChooser(); fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase))); if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog((Component) event.getSource())) { @@ -753,11 +742,11 @@ final class ExtractUnallocAction extends AbstractAction { } // A Custome JFileChooser for this Action Class. - private class CustomFileChooser extends JFileChooser { + public static class CustomFileChooser extends JFileChooser { private static final long serialVersionUID = 1L; - CustomFileChooser() { + public CustomFileChooser() { initalize(); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java index 5bdec7daa6..5a28bd9c12 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2019 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.AbstractFile; /** @@ -51,6 +52,9 @@ public class ExtractActionHelper { private final Logger logger = Logger.getLogger(ExtractActionHelper.class.getName()); private String userDefinedExportPath; + + private final JFileChooserFactory extractFileHelper = new JFileChooserFactory(); + private final JFileChooserFactory extractFilesHelper = new JFileChooserFactory(); /** * Extract the specified collection of files with an event specified for @@ -89,7 +93,7 @@ public class ExtractActionHelper { logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS return; } - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = extractFileHelper.getChooser(); fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase))); // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName()))); @@ -117,7 +121,7 @@ public class ExtractActionHelper { logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS return; } - JFileChooser folderChooser = new JFileChooser(); + JFileChooser folderChooser = extractFilesHelper.getChooser(); folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); folderChooser.setCurrentDirectory(new File(getExportDirectory(openCase))); if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java index 6043ff1a46..de33c0ca2b 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.casemodule.GeneralFilter; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * A panel to allow the user to set the custom properties of the geolocation @@ -46,6 +47,8 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(GeolocationSettingsPanel.class.getName()); + + private static final JFileChooserFactory chooserHelper = new JFileChooserFactory(); /** * Creates new GeolocationSettingsPanel @@ -313,7 +316,7 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio }// //GEN-END:initComponents private void zipFileBrowseBntActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zipFileBrowseBntActionPerformed - JFileChooser fileWindow = new JFileChooser(); + JFileChooser fileWindow = chooserHelper.getChooser(); fileWindow.setFileSelectionMode(JFileChooser.FILES_ONLY); GeneralFilter fileFilter = new GeneralFilter(Arrays.asList(".zip"), "Zips (*.zip)"); //NON-NLS fileWindow.setDragEnabled(false); @@ -374,7 +377,7 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio }//GEN-LAST:event_mbtilesRBtnActionPerformed private void mbtilesBrowseBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mbtilesBrowseBtnActionPerformed - JFileChooser fileWindow = new JFileChooser(); + JFileChooser fileWindow = chooserHelper.getChooser(); fileWindow.setFileSelectionMode(JFileChooser.FILES_ONLY); GeneralFilter fileFilter = new GeneralFilter(Arrays.asList(".mbtiles"), "MBTiles (*.mbtiles)"); //NON-NLS fileWindow.setDragEnabled(false); diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/JFileChooserFactory.java b/Core/src/org/sleuthkit/autopsy/guiutils/JFileChooserFactory.java new file mode 100755 index 0000000000..85e7936882 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/guiutils/JFileChooserFactory.java @@ -0,0 +1,147 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 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.guiutils; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.awt.Cursor; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JFileChooser; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; + +/** + * Factory class for initializing JFileChooser instances in a background thread. + * + * It is a known issue that on Windows a JFileChooser can take an indeterminate + * amount of time to initialize. Therefore when a JFileChooser is initialized on + * the EDT there is the potential for the UI to appear hung while initialization + * is occurring. + * + * Initializing a JFileChooser in a background thread should prevent the UI from + * hanging. Using this Factory class at component construction time should allow + * enough time for the JFileChooser to be initialized in the background before + * the UI user causes an event which will launch the JFileChooser. If the + * JFileChooser is not initialized prior to the event occurring, the EDT will be + * blocked, but the wait cursor will appear. + * + * https://stackoverflow.com/questions/49792375/jfilechooser-is-very-slow-when-using-windows-look-and-feel + */ +public final class JFileChooserFactory { + + private static final Logger logger = Logger.getLogger(JFileChooserFactory.class.getName()); + + private final FutureTask futureFileChooser; + private JFileChooser chooser; + private final ExecutorService executor; + + /** + * Create a new instance of the factory. The constructor will kick off an + * executor to execute the initializing the JFileChooser task. + */ + public JFileChooserFactory() { + this(null); + } + + /** + * Create a new instance of the factory using a class that extends + * JFileChooser. The class default constructor will be called to initialize + * the class. + * + * The passed in Class must be public and its default constructor must be + * public. + * + * @param cls Class type to initialize. + */ + public JFileChooserFactory(Class cls) { + if (cls == null) { + futureFileChooser = new FutureTask<>(JFileChooser::new); + } else { + futureFileChooser = new FutureTask<>(new ChooserCallable(cls)); + } + + executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("JFileChooser-background-thread").build()); + executor.execute(futureFileChooser); + } + + /** + * Return and instance of JFileChooser to the caller. + * + * This call may block the EDT if the JFileChooser initialization has not + * completed. + * + * @return + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public JFileChooser getChooser() { + if (chooser == null) { + // In case this takes a moment show the wait cursor. + try { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + // get() will only return when the initilization of the + // JFileChooser has completed. + chooser = futureFileChooser.get(); + } catch (InterruptedException | ExecutionException ex) { + // An exception is generally not expected. On the off chance + // one does occur save the situation by created a new + // instance in the EDT. + logger.log(Level.WARNING, "Failed to initialize JFileChooser in background thread."); + chooser = new JFileChooser(); + } + } finally { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + executor.shutdown(); + } + } + + return chooser; + } + + /** + * Simple Callable that will initialize any subclass of JFileChooser using + * the default constructor. + * + * Note that the class and default constructor must be public for this to + * work properly. + */ + private class ChooserCallable implements Callable { + + private final Class type; + + /** + * Construct a new instance for the given class type. + * + * @param type Class type to initialize. + */ + ChooserCallable(Class type) { + this.type = type; + } + + @Override + public JFileChooser call() throws Exception { + return type.newInstance(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 81fbd99d69..bc673144c8 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,6 +61,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Dashboard for viewing metrics and controlling the health monitor. @@ -86,6 +87,8 @@ public class HealthMonitorDashboard { private JDialog dialog = null; private final Container parentWindow; + private final JFileChooserFactory chooserHelper; + /** * Create an instance of the dashboard. * Call display() after creation to show the dashboard. @@ -95,6 +98,7 @@ public class HealthMonitorDashboard { timingData = new HashMap<>(); userData = new ArrayList<>(); parentWindow = parent; + chooserHelper = new JFileChooserFactory(); } /** @@ -495,7 +499,7 @@ public class HealthMonitorDashboard { reportButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { - JFileChooser reportFileChooser = new JFileChooser(); + JFileChooser reportFileChooser = chooserHelper.getChooser(); reportFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); reportFileChooser.setCurrentDirectory(new File(UserPreferences.getHealthMonitorReportPath())); final DateFormat csvTimestampFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED index 9c1a1bfa5c..11fbd0a9d8 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED @@ -115,7 +115,7 @@ IngestJobSettingsPanel.jButtonSelectAll.text=Select All IngestJobSettingsPanel.jButtonDeselectAll.text=Deselect All IngestManager.cancellingIngest.msgDlg.text=Cancelling all currently running ingest jobs IngestManager.serviceIsDown.msgDlg.text={0} is down -ProfilePanel.messages.profileNameContainsIllegalCharacter=Profile name contains an illegal character +ProfilePanel.messages.profileNameContainsIllegalCharacter=Profile name contains an illegal character. Only \nletters, digits, and underscore characters are allowed. ProfilePanel.messages.profilesMustBeNamed=Ingest profile must be named. ProfilePanel.newProfileText=NewEmptyProfile ProfilePanel.profileDescLabel.text=Description: diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties index 1456e6922a..2b0efb2f0b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties @@ -135,7 +135,6 @@ ModuleTableModel.colName.module=\u30e2\u30b8\u30e5\u30fc\u30eb OpenIDE-Module-Name=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8 OptionsCategory_Keywords_IngestOptions=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8 OptionsCategory_Name_IngestOptions=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8 -ProfilePanel.messages.profileNameContainsIllegalCharacter=\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u540d\u306b\u4e0d\u6b63\u306a\u6587\u5b57\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059 ProfilePanel.messages.profilesMustBeNamed=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306b\u540d\u524d\u3092\u4ed8\u3051\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 ProfilePanel.newProfileText=NewEmptyProfile ProfilePanel.profileDescLabel.text=\u8aac\u660e\: diff --git a/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java index dc145a6824..5efbb1bd4e 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,6 @@ package org.sleuthkit.autopsy.ingest; import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; @@ -39,13 +35,12 @@ class ProfilePanel extends IngestModuleGlobalSettingsPanel { "ProfilePanel.profileNameLabel.text=Profile Name:", "ProfilePanel.newProfileText=NewEmptyProfile", "ProfilePanel.messages.profilesMustBeNamed=Ingest profile must be named.", - "ProfilePanel.messages.profileNameContainsIllegalCharacter=Profile name contains an illegal character"}) + "ProfilePanel.messages.profileNameContainsIllegalCharacter=Profile name contains an illegal character. Only \nletters, digits, and underscore characters are allowed."}) private final IngestJobSettingsPanel ingestSettingsPanel; private final IngestJobSettings settings; private IngestProfile profile; private final static String NEW_PROFILE_NAME = NbBundle.getMessage(ProfilePanel.class, "ProfilePanel.newProfileText"); - private static final List ILLEGAL_NAME_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", "/", ":", "*", "?", "\"", "<", ">"))); /** * Creates new form ProfilePanel @@ -231,8 +226,12 @@ class ProfilePanel extends IngestModuleGlobalSettingsPanel { /** * Save a new or edited profile. */ - void store() { + boolean store() { + if (!isValidDefinition(false)) { + return false; + } saveSettings(); + return true; } void load() { @@ -240,41 +239,33 @@ class ProfilePanel extends IngestModuleGlobalSettingsPanel { /** * Checks that information entered constitutes a valid ingest profile. + * + * @param dispayWarnings boolean flag whether to display warnings if an error occurred. * * @return true for valid, false for invalid. */ - boolean isValidDefinition() { - if (getProfileName().isEmpty()) { - NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( - NbBundle.getMessage(ProfilePanel.class, "ProfilePanel.messages.profilesMustBeNamed"), - NotifyDescriptor.WARNING_MESSAGE); - DialogDisplayer.getDefault().notify(notifyDesc); - return false; - } - if (!containsOnlyLegalChars(getProfileName(), ILLEGAL_NAME_CHARS)) { - NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( - NbBundle.getMessage(ProfilePanel.class, "ProfilePanel.messages.profileNameContainsIllegalCharacter"), - NotifyDescriptor.WARNING_MESSAGE); - DialogDisplayer.getDefault().notify(notifyDesc); - return false; - } - return true; - } - - /** - * Checks an input string for the use of illegal characters. - * - * @param toBeChecked The input string. - * @param illegalChars The characters deemed to be illegal. - * - * @return True if the string does not contain illegal characters, false - * otherwise. - */ - private static boolean containsOnlyLegalChars(String toBeChecked, List illegalChars) { - for (String illegalChar : illegalChars) { - if (toBeChecked.contains(illegalChar)) { - return false; + boolean isValidDefinition(boolean dispayWarnings) { + String profileName = getProfileName(); + if (profileName.isEmpty()) { + if (dispayWarnings) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(ProfilePanel.class, "ProfilePanel.messages.profilesMustBeNamed"), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); } + return false; + } + + // check if the name contains illegal characters + String sanitizedName = profileName.replaceAll("[^A-Za-z0-9_]", ""); + if (!(profileName.equals(sanitizedName))) { + if (dispayWarnings) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(ProfilePanel.class, "ProfilePanel.messages.profileNameContainsIllegalCharacter"), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + } + return false; } return true; } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java index c841431171..300cc613d8 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java @@ -416,7 +416,7 @@ class ProfileSettingsPanel extends IngestModuleGlobalSettingsPanel implements Op do { option = JOptionPane.CANCEL_OPTION; dialog.display(panel); - } while (option == JOptionPane.OK_OPTION && !panel.isValidDefinition()); + } while (option == JOptionPane.OK_OPTION && !panel.isValidDefinition(true)); if (option == JOptionPane.OK_OPTION) { diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java index a5fdfd3bfc..688c5e72a3 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 * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,7 @@ 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.guiutils.JFileChooserFactory; import org.sleuthkit.autopsy.logicalimager.dsp.DriveListUtils; /** @@ -63,6 +64,7 @@ final class ConfigVisualPanel1 extends JPanel { 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 final JFileChooserFactory chooserHelper; /** * Creates new form ConfigVisualPanel1 @@ -74,6 +76,7 @@ final class ConfigVisualPanel1 extends JPanel { refreshDriveList(); updateControls(); }); + chooserHelper = new JFileChooserFactory(); } @NbBundle.Messages({ @@ -332,7 +335,7 @@ final class ConfigVisualPanel1 extends JPanel { "ConfigVisualPanel1.configurationError=Configuration error",}) private void chooseFile(String title) { final String jsonExt = ".json"; // NON-NLS - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = chooserHelper.getChooser(); fileChooser.setDialogTitle(title); fileChooser.setDragEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java index 6a1198ea63..cc6297ae15 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 * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,6 +44,7 @@ import javax.swing.table.TableColumn; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Panel for adding an logical image file from drive letters. Allows the user to @@ -64,10 +65,10 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { private static final int NUMBER_OF_VISIBLE_COLUMNS = 2; private static final String[] EMPTY_LIST_DATA = {}; - private final JFileChooser fileChooser = new JFileChooser(); private final Pattern regex = Pattern.compile("Logical_Imager_(.+)_(\\d{4})(\\d{2})(\\d{2})_(\\d{2})_(\\d{2})_(\\d{2})"); private Path manualImageDirPath; private DefaultTableModel imageTableModel; + private final JFileChooserFactory chooserHelper; /** * Creates new form LogicalImagerPanel @@ -80,6 +81,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { configureImageTable(); jScrollPane1.setBorder(null); clearImageTable(); + chooserHelper = new JFileChooserFactory(); } /** @@ -316,6 +318,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { "LogicalImagerPanel.messageLabel.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS" }) private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + JFileChooser fileChooser = chooserHelper.getChooser(); imageTable.clearSelection(); manualImageDirPath = null; setErrorMessage(NO_IMAGE_SELECTED); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java index 63a8c84e93..ee7634a276 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2020 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,6 +45,7 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Instances of this class allow a user to create a new hash database and add it @@ -56,12 +57,14 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { private static final String DEFAULT_FILE_NAME = NbBundle .getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.defaultFileName"); + private static final long serialVersionUID = 1L; private JFileChooser fileChooser = null; private HashDb newHashDb = null; private final static String LAST_FILE_PATH_KEY = "HashDbCreate_Path"; private CentralRepoOrganization selectedOrg = null; private List orgs = null; static final String HASH_DATABASE_DIR_NAME = "HashDatabases"; + private final JFileChooserFactory chooserFactory; /** * Displays a dialog that allows a user to create a new hash database and @@ -70,10 +73,11 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { */ HashDbCreateDatabaseDialog() { super((JFrame) WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.createHashDbMsg"), true); - initFileChooser(); initComponents(); + chooserFactory = new JFileChooserFactory(CustomFileChooser.class); enableComponents(); display(); + } /** @@ -84,43 +88,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { HashDb getHashDatabase() { return newHashDb; } - - private void initFileChooser() { - fileChooser = new JFileChooser() { - @Override - public void approveSelection() { - File selectedFile = getSelectedFile(); - if (!FilenameUtils.getExtension(selectedFile.getName()).equalsIgnoreCase(HashDbManager.getHashDatabaseFileExtension())) { - if (JOptionPane.showConfirmDialog(this, - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.hashDbMustHaveFileExtensionMsg", - HashDbManager.getHashDatabaseFileExtension()), - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.fileNameErr"), - JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { - cancelSelection(); - } - return; - } - if (selectedFile.exists()) { - if (JOptionPane.showConfirmDialog(this, - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.fileNameAlreadyExistsMsg"), - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.fileExistsErr"), - JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { - cancelSelection(); - } - return; - } - super.approveSelection(); - } - }; - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - fileChooser.setDragEnabled(false); - fileChooser.setMultiSelectionEnabled(false); - } - + private void display() { setLocationRelativeTo(getOwner()); setVisible(true); @@ -169,6 +137,43 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { Logger.getLogger(ImportCentralRepoDbProgressDialog.class.getName()).log(Level.SEVERE, "Failure loading organizations", ex); } } + + /** + * Customize the JFileChooser. + */ + public static class CustomFileChooser extends JFileChooser { + + private static final long serialVersionUID = 1L; + + @Override + public void approveSelection() { + File selectedFile = getSelectedFile(); + if (!FilenameUtils.getExtension(selectedFile.getName()).equalsIgnoreCase(HashDbManager.getHashDatabaseFileExtension())) { + if (JOptionPane.showConfirmDialog(this, + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.hashDbMustHaveFileExtensionMsg", + HashDbManager.getHashDatabaseFileExtension()), + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.fileNameErr"), + JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { + cancelSelection(); + } + return; + } + if (selectedFile.exists()) { + if (JOptionPane.showConfirmDialog(this, + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.fileNameAlreadyExistsMsg"), + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.fileExistsErr"), + JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { + cancelSelection(); + } + return; + } + super.approveSelection(); + } + } /** * This method is called from within the constructor to initialize the form. @@ -435,6 +440,16 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { path.append(File.separator).append(DEFAULT_FILE_NAME); } path.append(".").append(HashDbManager.getHashDatabaseFileExtension()); + + if(fileChooser == null) { + fileChooser = chooserFactory.getChooser(); + + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setDragEnabled(false); + fileChooser.setMultiSelectionEnabled(false); + } + + fileChooser.setSelectedFile(new File(path.toString())); if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { File databaseFile = fileChooser.getSelectedFile(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index c854cb165d..6f4d2606d7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2020 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerExc import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Instances of this class allow a user to select an existing hash database and @@ -52,12 +53,13 @@ import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class HashDbImportDatabaseDialog extends javax.swing.JDialog { - private final JFileChooser fileChooser; + private JFileChooser fileChooser; private String selectedFilePath = ""; private HashDb selectedHashDb = null; private final static String LAST_FILE_PATH_KEY = "HashDbImport_Path"; private CentralRepoOrganization selectedOrg = null; private List orgs = null; + private final JFileChooserFactory chooserHelper; /** * Displays a dialog that allows a user to select an existing hash database @@ -68,10 +70,9 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { super((JFrame) WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.importHashDbMsg"), true); - this.fileChooser = new JFileChooser(); + chooserHelper = new JFileChooserFactory(); initComponents(); enableComponents(); - initFileChooser(); display(); } @@ -84,16 +85,6 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { return selectedHashDb; } - private void initFileChooser() { - fileChooser.setDragEnabled(false); - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - String[] EXTENSION = new String[]{"txt", "kdb", "idx", "hash", "Hash", "hsh"}; //NON-NLS - FileNameExtensionFilter filter = new FileNameExtensionFilter( - NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.fileNameExtFilter.text"), EXTENSION); - fileChooser.setFileFilter(filter); - fileChooser.setMultiSelectionEnabled(false); - } - private void display() { setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); setVisible(true); @@ -421,6 +412,17 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { }// //GEN-END:initComponents private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed + if(fileChooser == null) { + fileChooser = chooserHelper.getChooser(); + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + String[] EXTENSION = new String[]{"txt", "kdb", "idx", "hash", "Hash", "hsh"}; //NON-NLS + FileNameExtensionFilter filter = new FileNameExtensionFilter( + NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.fileNameExtFilter.text"), EXTENSION); + fileChooser.setFileFilter(filter); + fileChooser.setMultiSelectionEnabled(false); + } + String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), HashDbCreateDatabaseDialog.HASH_DATABASE_DIR_NAME).toString(); if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) { lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index 40544c5485..cf099075ed 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -65,6 +65,7 @@ import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; /** @@ -93,6 +94,8 @@ public class HashDbManager implements PropertyChangeListener { private static final String DB_NAME_PARAM = "dbName"; private static final String KNOWN_STATUS_PARAM = "knownStatus"; private static final Pattern OFFICIAL_FILENAME = Pattern.compile("(?<" + DB_NAME_PARAM + ">.+?)\\.(?<" + KNOWN_STATUS_PARAM + ">.+?)\\." + KDB_EXT); + + private final JFileChooserFactory chooserHelper; private static final FilenameFilter DEFAULT_KDB_FILTER = new FilenameFilter() { @Override @@ -136,6 +139,7 @@ public class HashDbManager implements PropertyChangeListener { } private HashDbManager() { + chooserHelper = new JFileChooserFactory(); loadHashsetsConfiguration(); } @@ -870,7 +874,7 @@ public class HashDbManager implements PropertyChangeListener { private String searchForFile() { String filePath = null; - JFileChooser fc = new JFileChooser(); + JFileChooser fc = chooserHelper.getChooser(); fc.setDragEnabled(false); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); String[] EXTENSION = new String[]{"txt", "idx", "hash", "Hash", "kdb"}; //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.java index 5a7d5b9b35..e32d3a0d0d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.report.modules.stix; import java.io.File; import javax.swing.JFileChooser; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * Configuration panel for STIX report generation. @@ -29,6 +30,7 @@ public class STIXReportModuleConfigPanel extends javax.swing.JPanel { String stixFile = null; boolean showAllResults; + private final JFileChooserFactory chooserHelper; /** * Creates new form STIXReportModuleConfigPanel @@ -37,6 +39,7 @@ public class STIXReportModuleConfigPanel extends javax.swing.JPanel { initComponents(); showAllResults = false; jCheckBox1.setSelected(false); + chooserHelper = new JFileChooserFactory(); } void setConfiguration(STIXReportModuleSettings settings) { @@ -138,7 +141,7 @@ public class STIXReportModuleConfigPanel extends javax.swing.JPanel { private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = chooserHelper.getChooser(); fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); File currentSelection = new File(jStixFileTextField.getText()); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 075ba9395b..ae532a1db9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -262,6 +262,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { * Shut down parts of the AutoIngestDashboard which were initialized */ void shutDown() { + scheduledRefreshThreadPoolExecutor.shutdownNow(); if (autoIngestMonitor != null) { autoIngestMonitor.shutDown(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 415b3237b9..24a1e57fb9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -70,7 +70,7 @@ final class AutoIngestJobsNode extends AbstractNode { * refresh events */ AutoIngestJobsNode(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { - super(Children.create(new AutoIngestNodeChildren(monitor, status, eventBus), true)); + super(Children.create(new AutoIngestNodeChildren(monitor, status, eventBus), false)); refreshChildrenEventBus = eventBus; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java index 5a42a01e80..c2b7ae5259 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java @@ -30,32 +30,35 @@ import javax.annotation.concurrent.Immutable; public final class Manifest implements Serializable { private static final long serialVersionUID = 1L; - private final String filePath; + private final Path filePath; private final Date dateFileCreated; private final String caseName; private final String deviceId; - private final String dataSourcePath; + private final Path dataSourcePath; + private final String dataSourceFileName; private final Map manifestProperties; public Manifest(Path manifestFilePath, Date dateFileCreated, String caseName, String deviceId, Path dataSourcePath, Map manifestProperties) { - this.filePath = manifestFilePath.toString(); - this.dateFileCreated = dateFileCreated; + this.filePath = Paths.get(manifestFilePath.toString()); + this.dateFileCreated = new Date(dateFileCreated.getTime()); this.caseName = caseName; this.deviceId = deviceId; if (null != dataSourcePath) { - this.dataSourcePath = dataSourcePath.toString(); + this.dataSourcePath = Paths.get(dataSourcePath.toString()); + dataSourceFileName = dataSourcePath.getFileName().toString(); } else { - this.dataSourcePath = ""; + this.dataSourcePath = Paths.get(""); + dataSourceFileName = ""; } this.manifestProperties = new HashMap<>(manifestProperties); } public Path getFilePath() { - return Paths.get(this.filePath); + return this.filePath; } public Date getDateFileCreated() { - return new Date(this.dateFileCreated.getTime()); + return dateFileCreated; } public String getCaseName() { @@ -67,11 +70,11 @@ public final class Manifest implements Serializable { } public Path getDataSourcePath() { - return Paths.get(dataSourcePath); + return dataSourcePath; } public String getDataSourceFileName() { - return Paths.get(dataSourcePath).getFileName().toString(); + return dataSourceFileName; } public Map getManifestProperties() { diff --git a/docs/doxygen-user_fr/Doxyfile b/docs/doxygen-user_fr/Doxyfile index 80ef7fab53..177c9cdb6e 100644 --- a/docs/doxygen-user_fr/Doxyfile +++ b/docs/doxygen-user_fr/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Documentation utilisateur Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.19.0 +PROJECT_NUMBER = 4.19.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.19.0 +HTML_OUTPUT = 4.19.1 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen-user_fr/lastupdated b/docs/doxygen-user_fr/lastupdated new file mode 100644 index 0000000000..6873d4c1a4 --- /dev/null +++ b/docs/doxygen-user_fr/lastupdated @@ -0,0 +1,2 @@ +# Sun Aug 29 15:02:07 2021 +0200 +user-docs_fr.lastupdated=4f62b90f652ba12dae1b2286fb3eb065f00e5311 \ No newline at end of file diff --git a/docs/doxygen-user_fr/main.dox b/docs/doxygen-user_fr/main.dox index aabf341c7c..936de4aee8 100644 --- a/docs/doxygen-user_fr/main.dox +++ b/docs/doxygen-user_fr/main.dox @@ -8,6 +8,9 @@ Ceci est le guide de l'utilisateur de la "Options" et décritent dans cette documentation sont accessibles via la barre de menu système sous "Préférences" ou via le raccourci Cmd +, (touche "Cmd" + touche "plus"). +Version originale de ce guide: +- Dernière version à jour + Rubriques d'aide ------- Les rubriques suivantes sont disponibles: diff --git a/test/script/regression.py b/test/script/regression.py index 1b42f623bc..e898bec3e5 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -16,18 +16,22 @@ # 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. +import sys +import subprocess +import os + +# import db_diff +sys.path.insert(0, os.path.join(subprocess.getoutput("cygpath -u $TSK_HOME"), "db_diff")) from tskdbdiff import TskDbDiff, TskDbDiffException, PGSettings import codecs import datetime import logging -import os + import re import shutil import socket import sqlite3 -import subprocess -import sys from sys import platform as _platform import time import traceback diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py deleted file mode 100644 index f0aae2c2a5..0000000000 --- a/test/script/tskdbdiff.py +++ /dev/null @@ -1,1200 +0,0 @@ -# Requires python3 - -import re -import sqlite3 -import subprocess -import shutil -import os -import codecs -import datetime -import sys -from typing import Callable, Dict, Union, List - -import psycopg2 -import psycopg2.extras -import socket -import csv - -class TskDbDiff(object): - """Compares two TSK/Autospy SQLite databases. - - Attributes: - gold_artifacts: - autopsy_artifacts: - gold_attributes: - autopsy_attributes: - gold_objects: - autopsy_objects: - artifact_comparison: - attribute_comparision: - report_errors: a listof_listof_String, the error messages that will be - printed to screen in the run_diff method - passed: a boolean, did the diff pass? - autopsy_db_file: - gold_db_file: - """ - def __init__(self, output_db, gold_db, output_dir=None, gold_bb_dump=None, gold_dump=None, verbose=False, isMultiUser=False, pgSettings=None): - """Constructor for TskDbDiff. - - Args: - output_db_path: path to output database (non-gold standard) - gold_db_path: path to gold database - output_dir: (optional) Path to folder where generated files will be put. - gold_bb_dump: (optional) path to file where the gold blackboard dump is located - gold_dump: (optional) path to file where the gold non-blackboard dump is located - verbose: (optional) a boolean, if true, diff results are sent to stdout. - """ - - self.output_db_file = output_db - self.gold_db_file = gold_db - self.output_dir = output_dir - self.gold_bb_dump = gold_bb_dump - self.gold_dump = gold_dump - self._generate_gold_dump = False - self._generate_gold_bb_dump = False - self._bb_dump_diff = "" - self._dump_diff = "" - self._bb_dump = "" - self._dump = "" - self.verbose = verbose - self.isMultiUser = isMultiUser - self.pgSettings = pgSettings - - if self.isMultiUser and not self.pgSettings: - print("Missing PostgreSQL database connection settings data.") - sys.exit(1) - - if self.gold_bb_dump is None: - self._generate_gold_bb_dump = True - if self.gold_dump is None: - self._generate_gold_dump = True - - def run_diff(self): - """Compare the databases. - - Raises: - TskDbDiffException: if an error occurs while diffing or dumping the database - """ - - self._init_diff() - id_obj_path_table = -1 - # generate the gold database dumps if necessary - if self._generate_gold_dump: - id_obj_path_table = TskDbDiff._dump_output_db_nonbb(self.gold_db_file, self.gold_dump, self.isMultiUser, self.pgSettings) - if self._generate_gold_bb_dump: - TskDbDiff._dump_output_db_bb(self.gold_db_file, self.gold_bb_dump, self.isMultiUser, self.pgSettings, id_obj_path_table) - - # generate the output database dumps (both DB and BB) - id_obj_path_table = TskDbDiff._dump_output_db_nonbb(self.output_db_file, self._dump, self.isMultiUser, self.pgSettings) - TskDbDiff._dump_output_db_bb(self.output_db_file, self._bb_dump, self.isMultiUser, self.pgSettings, id_obj_path_table) - - # Compare non-BB - dump_diff_pass = self._diff(self._dump, self.gold_dump, self._dump_diff) - - # Compare BB - bb_dump_diff_pass = self._diff(self._bb_dump, self.gold_bb_dump, self._bb_dump_diff) - - self._cleanup_diff() - return dump_diff_pass, bb_dump_diff_pass - - - def _init_diff(self): - """Set up the necessary files based on the arguments given at construction""" - if self.output_dir is None: - # No stored files - self._bb_dump = TskDbDiff._get_tmp_file("BlackboardDump", ".txt") - self._bb_dump_diff = TskDbDiff._get_tmp_file("BlackboardDump-Diff", ".txt") - self._dump = TskDbDiff._get_tmp_file("DBDump", ".txt") - self._dump_diff = TskDbDiff._get_tmp_file("DBDump-Diff", ".txt") - else: - self._bb_dump = os.path.join(self.output_dir, "BlackboardDump.txt") - self._bb_dump_diff = os.path.join(self.output_dir, "BlackboardDump-Diff.txt") - self._dump = os.path.join(self.output_dir, "DBDump.txt") - self._dump_diff = os.path.join(self.output_dir, "DBDump-Diff.txt") - - # Sorting gold before comparing (sort behaves differently in different environments) - new_bb = TskDbDiff._get_tmp_file("GoldBlackboardDump", ".txt") - new_db = TskDbDiff._get_tmp_file("GoldDBDump", ".txt") - if self.gold_bb_dump is not None: - srtcmdlst = ["sort", self.gold_bb_dump, "-o", new_bb] - subprocess.call(srtcmdlst) - srtcmdlst = ["sort", self.gold_dump, "-o", new_db] - subprocess.call(srtcmdlst) - self.gold_bb_dump = new_bb - self.gold_dump = new_db - - - def _cleanup_diff(self): - if self.output_dir is None: - #cleanup temp files - os.remove(self._dump) - os.remove(self._bb_dump) - if os.path.isfile(self._dump_diff): - os.remove(self._dump_diff) - if os.path.isfile(self._bb_dump_diff): - os.remove(self._bb_dump_diff) - - if self.gold_bb_dump is None: - os.remove(self.gold_bb_dump) - os.remove(self.gold_dump) - - - def _diff(self, output_file, gold_file, diff_path): - """Compare two text files. - - Args: - output_file: a pathto_File, the latest text file - gold_file: a pathto_File, the gold text file - diff_path: The file to write the differences to - Returns False if different - """ - - if (not os.path.isfile(output_file)): - return False - - if (not os.path.isfile(gold_file)): - return False - - # It is faster to read the contents in and directly compare - output_data = codecs.open(output_file, "r", "utf_8").read() - gold_data = codecs.open(gold_file, "r", "utf_8").read() - if (gold_data == output_data): - return True - - # If they are different, invoke 'diff' - diff_file = codecs.open(diff_path, "wb", "utf_8") - # Gold needs to be passed in as 1st arg and output as 2nd - dffcmdlst = ["diff", gold_file, output_file] - subprocess.call(dffcmdlst, stdout = diff_file) - - # create file path for gold files inside output folder. In case of diff, both gold and current run files - # are available in the report output folder. Prefix Gold- is added to the filename. - gold_file_in_output_dir = os.path.join(os.path.dirname(output_file), "Gold-" + os.path.basename(output_file)) - shutil.copy(gold_file, gold_file_in_output_dir) - - return False - - - @staticmethod - def _get_associated_artifact_type(cur, artifact_id, isMultiUser): - if isMultiUser: - cur.execute( - "SELECT tsk_files.parent_path, blackboard_artifact_types.display_name FROM blackboard_artifact_types INNER JOIN blackboard_artifacts ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id INNER JOIN tsk_files ON tsk_files.obj_id = blackboard_artifacts.obj_id WHERE artifact_id=%s", - [artifact_id]) - else: - cur.execute( - "SELECT tsk_files.parent_path, blackboard_artifact_types.display_name FROM blackboard_artifact_types INNER JOIN blackboard_artifacts ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id INNER JOIN tsk_files ON tsk_files.obj_id = blackboard_artifacts.obj_id WHERE artifact_id=?", - [artifact_id]) - - info = cur.fetchone() - - return "File path: " + info[0] + " Artifact Type: " + info[1] - - - @staticmethod - def _dump_output_db_bb(db_file, bb_dump_file, isMultiUser, pgSettings, id_obj_path_table): - """Dumps sorted text results to the given output location. - - Smart method that deals with a blackboard comparison to avoid issues - with different IDs based on when artifacts were created. - - Args: - db_file: a pathto_File, the output database. - bb_dump_file: a pathto_File, the sorted dump file to write to - """ - - unsorted_dump = TskDbDiff._get_tmp_file("dump_data", ".txt") - if isMultiUser: - conn, unused_db = db_connect(db_file, isMultiUser, pgSettings) - artifact_cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) - else: # Use Sqlite - conn = sqlite3.connect(db_file) - conn.text_factory = lambda x: x.decode("utf-8", "ignore") - conn.row_factory = sqlite3.Row - artifact_cursor = conn.cursor() - # Get the list of all artifacts (along with type and associated file) - # @@@ Could add a SORT by parent_path in here since that is how we are going to later sort it. - artifact_cursor.execute("SELECT tsk_files.parent_path, tsk_files.name, blackboard_artifact_types.display_name, blackboard_artifacts.artifact_id FROM blackboard_artifact_types INNER JOIN blackboard_artifacts ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id INNER JOIN tsk_files ON tsk_files.obj_id = blackboard_artifacts.obj_id") - database_log = codecs.open(unsorted_dump, "wb", "utf_8") - row = artifact_cursor.fetchone() - appnd = False - counter = 0 - artifact_count = 0 - artifact_fail = 0 - - # Cycle through artifacts - try: - while (row != None): - - # File Name and artifact type - # Remove parent object ID from Unalloc file name - normalizedName = re.sub('^Unalloc_[0-9]+_', 'Unalloc_', row["name"]) - if(row["parent_path"] != None): - database_log.write(row["parent_path"] + normalizedName + ' ') - else: - database_log.write(normalizedName + ' ') - - if isMultiUser: - attribute_cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) - else: - attribute_cursor = conn.cursor() - looptry = True - artifact_count += 1 - try: - art_id = "" - art_id = str(row["artifact_id"]) - - # Get attributes for this artifact - if isMultiUser: - attribute_cursor.execute("SELECT blackboard_attributes.source, blackboard_attributes.attribute_type_id, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double FROM blackboard_attributes INNER JOIN blackboard_attribute_types ON blackboard_attributes.attribute_type_id = blackboard_attribute_types.attribute_type_id WHERE artifact_id = %s ORDER BY blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double", [art_id]) - else: - attribute_cursor.execute("SELECT blackboard_attributes.source, blackboard_attributes.attribute_type_id, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double FROM blackboard_attributes INNER JOIN blackboard_attribute_types ON blackboard_attributes.attribute_type_id = blackboard_attribute_types.attribute_type_id WHERE artifact_id =? ORDER BY blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double", [art_id]) - - attributes = attribute_cursor.fetchall() - - # Print attributes - if (len(attributes) == 0): - # @@@@ This should be - database_log.write(' \n') - row = artifact_cursor.fetchone() - continue - - src = attributes[0][0] - for attr in attributes: - numvals = 0 - for x in range(3, 6): - if(attr[x] != None): - numvals += 1 - if(numvals > 1): - msg = "There were too many values for attribute type: " + attr["display_name"] + " for artifact with id #" + str(row["artifact_id"]) + ".\n" - - if(not attr["source"] == src): - msg = "There were inconsistent sources for artifact with id #" + str(row["artifact_id"]) + ".\n" - - try: - if attr["value_type"] == 0: - attr_value_as_string = str(attr["value_text"]) - elif attr["value_type"] == 1: - attr_value_as_string = str(attr["value_int32"]) - elif attr["value_type"] == 2: - attr_value_as_string = str(attr["value_int64"]) - if attr["attribute_type_id"] == 36 and id_obj_path_table != -1 and int(attr_value_as_string) > 0: #normalize positive TSK_PATH_IDs from being object id to a path if the obj_id_path_table was generated - attr_value_as_string = id_obj_path_table[int(attr_value_as_string)] - elif attr["value_type"] == 3: - attr_value_as_string = "%20.10f" % float((attr["value_double"])) #use exact format from db schema to avoid python auto format double value to (0E-10) scientific style - elif attr["value_type"] == 4: - attr_value_as_string = "bytes" - elif attr["value_type"] == 5: - attr_value_as_string = str(attr["value_int64"]) - if attr["display_name"] == "Associated Artifact": - attr_value_as_string = TskDbDiff._get_associated_artifact_type(attribute_cursor, attr_value_as_string, isMultiUser) - patrn = re.compile("[\n\0\a\b\r\f]") - attr_value_as_string = re.sub(patrn, ' ', attr_value_as_string) - if attr["source"] == "Keyword Search" and attr["display_name"] == "Keyword Preview": - attr_value_as_string = "" - database_log.write('') - except IOError as e: - print("IO error") - raise TskDbDiffException("Unexpected IO error while writing to database log." + str(e)) - - except sqlite3.Error as e: - msg = "Attributes in artifact id (in output DB)# " + str(row["artifact_id"]) + " encountered an error: " + str(e) +" .\n" - print("Attributes in artifact id (in output DB)# ", str(row["artifact_id"]), " encountered an error: ", str(e)) - print() - looptry = False - artifact_fail += 1 - database_log.write('Error Extracting Attributes') - database_log.close() - raise TskDbDiffException(msg) - finally: - attribute_cursor.close() - - - # @@@@ This should be - database_log.write(' \n') - row = artifact_cursor.fetchone() - - if(artifact_fail > 0): - msg ="There were " + str(artifact_count) + " artifacts and " + str(artifact_fail) + " threw an exception while loading.\n" - except Exception as e: - raise TskDbDiffException("Unexpected error while dumping blackboard database: " + str(e)) - finally: - database_log.close() - artifact_cursor.close() - conn.close() - - # Now sort the file - srtcmdlst = ["sort", unsorted_dump, "-o", bb_dump_file] - subprocess.call(srtcmdlst) - - @staticmethod - def _dump_output_db_nonbb(db_file, dump_file, isMultiUser, pgSettings): - """Dumps a database to a text file. - - Does not dump the artifact and attributes. - - Args: - db_file: a pathto_File, the database file to dump - dump_file: a pathto_File, the location to dump the non-blackboard database items - """ - - conn, backup_db_file = db_connect(db_file, isMultiUser, pgSettings) - guid_utils = TskGuidUtils.create(conn) - - if isMultiUser: - table_cols = get_pg_table_columns(conn) - schema = get_pg_schema(db_file, pgSettings.username, pgSettings.password, - pgSettings.pgHost, pgSettings.pgPort) - else: - table_cols = get_sqlite_table_columns(conn) - schema = get_sqlite_schema(conn) - - with codecs.open(dump_file, "wb", "utf_8") as output_file: - output_file.write(schema + "\n") - for table, cols in sorted(table_cols.items(), key=lambda pr: pr[0]): - normalizer = TABLE_NORMALIZATIONS[table] if table in TABLE_NORMALIZATIONS else None - write_normalized(guid_utils, output_file, conn, table, cols, normalizer) - - # Now sort the file - srtcmdlst = ["sort", dump_file, "-o", dump_file] - subprocess.call(srtcmdlst) - - conn.close() - # cleanup the backup - # if backup_db_file: - # os.remove(backup_db_file) - return guid_utils.obj_id_guids - - @staticmethod - def dump_output_db(db_file, dump_file, bb_dump_file, isMultiUser, pgSettings): - """Dumps the given database to text files for later comparison. - - Args: - db_file: a pathto_File, the database file to dump - dump_file: a pathto_File, the location to dump the non-blackboard database items - bb_dump_file: a pathto_File, the location to dump the blackboard database items - """ - id_obj_path_table = TskDbDiff._dump_output_db_nonbb(db_file, dump_file, isMultiUser, pgSettings) - TskDbDiff._dump_output_db_bb(db_file, bb_dump_file, isMultiUser, pgSettings, id_obj_path_table) - - @staticmethod - def _get_tmp_file(base, ext): - time = datetime.datetime.now().time().strftime("%H%M%f") - return os.path.join(os.environ['TMP'], base + time + ext) - - -class TskDbDiffException(Exception): - pass - -class PGSettings(object): - def __init__(self, pgHost=None, pgPort=5432, user=None, password=None): - self.pgHost = pgHost - self.pgPort = pgPort - self.username = user - self.password = password - - def get_pgHost(self): - return self.pgHost - - def get_pgPort(self): - return self.pgPort - - def get_username(self): - return self.username - - def get_password(self): - return self.password - - -class TskGuidUtils: - """ - This class provides guids for potentially volatile data. - """ - - @staticmethod - def _get_guid_dict(db_conn, select_statement, delim="", normalizer: Union[Callable[[str], str], None] = None): - """ - Retrieves a dictionary mapping the first item selected to a concatenation of the remaining values. - Args: - db_conn: The database connection. - select_statement: The select statement. - delim: The delimiter for how row data from index 1 to end shall be concatenated. - normalizer: Means of normalizing the generated string or None. - - Returns: A dictionary mapping the key (the first item in the select statement) to a concatenation of the remaining values. - - """ - cursor = db_conn.cursor() - cursor.execute(select_statement) - ret_dict = {} - for row in cursor: - # concatenate value rows with delimiter filtering out any null values. - value_str = delim.join([str(col) for col in filter(lambda col: col is not None, row[1:])]) - if normalizer: - value_str = normalizer(value_str) - ret_dict[row[0]] = value_str - - return ret_dict - - @staticmethod - def create(db_conn): - """ - Creates an instance of this class by querying for relevant guid data. - Args: - db_conn: The database connection. - - Returns: The instance of this class. - - """ - guid_files = TskGuidUtils._get_guid_dict(db_conn, "SELECT obj_id, parent_path, name FROM tsk_files", - normalizer=normalize_file_path) - guid_vs_parts = TskGuidUtils._get_guid_dict(db_conn, "SELECT obj_id, addr, start FROM tsk_vs_parts", "_") - guid_vs_info = TskGuidUtils._get_guid_dict(db_conn, "SELECT obj_id, vs_type, img_offset FROM tsk_vs_info", "_") - guid_fs_info = TskGuidUtils._get_guid_dict(db_conn, "SELECT obj_id, img_offset, fs_type FROM tsk_fs_info", "_") - guid_image_names = TskGuidUtils._get_guid_dict(db_conn, "SELECT obj_id, name FROM tsk_image_names " - "WHERE sequence=0", - normalizer=get_filename) - guid_os_accounts = TskGuidUtils._get_guid_dict(db_conn, "SELECT os_account_obj_id, addr FROM tsk_os_accounts") - guid_reports = TskGuidUtils._get_guid_dict(db_conn, "SELECT obj_id, path FROM reports", - normalizer=normalize_file_path) - - objid_artifacts = TskGuidUtils._get_guid_dict(db_conn, - "SELECT blackboard_artifacts.artifact_obj_id, " - "blackboard_artifact_types.type_name " - "FROM blackboard_artifacts " - "INNER JOIN blackboard_artifact_types " - "ON blackboard_artifact_types.artifact_type_id = " - "blackboard_artifacts.artifact_type_id") - - artifact_objid_artifacts = TskGuidUtils._get_guid_dict(db_conn, - "SELECT blackboard_artifacts.artifact_id, " - "blackboard_artifact_types.type_name " - "FROM blackboard_artifacts " - "INNER JOIN blackboard_artifact_types " - "ON blackboard_artifact_types.artifact_type_id = " - "blackboard_artifacts.artifact_type_id") - - cursor = db_conn.cursor() - cursor.execute("SELECT obj_id, par_obj_id FROM tsk_objects") - par_obj_objects = dict([(row[0], row[1]) for row in cursor]) - - guid_artifacts = {} - for k, v in objid_artifacts.items(): - if k in par_obj_objects: - par_obj_id = par_obj_objects[k] - - # check for artifact parent in files, images, reports - path = '' - for artifact_parent_dict in [guid_files, guid_image_names, guid_reports]: - if par_obj_id in artifact_parent_dict: - path = artifact_parent_dict[par_obj_id] - break - - guid_artifacts[k] = "/".join([path, v]) - - return TskGuidUtils( - # aggregate all the object id dictionaries together - obj_id_guids={**guid_files, **guid_reports, **guid_os_accounts, **guid_vs_parts, **guid_vs_info, - **guid_fs_info, **guid_fs_info, **guid_image_names, **guid_artifacts}, - artifact_types=artifact_objid_artifacts) - - artifact_types: Dict[int, str] - obj_id_guids: Dict[int, any] - - def __init__(self, obj_id_guids: Dict[int, any], artifact_types: Dict[int, str]): - """ - Main constructor. - Args: - obj_id_guids: A dictionary mapping object ids to their guids. - artifact_types: A dictionary mapping artifact ids to their types. - """ - self.artifact_types = artifact_types - self.obj_id_guids = obj_id_guids - - def get_guid_for_objid(self, obj_id, omitted_value: Union[str, None] = 'Object ID Omitted'): - """ - Returns the guid for the specified object id or returns omitted value if the object id is not found. - Args: - obj_id: The object id. - omitted_value: The value if no object id mapping is found. - - Returns: The relevant guid or the omitted_value. - - """ - return self.obj_id_guids[obj_id] if obj_id in self.obj_id_guids else omitted_value - - def get_guid_for_file_objid(self, obj_id, omitted_value: Union[str, None] = 'Object ID Omitted'): - # this method is just an alias for get_guid_for_objid - return self.get_guid_for_objid(obj_id, omitted_value) - - def get_guid_for_accountid(self, account_id, omitted_value: Union[str, None] = 'Account ID Omitted'): - # this method is just an alias for get_guid_for_objid - return self.get_guid_for_objid(account_id, omitted_value) - - def get_guid_for_artifactid(self, artifact_id, omitted_value: Union[str, None] = 'Artifact ID Omitted'): - """ - Returns the guid for the specified artifact id or returns omitted value if the artifact id is not found. - Args: - artifact_id: The artifact id. - omitted_value: The value if no object id mapping is found. - - Returns: The relevant guid or the omitted_value. - """ - return self.artifact_types[artifact_id] if artifact_id in self.artifact_types else omitted_value - - -class NormalizeRow: - """ - Given a dictionary representing a row (i.e. column name mapped to value), returns a normalized representation of - that row such that the values should be less volatile from run to run. - """ - row_masker: Callable[[TskGuidUtils, Dict[str, any]], Dict[str, any]] - - def __init__(self, row_masker: Callable[[TskGuidUtils, Dict[str, any]], Union[Dict[str, any], None]]): - """ - Main constructor. - Args: - row_masker: The function to be called to mask the specified row. - """ - self.row_masker = row_masker - - def normalize(self, guid_util: TskGuidUtils, row: Dict[str, any]) -> Union[Dict[str, any], None]: - """ - Normalizes a row such that the values should be less volatile from run to run. - Args: - guid_util: The TskGuidUtils instance providing guids for volatile ids. - row: The row values mapping column name to value. - - Returns: The normalized row or None if the row should be ignored. - - """ - return self.row_masker(guid_util, row) - - -class NormalizeColumns(NormalizeRow): - """ - Utility for normalizing specific column values of a row so they are not volatile values that will change from run - to run. - """ - - @classmethod - def _normalize_col_vals(cls, - col_mask: Dict[str, Union[any, Callable[[TskGuidUtils, any], any]]], - guid_util: TskGuidUtils, - row: Dict[str, any]): - """ - Normalizes column values for each column rule provided. - Args: - col_mask: A dictionary mapping columns to either the replacement value or a function to retrieve the - replacement value given the TskGuidUtils instance and original value as arguments. - guid_util: The TskGuidUtil used to provide guids for volatile values. - row: The dictionary representing the row mapping column names to values. - - Returns: The new row representation. - - """ - row_copy = row.copy() - for key, val in col_mask.items(): - # only replace values if present in row - if key in row_copy: - # if a column replacing function, call with original value - if isinstance(val, Callable): - row_copy[key] = val(guid_util, row[key]) - # otherwise, just replace with mask value - else: - row_copy[key] = val - - return row_copy - - def __init__(self, col_mask: Dict[str, Union[any, Callable[[any], any]]]): - super().__init__(lambda guid_util, row: NormalizeColumns._normalize_col_vals(col_mask, guid_util, row)) - - -def get_path_segs(path: Union[str, None]) -> Union[List[str], None]: - """ - Breaks a path string into its folders and filenames. - Args: - path: The path string or None. - - Returns: The path segments or None. - - """ - if path: - # split on backslash or forward slash - return list(filter(lambda x: len(x.strip()) > 0, [s for s in re.split(r"[\\/]", path)])) - else: - return None - - -def get_filename(path: Union[str, None]) -> Union[str, None]: - """ - Returns the last segment of a file path. - Args: - path: The path. - - Returns: The last segment of the path - - """ - path_segs = get_path_segs(path) - if path_segs is not None and len(path_segs) > 0: - return path_segs[-1] - else: - return None - - -def index_of(lst, search_item) -> int: - """ - Returns the index of the item in the list or -1. - Args: - lst: The list. - search_item: The item to search for. - - Returns: The index in the list of the item or -1. - - """ - for idx, item in enumerate(lst): - if item == search_item: - return idx - - return -1 - - -def get_sql_insert_value(val) -> str: - """ - Returns the value that would appear in a sql insert statement (i.e. string becomes 'string', None becomes NULL) - Args: - val: The original value. - - Returns: The sql insert equivalent value. - - """ - if val is None: - return "NULL" - - if isinstance(val, str): - escaped_val = val.replace('\n', '\\n').replace("'", "''") - return f"'{escaped_val}'" - - return str(val) - - -def get_sqlite_table_columns(conn) -> Dict[str, List[str]]: - """ - Retrieves a dictionary mapping table names to a list of all the columns for that table - where the columns are in ordinal value. - Args: - conn: The database connection. - - Returns: A dictionary of the form { table_name: [col_name1, col_name2...col_nameN] } - - """ - cur = conn.cursor() - cur.execute("SELECT name FROM sqlite_master tables WHERE tables.type='table'") - tables = list([table[0] for table in cur.fetchall()]) - cur.close() - - to_ret = {} - for table in tables: - cur = conn.cursor() - cur.execute('SELECT name FROM pragma_table_info(?) ORDER BY cid', [table]) - to_ret[table] = list([col[0] for col in cur.fetchall()]) - - return to_ret - - -def get_pg_table_columns(conn) -> Dict[str, List[str]]: - """ - Returns a dictionary mapping table names to the list of their columns in ordinal order. - Args: - conn: The pg database connection. - - Returns: The dictionary of tables mapped to a list of their ordinal-orderd column names. - """ - cursor = conn.cursor() - cursor.execute(""" - SELECT cols.table_name, cols.column_name - FROM information_schema.columns cols - WHERE cols.column_name IS NOT NULL - AND cols.table_name IS NOT NULL - AND cols.table_name IN ( - SELECT tables.tablename FROM pg_catalog.pg_tables tables - WHERE LOWER(schemaname) = 'public' - ) - ORDER by cols.table_name, cols.ordinal_position; - """) - mapping = {} - for row in cursor: - mapping.setdefault(row[0], []).append(row[1]) - - cursor.close() - return mapping - - -def sanitize_schema(original: str) -> str: - """ - Sanitizes sql script representing table/index creations. - Args: - original: The original sql schema creation script. - - Returns: The sanitized schema. - """ - sanitized_lines = [] - dump_line = '' - for line in original.splitlines(): - line = line.strip('\r\n ') - lower_line = line.lower() - # It's comment or alter statement or catalog entry or set idle entry or empty line - if (not line or - line.startswith('--') or - lower_line.startswith('set') or - " set default nextval" in lower_line or - " owner to " in lower_line or - " owned by " in lower_line or - "pg_catalog" in lower_line or - "idle_in_transaction_session_timeout" in lower_line): - continue - - # if there is no white space or parenthesis delimiter, add a space - if re.match(r'^.+?[^\s()]$', dump_line) and re.match(r'^[^\s()]', line): - dump_line += ' ' - - # append the line to the outputted line - dump_line += line - - # if line ends with ';' then this will be one statement in diff - if line.endswith(';'): - sanitized_lines.append(dump_line) - dump_line = '' - - if len(dump_line.strip()) > 0: - sanitized_lines.append(dump_line) - - return "\n".join(sanitized_lines) - - -def get_pg_schema(dbname: str, pg_username: str, pg_pword: str, pg_host: str, pg_port: Union[str, int]): - """ - Gets the schema to be added to the dump text from the postgres database. - Args: - dbname: The name of the database. - pg_username: The postgres user name. - pg_pword: The postgres password. - pg_host: The postgres host. - pg_port: The postgres port. - - Returns: The normalized schema. - - """ - os.environ['PGPASSWORD'] = pg_pword - pg_dump = ["pg_dump", "-U", pg_username, "-h", pg_host, "-p", str(pg_port), - "--schema-only", "-d", dbname, "-t", "public.*"] - output = subprocess.check_output(pg_dump) - output_str = output.decode('UTF-8') - return sanitize_schema(output_str) - - -def get_sqlite_schema(db_conn): - """ - Gets the schema to be added to the dump text from the sqlite database. - Args: - db_conn: The database connection. - - Returns: The normalized schema. - - """ - cursor = db_conn.cursor() - query = "SELECT sql FROM sqlite_master " \ - "WHERE type IN ('table', 'index') AND sql IS NOT NULL " \ - "ORDER BY type DESC, tbl_name ASC" - - cursor.execute(query) - schema = '\n'.join([str(row[0]) + ';' for row in cursor]) - return sanitize_schema(schema) - - -def _mask_event_desc(desc: str) -> str: - """ - Masks dynamic event descriptions of the form ":" so the artifact id is no longer - present. - Args: - desc: The original description. - - Returns: The normalized description. - - """ - - # Takes a string like "Shell Bags: 30840" and replaces with "ShellBags:" - match = re.search(r"^\s*(.+?)\s*:\s*\d+\s*$", desc.strip()) - if match: - return f"{match.group(1)}:" - - return desc - - -def normalize_tsk_event_descriptions(guid_util: TskGuidUtils, row: Dict[str, any]) -> Dict[str, any]: - """ - Normalizes event description rows masking possibly changing column values. - Args: - guid_util: Provides guids for ids that may change from run to run. - row: A dictionary mapping column names to values. - - Returns: The normalized event description row. - """ - row_copy = row.copy() - # replace object ids with information that is deterministic - row_copy['event_description_id'] = MASKED_ID - row_copy['content_obj_id'] = guid_util.get_guid_for_file_objid(row['content_obj_id']) - row_copy['artifact_id'] = guid_util.get_guid_for_artifactid(row['artifact_id']) \ - if row['artifact_id'] is not None else None - row_copy['data_source_obj_id'] = guid_util.get_guid_for_file_objid(row['data_source_obj_id']) - - if row['full_description'] == row['med_description'] == row['short_description']: - row_copy['full_description'] = _mask_event_desc(row['full_description']) - row_copy['med_description'] = _mask_event_desc(row['med_description']) - row_copy['short_description'] = _mask_event_desc(row['short_description']) - - return row_copy - - -def normalize_ingest_jobs(guid_util: TskGuidUtils, row: Dict[str, any]) -> Dict[str, any]: - """ - Normalizes ingest jobs table rows. - Args: - guid_util: Provides guids for ids that may change from run to run. - row: A dictionary mapping column names to values. - - Returns: The normalized ingest job row. - - """ - row_copy = row.copy() - row_copy['host_name'] = "{host_name}" - - start_time = row['start_date_time'] - end_time = row['end_date_time'] - if start_time <= end_time: - row_copy['start_date_time'] = MASKED_TIME - row_copy['end_date_time'] = MASKED_TIME - - return row_copy - - -def normalize_unalloc_files(path_str: Union[str, None]) -> Union[str, None]: - """ - Normalizes a path string removing timestamps from unalloc files. - Args: - path_str: The original path string. - - Returns: The path string where timestamps are removed from unalloc strings. - - """ - # takes a file name like "Unalloc_30580_7466496_2980941312" and removes the object id to become - # "Unalloc_7466496_2980941312" - return None if path_str is None else re.sub('Unalloc_[0-9]+_', 'Unalloc_', path_str) - - -def normalize_regripper_files(path_str: Union[str, None]) -> Union[str, None]: - """ - Normalizes a path string removing timestamps from regripper files. - Args: - path_str: The original path string. - - Returns: The path string where timestamps are removed from regripper paths. - - """ - # takes a file name like "regripper-12345-full" and removes the id to become "regripper-full" - return None if path_str is None else re.sub(r'regripper-[0-9]+-full', 'regripper-full', path_str) - - -def normalize_file_path(path_str: Union[str, None]) -> Union[str, None]: - """ - Normalizes file paths removing or replacing pieces that will change from run to run (i.e. object id) - Args: - path_str: The original path string. - - Returns: The normalized path string - """ - return normalize_unalloc_files(normalize_regripper_files(path_str)) - - -def normalize_tsk_files(guid_util: TskGuidUtils, row: Dict[str, any]) -> Dict[str, any]: - """ - Normalizes files table rows. - Args: - guid_util: Provides guids for ids that may change from run to run. - row: A dictionary mapping column names to values. - - Returns: The normalized files table row. - - """ - # Ignore TIFF size and hash if extracted from PDFs. - # See JIRA-6951 for more details. - row_copy = row.copy() - if row['extension'] is not None and row['extension'].strip().lower() == 'tif' and \ - row['parent_path'] is not None and row['parent_path'].strip().lower().endswith('.pdf/'): - row_copy['size'] = "SIZE_IGNORED" - row_copy['md5'] = "MD5_IGNORED" - row_copy['sha256'] = "SHA256_IGNORED" - - row_copy['data_source_obj_id'] = guid_util.get_guid_for_file_objid(row['data_source_obj_id']) - row_copy['obj_id'] = MASKED_OBJ_ID - row_copy['os_account_obj_id'] = 'MASKED_OS_ACCOUNT_OBJ_ID' - row_copy['parent_path'] = normalize_file_path(row['parent_path']) - row_copy['name'] = normalize_file_path(row['name']) - return row_copy - - -def normalize_tsk_files_path(guid_util: TskGuidUtils, row: Dict[str, any]) -> Dict[str, any]: - """ - Normalizes file path table rows. - Args: - guid_util: Provides guids for ids that may change from run to run. - row: A dictionary mapping column names to values. - - Returns: The normalized file path table row. - """ - row_copy = row.copy() - path = row['path'] - if path is not None: - path_parts = get_path_segs(path) - module_output_idx = index_of(path_parts, 'ModuleOutput') - if module_output_idx >= 0: - # remove everything up to and including ModuleOutput if ModuleOutput present - path_parts = path_parts[module_output_idx:] - if len(path_parts) > 2 and path_parts[1] == 'EFE': - # for embedded file extractor, the next folder is the object id and should be omitted - del path_parts[2] - - row_copy['path'] = os.path.join(*path_parts) if len(path_parts) > 0 else '/' - - row_copy['obj_id'] = guid_util.get_guid_for_file_objid(row['obj_id']) - return row_copy - - -def normalize_tsk_objects_path(guid_util: TskGuidUtils, objid: int, - no_path_placeholder: Union[str, None]) -> Union[str, None]: - """ - Returns a normalized path to be used in a tsk_objects table row. - Args: - guid_util: The utility for fetching guids. - objid: The object id of the item. - no_path_placeholder: text to return if no path value found. - - Returns: The 'no_path_placeholder' text if no path. Otherwise, the normalized path. - - """ - path = guid_util.get_guid_for_objid(objid, omitted_value=None) - - if path is None: - return no_path_placeholder - else: - # remove host name (for multi-user) and dates/times from path for reports - path_parts = get_path_segs(path) - module_output_idx = index_of(path_parts, 'ModuleOutput') - if module_output_idx >= 0: - # remove everything up to and including ModuleOutput if ModuleOutput present - path_parts = path_parts[module_output_idx:] - - if "BulkExtractor" in path_parts or "Smirk" in path_parts: - # chop off the last folder (which contains a date/time) - path_parts = path_parts[:-1] - - if path_parts and len(path_parts) >= 2: - for idx in range(0, len(path_parts) - 1): - if path_parts[idx].lower() == "reports" and \ - path_parts[idx + 1].lower().startswith("autopsytestcase html report"): - path_parts = ["Reports", "AutopsyTestCase HTML Report"] - break - - path = os.path.join(*path_parts) if len(path_parts) > 0 else '/' - - return path - - -def normalize_tsk_objects(guid_util: TskGuidUtils, row: Dict[str, any]) -> Dict[str, any]: - """ - Normalizes object table rows. - Args: - guid_util: Provides guids for ids that may change from run to run. - row: A dictionary mapping column names to values. - - Returns: The normalized object table row. - """ - row_copy = row.copy() - row_copy['obj_id'] = None if row['obj_id'] is None else \ - normalize_tsk_objects_path(guid_util, row['obj_id'], MASKED_OBJ_ID) - - row_copy['par_obj_id'] = None if row['par_obj_id'] is None else \ - normalize_tsk_objects_path(guid_util, row['par_obj_id'], 'MASKED_PARENT_OBJ_ID') - - return row_copy - - -MASKED_TIME = "MASKED_TIME" -MASKED_OBJ_ID = "MASKED_OBJ_ID" -MASKED_ID = "MASKED_ID" - -IGNORE_TABLE = "IGNORE_TABLE" - -TableNormalization = Union[IGNORE_TABLE, NormalizeRow] - -""" -This dictionary maps tables where data should be specially handled to how they should be handled. -""" -TABLE_NORMALIZATIONS: Dict[str, TableNormalization] = { - "blackboard_artifacts": IGNORE_TABLE, - "blackboard_attributes": IGNORE_TABLE, - "data_source_info": NormalizeColumns({ - "device_id": "{device id}", - "added_date_time": "{dateTime}" - }), - "image_gallery_groups": NormalizeColumns({ - "group_id": MASKED_ID, - "data_source_obj_id": lambda guid_util, col: guid_util.get_guid_for_objid(col, omitted_value=None), - }), - "image_gallery_groups_seen": IGNORE_TABLE, - "ingest_jobs": NormalizeRow(normalize_ingest_jobs), - "reports": NormalizeColumns({ - "obj_id": MASKED_OBJ_ID, - "path": "AutopsyTestCase", - "crtime": MASKED_TIME - }), - "tsk_aggregate_score": NormalizeColumns({ - "obj_id": lambda guid_util, col: guid_util.get_guid_for_objid(col, omitted_value="Object ID Omitted"), - "data_source_obj_id": lambda guid_util, col: guid_util.get_guid_for_objid(col, omitted_value="Data Source Object ID Omitted"), - }), - "tsk_analysis_results": NormalizeColumns({ - "artifact_obj_id": - lambda guid_util, col: guid_util.get_guid_for_objid(col, omitted_value="Artifact Object ID Omitted"), - }), - "tsk_data_artifacts": NormalizeColumns({ - "artifact_obj_id": - lambda guid_util, col: guid_util.get_guid_for_file_objid(col, omitted_value="Artifact Object ID Omitted"), - "os_account_obj_id": - lambda guid_util, col: guid_util.get_guid_for_file_objid(col, omitted_value="Account Object ID Omitted"), - }), - "tsk_event_descriptions": NormalizeRow(normalize_tsk_event_descriptions), - "tsk_events": NormalizeColumns({ - "event_id": "MASKED_EVENT_ID", - "event_description_id": 'ID OMITTED' - }), - "tsk_examiners": NormalizeColumns({ - "login_name": "{examiner_name}" - }), - "tsk_files": NormalizeRow(normalize_tsk_files), - "tsk_file_layout": NormalizeColumns({ - "obj_id": lambda guid_util, col: guid_util.get_guid_for_file_objid(col) - }), - "tsk_files_path": NormalizeRow(normalize_tsk_files_path), - "tsk_image_names": NormalizeColumns({ - "name": lambda guid_util, col: get_filename(col) - }), - "tsk_objects": NormalizeRow(normalize_tsk_objects), - "tsk_os_account_attributes": NormalizeColumns({ - "id": MASKED_ID, - "os_account_obj_id": lambda guid_util, col: guid_util.get_guid_for_accountid(col), - "source_obj_id": lambda guid_util, col: guid_util.get_guid_for_objid(col) - }), - "tsk_os_account_instances": NormalizeColumns({ - "id": MASKED_ID, - "os_account_obj_id": lambda guid_util, col: guid_util.get_guid_for_accountid(col) - }), - "tsk_os_accounts": NormalizeColumns({ - "os_account_obj_id": MASKED_OBJ_ID - }), - "tsk_vs_parts": NormalizeColumns({ - "obj_id": MASKED_OBJ_ID - }) -} - - -def write_normalized(guid_utils: TskGuidUtils, output_file, db_conn, table: str, column_names: List[str], - normalizer: Union[TableNormalization, None] = None): - """ - Outputs rows of a file as their normalized values (where values should not change from run to run). - Args: - guid_utils: Provides guids to replace values that would potentially change from run to run. - output_file: The file where the normalized dump will be written. - db_conn: The database connection. - table: The name of the table. - column_names: The name of the columns in the table in ordinal order. - normalizer: The normalizer (if any) to use so that data is properly normalized. - """ - if normalizer == IGNORE_TABLE: - return - - cursor = db_conn.cursor() - - joined_columns = ",".join([col for col in column_names]) - cursor.execute(f"SELECT {joined_columns} FROM {table}") - for row in cursor: - if len(row) != len(column_names): - print( - f"ERROR: in {table}, number of columns retrieved: {len(row)} but columns are" - f" {len(column_names)} with {str(column_names)}") - continue - - row_dict = {} - for col_idx in range(0, len(column_names)): - row_dict[column_names[col_idx]] = row[col_idx] - - if normalizer and isinstance(normalizer, NormalizeRow): - row_masker: NormalizeRow = normalizer - row_dict = row_masker.normalize(guid_utils, row_dict) - - if row_dict is not None: - # show row as json-like value - entries = [] - for column in column_names: - dict_value = row_dict[column] if column in row_dict and row_dict[column] is not None else None - value = get_sql_insert_value(dict_value) - if value is not None: - entries.append((column, value)) - insert_values = ", ".join([f"{pr[0]}: {pr[1]}" for pr in entries]) - insert_statement = f"{table}: {{{insert_values}}}\n" - output_file.write(insert_statement) - - -def db_connect(db_file, is_multi_user, pg_settings=None): - if is_multi_user: # use PostgreSQL - try: - return psycopg2.connect("dbname=" + db_file + " user=" + pg_settings.username + " host=" + - pg_settings.pgHost + " password=" + pg_settings.password), None - except: - print("Failed to connect to the database: " + db_file) - else: # Sqlite - # Make a copy that we can modify - backup_db_file = TskDbDiff._get_tmp_file("tsk_backup_db", ".db") - shutil.copy(db_file, backup_db_file) - # We sometimes get situations with messed up permissions - os.chmod(backup_db_file, 0o777) - return sqlite3.connect(backup_db_file), backup_db_file - - -def main(): - try: - sys.argv.pop(0) - output_db = sys.argv.pop(0) - gold_db = sys.argv.pop(0) - except: - print("usage: tskdbdiff [OUTPUT DB PATH] [GOLD DB PATH]") - sys.exit(1) - - db_diff = TskDbDiff(output_db, gold_db, output_dir=".") - dump_passed, bb_dump_passed = db_diff.run_diff() - - if dump_passed and bb_dump_passed: - print("Database comparison passed.") - if not dump_passed: - print("Non blackboard database comparison failed.") - if not bb_dump_passed: - print("Blackboard database comparison failed.") - - sys.exit(0) - - -if __name__ == "__main__": - if sys.hexversion < 0x03000000: - print("Python 3 required") - sys.exit(1) - - main()