diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java index 8bec55f53c..907f4bdcd6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java @@ -24,6 +24,7 @@ import java.util.logging.Level; import org.netbeans.spi.sendopts.OptionProcessor; import org.openide.util.Lookup; import org.sleuthkit.autopsy.commandlineingest.CommandLineIngestManager; +import org.sleuthkit.autopsy.commandlineingest.CommandLineOpenCaseManager; import org.sleuthkit.autopsy.commandlineingest.CommandLineOptionProcessor; import org.sleuthkit.autopsy.commandlineingest.CommandLineStartupWindow; import org.sleuthkit.autopsy.coreutils.Logger; @@ -58,6 +59,12 @@ public class StartupWindowProvider implements StartupWindowInterface { private void init() { if (startupWindowToUse == null) { + + if (openCaseInUI()) { + new CommandLineOpenCaseManager().start(); + return; + } + // first check whether we are running from command line if (isRunningFromCommandLine()) { // Autopsy is running from command line @@ -131,6 +138,28 @@ public class StartupWindowProvider implements StartupWindowInterface { return false; } + /** + * Checks whether Autopsy was launched from the command line with the option + * to open an existing case. + * + * @return True if opening an existing case. + */ + private boolean openCaseInUI() { + // first look up all OptionProcessors and see if running from command line option is set + Collection optionProcessors = Lookup.getDefault().lookupAll(OptionProcessor.class); + Iterator optionsIterator = optionProcessors.iterator(); + while (optionsIterator.hasNext()) { + // find CommandLineOptionProcessor + OptionProcessor processor = optionsIterator.next(); + if ((processor instanceof OpenFromArguments)) { + // check if we are running from command line + String arg = ((OpenFromArguments) processor).getDefaultArg(); + return arg != null && !arg.isEmpty(); + } + } + return false; + } + @Override public void open() { if (startupWindowToUse != null) { diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java index 29d3a2e9c5..d9a0c04234 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java @@ -34,7 +34,8 @@ class CommandLineCommand { ADD_DATA_SOURCE, RUN_INGEST, LIST_ALL_DATA_SOURCES, - GENERATE_REPORTS; + GENERATE_REPORTS, + OPEN_CASE_IN_UI; } /** diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java index f5d9a5c641..a608d4a770 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java @@ -74,7 +74,7 @@ import org.sleuthkit.datamodel.TskCoreException; * cause Autopsy to create a case, add a specified data source, run ingest on * that data source, list all data sources in the case, and generate reports. */ -public class CommandLineIngestManager { +public class CommandLineIngestManager extends CommandLineManager{ private static final Logger LOGGER = Logger.getLogger(CommandLineIngestManager.class.getName()); private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); @@ -184,7 +184,7 @@ public class CommandLineIngestManager { // open the case, if it hasn't been already opened by CREATE_CASE command if (caseForJob == null) { String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - openCase(caseDirPath); + caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); } String dataSourcePath = inputs.get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); @@ -210,7 +210,7 @@ public class CommandLineIngestManager { // open the case, if it hasn't been already opened by CREATE_CASE or ADD_DATA_SOURCE commands if (caseForJob == null) { String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - openCase(caseDirPath); + caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); } // populate the AutoIngestDataSource structure, if that hasn't been done by ADD_DATA_SOURCE command @@ -265,7 +265,7 @@ public class CommandLineIngestManager { // open the case, if it hasn't been already opened by previous command if (caseForJob == null) { String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - openCase(caseDirPath); + caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); } String outputDirPath = getOutputDirPath(caseForJob); @@ -288,7 +288,7 @@ public class CommandLineIngestManager { // open the case, if it hasn't been already opened by previous command if (caseForJob == null) { String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - openCase(caseDirPath); + caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); } // generate reports @@ -369,55 +369,6 @@ public class CommandLineIngestManager { LOGGER.log(Level.INFO, "Opened case {0}", caseForJob.getName()); } - /** - * Opens existing case. - * - * @param caseFolderPath full path to case directory - * - * @throws CaseActionException - */ - private void openCase(String caseFolderPath) throws CaseActionException { - - LOGGER.log(Level.INFO, "Opening case in directory {0}", caseFolderPath); - - String metadataFilePath = findAutFile(caseFolderPath); - Case.openAsCurrentCase(metadataFilePath); - - caseForJob = Case.getCurrentCase(); - LOGGER.log(Level.INFO, "Opened case {0}", caseForJob.getName()); - } - - /** - * Finds the path to the .aut file for the specified case directory. - * - * @param caseDirectory the directory to check for a .aut file - * - * @return the path to the first .aut file found in the directory - * - * @throws CaseActionException if there was an issue finding a .aut file - */ - private String findAutFile(String caseDirectory) throws CaseActionException { - File caseFolder = Paths.get(caseDirectory).toFile(); - if (caseFolder.exists()) { - /* - * Search for '*.aut' files. - */ - File[] fileArray = caseFolder.listFiles(); - if (fileArray == null) { - throw new CaseActionException("No files found in case directory"); - } - String autFilePath = null; - for (File file : fileArray) { - String name = file.getName().toLowerCase(); - if (autFilePath == null && name.endsWith(getFileExtension())) { - return file.getAbsolutePath(); - } - } - throw new CaseActionException("No .aut files found in case directory"); - } - throw new CaseActionException("Case directory was not found"); - } - /** * Passes the data source for the current job through a data source * processor that adds it to the case database. diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineManager.java new file mode 100755 index 0000000000..b14dc7f387 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineManager.java @@ -0,0 +1,87 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.commandlineingest; + +import java.io.File; +import java.nio.file.Paths; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import static org.sleuthkit.autopsy.casemodule.CaseMetadata.getFileExtension; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Base class for the command line managers. + */ +class CommandLineManager { + + private static final Logger LOGGER = Logger.getLogger(CommandLineOpenCaseManager.class.getName()); + + /** + * Opens existing case. + * + * @param caseFolderPath full path to case directory + * + * @throws CaseActionException + */ + Case openCase(String caseFolderPath) throws CaseActionException { + + LOGGER.log(Level.INFO, "Opening case in directory {0}", caseFolderPath); + + String metadataFilePath = findAutFile(caseFolderPath); + Case.openAsCurrentCase(metadataFilePath); + + Case newCase = Case.getCurrentCase(); + LOGGER.log(Level.INFO, "Opened case {0}", newCase.getName()); + + return newCase; + } + + /** + * Finds the path to the .aut file for the specified case directory. + * + * @param caseDirectory the directory to check for a .aut file + * + * @return the path to the first .aut file found in the directory + * + * @throws CaseActionException if there was an issue finding a .aut file + */ + private String findAutFile(String caseDirectory) throws CaseActionException { + File caseFolder = Paths.get(caseDirectory).toFile(); + if (caseFolder.exists()) { + /* + * Search for '*.aut' files. + */ + File[] fileArray = caseFolder.listFiles(); + if (fileArray == null) { + throw new CaseActionException("No files found in case directory"); + } + String autFilePath = null; + for (File file : fileArray) { + String name = file.getName().toLowerCase(); + if (autFilePath == null && name.endsWith(getFileExtension())) { + return file.getAbsolutePath(); + } + } + throw new CaseActionException("No .aut files found in case directory"); + } + throw new CaseActionException("Case directory was not found"); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOpenCaseManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOpenCaseManager.java new file mode 100755 index 0000000000..d990f2a1cf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOpenCaseManager.java @@ -0,0 +1,85 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.commandlineingest; + +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Iterator; +import java.util.logging.Level; +import org.netbeans.spi.sendopts.OptionProcessor; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.OpenFromArguments; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Handles the opening of a case from the command line. + * + */ +public class CommandLineOpenCaseManager extends CommandLineManager { + + private static final Logger LOGGER = Logger.getLogger(CommandLineOpenCaseManager.class.getName()); + + /** + * Starts the thread to open the case. + */ + public void start() { + new Thread(new CommandLineOpenCaseManager.JobProcessingTask()).start(); + } + + /** + * A runnable class that open the given class in the list of command line + * options. + */ + private final class JobProcessingTask implements Runnable { + + @Override + public void run() { + String casePath = ""; + + // first look up all OptionProcessors and get input data from CommandLineOptionProcessor + Collection optionProcessors = Lookup.getDefault().lookupAll(OptionProcessor.class); + Iterator optionsIterator = optionProcessors.iterator(); + while (optionsIterator.hasNext()) { + // find CommandLineOptionProcessor + OptionProcessor processor = optionsIterator.next(); + if (processor instanceof OpenFromArguments) { + // check if we are running from command line + casePath = Paths.get(((OpenFromArguments) processor).getDefaultArg()).toAbsolutePath().toString(); + break; + } + } + + if (casePath == null || casePath.isEmpty()) { + LOGGER.log(Level.SEVERE, "No command line commands specified"); + System.err.println("No command line commands specified"); + return; + } + + try { + CommandLineOpenCaseManager.this.openCase(casePath); + LOGGER.log(Level.INFO, "Opening case at " + casePath); + } catch (CaseActionException ex) { + LOGGER.log(Level.SEVERE, "Error opening case from command line ", ex); + System.err.println("Error opening case "); + } + } + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java index f23bd5f483..4658158314 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java @@ -52,6 +52,7 @@ public class CommandLineOptionProcessor extends OptionProcessor { private final Option ingestProfileOption = Option.requiredArgument('p', "ingestProfile"); private final Option listAllDataSourcesCommandOption = Option.withoutArgument('l', "listAllDataSources"); private final Option generateReportsOption = Option.withoutArgument('g', "generateReports"); + private final Option runUICommandOption = Option.requiredArgument('u', "runUI"); private boolean runFromCommandLine = false; @@ -75,6 +76,7 @@ public class CommandLineOptionProcessor extends OptionProcessor { set.add(ingestProfileOption); set.add(listAllDataSourcesCommandOption); set.add(generateReportsOption); + set.add(runUICommandOption); return set; } @@ -373,7 +375,7 @@ public class CommandLineOptionProcessor extends OptionProcessor { newCommand.addInputValue(CommandLineCommand.InputType.CASE_FOLDER_PATH.name(), caseDir); commands.add(newCommand); runFromCommandLine = true; - } + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties index cdab8b0cfa..3c5130c22e 100755 --- a/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties @@ -199,3 +199,4 @@ FileReportDataTypes.hash.text=Hash Value FileReportDataTypes.knownStatus.text=Known Status FileReportDataTypes.perms.text=Permissions FileReportDataTypes.path.text=Full Path +ReportWizardPortableCaseOptionsVisualPanel.includeAppCheckbox.text=Include application in folder (may add up to 1GB of files) diff --git a/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties-MERGED index 40dc146e1d..5dcbb7f7dd 100755 --- a/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/report/infrastructure/Bundle.properties-MERGED @@ -220,4 +220,5 @@ FileReportDataTypes.knownStatus.text=Known Status FileReportDataTypes.perms.text=Permissions FileReportDataTypes.path.text=Full Path ReportWizardPortableCaseOptionsVisualPanel.getName.title=Choose Portable Case settings +ReportWizardPortableCaseOptionsVisualPanel.includeAppCheckbox.text=Include application in folder (may add up to 1GB of files) TableReportGenerator.StatusColumn.Header=Review Status diff --git a/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.form b/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.form index 9359e187d4..4d404676e0 100644 --- a/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.form +++ b/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.form @@ -18,7 +18,7 @@ - + @@ -26,27 +26,32 @@ - + - + - + + - - - - - + + + + + + + + + + - @@ -60,6 +65,8 @@ + + @@ -106,11 +113,21 @@ - + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.java b/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.java index 72ffd4b8de..4967431884 100644 --- a/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportWizardPortableCaseOptionsVisualPanel.java @@ -33,11 +33,13 @@ import org.sleuthkit.autopsy.report.modules.portablecase.PortableCaseReportModul @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { + private static final long serialVersionUID = 1L; + private final ReportWizardPortableCaseOptionsPanel wizPanel; private PortableCaseReportModuleSettings settings = null; - private Map moduleConfigs; + private final Map moduleConfigs; private final boolean useCaseSpecificData; - + /** * Creates new form ReportWizardPortableCaseOptionsVisualPanel */ @@ -48,9 +50,9 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { initComponents(); customizeComponents(); } - + private void customizeComponents() { - + if (!PlatformUtil.isWindowsOS()) { errorLabel.setVisible(true); compressCheckbox.setEnabled(false); @@ -61,7 +63,7 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { for (ChunkSize chunkSize : ChunkSize.values()) { chunkSizeComboBox.addItem(chunkSize); } - + // initialize settings if (moduleConfigs != null) { // get configuration for this module @@ -75,42 +77,42 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { } } } - + if (settings == null) { // get default module configuration settings = new PortableCaseReportModuleSettings(); } - + // update according to input configuration compressCheckbox.setSelected(settings.shouldCompress()); chunkSizeComboBox.setEnabled(settings.shouldCompress()); chunkSizeComboBox.setSelectedItem(settings.getChunkSize()); - + // initialize other panels and pass them the settings - listPanel.setLayout(new GridLayout(1,2)); + listPanel.setLayout(new GridLayout(1, 2)); listPanel.add(new PortableCaseTagsListPanel(wizPanel, settings, useCaseSpecificData)); listPanel.add(new PortableCaseInterestingItemsListPanel(wizPanel, settings, useCaseSpecificData)); } - + @NbBundle.Messages({ - "ReportWizardPortableCaseOptionsVisualPanel.getName.title=Choose Portable Case settings", - }) + "ReportWizardPortableCaseOptionsVisualPanel.getName.title=Choose Portable Case settings",}) @Override public String getName() { return Bundle.ReportWizardPortableCaseOptionsVisualPanel_getName_title(); } - + /** * Get the selected chunk size - * + * * @return the chunk size that was selected */ private ChunkSize getChunkSize() { return (ChunkSize) chunkSizeComboBox.getSelectedItem(); } - + /** - * Update the selected compression options and enable/disable the finish button + * Update the selected compression options and enable/disable the finish + * button */ private void updateCompression() { if (settings != null) { @@ -118,7 +120,16 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { wizPanel.setFinish(settings.isValid()); } } - + + /** + * Update the include application option. + */ + private void updateIncludeApplication() { + if (settings != null) { + settings.setIncludeApplication(includeAppCheckbox.isSelected()); + } + } + /** * Get the user-selected settings. * @@ -126,7 +137,7 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { */ PortableCaseReportModuleSettings getPortableCaseReportSettings() { return settings; - } + } /** * This method is called from within the constructor to initialize the form. @@ -137,11 +148,12 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - jPanel1 = new javax.swing.JPanel(); + mainPanel = new javax.swing.JPanel(); chunkSizeComboBox = new javax.swing.JComboBox<>(); compressCheckbox = new javax.swing.JCheckBox(); errorLabel = new javax.swing.JLabel(); listPanel = new javax.swing.JPanel(); + includeAppCheckbox = new javax.swing.JCheckBox(); chunkSizeComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -168,32 +180,44 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { ); listPanelLayout.setVerticalGroup( listPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 217, Short.MAX_VALUE) + .addGap(0, 190, Short.MAX_VALUE) ); - javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); - jPanel1.setLayout(jPanel1Layout); - jPanel1Layout.setHorizontalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(compressCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(chunkSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 187, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(errorLabel) - .addContainerGap(41, Short.MAX_VALUE)) + org.openide.awt.Mnemonics.setLocalizedText(includeAppCheckbox, org.openide.util.NbBundle.getMessage(ReportWizardPortableCaseOptionsVisualPanel.class, "ReportWizardPortableCaseOptionsVisualPanel.includeAppCheckbox.text")); // NOI18N + includeAppCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + includeAppCheckboxActionPerformed(evt); + } + }); + + javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel); + mainPanel.setLayout(mainPanelLayout); + mainPanelLayout.setHorizontalGroup( + mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(listPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(mainPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(mainPanelLayout.createSequentialGroup() + .addComponent(compressCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chunkSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 187, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorLabel)) + .addComponent(includeAppCheckbox)) + .addContainerGap(41, Short.MAX_VALUE)) ); - jPanel1Layout.setVerticalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() + mainPanelLayout.setVerticalGroup( + mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(mainPanelLayout.createSequentialGroup() .addComponent(listPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(compressCheckbox) .addComponent(chunkSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(errorLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(includeAppCheckbox) .addContainerGap()) ); @@ -203,13 +227,13 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 463, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(mainPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 259, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(mainPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -218,16 +242,27 @@ class ReportWizardPortableCaseOptionsVisualPanel extends javax.swing.JPanel { }//GEN-LAST:event_chunkSizeComboBoxActionPerformed private void compressCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_compressCheckboxActionPerformed - chunkSizeComboBox.setEnabled(compressCheckbox.isSelected()); + chunkSizeComboBox.setEnabled(compressCheckbox.isSelected() && !includeAppCheckbox.isSelected()); updateCompression(); }//GEN-LAST:event_compressCheckboxActionPerformed + private void includeAppCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_includeAppCheckboxActionPerformed + if (includeAppCheckbox.isSelected()) { + chunkSizeComboBox.setEnabled(false); + chunkSizeComboBox.setSelectedItem(ChunkSize.NONE); + } else { + chunkSizeComboBox.setEnabled(compressCheckbox.isSelected()); + } + updateIncludeApplication(); + }//GEN-LAST:event_includeAppCheckboxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JComboBox chunkSizeComboBox; private javax.swing.JCheckBox compressCheckbox; private javax.swing.JLabel errorLabel; - private javax.swing.JPanel jPanel1; + private javax.swing.JCheckBox includeAppCheckbox; private javax.swing.JPanel listPanel; + private javax.swing.JPanel mainPanel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/Bundle.properties-MERGED index ba4b89820d..d114cbad3b 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/Bundle.properties-MERGED @@ -24,6 +24,7 @@ PortableCaseReportModule.generateReport.copyingFiles=Copying files tagged as {0} PortableCaseReportModule.generateReport.copyingTags=Copying tags... PortableCaseReportModule.generateReport.creatingCase=Creating portable case database... PortableCaseReportModule.generateReport.errorCopyingArtifacts=Error copying tagged artifacts +PortableCaseReportModule.generateReport.errorCopyingAutopsy=Error copying application PortableCaseReportModule.generateReport.errorCopyingFiles=Error copying tagged files PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results @@ -42,3 +43,4 @@ PortableCaseReportModule.generateReport.outputDirIsNotDir=Output folder {0} is n PortableCaseReportModule.generateReport.verifying=Verifying selected parameters... PortableCaseReportModule.getDescription.description=Copies selected items to a new single-user case that can be easily shared PortableCaseReportModule.getName.name=Portable Case +PortableCaseReportModule_generateReport_copyingAutopsy=Copying application... diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index c97183c31a..3843eb29f4 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -29,6 +29,7 @@ import java.util.logging.Level; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; @@ -50,6 +51,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; @@ -167,9 +169,9 @@ public class PortableCaseReportModule implements ReportModule { * exception is supplied then the error is SEVERE. Otherwise it is logged as * a WARNING. * - * @param logWarning Warning to write to the log + * @param logWarning Warning to write to the log * @param dialogWarning Warning to write to a pop-up window - * @param ex The exception (can be null) + * @param ex The exception (can be null) * @param progressPanel The report progress panel */ private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) { @@ -206,9 +208,11 @@ public class PortableCaseReportModule implements ReportModule { "PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files", "PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results", "PortableCaseReportModule.generateReport.errorCreatingImageTagTable=Error creating image tags table", + "PortableCaseReportModule.generateReport.errorCopyingAutopsy=Error copying application", "# {0} - attribute type name", "PortableCaseReportModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}", - "PortableCaseReportModule.generateReport.compressingCase=Compressing case..." + "PortableCaseReportModule.generateReport.compressingCase=Compressing case...", + "PortableCaseReportModule_generateReport_copyingAutopsy=Copying application..." }) public void generateReport(String reportPath, PortableCaseReportModuleSettings options, ReportProgressPanel progressPanel) { @@ -244,6 +248,10 @@ public class PortableCaseReportModule implements ReportModule { return; } + // If the applciation is included add an extra level to the directory structure + if (options.includeApplication()) { + outputDir = Paths.get(outputDir.toString(), caseName).toFile(); + } // Check that there will be something to copy List tagNames; if (options.areAllTagsSelected()) { @@ -419,22 +427,30 @@ public class PortableCaseReportModule implements ReportModule { //Attempt to generate and included the CASE-UCO report. generateCaseUcoReport(tagNames, setNames, progressPanel); + if (options.includeApplication()) { + try { + progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingAutopsy()); + copyApplication(getApplicationBasePath(), outputDir.getAbsolutePath()); + createAppLaunchBatFile(outputDir.getAbsolutePath()); + } catch (IOException ex) { + handleError("Error copying autopsy", Bundle.PortableCaseReportModule_generateReport_errorCopyingAutopsy(), ex, progressPanel); // NON-NLS + } + } + // Compress the case (if desired) if (options.shouldCompress()) { progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase()); - boolean success = compressCase(progressPanel); + if(!compressCase(progressPanel, options.includeApplication() ? outputDir.getAbsolutePath() : caseFolder.getAbsolutePath())){ + // Errors have been handled already + return; + } // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); return; } - - if (!success) { - // Errors have been handled already - return; - } } // Close the case connections and clear out the maps @@ -451,8 +467,8 @@ public class PortableCaseReportModule implements ReportModule { * Only one copy of the file will be saved in the report if it is the source * of more than one of the above. * - * @param tagNames TagNames to included in the report. - * @param setNames SET_NAMEs to include in the report. + * @param tagNames TagNames to included in the report. + * @param setNames SET_NAMEs to include in the report. * @param progressPanel ProgressPanel to relay progress messages. */ @NbBundle.Messages({ @@ -506,7 +522,7 @@ public class PortableCaseReportModule implements ReportModule { // Helper flag to ensure each data source is only written once in // a report. boolean dataSourceHasBeenIncluded = false; - + //Search content tags and artifact tags that match for (TagName tagName : tagNames) { for (ContentTag ct : tagsManager.getContentTagsByTagName(tagName, dataSource.getId())) { @@ -565,16 +581,19 @@ public class PortableCaseReportModule implements ReportModule { /** * Adds the content if and only if it has not already been seen. * - * @param content Content to add to the report. - * @param dataSource Parent dataSource of the content instance. - * @param tmpDir Path to the tmpDir to enforce uniqueness + * @param content Content to add to the report. + * @param dataSource Parent dataSource of the content + * instance. + * @param tmpDir Path to the tmpDir to enforce uniqueness * @param gson * @param exporter - * @param reportWriter Report generator instance to add the content to + * @param reportWriter Report generator instance to add the + * content to * @param dataSourceHasBeenIncluded Flag determining if the data source - * should be written to the report (false indicates that it should be written). - * - * @throws IOException If an I/O error occurs. + * should be written to the report (false + * indicates that it should be written). + * + * @throws IOException If an I/O error occurs. * @throws TskCoreException If an internal database error occurs. * * return True if the file was written during this operation. @@ -630,7 +649,7 @@ public class PortableCaseReportModule implements ReportModule { * Create the case directory and case database. portableSkCase will be set * if this completes without error. * - * @param outputDir The parent for the case folder + * @param outputDir The parent for the case folder * @param progressPanel */ @NbBundle.Messages({ @@ -734,7 +753,7 @@ public class PortableCaseReportModule implements ReportModule { /** * Add all files with a given tag to the portable case. * - * @param oldTagName The TagName object from the current case + * @param oldTagName The TagName object from the current case * @param progressPanel The progress panel * * @throws TskCoreException @@ -779,7 +798,7 @@ public class PortableCaseReportModule implements ReportModule { * @param tag The ContentTag in the current case * * @return The app_data string for this content tag or an empty string if - * there was none + * there was none * * @throws TskCoreException */ @@ -828,7 +847,7 @@ public class PortableCaseReportModule implements ReportModule { * Add an image tag to the portable case. * * @param newContentTag The content tag in the portable case - * @param appData The string to copy into app_data + * @param appData The string to copy into app_data * * @throws TskCoreException */ @@ -840,7 +859,7 @@ public class PortableCaseReportModule implements ReportModule { /** * Add all artifacts with a given tag to the portable case. * - * @param oldTagName The TagName object from the current case + * @param oldTagName The TagName object from the current case * @param progressPanel The progress panel * * @throws TskCoreException @@ -876,8 +895,8 @@ public class PortableCaseReportModule implements ReportModule { * Copy an artifact into the new case. Will also copy any associated * artifacts * - * @param newContentId The content ID (in the portable case) of the source - * content + * @param newContentId The content ID (in the portable case) of the source + * content * @param artifactToCopy The artifact to copy * * @return The new artifact in the portable case @@ -1000,7 +1019,7 @@ public class PortableCaseReportModule implements ReportModule { /** * Top level method to copy a content object to the portable case. * - * @param content The content object to copy + * @param content The content object to copy * @param progressPanel The progress panel * * @return The object ID of the copied content in the portable case @@ -1143,6 +1162,76 @@ public class PortableCaseReportModule implements ReportModule { return UNKNOWN_FILE_TYPE_FOLDER; } + /** + * Returns base path of the users autopsy installation. + * + * @return Path of autopsy installation. + */ + private Path getApplicationBasePath() { + return getAutopsyExePath().getParent().getParent(); + } + + /** + * Find the path of the installed version of autopsy. + * + * @return Path to the installed autopsy.exe. + */ + private Path getAutopsyExePath() { + // If this is an installed version, there should be an 64.exe file in the bin folder + String exeName = getAutopsyExeName(); + String installPath = PlatformUtil.getInstallPath(); + + return Paths.get(installPath, "bin", exeName); + } + + /** + * Generate the name of the autopsy exe. + * + * @return The name of the autopsy exe. + */ + private String getAutopsyExeName() { + String appName = UserPreferences.getAppName(); + return appName + "64.exe"; + } + + /** + * Copy the sorceFolder to destBaseFolder\appName. + * + * @param sourceFolder Autopsy installation directory. + * @param destBaseFolder Report base direction. + * @param appName Name of the application being copied. + * + * @throws IOException + */ + private void copyApplication(Path sourceFolder, String destBaseFolder) throws IOException { + + // Create an appName folder in the destination + Path destAppFolder = Paths.get(destBaseFolder, UserPreferences.getAppName()); + if (!destAppFolder.toFile().exists() && !destAppFolder.toFile().mkdirs()) { + throw new IOException("Failed to create directory " + destAppFolder.toString()); + } + + // Now copy the files + FileUtils.copyDirectory(sourceFolder.toFile(), destAppFolder.toFile()); + } + + /** + * Create a bat file at destBaseFolder that will launch the portable case. + * + * @param destBaseFolder Folder to create the bat file in. + * + * @throws IOException + */ + private void createAppLaunchBatFile(String destBaseFolder) throws IOException { + Path filePath = Paths.get(destBaseFolder, "open.bat"); + String appName = UserPreferences.getAppName(); + String exePath = "\"%~dp0" + appName + "\\bin\\" + getAutopsyExeName() + "\""; + String casePath = "..\\" + caseName; + try (FileWriter writer = new FileWriter(filePath.toFile())) { + writer.write(exePath + " \"" + casePath + "\""); + } + } + /** * Clear out the maps and other fields and close the database connections. */ @@ -1171,11 +1260,10 @@ public class PortableCaseReportModule implements ReportModule { } } - /*@Override - public JPanel getConfigurationPanel() { - configPanel = new CreatePortableCasePanel(); - return configPanel; - } */ + /* + * @Override public JPanel getConfigurationPanel() { configPanel = new + * CreatePortableCasePanel(); return configPanel; } + */ private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback { private final String tableName; @@ -1213,14 +1301,14 @@ public class PortableCaseReportModule implements ReportModule { "PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}", "PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case", "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",}) - private boolean compressCase(ReportProgressPanel progressPanel) { - - // Close the portable case database (we still need some of the variables that would be cleared by cleanup()) + private boolean compressCase(ReportProgressPanel progressPanel, String folderToCompress) { + closePortableCaseDatabase(); // Make a temporary folder for the compressed case - File tempZipFolder = Paths.get(currentCase.getTempDirectory(), "portableCase" + System.currentTimeMillis()).toFile(); // NON-NLS - if (!tempZipFolder.mkdir()) { + Path dirToCompress = Paths.get(folderToCompress); + File tempZipFolder = Paths.get(dirToCompress.getParent().toString(), "temp", "portableCase" + System.currentTimeMillis()).toFile(); + if (!tempZipFolder.mkdirs()) { handleError("Error creating temporary folder " + tempZipFolder.toString(), Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS return false; @@ -1245,7 +1333,7 @@ public class PortableCaseReportModule implements ReportModule { sevenZipExe.getAbsolutePath(), "a", // Add to archive zipFile.getAbsolutePath(), - caseFolder.getAbsolutePath(), + dirToCompress.toAbsolutePath().toString(), chunkOption ); @@ -1280,9 +1368,9 @@ public class PortableCaseReportModule implements ReportModule { // Delete everything in the case folder then copy over the compressed file(s) try { - FileUtils.cleanDirectory(caseFolder); - FileUtils.copyDirectory(tempZipFolder, caseFolder); - FileUtils.deleteDirectory(tempZipFolder); + FileUtils.cleanDirectory(dirToCompress.toFile()); + FileUtils.copyDirectory(tempZipFolder, dirToCompress.toFile()); + FileUtils.deleteDirectory(new File(tempZipFolder.getParent())); } catch (IOException ex) { handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS return false; diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModuleSettings.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModuleSettings.java index b3d1dea9e0..1f37b7fd9d 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModuleSettings.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModuleSettings.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ public class PortableCaseReportModuleSettings implements ReportModuleSettings { private ChunkSize chunkSize; private boolean allTagsSelected; private boolean allSetsSelected; + private boolean shouldIncludeApplication; /** * Enum for storing the display name for each chunk type and the @@ -141,6 +142,10 @@ public class PortableCaseReportModuleSettings implements ReportModuleSettings { public boolean areAllSetsSelected() { return allSetsSelected; } + + public boolean includeApplication() { + return shouldIncludeApplication; + } /** * @param allTagsSelected the allTagsSelected to set @@ -155,5 +160,9 @@ public class PortableCaseReportModuleSettings implements ReportModuleSettings { public void setAllSetsSelected(boolean allSetsSelected) { this.allSetsSelected = allSetsSelected; } + + public void setIncludeApplication(boolean includeApplication) { + this.shouldIncludeApplication = includeApplication; + } }