diff --git a/.gitignore b/.gitignore index 954207ceec..faa1b92a94 100755 --- a/.gitignore +++ b/.gitignore @@ -1,91 +1,103 @@ -/dist/ -/build/ -/*/build/ -*/nbproject/private/* -/nbproject/private/* - -/Core/release/ -/Core/src/org/sleuthkit/autopsy/coreutils/Version.properties -/Core/src/org/sleuthkit/autopsy/casemodule/docs/QuickStart.html -/Core/src/org/sleuthkit/autopsy/casemodule/docs/screenshot.png -/Core/src/org/sleuthkit/autopsy/datamodel/ranges.csv -/Core/build/ -/Core/dist/ -/Core/nbproject/* -/Core/test/qa-functional/data/* -!/Core/nbproject/project.xml -!/Core/nbproject/project.properties - -/CoreLibs/release/ -/CoreLibs/build/ -/CoreLibs/dist/ -/CoreLibs/nbproject/* -!/CoreLibs/nbproject/project.xml -!/CoreLibs/nbproject/project.properties - -/Core/test/qa-functional/data/ - -/KeywordSearch/release/ -/KeywordSearch/build/ -/KeywordSearch/dist/ -/KeywordSearch/nbproject/* -!/KeywordSearch/nbproject/project.xml -!/KeywordSearch/nbproject/project.properties - -*/genfiles.properties -genfiles.properties - -/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties -/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties -/branding/build/ -/branding/dist/ -/branding/nbproject/* -!/branding/nbproject/project.xml -!/branding/nbproject/project.properties - -/test/input/* -!/test/input/notablehashes.txt-md5.idx -!/test/input/notablekeywords.xml -!/test/input/NSRL.txt-md5.idx -/test/output/* -!/test/output/gold -/test/script/output_dir_link.txt -/test/output/gold/tmp -/test/script/ScriptLog.txt -/test/script/__pycache__/ -/test/script/*.pyc -/test/script/DBDump-Diff.txt -/test/script/DBDump.txt -/test/script/SortedData-Diff.txt -/test/script/SortedData.txt -/test/script/myconfig.xml -/test/script/*/*.xml -/test/build/ -/test/dist/ -/test/nbproject/* - -!/Testing/nbproject/project.xml -!/Testing/nbproject/project.properties -*~ -/netbeans-plat -/docs/doxygen-user/user-docs -/jdiff-javadocs/* -/jdiff-logs/* -/gen_version.txt -hs_err_pid*.log - -.DS_Store -.*.swp -/Experimental/release/ -/ImageGallery/release/ -/thunderbirdparser/release/ -/RecentActivity/release/ -/CentralRepository/release/ -/Tika/release - -.idea/ -*.iml - -*.img -*.vhd -*.E01 +/dist/ +/build/ +/*/build/ +*/nbproject/private/* +/nbproject/private/* + +/Core/release/ +/Core/src/org/sleuthkit/autopsy/coreutils/Version.properties +/Core/src/org/sleuthkit/autopsy/casemodule/docs/QuickStart.html +/Core/src/org/sleuthkit/autopsy/casemodule/docs/screenshot.png +/Core/src/org/sleuthkit/autopsy/datamodel/ranges.csv +/Core/build/ +/Core/dist/ +/Core/nbproject/* +/Core/test/qa-functional/data/* +!/Core/nbproject/project.xml +!/Core/nbproject/project.properties + +/CoreLibs/release/ +/CoreLibs/build/ +/CoreLibs/dist/ +/CoreLibs/nbproject/* +!/CoreLibs/nbproject/project.xml +!/CoreLibs/nbproject/project.properties + +/Core/test/qa-functional/data/ + +/KeywordSearch/release/ +/KeywordSearch/build/ +/KeywordSearch/dist/ +/KeywordSearch/nbproject/* +!/KeywordSearch/nbproject/project.xml +!/KeywordSearch/nbproject/project.properties + +*/genfiles.properties +genfiles.properties + +/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +/branding/build/ +/branding/dist/ +/branding/nbproject/* +!/branding/nbproject/project.xml +!/branding/nbproject/project.properties + +/test/input/* +!/test/input/notablehashes.txt-md5.idx +!/test/input/notablekeywords.xml +!/test/input/NSRL.txt-md5.idx +/test/output/* +!/test/output/gold +/test/script/output_dir_link.txt +/test/output/gold/tmp +/test/script/ScriptLog.txt +/test/script/__pycache__/ +/test/script/*.pyc +/test/script/DBDump-Diff.txt +/test/script/DBDump.txt +/test/script/SortedData-Diff.txt +/test/script/SortedData.txt +/test/script/myconfig.xml +/test/script/*/*.xml +/test/build/ +/test/dist/ +/test/nbproject/* + +!/Testing/nbproject/project.xml +!/Testing/nbproject/project.properties +*~ +/netbeans-plat +/docs/doxygen-user/user-docs +/jdiff-javadocs/* +/jdiff-logs/* +/gen_version.txt +hs_err_pid*.log + +.DS_Store +.*.swp +/Experimental/release/ +/ImageGallery/release/ +/thunderbirdparser/release/ +/RecentActivity/release/ +/CentralRepository/release/ +/Tika/release + +.idea/ +*.iml + +*.img +*.vhd +*.E01 + +/thirdparty/yara/yarabridge/yarabridge/x64/ +/thirdparty/yara/yarabridge/yarabridge.VC.db +/thirdparty/yara/yarabridge/yarabridge.VC.VC.opendb +/thirdparty/yara/yarabridge/x64/ +/thirdparty/yara/YaraWrapperTest/nbproject/private/ +/thirdparty/yara/YaraWrapperTest/build/ +/thirdparty/yara/YaraJNIWrapper/dist/ +/thirdparty/yara/YaraJNIWrapper/build/ +/thirdparty/yara/YaraJNIWrapper/nbproject/private/ +/thirdparty/yara/yarabridge/.vs/ + diff --git a/Core/build.xml b/Core/build.xml index e54c4eda97..a9807f490b 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -98,6 +98,11 @@ + + + + + @@ -197,7 +202,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 0c83dc776e..6986362a90 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -118,6 +118,7 @@ file.reference.StixLib.jar=release\\modules\\ext\\StixLib.jar file.reference.threetenbp-1.3.3.jar=release\\modules\\ext\\threetenbp-1.3.3.jar file.reference.webp-imageio-sejda-0.1.0.jar=release\\modules\\ext\\webp-imageio-sejda-0.1.0.jar file.reference.xmpcore-5.1.3.jar=release\\modules\\ext\\xmpcore-5.1.3.jar +file.reference.YaraJNIWrapper.jar=release\\modules\\ext\\YaraJNIWrapper.jar file.reference.zookeeper-3.4.6.jar=release\\modules\\ext\\zookeeper-3.4.6.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 2518563e0e..96d3ea1db6 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -548,18 +548,10 @@ ext/checker-compat-qual-2.5.3.jar release\modules\ext\checker-compat-qual-2.5.3.jar - - ext/sleuthkit-4.10.1.jar - release/modules/ext/sleuthkit-4.10.1.jar - ext/animal-sniffer-annotations-1.17.jar release\modules\ext\animal-sniffer-annotations-1.17.jar - - ext/sleuthkit-caseuco-4.10.1.jar - release/modules/ext/sleuthkit-caseuco-4.10.1.jar - ext/gax-1.44.0.jar release\modules\ext\gax-1.44.0.jar @@ -568,6 +560,10 @@ ext/jsoup-1.10.3.jar release\modules\ext\jsoup-1.10.3.jar + + ext/YaraJNIWrapper.jar + release/modules/ext/YaraJNIWrapper.jar + ext/grpc-context-1.19.0.jar release\modules\ext\grpc-context-1.19.0.jar @@ -672,6 +668,10 @@ ext/grpc-alts-1.19.0.jar release\modules\ext\grpc-alts-1.19.0.jar + + ext/sleuthkit-caseuco-4.10.1.jar + release/modules/ext/sleuthkit-caseuco-4.10.1.jar + ext/jdom-2.0.5.jar release\modules\ext\jdom-2.0.5.jar @@ -796,6 +796,10 @@ ext/sevenzipjbinding-AllPlatforms.jar release\modules\ext\sevenzipjbinding-AllPlatforms.jar + + ext/sleuthkit-4.10.1.jar + release/modules/ext/sleuthkit-4.10.1.jar + ext/jutf7-1.0.0.jar release\modules\ext\jutf7-1.0.0.jar diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java index b0f288673b..d3a0e61ecf 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java @@ -382,6 +382,31 @@ public class CentralRepoDbManager { CentralRepoDbUtil.setUseCentralRepo(true); saveNewCentralRepo(); } + + /** + * Set up a PostgresDb using the settings for the given database choice + * enum. + * + * @param choice Type of postgres DB to set up + * @throws CentralRepoException + */ + public void setupPostgresDb(CentralRepoDbChoice choice) throws CentralRepoException { + selectedDbChoice = choice; + DatabaseTestResult curStatus = testStatus(); + if (curStatus == DatabaseTestResult.DB_DOES_NOT_EXIST) { + createDb(); + curStatus = testStatus(); + } + + // the only successful setup status is tested ok + if (curStatus != DatabaseTestResult.TESTED_OK) { + throw new CentralRepoException("Unable to successfully create postgres database. Test failed with: " + curStatus); + } + + // if successfully got here, then save the settings + CentralRepoDbUtil.setUseCentralRepo(true); + saveNewCentralRepo(); + } /** * This method returns if changes to the central repository configuration diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java index 165d4ca6e0..dfb855a0b2 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java @@ -121,10 +121,12 @@ public final class PostgresCentralRepoSettings implements CentralRepoDbConnectiv * @return */ String getConnectionURL(boolean usePostgresDb) { - StringBuilder url = new StringBuilder(); - url.append(getJDBCBaseURI()); - url.append(getHost()); - url.append("/"); // NON-NLS + StringBuilder url = new StringBuilder() + .append(getJDBCBaseURI()) + .append(getHost()) + .append(":") // NON-NLS + .append(getPort()) + .append("/"); // NON-NLS if (usePostgresDb) { url.append("postgres"); // NON-NLS } else { @@ -153,7 +155,7 @@ public final class PostgresCentralRepoSettings implements CentralRepoDbConnectiv } catch (ClassNotFoundException | SQLException ex) { // TODO: Determine why a connection failure (ConnectionException) re-throws // the SQLException and does not print this log message? - LOGGER.log(Level.SEVERE, "Failed to acquire ephemeral connection to postgresql."); // NON-NLS + LOGGER.log(Level.SEVERE, "Failed to acquire ephemeral connection to postgresql.", ex); // NON-NLS conn = null; } return conn; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED index d4cca6c407..e95a759c4f 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED @@ -1,4 +1,10 @@ caseeventlistener.evidencetag=Evidence +CentralRepositoryNotificationDialog.bulletHeader=This data is used to: +CentralRepositoryNotificationDialog.bulletOne=Ignore common items (files, domains, and accounts) +CentralRepositoryNotificationDialog.bulletThree=Create personas that group accounts +CentralRepositoryNotificationDialog.bulletTwo=Identify where an item was previously seen +CentralRepositoryNotificationDialog.finalRemarks=To limit what is stored, use the Central Repository options panel. +CentralRepositoryNotificationDialog.header=Autopsy stores data about each case in its Central Repository. IngestEventsListener.ingestmodule.name=Central Repository IngestEventsListener.prevCaseComment.text=Previous Case: # {0} - typeName @@ -7,6 +13,3 @@ IngestEventsListener.prevCount.text=Number of previous {0}: {1} IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository) IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository) Installer.centralRepoUpgradeFailed.title=Central repository disabled -Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. You can use this to ignore previously seen files and make connections between cases. -Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it? -Installer.initialCreateSqlite.title=Enable Central Repository? diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java new file mode 100755 index 0000000000..883d979c90 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java @@ -0,0 +1,73 @@ +/* + * Central Repository + * + * 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.centralrepository.eventlisteners; + +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.Version; + +/** + * Notifies new installations or old upgrades that the central repository will + * be enabled by default. + */ +public class CentralRepositoryNotificationDialog { + + /** + * This dialog should display iff the mode is RELEASE and the + * application is running with a GUI. + */ + static boolean shouldDisplay() { + return Version.getBuildType() == Version.Type.RELEASE + && RuntimeProperties.runningWithGUI(); + } + + /** + * Displays an informational modal dialog to the user, which is dismissed by + * pressing 'OK'. + */ + @NbBundle.Messages({ + "CentralRepositoryNotificationDialog.header=Autopsy stores data about each case in its Central Repository.", + "CentralRepositoryNotificationDialog.bulletHeader=This data is used to:", + "CentralRepositoryNotificationDialog.bulletOne=Ignore common items (files, domains, and accounts)", + "CentralRepositoryNotificationDialog.bulletTwo=Identify where an item was previously seen", + "CentralRepositoryNotificationDialog.bulletThree=Create personas that group accounts", + "CentralRepositoryNotificationDialog.finalRemarks=To limit what is stored, use the Central Repository options panel." + }) + static void display() { + assert shouldDisplay(); + + MessageNotifyUtil.Message.info( + "" + + "" + + "
" + + "

" + Bundle.CentralRepositoryNotificationDialog_header() + "

" + + "

" + Bundle.CentralRepositoryNotificationDialog_bulletHeader() + "

" + + "
    " + + "
  • " + Bundle.CentralRepositoryNotificationDialog_bulletOne() + "
  • " + + "
  • " + Bundle.CentralRepositoryNotificationDialog_bulletTwo() + "
  • " + + "
  • " + Bundle.CentralRepositoryNotificationDialog_bulletThree() + "
  • " + + "
" + + "

" + Bundle.CentralRepositoryNotificationDialog_finalRemarks() + "

" + + "
" + + "" + + "" + ); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java index d4f0253cd2..b2ef0d437e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -25,14 +25,13 @@ import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openide.modules.ModuleInstall; import org.openide.util.NbBundle; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; -import org.sleuthkit.autopsy.coreutils.Version; /** * Adds/removes application event listeners responsible for adding data to the @@ -81,19 +80,10 @@ public class Installer extends ModuleInstall { * the org.sleuthkit.autopsy.core package when the already installed * Autopsy-Core module is restored (during application startup). */ - @NbBundle.Messages({ - "Installer.initialCreateSqlite.title=Enable Central Repository?", - "Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it?", - "Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. " - + "You can use this to ignore previously seen files and make connections between cases." - }) @Override public void restored() { addApplicationEventListeners(); - - if (Version.getBuildType() == Version.Type.RELEASE) { - setupDefaultCentralRepository(); - } + setupDefaultCentralRepository(); } /** @@ -107,9 +97,9 @@ public class Installer extends ModuleInstall { /** * Checks if the central repository has been set up and configured. If not, - * either offers to perform set up (running with a GUI) or does the set up - * unconditionally (not running with a GUI, e.g., in an automated ingest - * node). + * does the set up unconditionally. If the application is running with a + * GUI, a notification will be displayed to the user if the mode is RELEASE + * (in other words, developers are exempt from seeing the notification). */ private void setupDefaultCentralRepository() { Map centralRepoSettings = ModuleSettings.getConfigSettings("CentralRepository"); @@ -127,62 +117,30 @@ public class Installer extends ModuleInstall { ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); } } - - // if central repository hasn't been previously initialized, initialize it - if (!initialized) { - // if running with a GUI, prompt the user - if (RuntimeProperties.runningWithGUI()) { - try { - SwingUtilities.invokeAndWait(() -> { - try { - String dialogText - = "" - + "
" - + "

" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageHeader") + "

" - + "

" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageDesc") + "

" - + "
" - + ""; - - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), - dialogText, - NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.title"), - JOptionPane.YES_NO_OPTION)) { - - setupDefaultSqliteCentralRepo(); - } - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); - - doMessageBoxIfRunningInGUI(ex); - } - }); - } catch (InterruptedException | InvocationTargetException ex) { - logger.log(Level.SEVERE, "There was an error while running the swing utility invoke later while creating the central repository database", ex); - } - } // if no GUI, just initialize - else { - try { - setupDefaultSqliteCentralRepo(); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); - - doMessageBoxIfRunningInGUI(ex); - } - } - - ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); + + if(initialized) { + return; // Nothing to do } - } - /** - * Sets up a default single-user SQLite central repository. - * - * @throws CentralRepoException If there is an error setting up teh central - * repository. - */ - private void setupDefaultSqliteCentralRepo() throws CentralRepoException { - CentralRepoDbManager manager = new CentralRepoDbManager(); - manager.setupDefaultSqliteDb(); + if (CentralRepositoryNotificationDialog.shouldDisplay()) { + CentralRepositoryNotificationDialog.display(); + } + + try { + CentralRepoDbManager manager = new CentralRepoDbManager(); + if (UserPreferences.getIsMultiUserModeEnabled()) { + // Set up using existing multi-user settings. + manager.setupPostgresDb(CentralRepoDbChoice.POSTGRESQL_MULTIUSER); + } else { + manager.setupDefaultSqliteDb(); + } + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); + + doMessageBoxIfRunningInGUI(ex); + } + + ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); } /** diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties index 03e30a6923..dbdc049ee2 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties @@ -1,20 +1,3 @@ -# Copyright 2020 Basis Technology Corp. -# -# 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. -# - -DefaultArtifactContentViewer.copyMenuItem.text=Copy -DefaultArtifactContentViewer.selectAllMenuItem.text=Select All MessageArtifactViewer.ccLabel.text=CC: MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF MessageArtifactViewer.toText.text=to list goes here @@ -32,3 +15,19 @@ MessageArtifactViewer.subjectLabel.text=Subject: MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments MessageArtifactViewer.ccText.text=cc list goes here MessageArtifactViewer.textbodyScrollPane.TabConstraints.tabTitle=Text +DefaultTableArtifactContentViewer.selectAllMenuItem.text=Select All +# Copyright 2020 Basis Technology Corp. + +# 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. + +DefaultTableArtifactContentViewer.copyMenuItem.text=Copy diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties-MERGED index 0673318668..e2afbad508 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties-MERGED @@ -1,18 +1,3 @@ -# Copyright 2020 Basis Technology Corp. -# -# 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. -# - CalllogArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas. CallLogArtifactViewer_heading_metadata=Metadata CallLogArtifactViewer_heading_others=Other Attributes @@ -56,11 +41,15 @@ ContactArtifactViewer_persona_unknown=Unknown ContactArtifactViewer_phones_header=Phone DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database -DefaultArtifactContentViewer.attrsTableHeader.sources=Source(s) -DefaultArtifactContentViewer.attrsTableHeader.type=Type -DefaultArtifactContentViewer.attrsTableHeader.value=Value -DefaultArtifactContentViewer.copyMenuItem.text=Copy -DefaultArtifactContentViewer.selectAllMenuItem.text=Select All +DefaultTableArtifactContentViewer.attrsTableHeader.sources=Source(s) +DefaultTableArtifactContentViewer.attrsTableHeader.type=Type +DefaultTableArtifactContentViewer.attrsTableHeader.value=Value +GeneralPurposeArtifactViewer.details.attrHeader=Attributes +GeneralPurposeArtifactViewer.details.dataSource=Data Source +GeneralPurposeArtifactViewer.details.file=File +GeneralPurposeArtifactViewer.details.sourceHeader=Source +GeneralPurposeArtifactViewer.unknown.text=Unknown +GeneralPurposeArtifactViewer_menuitem_copy=Copy MessageAccountPanel.account.justification=Account found in Message artifact MessageAccountPanel_button_create_label=Create MessageAccountPanel_button_view_label=View @@ -87,6 +76,22 @@ MessageArtifactViewer.subjectLabel.text=Subject: MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments MessageArtifactViewer.ccText.text=cc list goes here MessageArtifactViewer.textbodyScrollPane.TabConstraints.tabTitle=Text +DefaultTableArtifactContentViewer.selectAllMenuItem.text=Select All +# Copyright 2020 Basis Technology Corp. + +# 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. + +DefaultTableArtifactContentViewer.copyMenuItem.text=Copy PersonaAccountFetcher.account.justification=Account found in Call Log artifact # {0} - Persona count PersonaDisplayTask_persona_count_suffix=(1 of {0}) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle_ja.properties index baee09ff5a..c76255ef36 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle_ja.properties @@ -41,8 +41,6 @@ DataContentViewerArtifact.failedToGetSourcePath.message=\u30b1\u30fc\u30b9\u30fb DefaultArtifactContentViewer.attrsTableHeader.sources=\u30bd\u30fc\u30b9 DefaultArtifactContentViewer.attrsTableHeader.type=\u30bf\u30a4\u30d7 DefaultArtifactContentViewer.attrsTableHeader.value=\u5024 -DefaultArtifactContentViewer.copyMenuItem.text=\u30b3\u30d4\u30fc -DefaultArtifactContentViewer.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629e MessageAccountPanel_button_create_label=\u4f5c\u6210 MessageAccountPanel_button_view_label=\u8868\u793a MessageAccountPanel_no_matches=\u4e00\u81f4\u3059\u308b\u3082\u306e\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 @@ -67,3 +65,5 @@ MessageArtifactViewer.toLabel.text=\u5b9b\u5148\uff1a MessageArtifactViewer.toText.text=\u3053\u3053\u306b\u5b9b\u5148\u30ea\u30b9\u30c8\u3092\u8a18\u5165 MessageArtifactViewer.viewInNewWindowButton.text=\u65b0\u3057\u3044\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u8868\u793a PersonaDisplayTask_persona_count_suffix=\uff081/{0}\uff09 +DefaultTableArtifactContentViewer.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629e +DefaultTableArtifactContentViewer.copyMenuItem.text=\u30b3\u30d4\u30fc diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java index a911ac20d7..fe5e19aff5 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java @@ -63,7 +63,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. * @param headerString Heading string to display. * * @return JLabel Heading label added. @@ -109,6 +109,23 @@ final class CommunicationArtifactViewerHelper { return headingLabel; } + /** + * Add a key value row to the specified panel with the specified layout and + * constraints. + * + * + * @param panel Panel to update. + * @param gridbagLayout Layout to use. + * @param constraints Constraints to use. + * @param keyString Key name to display. + * @param valueString Value string to display. + * + */ + static void addNameValueRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString, String valueString) { + addKey(panel, gridbagLayout, constraints, keyString); + addValue(panel, gridbagLayout, constraints, valueString); + } + /** * Adds the given component to the panel. * @@ -116,7 +133,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. * @param component Component to add. */ static void addComponent(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, JComponent component) { @@ -132,7 +149,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. */ static void addLineEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { // Place the filler just past the last column. @@ -159,7 +176,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. */ static void addPageEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { @@ -185,7 +202,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. */ static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { constraints.gridy++; @@ -203,7 +220,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. * @param keyString Key name to display. * * @return Label added. @@ -217,7 +234,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. * @param keyString Key name to display. * @param gridx column index, must be less than MAX_COLS - 1. * @@ -246,8 +263,8 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. - * @param keyString Value string to display. + * @param constraints Constraints to use. + * @param valueString Value string to display. * * @return Label added. */ @@ -260,7 +277,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. * @param keyString Value string to display. * @param gridx Column index, must be less than MAX_COLS; * @@ -367,7 +384,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. * @param accountIdentifier Account identifier to search the persona. * * @return List of AccountPersonaSearcherData objects. @@ -435,7 +452,7 @@ final class CommunicationArtifactViewerHelper { * * @param panel Panel to update. * @param gridbagLayout Layout to use. - * @param constraints Constrains to use. + * @param constraints Constraints to use. * @param contactId Contact name to display. * * @return A JLabel with the contact information. diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.form similarity index 85% rename from Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.form rename to Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.form index a6aa7f13ce..df38743b8b 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.form @@ -11,14 +11,14 @@ - + - + @@ -27,7 +27,7 @@ - + @@ -59,8 +59,11 @@ + + + - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.java similarity index 89% rename from Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.java index 8c0c89a865..0c90993a61 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultArtifactContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.java @@ -54,37 +54,36 @@ import com.google.gson.JsonArray; import java.util.Locale; import java.util.Map; import javax.swing.SwingUtilities; +import org.sleuthkit.autopsy.discovery.ui.AbstractArtifactDetailsPanel; //import org.sleuthkit.autopsy.contentviewers.Bundle; /** - * This class displays a Blackboard artifact as a table listing all it's - * attributes names and values. + * This class displays a Blackboard artifact as a table of its attributes. */ - @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public class DefaultArtifactContentViewer extends javax.swing.JPanel implements ArtifactContentViewer { +public class DefaultTableArtifactContentViewer extends AbstractArtifactDetailsPanel implements ArtifactContentViewer { @NbBundle.Messages({ - "DefaultArtifactContentViewer.attrsTableHeader.type=Type", - "DefaultArtifactContentViewer.attrsTableHeader.value=Value", - "DefaultArtifactContentViewer.attrsTableHeader.sources=Source(s)", + "DefaultTableArtifactContentViewer.attrsTableHeader.type=Type", + "DefaultTableArtifactContentViewer.attrsTableHeader.value=Value", + "DefaultTableArtifactContentViewer.attrsTableHeader.sources=Source(s)", "DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database", "DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database" }) - - private final static Logger logger = Logger.getLogger(DefaultArtifactContentViewer.class.getName()); - + + private final static Logger logger = Logger.getLogger(DefaultTableArtifactContentViewer.class.getName()); + private static final long serialVersionUID = 1L; - + private static final String[] COLUMN_HEADERS = { - Bundle.DefaultArtifactContentViewer_attrsTableHeader_type(), - Bundle.DefaultArtifactContentViewer_attrsTableHeader_value(), - Bundle.DefaultArtifactContentViewer_attrsTableHeader_sources()}; + Bundle.DefaultTableArtifactContentViewer_attrsTableHeader_type(), + Bundle.DefaultTableArtifactContentViewer_attrsTableHeader_value(), + Bundle.DefaultTableArtifactContentViewer_attrsTableHeader_sources()}; private static final int[] COLUMN_WIDTHS = {100, 800, 100}; private static final int CELL_BOTTOM_MARGIN = 5; private static final int CELL_RIGHT_MARGIN = 1; - public DefaultArtifactContentViewer() { + public DefaultTableArtifactContentViewer() { initResultsTable(); initComponents(); resultsTableScrollPane.setViewportView(resultsTable); @@ -124,7 +123,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements // do nothing } - @Override + @Override public void columnMarginChanged(ChangeEvent e) { updateRowHeights(); //When the user changes column width we may need to resize row height } @@ -153,12 +152,12 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements Component comp = resultsTable.prepareRenderer( resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex); final int rowHeight; - if (comp instanceof JTextArea) { + if (comp instanceof JTextArea) { final JTextArea tc = (JTextArea) comp; final View rootView = tc.getUI().getRootView(tc); java.awt.Insets i = tc.getInsets(); rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex) - .getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders + .getWidth() - (i.left + i.right + CELL_RIGHT_MARGIN), //current width minus borders Integer.MAX_VALUE); rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS); } else { @@ -202,17 +201,18 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements selectAllMenuItem = new javax.swing.JMenuItem(); resultsTableScrollPane = new javax.swing.JScrollPane(); - copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.copyMenuItem.text")); // NOI18N + copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultTableArtifactContentViewer.class, "DefaultTableArtifactContentViewer.copyMenuItem.text")); // NOI18N rightClickMenu.add(copyMenuItem); - selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.selectAllMenuItem.text")); // NOI18N + selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultTableArtifactContentViewer.class, "DefaultTableArtifactContentViewer.selectAllMenuItem.text")); // NOI18N rightClickMenu.add(selectAllMenuItem); - setPreferredSize(new java.awt.Dimension(100, 58)); + setPreferredSize(new java.awt.Dimension(0, 0)); resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34)); + resultsTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 0)); + resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(0, 0)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -267,7 +267,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements * Resets the components to an empty view state. */ private void resetComponents() { - + ((DefaultTableModel) resultsTable.getModel()).setRowCount(0); } @@ -279,7 +279,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements @Override public void setArtifact(BlackboardArtifact artifact) { try { - ResultsTableArtifact resultsTableArtifact = new ResultsTableArtifact(artifact, artifact.getParent()); + ResultsTableArtifact resultsTableArtifact = artifact == null ? null : new ResultsTableArtifact(artifact, artifact.getParent()); SwingUtilities.invokeLater(new Runnable() { @Override @@ -289,7 +289,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements }); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error getting parent content for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + logger.log(Level.SEVERE, String.format("Error getting parent content for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); } } @@ -301,7 +301,7 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements } /** - * This class is a container to hold the data necessary for the artifact + * This class is a container to hold the data necessary for the artifact * being viewed. */ private class ResultsTableArtifact { @@ -340,20 +340,20 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements */ String value; switch (attr.getAttributeType().getValueType()) { - + // Use Autopsy date formatting settings, not TSK defaults case DATETIME: value = epochTimeToString(attr.getValueLong()); break; - case JSON: + case JSON: // Get the attribute's JSON value and convert to indented multiline display string String jsonVal = attr.getValueString(); JsonParser parser = new JsonParser(); JsonObject json = parser.parse(jsonVal).getAsJsonObject(); - + value = toJsonDisplayString(json, ""); break; - + case STRING: case INTEGER: case LONG: @@ -398,43 +398,43 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements String getArtifactDisplayName() { return artifactDisplayName; } - + private static final String INDENT_RIGHT = " "; private static final String NEW_LINE = "\n"; - + /** * Recursively converts a JSON element into an indented multi-line * display string. * - * @param element JSON element to convert + * @param element JSON element to convert * @param startIndent Starting indentation for the element. * * @return A multi-line display string. */ private String toJsonDisplayString(JsonElement element, String startIndent) { - + StringBuilder sb = new StringBuilder(""); JsonObject obj = element.getAsJsonObject(); for (Map.Entry entry : obj.entrySet()) { - appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb ); + appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb); } String returnString = sb.toString(); - if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) { + if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) { returnString = returnString.substring(NEW_LINE.length()); } return returnString; } - - + /** - * Converts the given JSON element into string and appends to the given string builder. - * + * Converts the given JSON element into string and appends to the given + * string builder. + * * @param jsonKey * @param jsonElement * @param startIndent Starting indentation for the element. - * @param sb String builder to append to. + * @param sb String builder to append to. */ private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) { if (jsonElement.isJsonArray()) { @@ -463,11 +463,12 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey)); } } - + /** * Converts epoch time to readable string. - * + * * @param epochTime epoch time value to be converted to string. + * * @return String with human readable time. */ private String epochTimeToString(long epochTime) { @@ -482,21 +483,20 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements } /** - * Updates the table view with the given artifact data. - * + * Updates the table view with the given artifact data. + * * It should be called on EDT. * * @param resultsTableArtifact Artifact data to display in the view. */ private void updateView(ResultsTableArtifact resultsTableArtifact) { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel()); - tModel.setDataVector(resultsTableArtifact.getRows(), COLUMN_HEADERS); + String[][] rows = resultsTableArtifact == null ? new String[0][0] : resultsTableArtifact.getRows(); + tModel.setDataVector(rows, COLUMN_HEADERS); updateColumnSizes(); updateRowHeights(); resultsTable.clearSelection(); - this.setCursor(null); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.form new file mode 100644 index 0000000000..7fb4b28206 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.form @@ -0,0 +1,33 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java new file mode 100644 index 0000000000..15ef8c4796 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java @@ -0,0 +1,383 @@ +/* + * Autopsy + * + * 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.contentviewers.artifactviewers; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.SwingUtilities; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.discovery.ui.AbstractArtifactDetailsPanel; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Panel to display the details for an Artifact. + */ +@ServiceProvider(service = ArtifactContentViewer.class) +public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel implements ArtifactContentViewer { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(GeneralPurposeArtifactViewer.class.getName()); + // Number of columns in the gridbag layout. + private final static int MAX_COLS = 4; + private final static Insets ROW_INSETS = new java.awt.Insets(0, 12, 0, 0); + private final static Insets HEADER_INSETS = new java.awt.Insets(0, 0, 0, 0); + private final static double GLUE_WEIGHT_X = 1.0; + private final static double TEXT_WEIGHT_X = 0.0; + private final static int LABEL_COLUMN = 0; + private final static int VALUE_COLUMN = 1; + private final static int VALUE_WIDTH = 2; + private final static int LABEL_WIDTH = 1; + private static final Integer[] DEFAULT_ORDERING = new Integer[]{BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REFERRER.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS.getTypeID()}; + private final GridBagLayout gridBagLayout = new GridBagLayout(); + private final GridBagConstraints gridBagConstraints = new GridBagConstraints(); + private final Map orderingMap = new HashMap<>(); + + /** + * Creates new form GeneralPurposeArtifactViewer. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public GeneralPurposeArtifactViewer() { + orderingMap.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID(), DEFAULT_ORDERING); + orderingMap.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID(), DEFAULT_ORDERING); + orderingMap.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID(), DEFAULT_ORDERING); + orderingMap.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), DEFAULT_ORDERING); + orderingMap.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID(), DEFAULT_ORDERING); + orderingMap.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID(), DEFAULT_ORDERING); + initComponents(); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @NbBundle.Messages({"GeneralPurposeArtifactViewer.unknown.text=Unknown"}) + @Override + public void setArtifact(BlackboardArtifact artifact) { + resetComponent(); + if (artifact != null) { + String dataSourceName = Bundle.GeneralPurposeArtifactViewer_unknown_text(); + String sourceFileName = Bundle.GeneralPurposeArtifactViewer_unknown_text(); + Map> attributeMap = new HashMap<>(); + try { + // Get all the attributes and group them by the attributeType + for (BlackboardAttribute bba : artifact.getAttributes()) { + List attrList = attributeMap.get(bba.getAttributeType().getTypeID()); + if (attrList == null) { + attrList = new ArrayList<>(); + } + attrList.add(bba); + attributeMap.put(bba.getAttributeType().getTypeID(), attrList); + } + dataSourceName = artifact.getDataSource().getName(); + sourceFileName = artifact.getParent().getName(); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get attributes for artifact " + artifact.getArtifactID(), ex); + } + updateView(artifact.getArtifactTypeID(), attributeMap, dataSourceName, sourceFileName); + } + this.setLayout(this.gridBagLayout); + this.revalidate(); + this.repaint(); + } + + /** + * Reset the panel so that it is empty. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void resetComponent() { + // clear the panel + this.removeAll(); + gridBagConstraints.anchor = GridBagConstraints.FIRST_LINE_START; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridx = LABEL_COLUMN; + gridBagConstraints.weighty = 0.0; + gridBagConstraints.weightx = TEXT_WEIGHT_X; // keep components fixed horizontally. + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.insets = ROW_INSETS; + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + public Component getComponent() { + // Slap a vertical scrollbar on the panel. + return new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + public boolean isSupported(BlackboardArtifact artifact) { + return (artifact != null) + && (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() + || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID() + || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() + || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID()); + } + + @NbBundle.Messages({"GeneralPurposeArtifactViewer.details.attrHeader=Attributes", + "GeneralPurposeArtifactViewer.details.sourceHeader=Source", + "GeneralPurposeArtifactViewer.details.dataSource=Data Source", + "GeneralPurposeArtifactViewer.details.file=File"}) + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setPreferredSize(new java.awt.Dimension(0, 0)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 400, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 300, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + /** + * Update the view to reflect the current artifact's details. + * + * @param artifactTypeId The BlackboardArtifact type id for the artifact + * being displayed. + * @param attributeMap The map of attributes that exist for the artifact. + * @param dataSourceName The name of the datasource that caused the creation + * of the artifact. + * @param sourceFileName The name of the file that caused the creation of + * the artifact. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void updateView(Integer artifactTypeId, Map> attributeMap, String dataSourceName, String sourceFileName) { + if (!(artifactTypeId < 1 || artifactTypeId >= Integer.MAX_VALUE)) { + addHeader(Bundle.GeneralPurposeArtifactViewer_details_attrHeader()); + Integer[] orderingArray = orderingMap.get(artifactTypeId); + if (orderingArray == null) { + orderingArray = DEFAULT_ORDERING; + } + for (Integer attrId : orderingArray) { + List attrList = attributeMap.remove(attrId); + if (attrList != null) { + for (BlackboardAttribute bba : attrList) { + addNameValueRow(bba.getAttributeType().getDisplayName(), bba.getDisplayString()); + } + } + } + for (int key : attributeMap.keySet()) { + for (BlackboardAttribute bba : attributeMap.get(key)) { + addNameValueRow(bba.getAttributeType().getDisplayName(), bba.getDisplayString()); + } + } + addHeader(Bundle.GeneralPurposeArtifactViewer_details_sourceHeader()); + addNameValueRow(Bundle.GeneralPurposeArtifactViewer_details_dataSource(), dataSourceName); + addNameValueRow(Bundle.GeneralPurposeArtifactViewer_details_file(), sourceFileName); + // add veritcal glue at the end + addPageEndGlue(); + } + } + + /** + * Adds a new heading to the panel. + * + * @param headerString Heading string to display. + * + * @return JLabel Heading label added. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private JLabel addHeader(String headerString) { + // create label for heading + javax.swing.JLabel headingLabel = new javax.swing.JLabel(); + // add a blank line before the start of new section, unless it's + // the first section + if (gridBagConstraints.gridy != 0) { + gridBagConstraints.gridy++; + add(new javax.swing.JLabel(" "), gridBagConstraints); + addLineEndGlue(); + } + gridBagConstraints.gridy++; + gridBagConstraints.gridx = LABEL_COLUMN;; + // let the header span all of the row + gridBagConstraints.gridwidth = MAX_COLS; + gridBagConstraints.insets = HEADER_INSETS; + // set text + headingLabel.setText(headerString); + // make it large and bold + headingLabel.setFont(headingLabel.getFont().deriveFont(Font.BOLD, headingLabel.getFont().getSize() + 2)); + // add to panel + add(headingLabel, gridBagConstraints); + // reset constraints to normal + gridBagConstraints.gridwidth = LABEL_WIDTH; + // add line end glue + addLineEndGlue(); + gridBagConstraints.insets = ROW_INSETS; + return headingLabel; + } + + /** + * Add a key value row to the specified panel with the specified layout and + * constraints. + * + * @param keyString Key name to display. + * @param valueString Value string to display. + */ + private void addNameValueRow(String keyString, String valueString) { + addKeyAtCol(keyString); + addValueAtCol(valueString); + } + + /** + * Adds a filler/glue at the end of the line to keep the other columns + * aligned, in case the panel is resized. + */ + private void addLineEndGlue() { + // Place the filler just past the last column. + gridBagConstraints.gridx = MAX_COLS; + gridBagConstraints.weightx = GLUE_WEIGHT_X; // take up all the horizontal space + gridBagConstraints.fill = GridBagConstraints.BOTH; + javax.swing.Box.Filler horizontalFiller = new javax.swing.Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(32767, 0)); + add(horizontalFiller, gridBagConstraints); + // restore fill & weight + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.weightx = TEXT_WEIGHT_X; + } + + /** + * Adds a filler/glue at the bottom of the panel to keep the data rows + * aligned, in case the panel is resized. + */ + private void addPageEndGlue() { + gridBagConstraints.weighty = 1.0; // take up all the vertical space + gridBagConstraints.fill = GridBagConstraints.VERTICAL; + javax.swing.Box.Filler vertFiller = new javax.swing.Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(0, 32767)); + add(vertFiller, gridBagConstraints); + } + + /** + * Adds a label/key to the panel. + * + * @param keyString Key name to display. + * + * @return Label added. + */ + private JLabel addKeyAtCol(String keyString) { + // create label + javax.swing.JLabel keyLabel = new javax.swing.JLabel(); + gridBagConstraints.gridy++; + gridBagConstraints.gridx = LABEL_COLUMN; + gridBagConstraints.gridwidth = LABEL_WIDTH; + // set text + keyLabel.setText(keyString + ": "); + // add to panel + add(keyLabel, gridBagConstraints); + return keyLabel; + } + + /** + * Adds a value string to the panel at specified column. + * + * @param valueString Value string to display. + * + * @return Label added. + */ + private JTextPane addValueAtCol(String valueString) { + // create label, + JTextPane valueField = new JTextPane(); + valueField.setEditable(false); + valueField.setOpaque(false); + gridBagConstraints.gridx = VALUE_COLUMN; + GridBagConstraints cloneConstraints = (GridBagConstraints) gridBagConstraints.clone(); + // let the value span 2 cols + cloneConstraints.gridwidth = VALUE_WIDTH; + cloneConstraints.fill = GridBagConstraints.BOTH; + // set text + valueField.setText(valueString); + // attach a right click menu with Copy option + valueField.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mouseClicked(java.awt.event.MouseEvent evt) { + valueLabelMouseClicked(evt, valueField); + } + }); + // add label to panel + add(valueField, cloneConstraints); + // end the line + addLineEndGlue(); + return valueField; + } + + /** + * Event handler for mouse click event. Attaches a 'Copy' menu item to right + * click. + * + * @param evt Event to check. + * @param valueLabel Label to attach the menu item to. + */ + @NbBundle.Messages({ + "GeneralPurposeArtifactViewer_menuitem_copy=Copy" + }) + private void valueLabelMouseClicked(java.awt.event.MouseEvent evt, JTextPane valueLabel) { + if (SwingUtilities.isRightMouseButton(evt)) { + JPopupMenu popup = new JPopupMenu(); + JMenuItem copyMenu = new JMenuItem(Bundle.CommunicationArtifactViewerHelper_menuitem_copy()); // NON-NLS + copyMenu.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(valueLabel.getText()), null); + } + }); + popup.add(copyMenu); + popup.show(valueLabel, evt.getX(), evt.getY()); + } + } + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 6ccc70c920..b6a1373236 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -41,7 +41,7 @@ import org.sleuthkit.datamodel.TskException; import java.util.Collections; import java.util.HashSet; import org.sleuthkit.autopsy.contentviewers.artifactviewers.ArtifactContentViewer; -import org.sleuthkit.autopsy.contentviewers.artifactviewers.DefaultArtifactContentViewer; +import org.sleuthkit.autopsy.contentviewers.artifactviewers.DefaultTableArtifactContentViewer; /** * Instances of this class display the BlackboardArtifacts associated with the @@ -357,7 +357,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()) || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID()) || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()) - || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID())) { + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID()) + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()) + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID())) { return 3; } else { return 6; @@ -370,7 +372,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat return viewer; } } - return new DefaultArtifactContentViewer(); + return new DefaultTableArtifactContentViewer(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index c7ccf32099..34a6859292 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -128,8 +128,8 @@ public class BlackboardArtifactNode extends AbstractContentNode artifact.getSleuthkitCase().getContentById(objectID)); + if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID()) { + content = getPathIdFile(artifact); + } if (content == null) { - return Lookups.fixed(artifact); - } else { - return Lookups.fixed(artifact, content); + content = contentCache.get(objectID, () -> artifact.getSleuthkitCase().getContentById(objectID)); } } catch (ExecutionException ex) { logger.log(Level.SEVERE, MessageFormat.format("Error getting source content (artifact objID={0}", artifact.getId()), ex); //NON-NLS - return Lookups.fixed(artifact); + content = null; } + if (content == null) { + return Lookups.fixed(artifact); + } else { + return Lookups.fixed(artifact, content); + } + + } + + /** + * Private helper method to allow content specified in a path id attribute + * to be retrieved. + * + * @param artifact The artifact for which content may be specified as a tsk + * path attribute. + * + * @return The Content specified by the artifact's path id attribute or null + * if there was no content available. + * + * @throws ExecutionException Error retrieving the file specified by the + * path id from the cache. + */ + private static Content getPathIdFile(BlackboardArtifact artifact) throws ExecutionException { + try { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID)); + if (attribute != null) { + return contentCache.get(attribute.getValueLong(), () -> artifact.getSleuthkitCase().getContentById(attribute.getValueLong())); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, MessageFormat.format("Error getting content for path id attrbiute for artifact: ", artifact.getId()), ex); //NON-NLS + } + return null; } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java index 6c4601af88..651f5fe758 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java @@ -28,10 +28,11 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -93,16 +94,56 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { return ARTIFACT_UPDATE_TYPE_IDS; } + /** + * Removes fileDetails entries with redundant paths, sorts by date + * descending and limits to the limit provided. + * + * @param fileDetails The file details list. + * @param limit The maximum number of entries to return. + * @return The sorted limited list with unique paths. + */ + private List getSortedLimited(List fileDetails, int limit) { + Map fileDetailsMap = fileDetails.stream() + .filter(details -> details != null) + .collect(Collectors.toMap( + d -> d.getPath().toUpperCase(), + d -> d, + (d1, d2) -> Long.compare(d1.getDateAsLong(), d2.getDateAsLong()) > 0 ? d1 : d2)); + + return fileDetailsMap.values().stream() + .sorted((a, b) -> -Long.compare(a.getDateAsLong(), b.getDateAsLong())) + .limit(limit) + .collect(Collectors.toList()); + } + + /** + * Returns a RecentFileDetails object as derived from the recent document + * artifact or null if no appropriate object can be made. + * + * @param artifact The artifact. + * @return The derived object or null if artifact is invalid. + */ + private RecentFileDetails getRecentlyOpenedDocument(BlackboardArtifact artifact) { + String path = DataSourceInfoUtilities.getStringOrNull(artifact, PATH_ATT); + Long lastOpened = DataSourceInfoUtilities.getLongOrNull(artifact, DATETIME_ATT); + + if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) { + return null; + } else { + return new RecentFileDetails(path, lastOpened); + } + } + /** * Return a list of the most recently opened documents based on the * TSK_RECENT_OBJECT artifact. * * @param dataSource The data source to query. - * @param maxCount The maximum number of results to return, pass 0 to get - * a list of all results. + * @param maxCount The maximum number of results to return, pass 0 to get a + * list of all results. * * @return A list RecentFileDetails representing the most recently opened - * documents or an empty list if none were found. + * documents or an empty list if none were found. * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -112,36 +153,45 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { return Collections.emptyList(); } - List artifactList - = DataSourceInfoUtilities.getArtifacts(provider.get(), - new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_RECENT_OBJECT), - dataSource, - DATETIME_ATT, - DataSourceInfoUtilities.SortOrder.DESCENDING, - maxCount); + throwOnNonPositiveCount(maxCount); - List fileDetails = new ArrayList<>(); - for (BlackboardArtifact artifact : artifactList) { - Long accessedTime = null; - String path = ""; + List details = provider.get().getBlackboard() + .getArtifacts(ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID(), dataSource.getId()).stream() + .map(art -> getRecentlyOpenedDocument(art)) + .filter(d -> d != null) + .collect(Collectors.toList()); - // Get all the attributes in one call. - List attributeList = artifact.getAttributes(); - for (BlackboardAttribute attribute : attributeList) { + return getSortedLimited(details, maxCount); + } - if (attribute.getAttributeType().equals(DATETIME_ATT)) { - accessedTime = attribute.getValueLong(); - } else if (attribute.getAttributeType().equals(PATH_ATT)) { - path = attribute.getValueString(); - } - } + /** + * Returns a RecentDownloadDetails object as derived from the recent + * download artifact or null if no appropriate object can be made. + * + * @param artifact The artifact. + * @return The derived object or null if artifact is invalid. + */ + private RecentDownloadDetails getRecentDownload(BlackboardArtifact artifact) { + Long accessedTime = DataSourceInfoUtilities.getLongOrNull(artifact, DATETIME_ACCESSED_ATT); + String domain = DataSourceInfoUtilities.getStringOrNull(artifact, DOMAIN_ATT); + String path = DataSourceInfoUtilities.getStringOrNull(artifact, PATH_ATT); - if (accessedTime != null && accessedTime != 0) { - fileDetails.add(new RecentFileDetails(path, accessedTime)); - } + if (StringUtils.isBlank(path) || accessedTime == null || accessedTime == 0) { + return null; + } else { + return new RecentDownloadDetails(path, accessedTime, domain); } + } - return fileDetails; + /** + * Throws an IllegalArgumentException if count is less than 1. + * + * @param count The count. + */ + private void throwOnNonPositiveCount(int count) { + if (count < 1) { + throw new IllegalArgumentException("Invalid count: value must be greater than 0."); + } } /** @@ -149,11 +199,11 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { * artifact TSK_DATETIME_ACCESSED attribute. * * @param dataSource Data source to query. - * @param maxCount Maximum number of results to return, passing 0 will - * return all results. + * @param maxCount Maximum number of results to return, passing 0 will + * return all results. * * @return A list of RecentFileDetails objects or empty list if none were - * found. + * found. * * @throws TskCoreException * @throws SleuthkitCaseProviderException @@ -163,46 +213,23 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { return Collections.emptyList(); } - List artifactList - = DataSourceInfoUtilities.getArtifacts(provider.get(), - new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD), - dataSource, - DATETIME_ACCESSED_ATT, - DataSourceInfoUtilities.SortOrder.DESCENDING, - maxCount); + throwOnNonPositiveCount(maxCount); - List fileDetails = new ArrayList<>(); - for (BlackboardArtifact artifact : artifactList) { - // Get all the attributes in one call. - Long accessedTime = null; - String domain = ""; - String path = ""; + List details = provider.get().getBlackboard() + .getArtifacts(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID(), dataSource.getId()).stream() + .map(art -> getRecentDownload(art)) + .filter(d -> d != null) + .collect(Collectors.toList()); - List attributeList = artifact.getAttributes(); - for (BlackboardAttribute attribute : attributeList) { - - if (attribute.getAttributeType().equals(DATETIME_ACCESSED_ATT)) { - accessedTime = attribute.getValueLong(); - } else if (attribute.getAttributeType().equals(DOMAIN_ATT)) { - domain = attribute.getValueString(); - } else if (attribute.getAttributeType().equals(PATH_ATT)) { - path = attribute.getValueString(); - } - } - if (accessedTime != null && accessedTime != 0L) { - fileDetails.add(new RecentDownloadDetails(path, accessedTime, domain)); - } - } - - return fileDetails; + return getSortedLimited(details, maxCount); } /** * Returns a list of the most recent message attachments. * * @param dataSource Data source to query. - * @param maxCount Maximum number of results to return, passing 0 will - * return all results. + * @param maxCount Maximum number of results to return, passing 0 will + * return all results. * * @return A list of RecentFileDetails of the most recent attachments. * @@ -214,115 +241,72 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { return Collections.emptyList(); } - if (maxCount < 0) { - throw new IllegalArgumentException("Invalid maxCount passed to getRecentAttachments, value must be equal to or greater than 0"); + throwOnNonPositiveCount(maxCount); + + SleuthkitCase skCase = provider.get(); + + List associatedArtifacts = skCase.getBlackboard() + .getArtifacts(ASSOCATED_OBJ_ART.getTypeID(), dataSource.getId()); + + List details = new ArrayList<>(); + for (BlackboardArtifact artifact : associatedArtifacts) { + RecentAttachmentDetails thisDetails = getRecentAttachment(artifact, skCase); + + if (thisDetails != null) { + details.add(thisDetails); + } } - return createListFromMap(buildAttachmentMap(dataSource), maxCount); + return getSortedLimited(details, maxCount); } /** - * Build a map of all of the message attachment sorted in date order. + * Creates a RecentAttachmentDetails object from the associated object + * artifact or null if no RecentAttachmentDetails object can be derived. * - * @param dataSource Data source to query. - * - * @return Returns a SortedMap of details objects returned in descending - * order. - * - * @throws SleuthkitCaseProviderException + * @param artifact The associated object artifact. + * @param skCase The current case. + * @return The derived object or null. * @throws TskCoreException */ - private SortedMap> buildAttachmentMap(DataSource dataSource) throws SleuthkitCaseProviderException, TskCoreException { - SleuthkitCase skCase = provider.get(); - TreeMap> sortedMap = new TreeMap<>(); - - List associatedArtifacts = skCase.getBlackboard().getArtifacts(ASSOCATED_OBJ_ART.getTypeID(), dataSource.getId()); - for (BlackboardArtifact artifact : associatedArtifacts) { - BlackboardAttribute attribute = artifact.getAttribute(ASSOCATED_ATT); - if (attribute == null) { - continue; - } - - BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong()); - if (messageArtifact != null && isMessageArtifact(messageArtifact)) { - Content content = artifact.getParent(); - if (content instanceof AbstractFile) { - String sender; - Long date = null; - String path; - - BlackboardAttribute senderAttribute = messageArtifact.getAttribute(EMAIL_FROM_ATT); - if (senderAttribute != null) { - sender = senderAttribute.getValueString(); - } else { - sender = ""; - } - senderAttribute = messageArtifact.getAttribute(MSG_DATEIME_SENT_ATT); - if (senderAttribute != null) { - date = senderAttribute.getValueLong(); - } - - AbstractFile abstractFile = (AbstractFile) content; - - path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()).toString(); - - if (date != null && date != 0) { - List list = sortedMap.get(date); - if (list == null) { - list = new ArrayList<>(); - sortedMap.put(date, list); - } - RecentAttachmentDetails details = new RecentAttachmentDetails(path, date, sender); - if (!list.contains(details)) { - list.add(details); - } - } - } - } - } - return sortedMap.descendingMap(); - } - - /** - * Create a list of detail objects from the given sorted map of the max - * size. - * - * @param sortedMap A Map of attachment details sorted by date. - * @param maxCount Maximum number of values to return. - * - * @return A list of the details of the most recent attachments or empty - * list if none where found. - */ - private List createListFromMap(SortedMap> sortedMap, int maxCount) { - List fileList = new ArrayList<>(); - - for (List mapList : sortedMap.values()) { - if (maxCount == 0 || fileList.size() + mapList.size() <= maxCount) { - fileList.addAll(mapList); - continue; - } - - if (maxCount == fileList.size()) { - break; - } - - for (RecentAttachmentDetails details : mapList) { - if (fileList.size() < maxCount) { - fileList.add(details); - } else { - break; - } - } + private RecentAttachmentDetails getRecentAttachment(BlackboardArtifact artifact, SleuthkitCase skCase) throws TskCoreException { + // get associated artifact or return no result + BlackboardAttribute attribute = artifact.getAttribute(ASSOCATED_ATT); + if (attribute == null) { + return null; } - return fileList; + // get associated message artifact if exists or return no result + BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong()); + if (messageArtifact == null || !isMessageArtifact(messageArtifact)) { + return null; + } + + // get abstract file if exists or return no result + Content content = artifact.getParent(); + if (!(content instanceof AbstractFile)) { + return null; + } + + AbstractFile abstractFile = (AbstractFile) content; + + // get the path, sender, and date + String path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()).toString(); + String sender = DataSourceInfoUtilities.getStringOrNull(messageArtifact, EMAIL_FROM_ATT); + Long date = DataSourceInfoUtilities.getLongOrNull(messageArtifact, MSG_DATEIME_SENT_ATT); + + if (date == null || date == 0 || StringUtils.isBlank(path)) { + return null; + } else { + return new RecentAttachmentDetails(path, date, sender); + } } /** * Is the given artifact a message. * * @param nodeArtifact An artifact that might be a message. Must not be - * null. + * null. * * @return True if the given artifact is a message artifact */ @@ -330,6 +314,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { final int artifactTypeID = nodeArtifact.getArtifactTypeID(); return artifactTypeID == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(); + } /** @@ -391,8 +376,8 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { /** * Constructor for files with just a path and date. * - * @param path File path. - * @param date File access date\time in seconds with java epoch. + * @param path File path. + * @param date File access date\time in seconds with java epoch. * @param webDomain The webdomain from which the file was downloaded. */ RecentDownloadDetails(String path, long date, String webDomain) { @@ -404,7 +389,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { * Returns the web domain. * * @return The web domain or empty string if not available or - * applicable. + * applicable. */ public String getWebDomain() { return webDomain; @@ -422,10 +407,10 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { * Constructor for recent download files which have a path, date and * domain value. * - * @param path File path. - * @param date File crtime. + * @param path File path. + * @param date File crtime. * @param sender The sender of the message from which the file was - * attached. + * attached. */ RecentAttachmentDetails(String path, long date, String sender) { super(path, date); @@ -436,7 +421,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor { * Return the sender of the attached file. * * @return The sender of the attached file or empty string if not - * available. + * available. */ public String getSender() { return sender; diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TimelineSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TimelineSummary.java new file mode 100644 index 0000000000..1d634211be --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TimelineSummary.java @@ -0,0 +1,318 @@ +/* + * 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.datasourcesummary.datamodel; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import org.joda.time.Interval; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TimelineEvent; +import org.sleuthkit.datamodel.TimelineEventType; +import org.sleuthkit.datamodel.TimelineFilter; +import org.sleuthkit.datamodel.TimelineFilter.DataSourcesFilter; +import org.sleuthkit.datamodel.TimelineFilter.RootFilter; +import org.sleuthkit.datamodel.TimelineManager; +import org.sleuthkit.datamodel.TskCoreException; +import java.util.function.Supplier; +import org.sleuthkit.autopsy.core.UserPreferences; + +/** + * Provides data source summary information pertaining to Timeline data. + */ +public class TimelineSummary implements DefaultUpdateGovernor { + + private static final long DAY_SECS = 24 * 60 * 60; + private static final Set INGEST_JOB_EVENTS = new HashSet<>( + Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED)); + + private static final Set FILE_SYSTEM_EVENTS + = new HashSet<>(Arrays.asList( + TimelineEventType.FILE_MODIFIED, + TimelineEventType.FILE_ACCESSED, + TimelineEventType.FILE_CREATED, + TimelineEventType.FILE_CHANGED)); + + private final SleuthkitCaseProvider caseProvider; + private final Supplier timeZoneProvider; + + /** + * Default constructor. + */ + public TimelineSummary() { + this(SleuthkitCaseProvider.DEFAULT, () -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays())); + } + + /** + * Construct object with given SleuthkitCaseProvider + * + * @param caseProvider SleuthkitCaseProvider provider, cannot be null. + * @param timeZoneProvider The timezone provider, cannot be null. + */ + public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier timeZoneProvider) { + this.caseProvider = caseProvider; + this.timeZoneProvider = timeZoneProvider; + } + + @Override + public boolean isRefreshRequired(ModuleContentEvent evt) { + return true; + } + + @Override + public boolean isRefreshRequired(AbstractFile file) { + return true; + } + + @Override + public boolean isRefreshRequired(IngestManager.IngestJobEvent evt) { + return (evt != null && INGEST_JOB_EVENTS.contains(evt)); + } + + @Override + public Set getIngestJobEventUpdates() { + return INGEST_JOB_EVENTS; + } + + /** + * Retrieves timeline summary data. + * + * @param dataSource The data source for which timeline data will be + * retrieved. + * @param recentDaysNum The maximum number of most recent days' activity to + * include. + * @return The retrieved data. + * @throws SleuthkitCaseProviderException + * @throws TskCoreException + */ + public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException { + TimeZone timeZone = this.timeZoneProvider.get(); + TimelineManager timelineManager = this.caseProvider.get().getTimelineManager(); + + // get a mapping of days from epoch to the activity for that day + Map dateCounts = getTimelineEventsByDay(dataSource, timelineManager, timeZone); + + // get minimum and maximum usage date by iterating through + Long minDay = null; + Long maxDay = null; + for (long daysFromEpoch : dateCounts.keySet()) { + minDay = (minDay == null) ? daysFromEpoch : Math.min(minDay, daysFromEpoch); + maxDay = (maxDay == null) ? daysFromEpoch : Math.max(maxDay, daysFromEpoch); + } + + // if no min date or max date, no usage; return null. + if (minDay == null || maxDay == null) { + return null; + } + + Date minDate = new Date(minDay * 1000 * DAY_SECS); + Date maxDate = new Date(maxDay * 1000 * DAY_SECS); + + // The minimum recent day will be within recentDaysNum from the maximum day + // (+1 since maxDay included) or the minimum day of activity + long minRecentDay = Math.max(maxDay - recentDaysNum + 1, minDay); + + // get most recent days activity + List mostRecentActivityAmt = getMostRecentActivityAmounts(dateCounts, minRecentDay, maxDay); + + return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt); + } + + /** + * Given activity by day, converts to most recent days' activity handling + * empty values. + * + * @param dateCounts The day from epoch mapped to activity amounts for that + * day. + * @param minRecentDay The minimum recent day in days from epoch. + * @param maxDay The maximum recent day in days from epoch; + * @return The most recent daily activity amounts. + */ + private List getMostRecentActivityAmounts(Map dateCounts, long minRecentDay, long maxDay) { + List mostRecentActivityAmt = new ArrayList<>(); + + for (long curRecentDay = minRecentDay; curRecentDay <= maxDay; curRecentDay++) { + DailyActivityAmount prevCounts = dateCounts.get(curRecentDay); + DailyActivityAmount countsHandleNotFound = prevCounts != null + ? prevCounts + : new DailyActivityAmount(new Date(curRecentDay * DAY_SECS * 1000), 0, 0); + + mostRecentActivityAmt.add(countsHandleNotFound); + } + return mostRecentActivityAmt; + } + + /** + * Fetches timeline events per day for a particular data source. + * + * @param dataSource The data source. + * @param timelineManager The timeline manager to use while fetching the + * data. + * @param timeZone The time zone to use to determine which day activity + * belongs. + * @return A Map mapping days from epoch to the activity for that day. + * @throws TskCoreException + */ + private Map getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone) throws TskCoreException { + + DataSourcesFilter dataSourceFilter = new DataSourcesFilter(); + dataSourceFilter.addSubFilter(new TimelineFilter.DataSourceFilter(dataSource.getName(), dataSource.getId())); + + RootFilter dataSourceRootFilter = new RootFilter( + null, + null, + null, + null, + null, + dataSourceFilter, + null, + Collections.emptySet()); + + // get events for data source + long curRunTime = System.currentTimeMillis(); + List events = timelineManager.getEvents(new Interval(1, curRunTime), dataSourceRootFilter); + + // get counts of events per day (left is file system events, right is everything else) + Map dateCounts = new HashMap<>(); + for (TimelineEvent evt : events) { + long curSecondsFromEpoch = evt.getTime(); + long curDaysFromEpoch = Instant.ofEpochMilli(curSecondsFromEpoch * 1000) + .atZone(timeZone.toZoneId()) + .toLocalDate() + .toEpochDay(); + + DailyActivityAmount prevAmt = dateCounts.get(curDaysFromEpoch); + long prevFileEvtCount = prevAmt == null ? 0 : prevAmt.getFileActivityCount(); + long prevArtifactEvtCount = prevAmt == null ? 0 : prevAmt.getArtifactActivityCount(); + Date thisDay = prevAmt == null ? new Date(curDaysFromEpoch * 1000 * DAY_SECS) : prevAmt.getDay(); + + boolean isFileEvt = FILE_SYSTEM_EVENTS.contains(evt.getEventType()); + long curFileEvtCount = prevFileEvtCount + (isFileEvt ? 1 : 0); + long curArtifactEvtCount = prevArtifactEvtCount + (isFileEvt ? 0 : 1); + + dateCounts.put(curDaysFromEpoch, new DailyActivityAmount(thisDay, curFileEvtCount, curArtifactEvtCount)); + } + + return dateCounts; + } + + /** + * All the data to be represented in the timeline summary tab. + */ + public static class TimelineSummaryData { + + private final Date minDate; + private final Date maxDate; + private final List histogramActivity; + + /** + * Main constructor. + * + * @param minDate Earliest usage date recorded for the data source. + * @param maxDate Latest usage date recorded for the data source. + * @param recentDaysActivity A list of activity prior to and including + * the latest usage date by day. + */ + TimelineSummaryData(Date minDate, Date maxDate, List recentDaysActivity) { + this.minDate = minDate; + this.maxDate = maxDate; + this.histogramActivity = (recentDaysActivity == null) ? Collections.emptyList() : Collections.unmodifiableList(recentDaysActivity); + } + + /** + * @return Earliest usage date recorded for the data source. + */ + public Date getMinDate() { + return minDate; + } + + /** + * @return Latest usage date recorded for the data source. + */ + public Date getMaxDate() { + return maxDate; + } + + /** + * @return A list of activity prior to and including the latest usage + * date by day. + */ + public List getMostRecentDaysActivity() { + return histogramActivity; + } + } + + /** + * Represents the amount of usage based on timeline events for a day. + */ + public static class DailyActivityAmount { + + private final Date day; + private final long fileActivityCount; + private final long artifactActivityCount; + + /** + * Main constructor. + * + * @param day The day for which activity is being measured. + * @param fileActivityCount The amount of file activity timeline events. + * @param artifactActivityCount The amount of artifact timeline events. + */ + DailyActivityAmount(Date day, long fileActivityCount, long artifactActivityCount) { + this.day = day; + this.fileActivityCount = fileActivityCount; + this.artifactActivityCount = artifactActivityCount; + } + + /** + * @return The day for which activity is being measured. + */ + public Date getDay() { + return day; + } + + /** + * @return The amount of file activity timeline events. + */ + public long getFileActivityCount() { + return fileActivityCount; + } + + /** + * @return The amount of artifact timeline events. + */ + public long getArtifactActivityCount() { + return artifactActivityCount; + } + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java index 16da6f5c4b..b7efda6e29 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java @@ -137,7 +137,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { return (a.getProgramName() == null ? "" : a.getProgramName()) .compareToIgnoreCase((b.getProgramName() == null ? "" : b.getProgramName())); }; - + private static final Set ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList( ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), @@ -172,9 +172,9 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * is designed with unit testing in mind since mocked dependencies can be * utilized. * - * @param provider The object providing the current SleuthkitCase. + * @param provider The object providing the current SleuthkitCase. * @param translationService The translation service. - * @param logger The logger to use. + * @param logger The logger to use. */ public UserActivitySummary( SleuthkitCaseProvider provider, @@ -206,7 +206,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * Gets a list of recent domains based on the datasource. * * @param dataSource The datasource to query for recent domains. - * @param count The max count of items to return. + * @param count The max count of items to return. * * @return The list of items retrieved from the database. * @@ -242,12 +242,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * Creates a TopDomainsResult from data or null if no visit date exists * within DOMAIN_WINDOW_MS of mostRecentMs. * - * @param domain The domain. - * @param visits The number of visits. + * @param domain The domain. + * @param visits The number of visits. * @param mostRecentMs The most recent visit of any domain. * * @return The TopDomainsResult or null if no visits to this domain within - * 30 days of mostRecentMs. + * 30 days of mostRecentMs. */ private TopDomainsResult getDomainsResult(String domain, List visits, long mostRecentMs) { long visitCount = 0; @@ -280,9 +280,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * @param dataSource The datasource. * * @return A tuple where the first value is the latest web history accessed - * date in milliseconds and the second value maps normalized - * (lowercase; trimmed) domain names to when those domains were - * visited. + * date in milliseconds and the second value maps normalized (lowercase; + * trimmed) domain names to when those domains were visited. * * @throws TskCoreException * @throws SleuthkitCaseProviderException @@ -349,7 +348,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * @param artifact The artifact. * * @return The TopWebSearchResult or null if the search string or date - * accessed cannot be determined. + * accessed cannot be determined. */ private static TopWebSearchResult getWebSearchResult(BlackboardArtifact artifact) { String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT); @@ -364,11 +363,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * term. * * @param dataSource The data source. - * @param count The maximum number of records to be shown (must be > - * 0). + * @param count The maximum number of records to be shown (must be > 0). * * @return The list of most recent web searches where most recent search - * appears first. + * appears first. * * @throws * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException @@ -386,21 +384,22 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { .getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId()); // group by search string (case insensitive) - Collection> resultGroups = webSearchArtifacts + Collection resultGroups = webSearchArtifacts .stream() // get items where search string and date is not null .map(UserActivitySummary::getWebSearchResult) // remove null records .filter(result -> result != null) - // get these messages grouped by search to string - .collect(Collectors.groupingBy((result) -> result.getSearchString().toUpperCase())) + // get the latest message for each search string + .collect(Collectors.toMap( + (result) -> result.getSearchString().toUpperCase(), + result -> result, + (result1, result2) -> TOP_WEBSEARCH_RESULT_DATE_COMPARE.compare(result1, result2) >= 0 ? result1 : result2)) .values(); // get the most recent date for each search term List results = resultGroups .stream() - // get the most recent access per search type - .map((list) -> list.stream().max(TOP_WEBSEARCH_RESULT_DATE_COMPARE).get()) // get most recent searches first .sorted(TOP_WEBSEARCH_RESULT_DATE_COMPARE.reversed()) .limit(count) @@ -424,7 +423,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * @param original The original text. * * @return The translated text or null if no translation can be determined - * or exists. + * or exists. */ private String getTranslationOrNull(String original) { if (!translationService.hasProvider() || StringUtils.isBlank(original)) { @@ -448,15 +447,34 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { return translated; } + /** + * Gives the most recent TopDeviceAttachedResult. If one is null, the other + * is returned. + * + * @param r1 A result. + * @param r2 Another result. + * @return The most recent one with a non-null date. + */ + private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) { + if (r2.getDateAccessed() == null) { + return r1; + } + + if (r1.getDateAccessed() == null) { + return r2; + } + + return r1.getDateAccessed().compareTo(r2.getDateAccessed()) >= 0 ? r1 : r2; + } + /** * Retrieves most recent devices used by most recent date attached. * * @param dataSource The data source. - * @param count The maximum number of records to be shown (must be > - * 0). + * @param count The maximum number of records to be shown (must be > 0). * * @return The list of most recent devices attached where most recent device - * attached appears first. + * attached appears first. * * @throws * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException @@ -469,7 +487,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { return Collections.emptyList(); } - return DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED, + Collection results = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED, dataSource, TYPE_DATETIME, DataSourceInfoUtilities.SortOrder.DESCENDING, 0) .stream() .map(artifact -> { @@ -482,9 +500,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { }) // remove Root Hub identifier .filter(result -> { - return result.getDeviceModel() == null + return result.getDeviceId() == null + || result.getDeviceModel() == null || !DEVICE_EXCLUDE_LIST.contains(result.getDeviceModel().trim().toUpperCase()); }) + .collect(Collectors.toMap(result -> result.getDeviceId(), result -> result, (r1, r2) -> getMostRecentDevice(r1, r2))) + .values(); + + return results.stream() .limit(count) .collect(Collectors.toList()); } @@ -495,7 +518,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * @param artifact The artifact. * * @return The TopAccountResult or null if the account type or message date - * cannot be determined. + * cannot be determined. */ private static TopAccountResult getMessageAccountResult(BlackboardArtifact artifact) { String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE); @@ -509,12 +532,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * Obtains a TopAccountResult from a blackboard artifact. The date is * maximum of any found dates for attribute types provided. * - * @param artifact The artifact. + * @param artifact The artifact. * @param messageType The type of message this is. - * @param dateAttrs The date attribute types. + * @param dateAttrs The date attribute types. * * @return The TopAccountResult or null if the account type or max date are - * not provided. + * not provided. */ private static TopAccountResult getAccountResult(BlackboardArtifact artifact, String messageType, BlackboardAttribute.Type... dateAttrs) { String type = messageType; @@ -538,11 +561,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * sent. * * @param dataSource The data source. - * @param count The maximum number of records to be shown (must be > - * 0). + * @param count The maximum number of records to be shown (must be > 0). * * @return The list of most recent accounts used where the most recent - * account by last message sent occurs first. + * account by last message sent occurs first. * * @throws * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException @@ -585,18 +607,19 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { Stream allResults = Stream.concat(messageResults, Stream.concat(emailResults, calllogResults)); // get them grouped by account type - Collection> groupedResults = allResults + Collection groupedResults = allResults // remove null records .filter(result -> result != null) - // get these messages grouped by account type - .collect(Collectors.groupingBy(TopAccountResult::getAccountType)) + // get these messages grouped by account type and get the most recent of each type + .collect(Collectors.toMap( + result -> result.getAccountType(), + result -> result, + (result1, result2) -> TOP_ACCOUNT_RESULT_DATE_COMPARE.compare(result1, result2) >= 0 ? result1 : result2)) .values(); // get account type sorted by most recent date return groupedResults .stream() - // get the most recent access per account type - .map((accountGroup) -> accountGroup.stream().max(TOP_ACCOUNT_RESULT_DATE_COMPARE).get()) // get most recent accounts accessed .sorted(TOP_ACCOUNT_RESULT_DATE_COMPARE.reversed()) // limit to count @@ -608,7 +631,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { /** * Determines a short folder name if any. Otherwise, returns empty string. * - * @param strPath The string path. + * @param strPath The string path. * @param applicationName The application name. * * @return The short folder name or empty string if not found. @@ -659,7 +682,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { if (StringUtils.startsWithIgnoreCase(path, WINDOWS_PREFIX)) { return null; } - + Integer count = DataSourceInfoUtilities.getIntOrNull(artifact, TYPE_COUNT); Long longCount = (count == null) ? null : (long) count; @@ -696,7 +719,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * @param long2 Second possibly null long. * * @return Returns the compare value: 1,0,-1 favoring the higher non-null - * value. + * value. */ private static int nullableCompare(Long long1, Long long2) { if (long1 == null && long2 == null) { @@ -721,7 +744,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { return longNum != null && longNum > 0; } - /** * Retrieves the top programs results for the given data source limited to * the count provided as a parameter. The highest run times are at the top @@ -731,12 +753,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * be ignored and all items will be returned. * * @param dataSource The datasource. If the datasource is null, an empty - * list will be returned. - * @param count The number of results to return. This value must be > 0 - * or an IllegalArgumentException will be thrown. + * list will be returned. + * @param count The number of results to return. This value must be > 0 or + * an IllegalArgumentException will be thrown. * * @return The sorted list and limited to the count if last run or run count - * information is available on any item. + * information is available on any item. * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -759,7 +781,9 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { // The value will be a TopProgramsResult with the max run times // and most recent last run date for each program name / program path pair. .collect(Collectors.toMap( - res -> Pair.of(res.getProgramName(), res.getProgramPath()), + res -> Pair.of( + res.getProgramName() == null ? null : res.getProgramName().toUpperCase(), + res.getProgramPath() == null ? null : res.getProgramPath().toUpperCase()), res -> res, (res1, res2) -> { return new TopProgramsResult( @@ -852,10 +876,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { /** * Main constructor. * - * @param deviceId The device id. + * @param deviceId The device id. * @param dateAccessed The date last attached. - * @param deviceMake The device make. - * @param deviceModel The device model. + * @param deviceMake The device make. + * @param deviceModel The device model. */ public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel) { this.deviceId = deviceId; @@ -906,7 +930,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * Main constructor. * * @param accountType The account type. - * @param lastAccess The date the account was last accessed. + * @param lastAccess The date the account was last accessed. */ public TopAccountResult(String accountType, Date lastAccess) { this.accountType = accountType; @@ -940,9 +964,9 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { /** * Describes a top domain result. * - * @param domain The domain. + * @param domain The domain. * @param visitTimes The number of times it was visited. - * @param lastVisit The date of the last visit. + * @param lastVisit The date of the last visit. */ public TopDomainsResult(String domain, Long visitTimes, Date lastVisit) { this.domain = domain; @@ -987,7 +1011,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * * @param programName The name of the program. * @param programPath The path of the program. - * @param runTimes The number of runs. + * @param runTimes The number of runs. */ TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) { this.programName = programName; diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties index 49b55e369a..aed1969445 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties @@ -44,3 +44,4 @@ PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. WhereUsedPanel.cityCountsLabel.text=Cities WhereUsedPanel.viewInGeolocationBtn.text=View In Geolocation +TimelinePanel.activityRangeLabel.text=Activity Range diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index a4544bdf63..6cca4f4351 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -3,6 +3,7 @@ AnalysisPanel_keyColumn_title=Name AnalysisPanel_keywordSearchModuleName=Keyword Search # {0} - module name BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source. +ContainerPanel_setFieldsForNonImageDataSource_na=N/A CTL_DataSourceSummaryAction=Data Source Summary DataSourceSummaryDialog.closeButton.text=Close ContainerPanel.displayNameLabel.text=Display Name: @@ -48,6 +49,7 @@ DataSourceSummaryTabbedPane_geolocationTab_title=Geolocation DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files +DataSourceSummaryTabbedPane_timelineTab_title=Timeline DataSourceSummaryTabbedPane_typesTab_title=Types DataSourceSummaryTabbedPane_userActivityTab_title=User Activity GeolocationPanel_cityColumn_title=City @@ -68,6 +70,11 @@ SizeRepresentationUtil_units_kilobytes=\ kB SizeRepresentationUtil_units_megabytes=\ MB SizeRepresentationUtil_units_petabytes=\ PB SizeRepresentationUtil_units_terabytes=\ TB +TimelinePanel_earliestLabel_title=Earliest +TimelinePanel_latestLabel_title=Latest +TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events +TimlinePanel_last30DaysChart_fileEvts_title=File Events +TimlinePanel_last30DaysChart_title=Last 30 Days TypesPanel_artifactsTypesPieChart_title=Artifact Types TypesPanel_fileMimeTypesChart_audio_title=Audio TypesPanel_fileMimeTypesChart_documents_title=Documents @@ -100,6 +107,7 @@ PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. GeolocationPanel.cityCountsLabel.text=Cities +TimelinePanel.activityRangeLabel.text=Activity Range UserActivityPanel_noDataExists=No communication data exists UserActivityPanel_tab_title=User Activity UserActivityPanel_TopAccountTableModel_accountType_header=Account Type diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index a852a0b886..bc331d952b 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.table.DefaultTableModel; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; @@ -52,7 +53,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { /** * Main constructor. * - * @param dataSource The original datasource. + * @param dataSource The original datasource. * @param unallocatedFilesSize The unallocated file size. */ ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize) { @@ -165,8 +166,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { private void updateDetailsPanelData(DataSource selectedDataSource, Long unallocatedFilesSize) { clearTableValues(); if (selectedDataSource != null) { - unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(unallocatedFilesSize)); - timeZoneValue.setText(selectedDataSource.getTimeZone()); displayNameValue.setText(selectedDataSource.getName()); originalNameValue.setText(selectedDataSource.getName()); deviceIdValue.setText(selectedDataSource.getDeviceId()); @@ -178,24 +177,48 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { } if (selectedDataSource instanceof Image) { - setFieldsForImage((Image) selectedDataSource); + setFieldsForImage((Image) selectedDataSource, unallocatedFilesSize); + } else { + setFieldsForNonImageDataSource(); } } - + this.repaint(); } + @Messages({ + "ContainerPanel_setFieldsForNonImageDataSource_na=N/A" + }) + private void setFieldsForNonImageDataSource() { + String NA = Bundle.ContainerPanel_setFieldsForNonImageDataSource_na(); + + unallocatedSizeValue.setText(NA); + imageTypeValue.setText(NA); + sizeValue.setText(NA); + sectorSizeValue.setText(NA); + timeZoneValue.setText(NA); + + ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{NA}); + + md5HashValue.setText(NA); + sha1HashValue.setText(NA); + sha256HashValue.setText(NA); + } + /** * Sets text fields for an image. This should be called after * clearTableValues and before updateFieldVisibility to ensure the proper * rendering. * * @param selectedImage The selected image. + * @param unallocatedFilesSize Unallocated file size in bytes. */ - private void setFieldsForImage(Image selectedImage) { + private void setFieldsForImage(Image selectedImage, Long unallocatedFilesSize) { + unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(unallocatedFilesSize)); imageTypeValue.setText(selectedImage.getType().getName()); sizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSize())); sectorSizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSsize())); + timeZoneValue.setText(selectedImage.getTimeZone()); for (String path : selectedImage.getPaths()) { ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path}); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java index 1945abcec4..80d4231ec7 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java @@ -39,7 +39,8 @@ import org.sleuthkit.datamodel.DataSource; "DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files", "DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases", "DataSourceSummaryTabbedPane_analysisTab_title=Analysis", - "DataSourceSummaryTabbedPane_whereUsedTab_title=Where Used" + "DataSourceSummaryTabbedPane_whereUsedTab_title=Where Used", + "DataSourceSummaryTabbedPane_timelineTab_title=Timeline" }) public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { @@ -125,6 +126,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_recentFileTab_title(), new RecentFilesPanel()), new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_pastCasesTab_title(), new PastCasesPanel()), new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_whereUsedTab_title(), new WhereUsedPanel()), + new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_timelineTab_title(), new TimelinePanel()), // do nothing on closing new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel, ingestHistoryPanel::setDataSource, () -> { }), diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java index 76d36f4785..2400334ee3 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.datasourcesummary.ui; import java.util.Arrays; import java.util.List; -import java.util.function.Function; import org.apache.commons.lang3.tuple.Pair; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory; @@ -28,7 +27,6 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; @@ -103,31 +101,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel { * @param result The result. */ private void handleResult(DataFetchResult result) { - showResultWithModuleCheck(notableFileTable, getSubResult(result, (res) -> res.getTaggedNotable()), CR_FACTORY, CR_NAME); - showResultWithModuleCheck(sameIdTable, getSubResult(result, (res) -> res.getSameIdsResults()), CR_FACTORY, CR_NAME); - } - - /** - * Given an input data fetch result, creates an error result if the original - * is an error. Otherwise, uses the getSubResult function on the underlying - * data to create a new DataFetchResult. - * - * @param inputResult The input result. - * @param getSubComponent The means of getting the data given the original - * data. - * - * @return The new result with the error of the original or the processed - * data. - */ - private DataFetchResult getSubResult(DataFetchResult inputResult, Function getSubResult) { - if (inputResult == null) { - return null; - } else if (inputResult.getResultType() == ResultType.SUCCESS) { - O innerData = (inputResult.getData() == null) ? null : getSubResult.apply(inputResult.getData()); - return DataFetchResult.getSuccessResult(innerData); - } else { - return DataFetchResult.getErrorResult(inputResult.getException()); - } + showResultWithModuleCheck(notableFileTable, DataFetchResult.getSubResult(result, (res) -> res.getTaggedNotable()), CR_FACTORY, CR_NAME); + showResultWithModuleCheck(sameIdTable, DataFetchResult.getSubResult(result, (res) -> res.getSameIdsResults()), CR_FACTORY, CR_NAME); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.form new file mode 100644 index 0000000000..e3493d7a0d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.form @@ -0,0 +1,214 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java new file mode 100644 index 0000000000..87f170ccab --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java @@ -0,0 +1,263 @@ +/* + * 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.datasourcesummary.ui; + +import java.awt.Color; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import org.apache.commons.collections.CollectionUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.BarChartItem; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.BarChartSeries; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.OrderedKey; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel; +import org.sleuthkit.datamodel.DataSource; + +/** + * A tab shown in data source summary displaying information about a data + * source's timeline events. + */ +@Messages({ + "TimelinePanel_earliestLabel_title=Earliest", + "TimelinePanel_latestLabel_title=Latest", + "TimlinePanel_last30DaysChart_title=Last 30 Days", + "TimlinePanel_last30DaysChart_fileEvts_title=File Events", + "TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",}) +public class TimelinePanel extends BaseDataSourceSummaryPanel { + + private static final long serialVersionUID = 1L; + private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy"); + private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d"); + private static final int MOST_RECENT_DAYS_COUNT = 30; + + /** + * Creates a DateFormat formatter that uses UTC for time zone. + * + * @param formatString The date format string. + * @return The data format. + */ + private static DateFormat getUtcFormat(String formatString) { + return new SimpleDateFormat(formatString, Locale.getDefault()); + } + + // components displayed in the tab + private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel(); + private final LoadableLabel earliestLabel = new LoadableLabel(Bundle.TimelinePanel_earliestLabel_title()); + private final LoadableLabel latestLabel = new LoadableLabel(Bundle.TimelinePanel_latestLabel_title()); + private final BarChartPanel last30DaysChart = new BarChartPanel(Bundle.TimlinePanel_last30DaysChart_title(), "", ""); + + // all loadable components on this tab + private final List> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart); + + // actions to load data for this tab + private final List> dataFetchComponents; + + public TimelinePanel() { + this(new TimelineSummary()); + } + + /** + * Creates new form PastCasesPanel + */ + public TimelinePanel(TimelineSummary timelineData) { + // set up data acquisition methods + dataFetchComponents = Arrays.asList( + new DataFetchWorker.DataFetchComponents<>( + (dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT), + (result) -> handleResult(result)) + ); + + initComponents(); + } + + /** + * Formats a date using a DateFormat. In the event that the date is null, + * returns a null string. + * + * @param date The date to format. + * @param formatter The DateFormat to use to format the date. + * @return The formatted string generated from the formatter or null if the + * date is null. + */ + private static String formatDate(Date date, DateFormat formatter) { + return date == null ? null : formatter.format(date); + } + + private static final Color FILE_EVT_COLOR = new Color(228, 22, 28); + private static final Color ARTIFACT_EVT_COLOR = new Color(21, 227, 100); + + /** + * Converts DailyActivityAmount data retrieved from TimelineSummary into + * data to be displayed as a bar chart. + * + * @param recentDaysActivity The data retrieved from TimelineSummary. + * @return The data to be displayed in the BarChart. + */ + private List parseChartData(List recentDaysActivity) { + // if no data, return null indicating no result. + if (CollectionUtils.isEmpty(recentDaysActivity)) { + return null; + } + + // Create a bar chart item for each recent days activity item + List fileEvtCounts = new ArrayList<>(); + List artifactEvtCounts = new ArrayList<>(); + + for (int i = 0; i < recentDaysActivity.size(); i++) { + DailyActivityAmount curItem = recentDaysActivity.get(i); + + long fileAmt = curItem.getFileActivityCount(); + long artifactAmt = curItem.getArtifactActivityCount() * 100; + String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1) + ? formatDate(curItem.getDay(), CHART_FORMAT) : ""; + + OrderedKey thisKey = new OrderedKey(formattedDate, i); + fileEvtCounts.add(new BarChartItem(thisKey, fileAmt)); + artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt)); + } + + return Arrays.asList( + new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts), + new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts)); + } + + /** + * Handles displaying the result for each displayable item in the + * TimelinePanel by breaking the TimelineSummaryData result into its + * constituent parts and then sending each data item to the pertinent + * component. + * + * @param result The result to be displayed on this tab. + */ + private void handleResult(DataFetchResult result) { + earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT))); + latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT))); + last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity()))); + } + + @Override + protected void fetchInformation(DataSource dataSource) { + fetchInformation(dataFetchComponents, dataSource); + } + + @Override + protected void onNewDataSource(DataSource dataSource) { + onNewDataSource(dataFetchComponents, loadableComponents, dataSource); + } + + @Override + public void close() { + ingestRunningLabel.unregister(); + super.close(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane(); + javax.swing.JPanel mainContentPanel = new javax.swing.JPanel(); + javax.swing.JPanel ingestRunningPanel = ingestRunningLabel; + javax.swing.JLabel activityRangeLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JPanel earliestLabelPanel = earliestLabel; + javax.swing.JPanel latestLabelPanel = latestLabel; + javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); + javax.swing.JPanel sameIdPanel = last30DaysChart; + javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); + + mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS)); + + ingestRunningPanel.setAlignmentX(0.0F); + ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25)); + ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25)); + ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25)); + mainContentPanel.add(ingestRunningPanel); + + activityRangeLabel.setFont(new java.awt.Font("Segoe UI", 1, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(activityRangeLabel, org.openide.util.NbBundle.getMessage(TimelinePanel.class, "TimelinePanel.activityRangeLabel.text")); // NOI18N + mainContentPanel.add(activityRangeLabel); + activityRangeLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(TimelinePanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N + + filler1.setAlignmentX(0.0F); + mainContentPanel.add(filler1); + + earliestLabelPanel.setAlignmentX(0.0F); + earliestLabelPanel.setMaximumSize(new java.awt.Dimension(32767, 20)); + earliestLabelPanel.setMinimumSize(new java.awt.Dimension(100, 20)); + earliestLabelPanel.setPreferredSize(new java.awt.Dimension(100, 20)); + mainContentPanel.add(earliestLabelPanel); + + latestLabelPanel.setAlignmentX(0.0F); + latestLabelPanel.setMaximumSize(new java.awt.Dimension(32767, 20)); + latestLabelPanel.setMinimumSize(new java.awt.Dimension(100, 20)); + latestLabelPanel.setPreferredSize(new java.awt.Dimension(100, 20)); + mainContentPanel.add(latestLabelPanel); + + filler2.setAlignmentX(0.0F); + mainContentPanel.add(filler2); + + sameIdPanel.setAlignmentX(0.0F); + sameIdPanel.setMaximumSize(new java.awt.Dimension(600, 300)); + sameIdPanel.setMinimumSize(new java.awt.Dimension(600, 300)); + sameIdPanel.setPreferredSize(new java.awt.Dimension(600, 300)); + sameIdPanel.setVerifyInputWhenFocusTarget(false); + mainContentPanel.add(sameIdPanel); + + filler5.setAlignmentX(0.0F); + mainContentPanel.add(filler5); + + mainScrollPane.setViewportView(mainContentPanel); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java index 448bf6bbea..247aa0c304 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.datasourcesummary.ui; -import java.awt.BorderLayout; import java.awt.Color; import java.sql.SQLException; import java.text.DecimalFormat; @@ -30,7 +29,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.swing.JLabel; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory; @@ -40,13 +38,13 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.MimeTypeSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.AbstractLoadableComponent; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel.PieChartItem; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; @@ -78,46 +76,6 @@ import org.sleuthkit.datamodel.TskCoreException; "TypesPanel_sizeLabel_title=Size"}) class TypesPanel extends BaseDataSourceSummaryPanel { - /** - * A label that allows for displaying loading messages and can be used with - * a DataFetchResult. Text displays as ":". - */ - private static class LoadableLabel extends AbstractLoadableComponent { - - private static final long serialVersionUID = 1L; - - private final JLabel label = new JLabel(); - private final String key; - - /** - * Main constructor for the label. - * - * @param key The key to be displayed. - */ - LoadableLabel(String key) { - this.key = key; - setLayout(new BorderLayout()); - add(label, BorderLayout.CENTER); - this.showResults(null); - } - - private void setValue(String value) { - String formattedKey = StringUtils.isBlank(key) ? "" : key; - String formattedValue = StringUtils.isBlank(value) ? "" : value; - label.setText(String.format("%s: %s", formattedKey, formattedValue)); - } - - @Override - protected void setMessage(boolean visible, String message) { - setValue(message); - } - - @Override - protected void setResults(String data) { - setValue(data); - } - } - /** * Data for types pie chart. */ @@ -129,9 +87,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel { /** * Main constructor. * - * @param pieSlices The pie slices. + * @param pieSlices The pie slices. * @param usefulContent True if this is useful content; false if there - * is 0 mime type information. + * is 0 mime type information. */ public TypesPieChartData(List pieSlices, boolean usefulContent) { this.pieSlices = pieSlices; @@ -165,9 +123,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel { /** * Main constructor. * - * @param label The label for this slice. + * @param label The label for this slice. * @param mimeTypes The mime types associated with this slice. - * @param color The color associated with this slice. + * @param color The color associated with this slice. */ TypesPieCategory(String label, Set mimeTypes, Color color) { this.label = label; @@ -178,9 +136,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel { /** * Constructor that accepts FileTypeCategory. * - * @param label The label for this slice. + * @param label The label for this slice. * @param mimeTypes The mime types associated with this slice. - * @param color The color associated with this slice. + * @param color The color associated with this slice. */ TypesPieCategory(String label, FileTypeCategory fileCategory, Color color) { this(label, fileCategory.getMediaTypes(), color); @@ -278,8 +236,8 @@ class TypesPanel extends BaseDataSourceSummaryPanel { /** * Creates a new TypesPanel. * - * @param mimeTypeData The service for mime types. - * @param typeData The service for file types data. + * @param mimeTypeData The service for mime types. + * @param typeData The service for file types data. * @param containerData The service for container information. */ public TypesPanel( @@ -358,7 +316,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel { * Gets all the data for the file type pie chart. * * @param mimeTypeData The means of acquiring data. - * @param dataSource The datasource. + * @param dataSource The datasource. * * @return The pie chart items. */ diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java new file mode 100644 index 0000000000..3f3822f7c2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java @@ -0,0 +1,307 @@ +/* + * 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.datasourcesummary.uiutils; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.util.Collections; +import java.util.List; +import javax.swing.JLabel; +import org.apache.commons.collections4.CollectionUtils; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.renderer.category.BarRenderer; +import org.jfree.chart.renderer.category.StandardBarPainter; +import org.jfree.data.category.DefaultCategoryDataset; + +/** + * A bar chart panel. + */ +public class BarChartPanel extends AbstractLoadableComponent> { + + /** + * Represents a series in a bar chart where all items pertain to one + * category. + */ + public static class BarChartSeries { + + private final Comparable key; + private final Color color; + private final List items; + + /** + * Main constructor. + * + * @param color The color for this series. + * @param items The bars to be displayed for this series. + */ + public BarChartSeries(Comparable key, Color color, List items) { + this.key = key; + this.color = color; + this.items = (items == null) ? Collections.emptyList() : Collections.unmodifiableList(items); + } + + /** + * @return The color for this series. + */ + public Color getColor() { + return color; + } + + /** + * @return The bars to be displayed for this series. + */ + public List getItems() { + return items; + } + + /** + * @return The key for this item. + */ + public Comparable getKey() { + return key; + } + } + + /** + * An individual bar to be displayed in the bar chart. + */ + public static class BarChartItem { + + private final Comparable key; + private final double value; + + /** + * Main constructor. + * + * @param label The key for this bar. Also serves as the label using + * toString(). + * @param value The value for this item. + */ + public BarChartItem(Comparable key, double value) { + this.key = key; + this.value = value; + } + + /** + * @return The key for this item. + */ + public Comparable getKey() { + return key; + } + + /** + * @return The value for this item. + */ + public double getValue() { + return value; + } + } + + /** + * JFreeChart bar charts don't preserve the order of bars provided to the + * chart, but instead uses the comparable nature to order items. This + * provides order using a provided index as well as the value for the axis. + */ + public static class OrderedKey implements Comparable { + + private final Object keyValue; + private final int keyIndex; + + /** + * Main constructor. + * + * @param keyValue The value for the key to be displayed in the domain + * axis. + * @param keyIndex The index at which it will be displayed. + */ + public OrderedKey(Object keyValue, int keyIndex) { + this.keyValue = keyValue; + this.keyIndex = keyIndex; + } + + /** + * @return The value for the key to be displayed in the domain axis. + */ + Object getKeyValue() { + return keyValue; + } + + /** + * @return The index at which it will be displayed. + */ + int getKeyIndex() { + return keyIndex; + } + + @Override + public int compareTo(OrderedKey o) { + // this will have a higher value than null. + if (o == null) { + return 1; + } + + // compare by index + return Integer.compare(this.getKeyIndex(), o.getKeyIndex()); + } + + @Override + public int hashCode() { + int hash = 3; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final OrderedKey other = (OrderedKey) obj; + if (this.keyIndex != other.keyIndex) { + return false; + } + return true; + } + + @Override + public String toString() { + // use toString on the key. + return this.getKeyValue() == null ? null : this.getKeyValue().toString(); + } + } + + private static final long serialVersionUID = 1L; + + private static final Font DEFAULT_FONT = new JLabel().getFont(); + private static final Font DEFAULT_HEADER_FONT = new Font(DEFAULT_FONT.getName(), DEFAULT_FONT.getStyle(), (int) (DEFAULT_FONT.getSize() * 1.5)); + + private final ChartMessageOverlay overlay = new ChartMessageOverlay(); + private final DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + private final JFreeChart chart; + private final CategoryPlot plot; + + /** + * Main constructor assuming null values for all items. + */ + public BarChartPanel() { + this(null, null, null); + } + + /** + * Main constructor for the pie chart. + * + * @param title The title for this pie chart. + * @param categoryLabel The x-axis label. + * @param valueLabel The y-axis label. + */ + public BarChartPanel(String title, String categoryLabel, String valueLabel) { + this.chart = ChartFactory.createStackedBarChart( + title, + categoryLabel, + valueLabel, + dataset, + PlotOrientation.VERTICAL, + true, false, false); + + // set style to match autopsy components + chart.setBackgroundPaint(null); + chart.getTitle().setFont(DEFAULT_HEADER_FONT); + + this.plot = ((CategoryPlot) chart.getPlot()); + this.plot.getRenderer().setBaseItemLabelFont(DEFAULT_FONT); + plot.setBackgroundPaint(null); + plot.setOutlinePaint(null); + + // hide y axis labels + ValueAxis range = plot.getRangeAxis(); + range.setVisible(false); + + // make sure x axis labels don't get cut off + plot.getDomainAxis().setMaximumCategoryLabelWidthRatio(10); + + ((BarRenderer) plot.getRenderer()).setBarPainter(new StandardBarPainter()); + + // Create Panel + ChartPanel panel = new ChartPanel(chart); + panel.addOverlay(overlay); + panel.setPopupMenu(null); + + this.setLayout(new BorderLayout()); + this.add(panel, BorderLayout.CENTER); + } + + /** + * @return The title for this chart if one exists. + */ + public String getTitle() { + return (this.chart == null || this.chart.getTitle() == null) + ? null + : this.chart.getTitle().getText(); + } + + /** + * Sets the title for this pie chart. + * + * @param title The title. + * + * @return As a utility, returns this. + */ + public BarChartPanel setTitle(String title) { + this.chart.getTitle().setText(title); + return this; + } + + @Override + protected void setMessage(boolean visible, String message) { + this.overlay.setVisible(visible); + this.overlay.setMessage(message); + } + + @Override + protected void setResults(List data) { + this.dataset.clear(); + + if (CollectionUtils.isNotEmpty(data)) { + for (int s = 0; s < data.size(); s++) { + BarChartPanel.BarChartSeries series = data.get(s); + if (series != null && CollectionUtils.isNotEmpty(series.getItems())) { + if (series.getColor() != null) { + this.plot.getRenderer().setSeriesPaint(s, series.getColor()); + } + + for (int i = 0; i < series.getItems().size(); i++) { + BarChartItem bar = series.getItems().get(i); + this.dataset.setValue(bar.getValue(), series.getKey(), bar.getKey()); + } + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ChartMessageOverlay.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ChartMessageOverlay.java new file mode 100644 index 0000000000..2e21dfb796 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ChartMessageOverlay.java @@ -0,0 +1,63 @@ +/* + * 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.datasourcesummary.uiutils; + +import java.awt.Graphics2D; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.panel.AbstractOverlay; +import org.jfree.chart.panel.Overlay; + +/** + * A JFreeChart message overlay that can show a message for the purposes of the + * LoadableComponent. + */ +class ChartMessageOverlay extends AbstractOverlay implements Overlay { + + private static final long serialVersionUID = 1L; + private final BaseMessageOverlay overlay = new BaseMessageOverlay(); + + // multiply this value by the smaller dimension (height or width) of the component + // to determine width of text to be displayed. + private static final double MESSAGE_WIDTH_FACTOR = .6; + + /** + * Sets this layer visible when painted. In order to be shown in UI, this + * component needs to be repainted. + * + * @param visible Whether or not it is visible. + */ + void setVisible(boolean visible) { + overlay.setVisible(visible); + } + + /** + * Sets the message to be displayed in the child jlabel. + * + * @param message The message to be displayed. + */ + void setMessage(String message) { + overlay.setMessage(message); + } + + @Override + public void paintOverlay(Graphics2D gd, ChartPanel cp) { + int labelWidth = (int) (Math.min(cp.getWidth(), cp.getHeight()) * MESSAGE_WIDTH_FACTOR); + overlay.paintOverlay(gd, cp.getWidth(), cp.getHeight(), labelWidth); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java index 93cc24f5fe..6e2d8f4991 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DataFetchResult.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datasourcesummary.uiutils; +import java.util.function.Function; + /** * The result of a loading process. */ @@ -30,6 +32,29 @@ public final class DataFetchResult { SUCCESS, ERROR } + /** + * A utility method that, given an input data fetch result, creates an error + * result if the original is an error. Otherwise, uses the getSubResult + * function on the underlying data to create a new DataFetchResult. + * + * @param inputResult The input result. + * @param getSubComponent The means of getting the data given the original + * data. + * + * @return The new result with the error of the original or the processed + * data. + */ + public static DataFetchResult getSubResult(DataFetchResult inputResult, Function getSubResult) { + if (inputResult == null) { + return null; + } else if (inputResult.getResultType() == ResultType.SUCCESS) { + O innerData = (inputResult.getData() == null) ? null : getSubResult.apply(inputResult.getData()); + return DataFetchResult.getSuccessResult(innerData); + } else { + return DataFetchResult.getErrorResult(inputResult.getException()); + } + } + /** * Creates a DataFetchResult of loaded data including the data. * @@ -59,9 +84,8 @@ public final class DataFetchResult { /** * Main constructor for the DataLoadingResult. * - * @param state The state of the result. - * @param data If the result is SUCCESS, the data related to this - * result. + * @param state The state of the result. + * @param data If the result is SUCCESS, the data related to this result. * @param exception If the result is ERROR, the related exception. */ private DataFetchResult(ResultType state, R data, Throwable exception) { diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/LoadableLabel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/LoadableLabel.java new file mode 100644 index 0000000000..3fdf81ad18 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/LoadableLabel.java @@ -0,0 +1,63 @@ +/* + * 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.datasourcesummary.uiutils; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import org.apache.commons.lang3.StringUtils; + +/** + * A label that allows for displaying loading messages and can be used with a + * DataFetchResult. Text displays as ":". + */ +public class LoadableLabel extends AbstractLoadableComponent { + + private static final long serialVersionUID = 1L; + + private final JLabel label = new JLabel(); + private final String key; + + /** + * Main constructor for the label. + * + * @param key The key to be displayed. + */ + public LoadableLabel(String key) { + this.key = key; + setLayout(new BorderLayout()); + add(label, BorderLayout.CENTER); + this.showResults(null); + } + + private void setValue(String value) { + String formattedKey = StringUtils.isBlank(key) ? "" : key; + String formattedValue = StringUtils.isBlank(value) ? "" : value; + label.setText(String.format("%s: %s", formattedKey, formattedValue)); + } + + @Override + protected void setMessage(boolean visible, String message) { + setValue(message); + } + + @Override + protected void setResults(String data) { + setValue(data); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java index 971cb83367..fa0d00dab6 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; -import java.awt.Graphics2D; import java.text.DecimalFormat; import java.util.List; import javax.swing.JLabel; @@ -30,8 +29,6 @@ import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.labels.PieSectionLabelGenerator; import org.jfree.chart.labels.StandardPieSectionLabelGenerator; -import org.jfree.chart.panel.AbstractOverlay; -import org.jfree.chart.panel.Overlay; import org.jfree.chart.plot.PiePlot; import org.jfree.data.general.DefaultPieDataset; import org.openide.util.NbBundle.Messages; @@ -59,7 +56,7 @@ public class PieChartPanel extends AbstractLoadableComponent data, String message) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java index e224c02d51..0b9a0a98c1 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java @@ -19,12 +19,14 @@ package org.sleuthkit.autopsy.discovery.search; import com.google.common.eventbus.EventBus; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey; import org.sleuthkit.autopsy.discovery.search.SearchData.Type; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; /** * Class to handle event bus and events for discovery tool. @@ -88,13 +90,13 @@ public final class DiscoveryEventUtils { //no arg constructor } } - + /** - * Event to signal that any background tasks currently running should - * be cancelled. + * Event to signal that any background tasks currently running should be + * cancelled. */ public static final class CancelBackgroundTasksEvent { - + public CancelBackgroundTasksEvent() { //no-arg constructor } @@ -124,6 +126,30 @@ public final class DiscoveryEventUtils { } } + /** + * Event to signal that the list should be populated. + */ + public static final class PopulateDomainTabsEvent { + + private final String domain; + + /** + * Construct a new PopulateDomainTabsEvent. + */ + public PopulateDomainTabsEvent(String domain) { + this.domain = domain; + } + + /** + * Get the domain for the details area. + * + * @return The the domain for the details area. + */ + public String getDomain() { + return domain; + } + } + /** * Event to signal the completion of a search being performed. */ @@ -203,6 +229,47 @@ public final class DiscoveryEventUtils { } + /** + * Event to signal the completion of a search being performed. + */ + public static final class ArtifactSearchResultEvent { + + private final List listOfArtifacts = new ArrayList<>(); + private final BlackboardArtifact.ARTIFACT_TYPE artifactType; + + /** + * Construct a new ArtifactSearchResultEvent with a list of specified + * artifacts and an artifact type. + * + * @param artifactType The type of artifacts in the list. + * @param listOfArtifacts The list of artifacts retrieved. + */ + public ArtifactSearchResultEvent(BlackboardArtifact.ARTIFACT_TYPE artifactType, List listOfArtifacts) { + if (listOfArtifacts != null) { + this.listOfArtifacts.addAll(listOfArtifacts); + } + this.artifactType = artifactType; + } + + /** + * Get the list of artifacts included in the event. + * + * @return The list of artifacts retrieved. + */ + public List getListOfArtifacts() { + return Collections.unmodifiableList(listOfArtifacts); + } + + /** + * Get the type of BlackboardArtifact type of which exist in the list. + * + * @return The BlackboardArtifact type of which exist in the list. + */ + public BlackboardArtifact.ARTIFACT_TYPE getArtifactType() { + return artifactType; + } + } + /** * Event to signal the completion of page retrieval and include the page * contents. diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java index 9505cfa5e2..f39bb56634 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java @@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; @@ -217,6 +218,15 @@ public class SearchFiltering { this.types = types; } + /** + * Get the list of artifact types specified by the filter. + * + * @return The list of artifact types specified by the filter. + */ + public List getTypes() { + return Collections.unmodifiableList(types); + } + @Override public String getWhereClause() { StringJoiner joiner = new StringJoiner(","); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java new file mode 100644 index 0000000000..b36e93728a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java @@ -0,0 +1,41 @@ +/* + * Autopsy + * + * 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.discovery.ui; + +import javax.swing.JPanel; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Class for ensuring all ArtifactDetailsPanels have a setArtifact method. + * + */ +public abstract class AbstractArtifactDetailsPanel extends JPanel { + + private static final long serialVersionUID = 1L; + + /** + * Called to display the contents of the given artifact. + * + * @param artifact the artifact to display. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + abstract public void setArtifact(BlackboardArtifact artifact); + +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java index 3e4ab45592..7fc964ad52 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractDiscoveryFilterPanel.java @@ -24,6 +24,7 @@ import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.event.ListSelectionListener; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Abstract class extending JPanel for filter controls. @@ -41,6 +42,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel { * selected, null to indicate leaving selected items * unchanged or that there are no items to select. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) abstract void configurePanel(boolean selected, int[] indicesSelected); /** @@ -48,6 +50,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel { * * @return The JCheckBox which enables and disables this filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) abstract JCheckBox getCheckbox(); /** @@ -57,6 +60,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel { * @return The JList which contains the values available for selection for * this filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) abstract JList getList(); /** @@ -65,6 +69,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel { * * @return The JLabel to display under the JCheckBox. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) abstract JLabel getAdditionalLabel(); /** @@ -73,6 +78,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel { * @return If the settings are invalid returns the error that has occurred, * otherwise returns empty string. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) abstract String checkForError(); /** @@ -82,6 +88,7 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel { * @param actionlistener The listener for the checkbox selection events. * @param listListener The listener for the list selection events. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void addListeners(ActionListener actionListener, ListSelectionListener listListener) { if (getCheckbox() != null) { getCheckbox().addActionListener(actionListener); @@ -97,11 +104,13 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel { * @return The AbstractFilter for the selected settings, null if the * settings are not in use. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) abstract AbstractFilter getFilter(); /** * Remove listeners from the checkbox and the list if they exist. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void removeListeners() { if (getCheckbox() != null) { for (ActionListener listener : getCheckbox().getActionListeners()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java index 6e8137a6ed..c8f71a5d99 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java @@ -32,6 +32,7 @@ import javax.swing.JSplitPane; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.GroupingAttributeType; import org.sleuthkit.autopsy.discovery.search.Group; import org.sleuthkit.autopsy.discovery.search.ResultsSorter.SortingMethod; @@ -65,6 +66,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li /** * Setup necessary for implementations of this abstract class. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) AbstractFiltersPanel() { firstColumnPanel.setLayout(new GridBagLayout()); secondColumnPanel.setLayout(new GridBagLayout()); @@ -75,6 +77,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return The type of results this panel filters. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) abstract SearchData.Type getType(); /** @@ -88,7 +91,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * list, null if none are selected. * @param column The column to add the DiscoveryFilterPanel to. */ - final synchronized void addFilter(AbstractDiscoveryFilterPanel filterPanel, boolean isSelected, int[] indicesSelected, int column) { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + final void addFilter(AbstractDiscoveryFilterPanel filterPanel, boolean isSelected, int[] indicesSelected, int column) { if (!isInitialized) { constraints.gridy = 0; constraints.anchor = GridBagConstraints.FIRST_LINE_START; @@ -132,6 +136,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @param splitPane The JSplitPane which the columns are added to. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) final void addPanelsToScrollPane(JSplitPane splitPane) { splitPane.setLeftComponent(firstColumnPanel); splitPane.setRightComponent(secondColumnPanel); @@ -142,7 +147,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li /** * Clear the filters from the panel */ - final synchronized void clearFilters() { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + final void clearFilters() { for (AbstractDiscoveryFilterPanel filterPanel : filters) { filterPanel.removeListeners(); } @@ -159,6 +165,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * column. * @param columnIndex The column to add the Component to. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void addToGridBagLayout(Component componentToAdd, Component additionalComponentToAdd, int columnIndex) { addToColumn(componentToAdd, columnIndex); if (additionalComponentToAdd != null) { @@ -174,6 +181,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * @param component The Component to add. * @param columnNumber The column to add the Component to. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void addToColumn(Component component, int columnNumber) { if (columnNumber == 0) { firstColumnPanel.add(component, constraints); @@ -186,7 +194,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * Check if the fields are valid, and fire a property change event to * indicate any errors. */ - synchronized void validateFields() { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void validateFields() { String errorString = null; for (AbstractDiscoveryFilterPanel filterPanel : filters) { errorString = filterPanel.checkForError(); @@ -197,6 +206,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li firePropertyChange("FilterError", null, errorString); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public void actionPerformed(ActionEvent e) { validateFields(); @@ -209,6 +219,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return True if the ObjectsDetectedFilter is supported, false otherwise. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) boolean isObjectsFilterSupported() { for (AbstractDiscoveryFilterPanel filter : filters) { if (filter instanceof ObjectDetectedFilterPanel) { @@ -223,6 +234,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return True if the HashSetFilter is supported, false otherwise. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) boolean isHashSetFilterSupported() { for (AbstractDiscoveryFilterPanel filter : filters) { if (filter instanceof HashSetFilterPanel) { @@ -237,6 +249,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return True if the InterestingItemsFilter is supported, false otherwise. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) boolean isInterestingItemsFilterSupported() { for (AbstractDiscoveryFilterPanel filter : filters) { if (filter instanceof InterestingItemsFilterPanel) { @@ -251,8 +264,8 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return The list of filters selected by the user. */ - synchronized List getFilters() { - + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + List getFilters() { List filtersToUse = new ArrayList<>(); if (getType() != SearchData.Type.DOMAIN) { //Domain type does not have a file type filtersToUse.add(new SearchFiltering.FileTypeFilter(getType())); @@ -268,6 +281,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li return filtersToUse; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public void valueChanged(ListSelectionEvent evt) { if (!evt.getValueIsAdjusting()) { @@ -282,6 +296,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return The most recently used sorting method. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) SortingMethod getLastSortingMethod() { return lastSortingMethod; } @@ -291,6 +306,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @param lastSortingMethod The most recently used sorting method. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) final void setLastSortingMethod(SortingMethod lastSortingMethod) { this.lastSortingMethod = lastSortingMethod; } @@ -300,6 +316,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return The most recently used grouping attribute. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) GroupingAttributeType getLastGroupingAttributeType() { return lastGroupingAttributeType; } @@ -310,6 +327,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * @param lastGroupingAttributeType The most recently used grouping * attribute. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) final void setLastGroupingAttributeType(GroupingAttributeType lastGroupingAttributeType) { this.lastGroupingAttributeType = lastGroupingAttributeType; } @@ -319,6 +337,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * * @return The most recently used group sorting algorithm. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) Group.GroupSortingAlgorithm getLastGroupSortingAlg() { return lastGroupSortingAlg; } @@ -329,6 +348,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li * @param lastGroupSortingAlg The most recently used group sorting * algorithm. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) final void setLastGroupSortingAlg(Group.GroupSortingAlgorithm lastGroupSortingAlg) { this.lastGroupSortingAlg = lastGroupSortingAlg; } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java index 089c18bf13..e6a1ccaaed 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java @@ -26,6 +26,7 @@ import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchData; import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -40,6 +41,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form ArtifactTypeFilterPanel */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ArtifactTypeFilterPanel() { initComponents(); setUpArtifactTypeFilter(); @@ -49,6 +51,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the data source filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpArtifactTypeFilter() { int count = 0; DefaultListModel artifactTypeModel = (DefaultListModel) artifactList.getModel(); @@ -104,6 +107,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { artifactList.setEnabled(artifactTypeCheckbox.isSelected()); }//GEN-LAST:event_artifactTypeCheckboxActionPerformed + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { artifactTypeCheckbox.setSelected(selected); @@ -119,11 +123,13 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return artifactTypeCheckbox; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return artifactList; @@ -134,6 +140,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected."}) @Override String checkForError() { @@ -143,6 +150,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { return ""; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (artifactTypeCheckbox.isSelected() && !artifactList.getSelectedValuesList().isEmpty()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form new file mode 100644 index 0000000000..51aeababb5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form @@ -0,0 +1,68 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java new file mode 100644 index 0000000000..9aa41b3c20 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java @@ -0,0 +1,352 @@ +/* + * Autopsy + * + * 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.discovery.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JPanel; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang.StringUtils; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Panel to display list of artifacts for selected domain. + * + */ +class ArtifactsListPanel extends JPanel { + + private static final long serialVersionUID = 1L; + private final DomainArtifactTableModel tableModel; + private static final Logger logger = Logger.getLogger(ArtifactsListPanel.class.getName()); + + /** + * Creates new form ArtifactsListPanel. + * + * @param artifactType The type of artifact displayed in this table. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + ArtifactsListPanel(BlackboardArtifact.ARTIFACT_TYPE artifactType) { + tableModel = new DomainArtifactTableModel(artifactType); + initComponents(); + jTable1.getRowSorter().toggleSortOrder(0); + jTable1.getRowSorter().toggleSortOrder(0); + } + + /** + * Add a listener to the table of artifacts to perform actions when an + * artifact is selected. + * + * @param listener The listener to add to the table of artifacts. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void addSelectionListener(ListSelectionListener listener) { + jTable1.getSelectionModel().addListSelectionListener(listener); + } + + /** + * Remove a listener from the table of artifacts. + * + * @param listener The listener to remove from the table of artifacts. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void removeListSelectionListener(ListSelectionListener listener) { + jTable1.getSelectionModel().removeListSelectionListener(listener); + } + + /** + * The artifact which is currently selected, null if no artifact is + * selected. + * + * @return The currently selected BlackboardArtifact or null if none is + * selected. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + BlackboardArtifact getSelectedArtifact() { + int selectedIndex = jTable1.getSelectionModel().getLeadSelectionIndex(); + if (selectedIndex < jTable1.getSelectionModel().getMinSelectionIndex() || jTable1.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > jTable1.getSelectionModel().getMaxSelectionIndex()) { + return null; + } + return tableModel.getArtifactByRow(jTable1.convertRowIndexToModel(selectedIndex)); + } + + /** + * Whether the list of artifacts is empty. + * + * @return true if the list of artifacts is empty, false if there are + * artifacts. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + boolean isEmpty() { + return tableModel.getRowCount() <= 0; + } + + /** + * Select the first available artifact in the list if it is not empty to + * populate the panel to the right. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void selectFirst() { + if (!isEmpty()) { + jTable1.setRowSelectionInterval(0, 0); + } else { + jTable1.clearSelection(); + } + } + + /** + * Add the specified list of artifacts to the list of artifacts which should + * be displayed. + * + * @param artifactList The list of artifacts to display. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void addArtifacts(List artifactList) { + tableModel.setContents(artifactList); + jTable1.validate(); + jTable1.repaint(); + tableModel.fireTableDataChanged(); + } + + /** + * Remove all artifacts from the list of artifacts displayed. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void clearArtifacts() { + tableModel.setContents(new ArrayList<>()); + tableModel.fireTableDataChanged(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane(); + jTable1 = new javax.swing.JTable(); + + setOpaque(false); + setPreferredSize(new java.awt.Dimension(300, 0)); + + jScrollPane1.setBorder(null); + jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 0)); + jScrollPane1.setPreferredSize(new java.awt.Dimension(0, 0)); + + jTable1.setAutoCreateRowSorter(true); + jTable1.setModel(tableModel); + jTable1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(jTable1); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 607, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + /** + * Table model which allows the artifact table in this panel to mimic a list + * of artifacts. + */ + private class DomainArtifactTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + private final List artifactList = new ArrayList<>(); + private final BlackboardArtifact.ARTIFACT_TYPE artifactType; + + /** + * Construct a new DomainArtifactTableModel. + * + * @param artifactType The type of artifact displayed in this table. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + DomainArtifactTableModel(BlackboardArtifact.ARTIFACT_TYPE artifactType) { + this.artifactType = artifactType; + } + + /** + * Set the list of artifacts which should be represented by this table + * model. + * + * @param artifacts The list of BlackboardArtifacts to represent. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void setContents(List artifacts) { + jTable1.clearSelection(); + artifactList.clear(); + artifactList.addAll(artifacts); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + public int getRowCount() { + return artifactList.size(); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + public int getColumnCount() { + if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE) { + return 3; + } else { + return 2; + } + } + + /** + * Get the BlackboardArtifact at the specified row. + * + * @param rowIndex The row the artifact to return is at. + * + * @return The BlackboardArtifact at the specified row. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + BlackboardArtifact getArtifactByRow(int rowIndex) { + return artifactList.get(rowIndex); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @NbBundle.Messages({"ArtifactsListPanel.value.noValue=No value available."}) + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + if (columnIndex < 2 || artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE) { + try { + for (BlackboardAttribute bba : getArtifactByRow(rowIndex).getAttributes()) { + if (!StringUtils.isBlank(bba.getDisplayString())) { + String stringFromAttribute = getStringForColumn(bba, columnIndex); + if (!StringUtils.isBlank(stringFromAttribute)) { + return stringFromAttribute; + } + } + } + return getFallbackValue(rowIndex, columnIndex); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting attributes for artifact " + getArtifactByRow(rowIndex).getArtifactID(), ex); + } + } + return Bundle.ArtifactsListPanel_value_noValue(); + } + + /** + * Get the appropriate String for the specified column from the + * BlackboardAttribute. + * + * @param bba The BlackboardAttribute which may contain a value. + * @param columnIndex The column the value will be displayed in. + * + * @return The value from the specified attribute which should be + * displayed in the specified column, null if the specified + * attribute does not contain a value for that column. + * + * @throws TskCoreException When unable to get abstract files based on + * the TSK_PATH_ID. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private String getStringForColumn(BlackboardAttribute bba, int columnIndex) throws TskCoreException { + if (columnIndex == 0 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID()) { + return bba.getDisplayString(); + } else if (columnIndex == 1) { + if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD || artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE) { + if (bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID()) { + return Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(bba.getValueLong()).getName(); + } else if (bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID()) { + return FilenameUtils.getName(bba.getDisplayString()); + } + } else if (bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE.getTypeID()) { + return bba.getDisplayString(); + } + } else if (columnIndex == 2 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID()) { + return Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(bba.getValueLong()).getMIMEType(); + } + return null; + } + + /** + * Private helper method to use when the value we want for either date + * or title is not available. + * + * + * @param rowIndex The row the artifact to return is at. + * @param columnIndex The column index the attribute will be displayed + * at. + * + * @return A string that can be used in place of the accessed date time + * attribute title when they are not available. + * + * @throws TskCoreException + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private String getFallbackValue(int rowIndex, int columnIndex) throws TskCoreException { + for (BlackboardAttribute bba : getArtifactByRow(rowIndex).getAttributes()) { + if (columnIndex == 0 && bba.getAttributeType().getTypeName().startsWith("TSK_DATETIME") && !StringUtils.isBlank(bba.getDisplayString())) { + return bba.getDisplayString(); + } else if (columnIndex == 1 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL.getTypeID() && !StringUtils.isBlank(bba.getDisplayString())) { + return bba.getDisplayString(); + } else if (columnIndex == 1 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID() && !StringUtils.isBlank(bba.getDisplayString())) { + return bba.getDisplayString(); + } else if (columnIndex == 1 && bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT.getTypeID() && !StringUtils.isBlank(bba.getDisplayString())) { + return bba.getDisplayString(); + } + } + return Bundle.ArtifactsListPanel_value_noValue(); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @NbBundle.Messages({"ArtifactsListPanel.titleColumn.name=Title", + "ArtifactsListPanel.fileNameColumn.name=Name", + "ArtifactsListPanel.dateColumn.name=Date/Time", + "ArtifactsListPanel.mimeTypeColumn.name=MIME Type"}) + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return Bundle.ArtifactsListPanel_dateColumn_name(); + case 1: + if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE || artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD) { + return Bundle.ArtifactsListPanel_fileNameColumn_name(); + } else { + return Bundle.ArtifactsListPanel_titleColumn_name(); + } + case 2: + return Bundle.ArtifactsListPanel_mimeTypeColumn_name(); + default: + return ""; + } + } + } + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTable jTable1; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java new file mode 100644 index 0000000000..371c99fae7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java @@ -0,0 +1,79 @@ +/* + * Autopsy + * + * 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.discovery.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.SwingWorker; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; +import org.sleuthkit.autopsy.discovery.search.DomainSearch; +import org.sleuthkit.autopsy.discovery.search.DomainSearchArtifactsRequest; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * SwingWorker to retrieve a list of artifacts for a specified type and domain. + */ +class ArtifactsWorker extends SwingWorker, Void> { + + private final BlackboardArtifact.ARTIFACT_TYPE artifactType; + private final static Logger logger = Logger.getLogger(ArtifactsWorker.class.getName()); + private final String domain; + + /** + * Construct a new ArtifactsWorker. + * + * @param artifactType The type of artifact being retrieved. + * @param domain The domain the artifacts should have as an attribute. + */ + ArtifactsWorker(BlackboardArtifact.ARTIFACT_TYPE artifactType, String domain) { + this.artifactType = artifactType; + this.domain = domain; + } + + @Override + protected List doInBackground() throws Exception { + if (artifactType != null && !StringUtils.isBlank(domain)) { + DomainSearch domainSearch = new DomainSearch(); + return domainSearch.getArtifacts(new DomainSearchArtifactsRequest(Case.getCurrentCase().getSleuthkitCase(), domain, artifactType)); + } + return new ArrayList<>(); + } + + @Override + protected void done() { + List listOfArtifacts = new ArrayList<>(); + if (!isCancelled()) { + try { + listOfArtifacts.addAll(get()); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Exception while trying to get list of artifacts for Domain details for artifact type: " + + artifactType.getDisplayName() + " and domain: " + domain, ex); + } catch (CancellationException ignored) { + //Worker was cancelled after previously finishing its background work, exception ignored to cut down on non-helpful logging + } + } + DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.ArtifactSearchResultEvent(artifactType, listOfArtifacts)); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties index 5fc4ff56e6..851045c71a 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties @@ -51,10 +51,12 @@ HashSetFilterPanel.hashSetCheckbox.text=Hash Set: PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences: DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show ObjectDetectedFilterPanel.text=Object Detected: -DetailsPanel.instancesList.border.title=Instances DateFilterPanel.mostRecentRadioButton.text=Only last: DateFilterPanel.dateFilterCheckBox.text=Date Filter: DomainSummaryPanel.activityLabel.text= DomainSummaryPanel.pagesLabel.text= DomainSummaryPanel.filesDownloadedLabel.text= DomainSummaryPanel.totalVisitsLabel.text= +FileDetailsPanel.instancesList.border.title=Instances +CookieDetailsPanel.jLabel1.text=Artifact: +CookieDetailsPanel.jLabel2.text= diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED index 7c2c5e6757..6c9f511ba6 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED @@ -1,3 +1,8 @@ +ArtifactsListPanel.dateColumn.name=Date/Time +ArtifactsListPanel.fileNameColumn.name=Name +ArtifactsListPanel.mimeTypeColumn.name=MIME Type +ArtifactsListPanel.titleColumn.name=Title +ArtifactsListPanel.value.noValue=No value available. ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected. CTL_OpenDiscoveryAction=Discovery DataSourceFilterPanel.error.text=At least one data source must be selected. @@ -126,13 +131,15 @@ HashSetFilterPanel.hashSetCheckbox.text=Hash Set: PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences: DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show ObjectDetectedFilterPanel.text=Object Detected: -DetailsPanel.instancesList.border.title=Instances DateFilterPanel.mostRecentRadioButton.text=Only last: DateFilterPanel.dateFilterCheckBox.text=Date Filter: DomainSummaryPanel.activityLabel.text= DomainSummaryPanel.pagesLabel.text= DomainSummaryPanel.filesDownloadedLabel.text= DomainSummaryPanel.totalVisitsLabel.text= +FileDetailsPanel.instancesList.border.title=Instances +CookieDetailsPanel.jLabel1.text=Artifact: +CookieDetailsPanel.jLabel2.text= VideoThumbnailPanel.bytes.text=bytes VideoThumbnailPanel.deleted.text=All instances of file are deleted. VideoThumbnailPanel.gigaBytes.text=GB diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form new file mode 100644 index 0000000000..2c7924e2a4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form @@ -0,0 +1,18 @@ + + +
+ + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java new file mode 100644 index 0000000000..81e4fcaddc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java @@ -0,0 +1,69 @@ +/* + * Autopsy + * + * 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.discovery.ui; + +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.corecomponents.DataContentPanel; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Details panel for displaying the collection of content viewers. + */ +final class ContentViewerDetailsPanel extends AbstractArtifactDetailsPanel { + + private static final long serialVersionUID = 1L; + private final DataContentPanel contentViewer = DataContentPanel.createInstance(); + + /** + * Creates new form ContentViewerDetailsPanel + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + ContentViewerDetailsPanel() { + initComponents(); + add(contentViewer); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setLayout(new java.awt.BorderLayout()); + }// //GEN-END:initComponents + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + @Override + public void setArtifact(BlackboardArtifact artifact) { + Node node = Node.EMPTY; + if (artifact != null) { + node = new BlackboardArtifactNode(artifact); + } + contentViewer.setNode(node); + } + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java index ab54df4341..065c989ddb 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DataSourceFilterPanel.java @@ -30,6 +30,7 @@ import javax.swing.JList; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.TskCoreException; @@ -45,6 +46,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form DataSourceFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DataSourceFilterPanel() { initComponents(); setUpDataSourceFilter(); @@ -109,6 +111,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel { private javax.swing.JScrollPane dataSourceScrollPane; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { dataSourceCheckbox.setSelected(selected); @@ -124,6 +127,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return dataSourceCheckbox; @@ -137,6 +141,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the data source filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpDataSourceFilter() { int count = 0; try { @@ -156,6 +161,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return dataSourceList; @@ -193,6 +199,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"DataSourceFilterPanel.error.text=At least one data source must be selected."}) @Override String checkForError() { @@ -202,6 +209,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel { return ""; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (dataSourceCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java index 528a5d1bc2..2fbe6610ff 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java @@ -33,6 +33,7 @@ import javax.swing.JSpinner; import javax.swing.event.ListSelectionListener; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.communications.Utils; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; /** @@ -48,6 +49,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { */ @NbBundle.Messages({"# {0} - timeZone", "DateFilterPanel.dateRange.text=Date Range ({0}):"}) + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DateFilterPanel() { initComponents(); rangeRadioButton.setText(Bundle.DateFilterPanel_dateRange_text(Utils.getUserPreferredZoneId().toString())); @@ -225,6 +227,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { endCheckBox.firePropertyChange("EndButtonChange", true, false); }//GEN-LAST:event_rangeRadioButtonStateChanged + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { dateFilterCheckBox.setSelected(selected); @@ -238,6 +241,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return dateFilterCheckBox; @@ -253,6 +257,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void addListeners(ActionListener actionListener, ListSelectionListener listListener) { dateFilterCheckBox.addActionListener(actionListener); @@ -274,6 +279,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { }); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void removeListeners() { for (ActionListener listener : dateFilterCheckBox.getActionListeners()) { @@ -302,6 +308,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"DateFilterPanel.invalidRange.text=Range or Only Last must be selected.", "DateFilterPanel.startOrEndNeeded.text=A start or end date must be specified to use the range filter.", "DateFilterPanel.startAfterEnd.text=Start date should be before the end date when both are enabled."}) @@ -320,6 +327,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { return ""; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (dateFilterCheckBox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java index f2f42bd113..e3ebba7a3d 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java @@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; import org.sleuthkit.autopsy.discovery.search.Group; @@ -99,6 +100,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { /** * Private constructor to construct a new DiscoveryDialog */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Messages("DiscoveryDialog.name.text=Discovery") private DiscoveryDialog() { super(WindowManager.getDefault().getMainWindow(), Bundle.DiscoveryDialog_name_text(), true); @@ -116,10 +118,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { @Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - SwingUtilities.invokeLater(() -> { - getSelectedFilterPanel().setLastGroupingAttributeType(groupByCombobox.getItemAt(groupByCombobox.getSelectedIndex())); - }); - + getSelectedFilterPanel().setLastGroupingAttributeType(groupByCombobox.getItemAt(groupByCombobox.getSelectedIndex())); } } }); @@ -127,9 +126,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { @Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - SwingUtilities.invokeLater(() -> { - getSelectedFilterPanel().setLastSortingMethod(orderByCombobox.getItemAt(orderByCombobox.getSelectedIndex())); - }); + getSelectedFilterPanel().setLastSortingMethod(orderByCombobox.getItemAt(orderByCombobox.getSelectedIndex())); } } }); @@ -137,9 +134,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { @Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - SwingUtilities.invokeLater(() -> { - getSelectedFilterPanel().setLastGroupSortingAlg(groupSortingComboBox.getItemAt(groupSortingComboBox.getSelectedIndex())); - }); + getSelectedFilterPanel().setLastGroupSortingAlg(groupSortingComboBox.getItemAt(groupSortingComboBox.getSelectedIndex())); } } }); @@ -151,6 +146,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { /** * Update the search settings to a default state. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void updateSearchSettings() { removeAllPanels(); imageFilterPanel = null; @@ -176,6 +172,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { /** * Set the type buttons to a default state where none are selected. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void unselectAllButtons() { imagesButton.setSelected(false); imagesButton.setEnabled(true); @@ -194,6 +191,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { /** * Private helper method to perform update of comboboxes update. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void updateComboBoxes() { // Set up the grouping attributes List groupingAttrs = new ArrayList<>(); @@ -230,6 +228,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { * * @return The panel that corresponds to the currently selected type. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private AbstractFiltersPanel getSelectedFilterPanel() { switch (type) { case IMAGE: @@ -251,6 +250,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { * * @param type The Type of GroupingAttribute to add. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void addTypeToGroupByComboBox(GroupingAttributeType type) { switch (type) { case FREQUENCY: @@ -282,7 +282,8 @@ final class DiscoveryDialog extends javax.swing.JDialog { /** * Validate the filter settings for File type filters. */ - synchronized void validateDialog() { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void validateDialog() { AbstractFiltersPanel panel = getSelectedFilterPanel(); if (panel != null) { panel.validateFields(); @@ -551,6 +552,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { /** * Helper method to remove all filter panels and their listeners */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void removeAllPanels() { if (imageFilterPanel != null) { remove(imageFilterPanel); @@ -635,6 +637,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { repaint(); }//GEN-LAST:event_domainsButtonActionPerformed + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public void dispose() { setVisible(false); @@ -643,6 +646,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { /** * Cancel the searchWorker if it exists. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void cancelSearch() { if (searchWorker != null) { searchWorker.cancel(true); @@ -656,6 +660,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { * @param error The error message to display, empty string if there is no * error. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setValid(String error) { if (StringUtils.isBlank(error)) { errorLabel.setText(""); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java index dce68e3a41..229c7510e3 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryThumbnailChildren.java @@ -27,6 +27,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.datamodel.AbstractFile; @@ -42,28 +43,27 @@ class DiscoveryThumbnailChildren extends Children.Keys { /* * Creates the list of thumbnails from the given list of AbstractFiles. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DiscoveryThumbnailChildren(List files) { super(false); - this.files = files; - } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override protected Node[] createNodes(AbstractFile t) { return new Node[]{new ThumbnailNode(t)}; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override protected void addNotify() { super.addNotify(); - Set thumbnails = new TreeSet<>((AbstractFile file1, AbstractFile file2) -> { int result = Long.compare(file1.getSize(), file2.getSize()); if (result == 0) { result = file1.getName().compareTo(file2.getName()); } - return result; }); thumbnails.addAll(files); @@ -75,10 +75,12 @@ class DiscoveryThumbnailChildren extends Children.Keys { */ static class ThumbnailNode extends FileNode { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ThumbnailNode(AbstractFile file) { super(file, false); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override protected Sheet createSheet() { Sheet sheet = super.createSheet(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form index 54630599ec..c84525148c 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.form @@ -1,6 +1,6 @@ -
+ diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java index 03a271dcdd..994f17c390 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java @@ -28,6 +28,7 @@ import java.beans.PropertyChangeListener; import java.util.List; import java.util.stream.Collectors; import javax.swing.JSplitPane; +import javax.swing.SwingUtilities; import javax.swing.plaf.basic.BasicSplitPaneDivider; import javax.swing.plaf.basic.BasicSplitPaneUI; import org.openide.util.NbBundle; @@ -40,6 +41,8 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; import org.sleuthkit.autopsy.discovery.search.SearchData.Type; import static org.sleuthkit.autopsy.discovery.search.SearchData.Type.DOMAIN; +import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; /** * Create a dialog for displaying the Discovery results. @@ -53,12 +56,12 @@ public final class DiscoveryTopComponent extends TopComponent { private static final long serialVersionUID = 1L; private static final String PREFERRED_ID = "DiscoveryTc"; // NON-NLS private static final int ANIMATION_INCREMENT = 30; - private volatile static int resultsAreaSize = 250; + private volatile static int previousDividerLocation = 250; private final GroupListPanel groupListPanel; - private final DetailsPanel detailsPanel; private final ResultsPanel resultsPanel; + private String selectedDomainTabName; private Type searchType; - private int dividerLocation = -1; + private int dividerLocation = JSplitPane.UNDEFINED_CONDITION; private SwingAnimator animator = null; /** @@ -70,10 +73,7 @@ public final class DiscoveryTopComponent extends TopComponent { setName(Bundle.DiscoveryTopComponent_name()); groupListPanel = new GroupListPanel(); resultsPanel = new ResultsPanel(); - detailsPanel = new DetailsPanel(); mainSplitPane.setLeftComponent(groupListPanel); - rightSplitPane.setTopComponent(resultsPanel); - rightSplitPane.setBottomComponent(detailsPanel); //set color of divider rightSplitPane.setUI(new BasicSplitPaneUI() { @Override @@ -87,14 +87,19 @@ public final class DiscoveryTopComponent extends TopComponent { public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equalsIgnoreCase(JSplitPane.DIVIDER_LOCATION_PROPERTY)) { //Only change the saved location when it was a manual change by the user and not the animation or the window opening initially - if ((animator == null || !animator.isRunning()) && evt.getNewValue() instanceof Integer - && ((int) evt.getNewValue() + 5) < (rightSplitPane.getHeight() - rightSplitPane.getDividerSize())) { - resultsAreaSize = (int) evt.getNewValue(); + if ((animator == null || !animator.isRunning()) + && evt.getNewValue() instanceof Integer + && evt.getOldValue() instanceof Integer + && ((int) evt.getNewValue() + 5) < (rightSplitPane.getHeight() - rightSplitPane.getDividerSize()) + && (JSplitPane.UNDEFINED_CONDITION != (int) evt.getNewValue()) + && ((int) evt.getOldValue() != JSplitPane.UNDEFINED_CONDITION)) { + previousDividerLocation = (int) evt.getNewValue(); } } } }); + rightSplitPane.setTopComponent(resultsPanel); } /** @@ -108,6 +113,7 @@ public final class DiscoveryTopComponent extends TopComponent { * @param ui The component which contains the split pane this divider is * in. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) BasicSplitPaneDividerImpl(BasicSplitPaneUI ui) { super(ui); this.setLayout(new BorderLayout()); @@ -129,11 +135,13 @@ public final class DiscoveryTopComponent extends TopComponent { /** * Reset the top component so it isn't displaying any results. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void resetTopComponent() { resultsPanel.resetResultViewer(); groupListPanel.resetGroupList(); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public void componentOpened() { super.componentOpened(); @@ -141,9 +149,9 @@ public final class DiscoveryTopComponent extends TopComponent { DiscoveryEventUtils.getDiscoveryEventBus().register(this); DiscoveryEventUtils.getDiscoveryEventBus().register(resultsPanel); DiscoveryEventUtils.getDiscoveryEventBus().register(groupListPanel); - DiscoveryEventUtils.getDiscoveryEventBus().register(detailsPanel); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override protected void componentClosed() { DiscoveryDialog.getDiscoveryDialogInstance().cancelSearch(); @@ -152,7 +160,11 @@ public final class DiscoveryTopComponent extends TopComponent { DiscoveryEventUtils.getDiscoveryEventBus().unregister(this); DiscoveryEventUtils.getDiscoveryEventBus().unregister(groupListPanel); DiscoveryEventUtils.getDiscoveryEventBus().unregister(resultsPanel); - DiscoveryEventUtils.getDiscoveryEventBus().unregister(detailsPanel); + DiscoveryEventUtils.getDiscoveryEventBus().unregister(rightSplitPane.getBottomComponent()); + if (rightSplitPane.getBottomComponent() instanceof DomainDetailsPanel) { + selectedDomainTabName = ((DomainDetailsPanel) rightSplitPane.getBottomComponent()).getSelectedTabName(); + } + rightSplitPane.setDividerLocation(JSplitPane.UNDEFINED_CONDITION); super.componentClosed(); } @@ -262,7 +274,7 @@ public final class DiscoveryTopComponent extends TopComponent { */ @Subscribe void handleDetailsVisibleEvent(DiscoveryEventUtils.DetailsVisibleEvent detailsVisibleEvent) { - if (resultsPanel.getActiveType() != DOMAIN) { + SwingUtilities.invokeLater(() -> { if (animator != null && animator.isRunning()) { animator.stop(); animator = null; @@ -274,7 +286,7 @@ public final class DiscoveryTopComponent extends TopComponent { animator = new SwingAnimator(new HideDetailsAreaCallback()); } animator.start(); - } + }); } /** @@ -289,12 +301,12 @@ public final class DiscoveryTopComponent extends TopComponent { "DiscoveryTopComponent.searchError.text=Error no type specified for search."}) @Subscribe void handleSearchStartedEvent(DiscoveryEventUtils.SearchStartedEvent searchStartedEvent) { - newSearchButton.setText(Bundle.DiscoveryTopComponent_cancelButton_text()); - progressMessageTextArea.setForeground(Color.red); - searchType = searchStartedEvent.getType(); - progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchInProgress_text(searchType.name())); - rightSplitPane.getComponent(1).setVisible(searchStartedEvent.getType() != DOMAIN); - rightSplitPane.getComponent(2).setVisible(searchStartedEvent.getType() != DOMAIN); + SwingUtilities.invokeLater(() -> { + newSearchButton.setText(Bundle.DiscoveryTopComponent_cancelButton_text()); + progressMessageTextArea.setForeground(Color.red); + searchType = searchStartedEvent.getType(); + progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchInProgress_text(searchType.name())); + }); } /** @@ -310,19 +322,52 @@ public final class DiscoveryTopComponent extends TopComponent { "DiscoveryTopComponent.domainSearch.text=Type: Domain", "DiscoveryTopComponent.additionalFilters.text=; "}) void handleSearchCompleteEvent(DiscoveryEventUtils.SearchCompleteEvent searchCompleteEvent) { - newSearchButton.setText(Bundle.DiscoveryTopComponent_newSearch_text()); - progressMessageTextArea.setForeground(Color.black); - String descriptionText = ""; - if (searchType == DOMAIN) { - //domain does not have a file type filter to add the type information so it is manually added - descriptionText = Bundle.DiscoveryTopComponent_domainSearch_text(); - if (!searchCompleteEvent.getFilters().isEmpty()) { - descriptionText += Bundle.DiscoveryTopComponent_additionalFilters_text(); + SwingUtilities.invokeLater(() -> { + newSearchButton.setText(Bundle.DiscoveryTopComponent_newSearch_text()); + progressMessageTextArea.setForeground(Color.black); + String descriptionText = ""; + if (searchType == DOMAIN) { + //domain does not have a file type filter to add the type information so it is manually added + descriptionText = Bundle.DiscoveryTopComponent_domainSearch_text(); + if (!searchCompleteEvent.getFilters().isEmpty()) { + descriptionText += Bundle.DiscoveryTopComponent_additionalFilters_text(); + } + selectedDomainTabName = validateLastSelectedType(searchCompleteEvent); + rightSplitPane.setBottomComponent(new DomainDetailsPanel(selectedDomainTabName)); + } else { + rightSplitPane.setBottomComponent(new FileDetailsPanel()); + } + DiscoveryEventUtils.getDiscoveryEventBus().register(rightSplitPane.getBottomComponent()); + descriptionText += searchCompleteEvent.getFilters().stream().map(AbstractFilter::getDesc).collect(Collectors.joining("; ")); + progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchComplete_text(descriptionText)); + progressMessageTextArea.setCaretPosition(0); + }); + } + + /** + * Get the name of the tab which was last selected unless the tab last + * selected would not be included in the types currently being displayed or + * was not previously set. + * + * @return The name of the tab which should be selected in the new + * DomainDetailsPanel. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private String validateLastSelectedType(DiscoveryEventUtils.SearchCompleteEvent searchCompleteEvent) { + String typeFilteredOn = selectedDomainTabName; + + for (AbstractFilter filter : searchCompleteEvent.getFilters()) { + if (filter instanceof ArtifactTypeFilter) { + for (ARTIFACT_TYPE type : ((ArtifactTypeFilter) filter).getTypes()) { + typeFilteredOn = type.getDisplayName(); + if (selectedDomainTabName == null || typeFilteredOn.equalsIgnoreCase(selectedDomainTabName)) { + break; + } + } + break; } } - descriptionText += searchCompleteEvent.getFilters().stream().map(AbstractFilter::getDesc).collect(Collectors.joining("; ")); - progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchComplete_text(descriptionText)); - progressMessageTextArea.setCaretPosition(0); + return typeFilteredOn; } /** @@ -334,9 +379,11 @@ public final class DiscoveryTopComponent extends TopComponent { @Messages({"DiscoveryTopComponent.searchCancelled.text=Search has been cancelled."}) @Subscribe void handleSearchCancelledEvent(DiscoveryEventUtils.SearchCancelledEvent searchCancelledEvent) { - newSearchButton.setText(Bundle.DiscoveryTopComponent_newSearch_text()); - progressMessageTextArea.setForeground(Color.red); - progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchCancelled_text()); + SwingUtilities.invokeLater(() -> { + newSearchButton.setText(Bundle.DiscoveryTopComponent_newSearch_text()); + progressMessageTextArea.setForeground(Color.red); + progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchCancelled_text()); + }); } @@ -345,16 +392,18 @@ public final class DiscoveryTopComponent extends TopComponent { */ private final class ShowDetailsAreaCallback implements SwingAnimatorCallback { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public void callback(Object caller) { dividerLocation -= ANIMATION_INCREMENT; repaint(); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public boolean hasTerminated() { - if (dividerLocation != JSplitPane.UNDEFINED_CONDITION && dividerLocation < resultsAreaSize) { - dividerLocation = resultsAreaSize; + if (dividerLocation != JSplitPane.UNDEFINED_CONDITION && dividerLocation < previousDividerLocation) { + dividerLocation = previousDividerLocation; animator = null; return true; } @@ -368,12 +417,14 @@ public final class DiscoveryTopComponent extends TopComponent { */ private final class HideDetailsAreaCallback implements SwingAnimatorCallback { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public void callback(Object caller) { dividerLocation += ANIMATION_INCREMENT; repaint(); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public boolean hasTerminated() { if (dividerLocation > rightSplitPane.getHeight() || dividerLocation == JSplitPane.UNDEFINED_CONDITION) { @@ -399,10 +450,11 @@ public final class DiscoveryTopComponent extends TopComponent { private static final long serialVersionUID = 1L; + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public void paintComponent(Graphics g) { if (animator != null && animator.isRunning() && (dividerLocation == JSplitPane.UNDEFINED_CONDITION - || (dividerLocation <= getHeight() && dividerLocation >= resultsAreaSize))) { + || (dividerLocation <= getHeight() && dividerLocation >= previousDividerLocation))) { setDividerLocation(dividerLocation); } super.paintComponent(g); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java index a1a15616e7..7b4287a0b6 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java @@ -51,6 +51,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import static org.sleuthkit.autopsy.coreutils.VideoUtils.getVideoFileInTempDir; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.discovery.search.ResultFile; @@ -173,6 +174,7 @@ final class DiscoveryUiUtils { * * @return True if the point is over the icon, false otherwise. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) static boolean isPointOnIcon(Component comp, Point point) { return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE; } @@ -186,6 +188,7 @@ final class DiscoveryUiUtils { * @param isDeletedLabel The label to set the icon and tooltip for. */ @NbBundle.Messages({"DiscoveryUiUtils.isDeleted.text=All instances of file are deleted."}) + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) static void setDeletedIcon(boolean isDeleted, javax.swing.JLabel isDeletedLabel) { if (isDeleted) { isDeletedLabel.setIcon(DELETED_ICON); @@ -203,6 +206,7 @@ final class DiscoveryUiUtils { * score of. * @param scoreLabel The label to set the icon and tooltip for. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) static void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) { switch (resultFile.getScore()) { case NOTABLE_SCORE: @@ -232,6 +236,7 @@ final class DiscoveryUiUtils { * Helper method to display an error message when the results of the * Discovery Top component may be incomplete. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete"}) static void displayErrorMessage(DiscoveryDialog dialog) { //check if modules run and assemble message diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentFilterPanel.java index 44e303ccc1..fe2dfe4b1f 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentFilterPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.discovery.ui; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchData; /** @@ -32,6 +33,7 @@ final class DocumentFilterPanel extends AbstractFiltersPanel { /** * Constructs a new DocumentFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DocumentFilterPanel() { super(); initComponents(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.form index 9329d6f976..7f61fbd6fb 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.form @@ -79,9 +79,6 @@ - - - @@ -91,6 +88,9 @@ + + + @@ -98,7 +98,6 @@ - @@ -108,6 +107,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.java index 08bdd70791..a0c37e8639 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DocumentPanel.java @@ -29,6 +29,7 @@ import javax.swing.JList; import javax.swing.ListCellRenderer; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.corecomponents.AutoWrappingJTextPane; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchData; /** @@ -43,6 +44,7 @@ class DocumentPanel extends javax.swing.JPanel implements ListCellRenderer list, DocumentWrapper value, int index, boolean isSelected, boolean cellHasFocus) { fileSizeLabel.setText(DiscoveryUiUtils.getFileSizeString(value.getResultFile().getFirstInstance().getSize())); @@ -180,6 +182,7 @@ class DocumentPanel extends javax.swing.JPanel implements ListCellRenderer getInstancesForSelected() { - synchronized (this) { - if (documentList.getSelectedIndex() == -1) { - return new ArrayList<>(); - } else { - return documentListModel.getElementAt(documentList.getSelectedIndex()).getResultFile().getAllInstances(); - } + if (documentList.getSelectedIndex() == -1) { + return new ArrayList<>(); + } else { + return documentListModel.getElementAt(documentList.getSelectedIndex()).getResultFile().getAllInstances(); } } @@ -120,9 +121,8 @@ final class DocumentPreviewViewer extends javax.swing.JPanel { * @param documentWrapper The object which contains the document preview * which will be displayed. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void addDocument(DocumentWrapper documentWrapper) { - synchronized (this) { - documentListModel.addElement(documentWrapper); - } + documentListModel.addElement(documentWrapper); } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.form new file mode 100644 index 0000000000..fbd55756e9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.form @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java new file mode 100644 index 0000000000..453785181c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java @@ -0,0 +1,192 @@ +/* + * Autopsy + * + * 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.discovery.ui; + +import org.sleuthkit.autopsy.contentviewers.artifactviewers.GeneralPurposeArtifactViewer; +import com.google.common.eventbus.Subscribe; +import java.util.logging.Level; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import org.sleuthkit.autopsy.contentviewers.artifactviewers.DefaultTableArtifactContentViewer; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * JPanel which should be used as a tab in the domain artifacts details area. + */ +final class DomainArtifactsTabPanel extends JPanel { + + private static final long serialVersionUID = 1L; + private final static Logger logger = Logger.getLogger(DomainArtifactsTabPanel.class.getName()); + private final ArtifactsListPanel listPanel; + private final BlackboardArtifact.ARTIFACT_TYPE artifactType; + private AbstractArtifactDetailsPanel rightPanel = null; + + private volatile ArtifactRetrievalStatus status = ArtifactRetrievalStatus.UNPOPULATED; + private final ListSelectionListener listener = new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent event) { + if (!event.getValueIsAdjusting()) { + rightPanel.setArtifact(listPanel.getSelectedArtifact()); + } + } + }; + + /** + * Creates new form CookiesPanel + * + * @param type The type of Artifact this tab is displaying information for. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + DomainArtifactsTabPanel(BlackboardArtifact.ARTIFACT_TYPE type) { + initComponents(); + this.artifactType = type; + listPanel = new ArtifactsListPanel(artifactType); + jSplitPane1.setLeftComponent(listPanel); + setRightComponent(); + listPanel.addSelectionListener(listener); + } + + /** + * Set the right component of the tab panel, which will display the details + * for the artifact. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void setRightComponent() { + switch (artifactType) { + case TSK_WEB_HISTORY: + case TSK_WEB_COOKIE: + case TSK_WEB_SEARCH_QUERY: + case TSK_WEB_BOOKMARK: + rightPanel = new GeneralPurposeArtifactViewer(); + break; + case TSK_WEB_DOWNLOAD: + case TSK_WEB_CACHE: + rightPanel = new ContentViewerDetailsPanel(); + break; + default: + rightPanel = new DefaultTableArtifactContentViewer(); + break; + } + if (rightPanel != null) { + jSplitPane1.setRightComponent(new JScrollPane(rightPanel)); + } + } + + /** + * Get the status of the panel which indicates if it is populated. + * + * @return The ArtifactRetrievalStatus of the panel. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + ArtifactRetrievalStatus getStatus() { + return status; + } + + /** + * Manually set the status of the panel. + * + * @param status The ArtifactRetrievalStatus of the panel. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void setStatus(ArtifactRetrievalStatus status) { + this.status = status; + if (status == ArtifactRetrievalStatus.UNPOPULATED && rightPanel != null) { + rightPanel.setArtifact(null); + } + } + + /** + * Handle the event which indicates the artifacts have been retrieved. + * + * @param artifactresultEvent The event which indicates the artifacts have + * been retrieved. + */ + @Subscribe + void handleArtifactSearchResultEvent(DiscoveryEventUtils.ArtifactSearchResultEvent artifactresultEvent) { + if (artifactType == artifactresultEvent.getArtifactType()) { + SwingUtilities.invokeLater(() -> { + listPanel.removeListSelectionListener(listener); + listPanel.addArtifacts(artifactresultEvent.getListOfArtifacts()); + status = ArtifactRetrievalStatus.POPULATED; + setEnabled(!listPanel.isEmpty()); + listPanel.addSelectionListener(listener); + listPanel.selectFirst(); + revalidate(); + repaint(); + try { + DiscoveryEventUtils.getDiscoveryEventBus().unregister(this); + } catch (IllegalArgumentException notRegistered) { + logger.log(Level.INFO, "Attempting to unregister tab which was not registered"); + // attempting to remove a tab that was never registered + } + }); + } + } + + /** + * Get the type of Artifact the panel exists for. + * + * @return The ARTIFACT_TYPE of the BlackboardArtifact being displayed. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + BlackboardArtifact.ARTIFACT_TYPE getArtifactType() { + return artifactType; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jSplitPane1 = new javax.swing.JSplitPane(); + + setMinimumSize(new java.awt.Dimension(0, 0)); + setPreferredSize(new java.awt.Dimension(0, 0)); + setLayout(new java.awt.BorderLayout()); + + jSplitPane1.setMinimumSize(new java.awt.Dimension(0, 0)); + jSplitPane1.setPreferredSize(new java.awt.Dimension(0, 0)); + add(jSplitPane1, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JSplitPane jSplitPane1; + // End of variables declaration//GEN-END:variables + + /** + * Enum to keep track of the populated state of this panel. + */ + enum ArtifactRetrievalStatus { + UNPOPULATED(), + POPULATING(), + POPULATED(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.form new file mode 100644 index 0000000000..b6e16aff08 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.form @@ -0,0 +1,46 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java new file mode 100644 index 0000000000..dd98d1c15d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java @@ -0,0 +1,186 @@ +/* + * Autopsy + * + * 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.discovery.ui; + +import com.google.common.eventbus.Subscribe; +import java.awt.Component; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.apache.commons.lang.StringUtils; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.autopsy.discovery.search.SearchData; + +/** + * Panel to display details area for domain discovery results. + * + */ +final class DomainDetailsPanel extends JPanel { + + private static final long serialVersionUID = 1L; + private ArtifactsWorker detailsWorker; + private String domain; + private String selectedTabName; + + /** + * Creates new form ArtifactDetailsPanel. + * + * @param selectedTabName The name of the tab to select initially. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + DomainDetailsPanel(String selectedTabName) { + initComponents(); + addArtifactTabs(selectedTabName); + } + + /** + * Add the tabs for each of the artifact types which we will be displaying. + * + * @param tabName The name of the tab to select initially. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void addArtifactTabs(String tabName) { + for (BlackboardArtifact.ARTIFACT_TYPE type : SearchData.Type.DOMAIN.getArtifactTypes()) { + jTabbedPane1.add(type.getDisplayName(), new DomainArtifactsTabPanel(type)); + } + selectedTabName = tabName; + selectTab(); + jTabbedPane1.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (jTabbedPane1.getSelectedIndex() >= 0) { + String newTabTitle = jTabbedPane1.getTitleAt(jTabbedPane1.getSelectedIndex()); + if (selectedTabName == null || !selectedTabName.equals(newTabTitle)) { + selectedTabName = newTabTitle; + runDomainWorker(); + } + } + } + }); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + /** + * Set the selected tab index to be the previously selected tab if a + * previously selected tab exists. + */ + private void selectTab() { + for (int i = 0; i < jTabbedPane1.getTabCount(); i++) { + if (!StringUtils.isBlank(selectedTabName) && selectedTabName.equals(jTabbedPane1.getTitleAt(i))) { + jTabbedPane1.setSelectedIndex(i); + return; + } + } + } + + /** + * Run the worker which retrieves the list of artifacts for the domain to + * populate the details area. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void runDomainWorker() { + Component selectedComponent = jTabbedPane1.getSelectedComponent(); + if (selectedComponent instanceof DomainArtifactsTabPanel) { + if (detailsWorker != null && !detailsWorker.isDone()) { + detailsWorker.cancel(true); + } + DomainArtifactsTabPanel selectedTab = (DomainArtifactsTabPanel) selectedComponent; + if (selectedTab.getStatus() == DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED) { + DiscoveryEventUtils.getDiscoveryEventBus().register(selectedTab); + selectedTab.setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATING); + detailsWorker = new ArtifactsWorker(selectedTab.getArtifactType(), domain); + detailsWorker.execute(); + } + } + } + + /** + * Populate the the details tabs. + * + * @param populateEvent The PopulateDomainTabsEvent which indicates which + * domain the details tabs should be populated for. + */ + @Subscribe + void handlePopulateDomainTabsEvent(DiscoveryEventUtils.PopulateDomainTabsEvent populateEvent) { + domain = populateEvent.getDomain(); + SwingUtilities.invokeLater(() -> { + resetTabsStatus(); + selectTab(); + runDomainWorker(); + if (StringUtils.isBlank(domain)) { + //send fade out event + DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.DetailsVisibleEvent(false)); + } else { + //send fade in event + DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.DetailsVisibleEvent(true)); + } + }); + } + + /** + * Private helper method to ensure tabs will re-populate after a new domain + * is selected. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void resetTabsStatus() { + for (Component comp : jTabbedPane1.getComponents()) { + if (comp instanceof DomainArtifactsTabPanel) { + ((DomainArtifactsTabPanel) comp).setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED); + } + } + } + + /** + * Get the name of the tab that was most recently selected. + * + * @return The name of the tab that was most recently selected. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + String getSelectedTabName() { + return selectedTabName; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jTabbedPane1 = new javax.swing.JTabbedPane(); + + setEnabled(false); + setMinimumSize(new java.awt.Dimension(0, 0)); + setPreferredSize(new java.awt.Dimension(0, 0)); + setLayout(new java.awt.BorderLayout()); + + jTabbedPane1.setMinimumSize(new java.awt.Dimension(0, 0)); + jTabbedPane1.setPreferredSize(new java.awt.Dimension(0, 0)); + add(jTabbedPane1, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTabbedPane jTabbedPane1; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java index bce5577054..3216110694 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.discovery.ui; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes; import org.sleuthkit.autopsy.discovery.search.ResultsSorter; import org.sleuthkit.autopsy.discovery.search.SearchData; @@ -34,6 +35,7 @@ public class DomainFilterPanel extends AbstractFiltersPanel { /** * Creates new form DomainFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public DomainFilterPanel() { super(); initComponents(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java index c6979663a7..74e0d7d0ba 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java @@ -30,6 +30,7 @@ import javax.swing.JComponent; import javax.swing.JList; import javax.swing.ListCellRenderer; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Class which displays a preview and details about a domain. @@ -43,6 +44,7 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< /** * Creates new form DomainPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DomainSummaryPanel() { initComponents(); domainNameLabel.setFont(domainNameLabel.getFont().deriveFont(domainNameLabel.getFont().getStyle(), domainNameLabel.getFont().getSize() + 6)); @@ -136,6 +138,7 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< private javax.swing.JLabel totalVisitsLabel; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"# {0} - startDate", "# {1} - endDate", "DomainSummaryPanel.activity.text=Activity: {0} to {1}", @@ -163,6 +166,7 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< return this; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public String getToolTipText(MouseEvent event) { if (event != null) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form index 22296c0178..9f1e3516b9 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form @@ -39,8 +39,6 @@ - -
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java index 593ae49949..66125e36d0 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java @@ -19,6 +19,9 @@ package org.sleuthkit.autopsy.discovery.ui; import javax.swing.DefaultListModel; +import javax.swing.event.ListSelectionListener; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; /** * A JPanel to display domain summaries. @@ -30,20 +33,20 @@ public class DomainSummaryViewer extends javax.swing.JPanel { private final DefaultListModel domainListModel = new DefaultListModel<>(); /** - * Clear the list of documents being displayed. + * Creates new form DomainSummaryPanel */ - void clearViewer() { - synchronized (this) { - domainListModel.removeAllElements(); - domainScrollPane.getVerticalScrollBar().setValue(0); - } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public DomainSummaryViewer() { + initComponents(); } /** - * Creates new form DomainSummaryPanel + * Clear the list of documents being displayed. */ - public DomainSummaryViewer() { - initComponents(); + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void clearViewer() { + domainListModel.removeAllElements(); + domainScrollPane.getVerticalScrollBar().setValue(0); } /** @@ -56,7 +59,7 @@ public class DomainSummaryViewer extends javax.swing.JPanel { private void initComponents() { domainScrollPane = new javax.swing.JScrollPane(); - javax.swing.JList domainList = new javax.swing.JList<>(); + domainList = new javax.swing.JList<>(); setLayout(new java.awt.BorderLayout()); @@ -69,6 +72,7 @@ public class DomainSummaryViewer extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JList domainList; private javax.swing.JScrollPane domainScrollPane; // End of variables declaration//GEN-END:variables @@ -78,9 +82,39 @@ public class DomainSummaryViewer extends javax.swing.JPanel { * @param domainWrapper The object which contains the domain summary which * will be displayed. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void addDomain(DomainWrapper domainWrapper) { - synchronized (this) { - domainListModel.addElement(domainWrapper); + domainListModel.addElement(domainWrapper); + } + + /** + * Send an event to perform the population of the domain details tabs to + * reflect the currently selected domain. Will populate the list with + * nothing when a domain is not used. + * + * @param useDomain If the currently selected domain should be used to + * retrieve a list. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void sendPopulateEvent(boolean useDomain) { + String domain = ""; + if (useDomain) { + if (domainList.getSelectedIndex() != -1) { + domain = domainListModel.getElementAt(domainList.getSelectedIndex()).getResultDomain().getDomain(); + } } + //send populateMesage + DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.PopulateDomainTabsEvent(domain)); + } + + /** + * Add a selection listener to the list of document previews being + * displayed. + * + * @param listener The ListSelectionListener to add to the selection model. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void addListSelectionListener(ListSelectionListener listener) { + domainList.getSelectionModel().addListSelectionListener(listener); } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DetailsPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/FileDetailsPanel.form similarity index 96% rename from Core/src/org/sleuthkit/autopsy/discovery/ui/DetailsPanel.form rename to Core/src/org/sleuthkit/autopsy/discovery/ui/FileDetailsPanel.form index bd3d8c5af9..1cac146780 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DetailsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/FileDetailsPanel.form @@ -105,8 +105,8 @@ - - + + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/FileDetailsPanel.java similarity index 82% rename from Core/src/org/sleuthkit/autopsy/discovery/ui/DetailsPanel.java rename to Core/src/org/sleuthkit/autopsy/discovery/ui/FileDetailsPanel.java index e302669278..ac7eb05f6d 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/FileDetailsPanel.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.corecomponents.DataContentPanel; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -48,7 +49,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Panel to display the details of the selected result. */ -final class DetailsPanel extends javax.swing.JPanel { +final class FileDetailsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; @@ -59,7 +60,8 @@ final class DetailsPanel extends javax.swing.JPanel { /** * Creates new form DetailsPanel. */ - DetailsPanel() { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + FileDetailsPanel() { initComponents(); dataContentPanel = DataContentPanel.createInstance(); detailsSplitPane.setBottomComponent(dataContentPanel); @@ -68,20 +70,18 @@ final class DetailsPanel extends javax.swing.JPanel { @Override public void mousePressed(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { - SwingUtilities.invokeLater(() -> { - instancesList.setSelectedIndex(instancesList.locationToIndex(e.getPoint())); - Set files = new HashSet<>(); - files.add(instancesList.getSelectedValue()); - JPopupMenu menu = new JPopupMenu(); - menu.add(new ViewContextAction(Bundle.ResultsPanel_viewFileInDir_name(), instancesList.getSelectedValue())); - menu.add(new ExternalViewerAction(Bundle.ResultsPanel_openInExternalViewer_name(), new FileNode(instancesList.getSelectedValue()))); - menu.add(ViewFileInTimelineAction.createViewFileAction(instancesList.getSelectedValue())); - menu.add(new DiscoveryExtractAction(files)); - menu.add(AddContentTagAction.getInstance().getMenuForContent(files)); - menu.add(DeleteFileContentTagAction.getInstance().getMenuForFiles(files)); - menu.add(AddContentToHashDbAction.getInstance().getMenuForFiles(files)); - menu.show(instancesList, e.getPoint().x, e.getPoint().y); - }); + instancesList.setSelectedIndex(instancesList.locationToIndex(e.getPoint())); + Set files = new HashSet<>(); + files.add(instancesList.getSelectedValue()); + JPopupMenu menu = new JPopupMenu(); + menu.add(new ViewContextAction(Bundle.ResultsPanel_viewFileInDir_name(), instancesList.getSelectedValue())); + menu.add(new ExternalViewerAction(Bundle.ResultsPanel_openInExternalViewer_name(), new FileNode(instancesList.getSelectedValue()))); + menu.add(ViewFileInTimelineAction.createViewFileAction(instancesList.getSelectedValue())); + menu.add(new DiscoveryExtractAction(files)); + menu.add(AddContentTagAction.getInstance().getMenuForContent(files)); + menu.add(DeleteFileContentTagAction.getInstance().getMenuForFiles(files)); + menu.add(AddContentToHashDbAction.getInstance().getMenuForFiles(files)); + menu.show(instancesList, e.getPoint().x, e.getPoint().y); } } }); @@ -89,14 +89,12 @@ final class DetailsPanel extends javax.swing.JPanel { @Override public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { - SwingUtilities.invokeLater(() -> { - AbstractFile file = getSelectedFile(); - if (file != null) { - dataContentPanel.setNode(new TableFilterNode(new FileNode(file), false)); - } else { - dataContentPanel.setNode(null); - } - }); + AbstractFile file = getSelectedFile(); + if (file != null) { + dataContentPanel.setNode(new TableFilterNode(new FileNode(file), false)); + } else { + dataContentPanel.setNode(null); + } } } }; @@ -112,7 +110,9 @@ final class DetailsPanel extends javax.swing.JPanel { */ @Subscribe void handleClearSelectionListener(DiscoveryEventUtils.ClearInstanceSelectionEvent clearEvent) { - instancesList.clearSelection(); + SwingUtilities.invokeLater(() -> { + instancesList.clearSelection(); + }); } /** @@ -122,9 +122,9 @@ final class DetailsPanel extends javax.swing.JPanel { * instances list should be populated */ @Subscribe - synchronized void handlePopulateInstancesListEvent(DiscoveryEventUtils.PopulateInstancesListEvent populateEvent) { + void handlePopulateInstancesListEvent(DiscoveryEventUtils.PopulateInstancesListEvent populateEvent) { + List files = populateEvent.getInstances(); SwingUtilities.invokeLater(() -> { - List files = populateEvent.getInstances(); if (files.isEmpty()) { //if there are no files currently remove the current items without removing listener to cause content viewer to reset instancesListModel.removeAllElements(); @@ -154,7 +154,8 @@ final class DetailsPanel extends javax.swing.JPanel { * * @return The AbstractFile which is currently selected. */ - synchronized AbstractFile getSelectedFile() { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + AbstractFile getSelectedFile() { if (instancesList.getSelectedIndex() == -1) { return null; } else { @@ -186,7 +187,7 @@ final class DetailsPanel extends javax.swing.JPanel { instancesScrollPane.setPreferredSize(new java.awt.Dimension(775, 60)); - instancesList.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(DetailsPanel.class, "DetailsPanel.instancesList.border.title"))); // NOI18N + instancesList.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(FileDetailsPanel.class, "FileDetailsPanel.instancesList.border.title"))); // NOI18N instancesList.setModel(instancesListModel); instancesList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); instancesList.setCellRenderer(new InstancesCellRenderer()); @@ -241,6 +242,7 @@ final class DetailsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/GroupListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/GroupListPanel.java index 714d5202f0..f4c9fab4a5 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/GroupListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/GroupListPanel.java @@ -31,6 +31,7 @@ import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey; @@ -56,6 +57,7 @@ final class GroupListPanel extends javax.swing.JPanel { /** * Creates new form GroupListPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) GroupListPanel() { initComponents(); } @@ -168,10 +170,9 @@ final class GroupListPanel extends javax.swing.JPanel { /** * Reset the group list to be empty. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void resetGroupList() { - SwingUtilities.invokeLater(() -> { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - }); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); groupKeyList.setListData(new GroupKey[0]); } @@ -211,6 +212,7 @@ final class GroupListPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public java.awt.Component getListCellRendererComponent( JList list, diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/HashSetFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/HashSetFilterPanel.java index 4698c43dc3..0257352a16 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/HashSetFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/HashSetFilterPanel.java @@ -27,6 +27,7 @@ import javax.swing.JLabel; import javax.swing.JList; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -43,6 +44,7 @@ final class HashSetFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form HashSetFilterPaenl. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) HashSetFilterPanel() { initComponents(); setUpHashFilter(); @@ -51,6 +53,7 @@ final class HashSetFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the hash filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpHashFilter() { int count = 0; try { @@ -123,6 +126,7 @@ final class HashSetFilterPanel extends AbstractDiscoveryFilterPanel { private javax.swing.JScrollPane hashSetScrollPane; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { boolean hasHashSets = hashSetList.getModel().getSize() > 0; @@ -140,6 +144,7 @@ final class HashSetFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return hashSetCheckbox; @@ -150,6 +155,7 @@ final class HashSetFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"HashSetFilterPanel.error.text=At least one hash set name must be selected."}) @Override String checkForError() { @@ -159,11 +165,13 @@ final class HashSetFilterPanel extends AbstractDiscoveryFilterPanel { return ""; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return hashSetList; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (hashSetCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageFilterPanel.java index 308cdd569b..4557b02d74 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageFilterPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.discovery.ui; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchData; /** @@ -32,6 +33,7 @@ final class ImageFilterPanel extends AbstractFiltersPanel { /** * Creates new form ImageFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ImageFilterPanel() { super(); initComponents(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.form index 46c5d3601f..2a52cb88d3 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.form @@ -84,7 +84,6 @@ - @@ -94,6 +93,7 @@ + @@ -101,9 +101,6 @@ - - - @@ -113,6 +110,9 @@ + + + @@ -120,7 +120,6 @@ - @@ -130,6 +129,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.java index d294fa866d..22ce4a0809 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailPanel.java @@ -28,6 +28,7 @@ import javax.swing.JComponent; import javax.swing.JList; import javax.swing.ListCellRenderer; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Class which displays a thumbnail and information for an image file. @@ -41,6 +42,7 @@ final class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellRe /** * Creates new form ImageThumbnailPanel */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ImageThumbnailPanel() { initComponents(); } @@ -129,6 +131,7 @@ final class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellRe private javax.swing.JLabel thumbnailLabel; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({ "# {0} - otherInstanceCount", "ImageThumbnailPanel.nameLabel.more.text= and {0} more", @@ -152,6 +155,7 @@ final class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellRe return this; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public String getToolTipText(MouseEvent event) { if (event != null) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailViewer.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailViewer.java index 273cca8023..cd9fd4909a 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailViewer.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ImageThumbnailViewer.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import javax.swing.DefaultListModel; import javax.swing.event.ListSelectionListener; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.datamodel.AbstractFile; /** @@ -37,6 +38,7 @@ final class ImageThumbnailViewer extends javax.swing.JPanel { /** * Creates new form ImageThumbnailViewer. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ImageThumbnailViewer() { initComponents(); @@ -77,6 +79,7 @@ final class ImageThumbnailViewer extends javax.swing.JPanel { * * @param listener The ListSelectionListener to add to the selection model. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void addListSelectionListener(ListSelectionListener listener) { thumbnailList.getSelectionModel().addListSelectionListener(listener); } @@ -88,24 +91,23 @@ final class ImageThumbnailViewer extends javax.swing.JPanel { * @return The list of AbstractFiles which are represented by the selected * image thumbnail. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) List getInstancesForSelected() { - synchronized (this) { - if (thumbnailList.getSelectedIndex() == -1) { - return new ArrayList<>(); - } else { - return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getAllInstances(); - } + if (thumbnailList.getSelectedIndex() == -1) { + return new ArrayList<>(); + } else { + return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getAllInstances(); } } /** * Clear the list of thumbnails being displayed. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void clearViewer() { - synchronized (this) { - thumbnailListModel.removeAllElements(); - thumbnailListScrollPane.getVerticalScrollBar().setValue(0); - } + thumbnailListModel.removeAllElements(); + thumbnailListScrollPane.getVerticalScrollBar().setValue(0); + } /** @@ -114,9 +116,8 @@ final class ImageThumbnailViewer extends javax.swing.JPanel { * @param thumbnailWrapper The object which contains the thumbnail which * will be displayed. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void addImage(ImageThumbnailWrapper thumbnailWrapper) { - synchronized (this) { - thumbnailListModel.addElement(thumbnailWrapper); - } + thumbnailListModel.addElement(thumbnailWrapper); } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/InterestingItemsFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/InterestingItemsFilterPanel.java index b77956937d..184ddb1d29 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/InterestingItemsFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/InterestingItemsFilterPanel.java @@ -27,6 +27,7 @@ import javax.swing.JLabel; import javax.swing.JList; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -43,6 +44,7 @@ final class InterestingItemsFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form InterestingItemsFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) InterestingItemsFilterPanel() { initComponents(); setUpInterestingItemsFilter(); @@ -51,6 +53,7 @@ final class InterestingItemsFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the interesting items filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpInterestingItemsFilter() { int count = 0; try { @@ -118,6 +121,7 @@ final class InterestingItemsFilterPanel extends AbstractDiscoveryFilterPanel { interestingItemsList.setEnabled(interestingItemsCheckbox.isSelected()); }//GEN-LAST:event_interestingItemsCheckboxActionPerformed + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { boolean hasInterestingItems = interestingItemsList.getModel().getSize() > 0; @@ -135,6 +139,7 @@ final class InterestingItemsFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return interestingItemsCheckbox; @@ -145,6 +150,7 @@ final class InterestingItemsFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"InterestingItemsFilterPanel.error.text=At least one interesting file set name must be selected."}) @Override String checkForError() { @@ -161,11 +167,13 @@ final class InterestingItemsFilterPanel extends AbstractDiscoveryFilterPanel { private javax.swing.JScrollPane interestingItemsScrollPane; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return interestingItemsList; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (interestingItemsCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java index eb1c1525c5..a6eb3de1c5 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java @@ -27,6 +27,7 @@ import javax.swing.JLabel; import javax.swing.JList; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -43,6 +44,7 @@ final class ObjectDetectedFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form ObjectDetectedFilter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ObjectDetectedFilterPanel() { initComponents(); setUpObjectFilter(); @@ -51,6 +53,7 @@ final class ObjectDetectedFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the object filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpObjectFilter() { int count = 0; try { @@ -129,6 +132,7 @@ final class ObjectDetectedFilterPanel extends AbstractDiscoveryFilterPanel { private javax.swing.JScrollPane objectsScrollPane; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { boolean hasObjects = objectsList.getModel().getSize() > 0; @@ -146,6 +150,7 @@ final class ObjectDetectedFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return objectsCheckbox; @@ -155,6 +160,8 @@ final class ObjectDetectedFilterPanel extends AbstractDiscoveryFilterPanel { JLabel getAdditionalLabel() { return null; } + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"ObjectDetectedFilterPanel.error.text=At least one object type name must be selected."}) @Override String checkForError() { @@ -164,11 +171,13 @@ final class ObjectDetectedFilterPanel extends AbstractDiscoveryFilterPanel { return ""; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return objectsList; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (objectsCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/OpenDiscoveryAction.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/OpenDiscoveryAction.java index 33228a6a9b..ec767c4c83 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/OpenDiscoveryAction.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/OpenDiscoveryAction.java @@ -31,6 +31,7 @@ import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Class to open the Discovery dialog. Allows the user to run searches and see @@ -76,6 +77,7 @@ public final class OpenDiscoveryAction extends CallableSystemAction implements P * * @return The toolbar button */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public Component getToolbarPresenter() { ImageIcon icon = new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/discovery-icon-24.png")); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ParentFolderFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ParentFolderFilterPanel.java index a2a0d9615f..2a77c28873 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ParentFolderFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ParentFolderFilterPanel.java @@ -26,6 +26,7 @@ import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ParentSearchTerm; @@ -41,6 +42,7 @@ final class ParentFolderFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form ParentFolderFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ParentFolderFilterPanel() { initComponents(); setUpParentPathFilter(); @@ -49,6 +51,7 @@ final class ParentFolderFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the parent path filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpParentPathFilter() { fullRadioButton.setSelected(true); includeRadioButton.setSelected(true); @@ -239,6 +242,7 @@ final class ParentFolderFilterPanel extends AbstractDiscoveryFilterPanel { private javax.swing.JRadioButton substringRadioButton; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { parentCheckbox.setSelected(selected); @@ -270,16 +274,19 @@ final class ParentFolderFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return parentCheckbox; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JLabel getAdditionalLabel() { return parentLabel; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"ParentFolderFilterPanel.error.text=At least one parent path must be entered."}) @Override String checkForError() { @@ -290,6 +297,7 @@ final class ParentFolderFilterPanel extends AbstractDiscoveryFilterPanel { return ""; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) /** * Utility method to get the parent path objects out of the JList. * @@ -303,11 +311,13 @@ final class ParentFolderFilterPanel extends AbstractDiscoveryFilterPanel { return results; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return parentList; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (parentCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/PastOccurrencesFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/PastOccurrencesFilterPanel.java index b19c793556..bedd0a24c8 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/PastOccurrencesFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/PastOccurrencesFilterPanel.java @@ -24,6 +24,7 @@ import javax.swing.JLabel; import javax.swing.JList; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.AbstractFilter; import org.sleuthkit.autopsy.discovery.search.SearchData; import org.sleuthkit.autopsy.discovery.search.SearchData.Frequency; @@ -41,6 +42,7 @@ final class PastOccurrencesFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form PastOccurrencesFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) PastOccurrencesFilterPanel(Type type) { initComponents(); this.type = type; @@ -101,6 +103,7 @@ final class PastOccurrencesFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the frequency filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpFrequencyFilter() { int count = 0; DefaultListModel frequencyListModel = (DefaultListModel) crFrequencyList.getModel(); @@ -126,6 +129,7 @@ final class PastOccurrencesFilterPanel extends AbstractDiscoveryFilterPanel { private javax.swing.JCheckBox pastOccurrencesCheckbox; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { boolean canBeFilteredOn = type != Type.DOMAIN || CentralRepository.isEnabled(); @@ -144,6 +148,7 @@ final class PastOccurrencesFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return pastOccurrencesCheckbox; @@ -154,6 +159,7 @@ final class PastOccurrencesFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @NbBundle.Messages({"PastOccurrencesFilterPanel.error.text=At least one value in the past occurrence filter must be selected."}) @Override String checkForError() { @@ -163,11 +169,13 @@ final class PastOccurrencesFilterPanel extends AbstractDiscoveryFilterPanel { return ""; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return crFrequencyList; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (pastOccurrencesCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java index c077f65f22..1132311e29 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java @@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey; @@ -85,6 +86,7 @@ final class ResultsPanel extends javax.swing.JPanel { */ @Messages({"ResultsPanel.viewFileInDir.name=View File in Directory", "ResultsPanel.openInExternalViewer.name=Open in External Viewer"}) + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ResultsPanel() { initComponents(); imageThumbnailViewer = new ImageThumbnailViewer(); @@ -125,9 +127,14 @@ final class ResultsPanel extends javax.swing.JPanel { } } }); - //JIRA-TODO 6307 Add listener for domainSummaryViewer when 6782, 6773, and the other details area related stories are done + domainSummaryViewer.addListSelectionListener((e) -> { + if (resultType == SearchData.Type.DOMAIN) { + domainSummaryViewer.sendPopulateEvent(!e.getValueIsAdjusting()); + } + }); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) SearchData.Type getActiveType() { return resultType; } @@ -139,6 +146,7 @@ final class ResultsPanel extends javax.swing.JPanel { * @return The list of AbstractFiles which are represented by the item * selected in the results viewer area. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private List getInstancesForSelected() { if (null != resultType) { switch (resultType) { @@ -174,12 +182,12 @@ final class ResultsPanel extends javax.swing.JPanel { */ @Subscribe void handlePageRetrievedEvent(DiscoveryEventUtils.PageRetrievedEvent pageRetrievedEvent) { + //send populateMesage + if (pageRetrievedEvent.getType() != DOMAIN) { + DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.PopulateInstancesListEvent(getInstancesForSelected())); + } + currentPage = pageRetrievedEvent.getPageNumber(); SwingUtilities.invokeLater(() -> { - //send populateMesage - if (pageRetrievedEvent.getType() != DOMAIN) { - DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.PopulateInstancesListEvent(getInstancesForSelected())); - } - currentPage = pageRetrievedEvent.getPageNumber(); updateControls(); resetResultViewer(); if (null != pageRetrievedEvent.getType()) { @@ -206,10 +214,9 @@ final class ResultsPanel extends javax.swing.JPanel { } resultsViewerPanel.revalidate(); resultsViewerPanel.repaint(); - } - ); + }); } - + @Subscribe void handleCancelBackgroundTasksEvent(DiscoveryEventUtils.CancelBackgroundTasksEvent cancelEvent) { for (SwingWorker thumbWorker : resultContentWorkers) { @@ -217,7 +224,6 @@ final class ResultsPanel extends javax.swing.JPanel { thumbWorker.cancel(true); } } - resultContentWorkers.clear(); } @@ -225,7 +231,8 @@ final class ResultsPanel extends javax.swing.JPanel { * Reset the result viewer and any associate workers to a default empty * state. */ - synchronized void resetResultViewer() { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void resetResultViewer() { resultsViewerPanel.remove(imageThumbnailViewer); resultsViewerPanel.remove(videoThumbnailViewer); resultsViewerPanel.remove(documentPreviewViewer); @@ -250,7 +257,8 @@ final class ResultsPanel extends javax.swing.JPanel { * * @param results The list of ResultFiles to populate the video viewer with. */ - synchronized void populateVideoViewer(List results) { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void populateVideoViewer(List results) { for (Result result : results) { VideoThumbnailWorker thumbWorker = new VideoThumbnailWorker((ResultFile) result); thumbWorker.execute(); @@ -265,7 +273,8 @@ final class ResultsPanel extends javax.swing.JPanel { * * @param results The list of ResultFiles to populate the image viewer with. */ - synchronized void populateImageViewer(List results) { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void populateImageViewer(List results) { for (Result result : results) { ImageThumbnailWorker thumbWorker = new ImageThumbnailWorker((ResultFile) result); thumbWorker.execute(); @@ -281,7 +290,8 @@ final class ResultsPanel extends javax.swing.JPanel { * @param results The list of ResultFiles to populate the document viewer * with. */ - synchronized void populateDocumentViewer(List results) { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void populateDocumentViewer(List results) { for (Result result : results) { DocumentPreviewWorker documentWorker = new DocumentPreviewWorker((ResultFile) result); documentWorker.execute(); @@ -297,7 +307,8 @@ final class ResultsPanel extends javax.swing.JPanel { * @param results The list of ResultDomains to populate the domain summary * viewer with. */ - synchronized void populateDomainViewer(List results) { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + void populateDomainViewer(List results) { SleuthkitCase currentCase; try { currentCase = Case.getCurrentCaseThrows().getSleuthkitCase(); @@ -322,14 +333,14 @@ final class ResultsPanel extends javax.swing.JPanel { */ @Subscribe void handleGroupSelectedEvent(DiscoveryEventUtils.GroupSelectedEvent groupSelectedEvent) { + searchFilters = groupSelectedEvent.getFilters(); + groupingAttribute = groupSelectedEvent.getGroupingAttr(); + groupSort = groupSelectedEvent.getGroupSort(); + fileSortMethod = groupSelectedEvent.getResultSort(); + selectedGroupKey = groupSelectedEvent.getGroupKey(); + resultType = groupSelectedEvent.getResultType(); + groupSize = groupSelectedEvent.getGroupSize(); SwingUtilities.invokeLater(() -> { - searchFilters = groupSelectedEvent.getFilters(); - groupingAttribute = groupSelectedEvent.getGroupingAttr(); - groupSort = groupSelectedEvent.getGroupSort(); - fileSortMethod = groupSelectedEvent.getResultSort(); - selectedGroupKey = groupSelectedEvent.getGroupKey(); - resultType = groupSelectedEvent.getResultType(); - groupSize = groupSelectedEvent.getGroupSize(); resetResultViewer(); setPage(0); }); @@ -343,9 +354,9 @@ final class ResultsPanel extends javax.swing.JPanel { */ @Subscribe void handleNoResultsEvent(DiscoveryEventUtils.NoResultsEvent noResultsEvent) { + groupSize = 0; + currentPage = 0; SwingUtilities.invokeLater(() -> { - groupSize = 0; - currentPage = 0; updateControls(); videoThumbnailViewer.clearViewer(); imageThumbnailViewer.clearViewer(); @@ -374,37 +385,33 @@ final class ResultsPanel extends javax.swing.JPanel { * @param startingEntry The index of the first file in the group to include * in this page. */ - @Subscribe - private synchronized void setPage(int startingEntry - ) { + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void setPage(int startingEntry) { int pageSize = pageSizeComboBox.getItemAt(pageSizeComboBox.getSelectedIndex()); - synchronized (this) { - if (pageWorker != null && !pageWorker.isDone()) { - pageWorker.cancel(true); - } - CentralRepository centralRepo = null; - if (CentralRepository.isEnabled()) { - try { - centralRepo = CentralRepository.getInstance(); - } catch (CentralRepoException ex) { - centralRepo = null; - logger.log(Level.SEVERE, "Error loading central repository database, no central repository options will be available for Discovery", ex); - } - } - if (groupSize != 0) { - pageWorker = new PageWorker(searchFilters, groupingAttribute, groupSort, fileSortMethod, selectedGroupKey, startingEntry, pageSize, resultType, centralRepo); - pageWorker.execute(); - } else { - SwingUtilities.invokeLater(() -> { - pageSizeComboBox.setEnabled(true); - }); + if (pageWorker != null && !pageWorker.isDone()) { + pageWorker.cancel(true); + } + CentralRepository centralRepo = null; + if (CentralRepository.isEnabled()) { + try { + centralRepo = CentralRepository.getInstance(); + } catch (CentralRepoException ex) { + centralRepo = null; + logger.log(Level.SEVERE, "Error loading central repository database, no central repository options will be available for Discovery", ex); } } + if (groupSize != 0) { + pageWorker = new PageWorker(searchFilters, groupingAttribute, groupSort, fileSortMethod, selectedGroupKey, startingEntry, pageSize, resultType, centralRepo); + pageWorker.execute(); + } else { + pageSizeComboBox.setEnabled(true); + } } /** * Enable the paging controls based on what exists in the page. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Messages({"# {0} - currentPage", "# {1} - totalPages", "ResultsPanel.currentPage.displayValue=Page: {0} of {1}"}) @@ -678,6 +685,7 @@ final class ResultsPanel extends javax.swing.JPanel { /** * Disable all the paging controls. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void disablePagingControls() { nextPageButton.setEnabled(false); previousPageButton.setEnabled(false); @@ -708,6 +716,7 @@ final class ResultsPanel extends javax.swing.JPanel { * @param file The ResultFile which represents the video file thumbnails * are being retrieved for. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) VideoThumbnailWorker(ResultFile file) { thumbnailWrapper = new VideoThumbnailsWrapper(file); videoThumbnailViewer.addVideo(thumbnailWrapper); @@ -746,6 +755,7 @@ final class ResultsPanel extends javax.swing.JPanel { * @param file The ResultFile which represents the image file thumbnails * are being retrieved for. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ImageThumbnailWorker(ResultFile file) { thumbnailWrapper = new ImageThumbnailWrapper(file); imageThumbnailViewer.addImage(thumbnailWrapper); @@ -788,6 +798,7 @@ final class ResultsPanel extends javax.swing.JPanel { * @param file The ResultFile which represents the document file a * preview is being retrieved for. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DocumentPreviewWorker(ResultFile file) { documentWrapper = new DocumentWrapper(file); documentPreviewViewer.addDocument(documentWrapper); @@ -836,6 +847,7 @@ final class ResultsPanel extends javax.swing.JPanel { * @param file The ResultFile which represents the domain attribute the * preview is being retrieved for. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DomainThumbnailWorker(SleuthkitCase caseDb, ResultDomain domain) { this.caseDb = caseDb; domainWrapper = new DomainWrapper(domain); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsSplitPaneDivider.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsSplitPaneDivider.java index cffb2dac7f..87c42fbc39 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsSplitPaneDivider.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsSplitPaneDivider.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.discovery.ui; import java.awt.Cursor; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; @@ -32,6 +33,7 @@ final class ResultsSplitPaneDivider extends javax.swing.JPanel { /** * Creates new form LabeledSplitPaneDivider. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) ResultsSplitPaneDivider() { initComponents(); } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/SizeFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/SizeFilterPanel.java index 0585750aa4..e037f1f8ee 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/SizeFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/SizeFilterPanel.java @@ -26,6 +26,7 @@ import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchData; import org.sleuthkit.autopsy.discovery.search.SearchData.FileSize; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; @@ -42,6 +43,7 @@ final class SizeFilterPanel extends AbstractDiscoveryFilterPanel { * * @param type The type of result being searched for. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) SizeFilterPanel(SearchData.Type type) { initComponents(); setUpSizeFilter(type); @@ -109,6 +111,7 @@ final class SizeFilterPanel extends AbstractDiscoveryFilterPanel { private javax.swing.JScrollPane sizeScrollPane; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { sizeCheckbox.setSelected(selected); @@ -124,6 +127,7 @@ final class SizeFilterPanel extends AbstractDiscoveryFilterPanel { } } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return sizeCheckbox; @@ -137,6 +141,7 @@ final class SizeFilterPanel extends AbstractDiscoveryFilterPanel { /** * Initialize the file size filter. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setUpSizeFilter(SearchData.Type fileType) { int count = 0; DefaultListModel sizeListModel = (DefaultListModel) sizeList.getModel(); @@ -169,6 +174,7 @@ final class SizeFilterPanel extends AbstractDiscoveryFilterPanel { @NbBundle.Messages({"SizeFilterPanel.error.text=At least one size must be selected."}) @Override + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) String checkForError() { if (sizeCheckbox.isSelected() && sizeList.getSelectedValuesList().isEmpty()) { return Bundle.SizeFilterPanel_error_text(); @@ -177,11 +183,13 @@ final class SizeFilterPanel extends AbstractDiscoveryFilterPanel { } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JList getList() { return sizeList; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (sizeCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/UserCreatedFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/UserCreatedFilterPanel.java index d65596633a..10d976f7fe 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/UserCreatedFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/UserCreatedFilterPanel.java @@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.discovery.search.AbstractFilter; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; /** @@ -34,6 +35,7 @@ final class UserCreatedFilterPanel extends AbstractDiscoveryFilterPanel { /** * Creates new form UserCreatedFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) UserCreatedFilterPanel() { initComponents(); } @@ -69,11 +71,13 @@ final class UserCreatedFilterPanel extends AbstractDiscoveryFilterPanel { ); }// //GEN-END:initComponents + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void configurePanel(boolean selected, int[] indicesSelected) { userCreatedCheckbox.setSelected(selected); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override JCheckBox getCheckbox() { return userCreatedCheckbox; @@ -99,6 +103,7 @@ final class UserCreatedFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override AbstractFilter getFilter() { if (userCreatedCheckbox.isSelected()) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoFilterPanel.java index 82b0d030bc..ca96c217c4 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoFilterPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.discovery.ui; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.SearchData; /** @@ -32,6 +33,7 @@ final class VideoFilterPanel extends AbstractFiltersPanel { /** * Creates new form VideoFilterPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) VideoFilterPanel() { super(); initComponents(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailPanel.java index 7a62937582..161e6a0770 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailPanel.java @@ -32,6 +32,7 @@ import javax.swing.JLabel; import javax.swing.JList; import javax.swing.ListCellRenderer; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Class which displays thumbnails and information for a video file. @@ -47,6 +48,7 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe /** * Creates new form VideoThumbnailPanel. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) VideoThumbnailPanel() { initComponents(); this.setFocusable(true); @@ -58,6 +60,7 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe * @param thumbnailWrapper The object which contains the video thumbnails to * add. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void addThumbnails(VideoThumbnailsWrapper thumbnailWrapper) { imagePanel.removeAll(); GridBagConstraints gridBagConstraints = new GridBagConstraints(); @@ -164,6 +167,7 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe private javax.swing.JLabel scoreLabel; // End of variables declaration//GEN-END:variables + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Messages({ "# {0} - otherInstanceCount", "VideoThumbnailPanel.nameLabel.more.text= and {0} more", @@ -231,6 +235,7 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe return Bundle.VideoThumbnailPanel_sizeLabel_text(size, units); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override public String getToolTipText(MouseEvent event) { if (event != null) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailViewer.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailViewer.java index 8824e6f5d8..f2c250344d 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailViewer.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/VideoThumbnailViewer.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import javax.swing.DefaultListModel; import javax.swing.event.ListSelectionListener; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.datamodel.AbstractFile; /** @@ -36,6 +37,7 @@ final class VideoThumbnailViewer extends javax.swing.JPanel { /** * Creates new form VideoThumbnailViewer. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) VideoThumbnailViewer() { initComponents(); } @@ -45,6 +47,7 @@ final class VideoThumbnailViewer extends javax.swing.JPanel { * * @param listener The ListSelectionListener to add to the selection model. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void addListSelectionListener(ListSelectionListener listener) { thumbnailList.getSelectionModel().addListSelectionListener(listener); } @@ -56,24 +59,22 @@ final class VideoThumbnailViewer extends javax.swing.JPanel { * @return The list of AbstractFiles which are represented by the selected * Video thumbnails. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) List getInstancesForSelected() { - synchronized (this) { - if (thumbnailList.getSelectedIndex() == -1) { - return new ArrayList<>(); - } else { - return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getAllInstances(); - } + if (thumbnailList.getSelectedIndex() == -1) { + return new ArrayList<>(); + } else { + return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getAllInstances(); } } /** * Clear the list of thumbnails being displayed. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void clearViewer() { - synchronized (this) { - thumbnailListModel.removeAllElements(); - thumbnailListScrollPane.getVerticalScrollBar().setValue(0); - } + thumbnailListModel.removeAllElements(); + thumbnailListScrollPane.getVerticalScrollBar().setValue(0); } /** @@ -82,10 +83,9 @@ final class VideoThumbnailViewer extends javax.swing.JPanel { * @param thumbnailWrapper The object which contains the thumbnails which * will be displayed. */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void addVideo(VideoThumbnailsWrapper thumbnailWrapper) { - synchronized (this) { - thumbnailListModel.addElement(thumbnailWrapper); - } + thumbnailListModel.addElement(thumbnailWrapper); } /** diff --git a/Core/src/org/sleuthkit/autopsy/guicomponeontutils/AbstractCheckboxListItem.java b/Core/src/org/sleuthkit/autopsy/guicomponeontutils/AbstractCheckboxListItem.java new file mode 100755 index 0000000000..df0a00888f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/guicomponeontutils/AbstractCheckboxListItem.java @@ -0,0 +1,51 @@ +/* + * 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.guicomponeontutils; + +import javax.swing.Icon; +import org.sleuthkit.autopsy.guiutils.CheckBoxJList; + +/** + * An abstract implementation of CheckBoxJList.CheckboxListItem so that + * implementing classes have default implementation. + */ +public abstract class AbstractCheckboxListItem implements CheckBoxJList.CheckboxListItem { + + private boolean checked = false; + + @Override + public boolean isChecked() { + return checked; + } + + @Override + public void setChecked(boolean checked) { + this.checked = checked; + } + + @Override + public boolean hasIcon() { + return false; + } + + @Override + public Icon getIcon() { + return null; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java b/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java index ab17187c4b..f59db1259f 100755 --- a/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java @@ -32,8 +32,10 @@ import javax.swing.ListSelectionModel; /** * A JList that renders the list items as check boxes. + * + * @param An object that implements CheckboxListItem */ -final class CheckBoxJList extends JList { +public final class CheckBoxJList extends JList { private static final long serialVersionUID = 1L; @@ -42,7 +44,7 @@ final class CheckBoxJList extends JLis * a checkbox in CheckBoxJList. * */ - interface CheckboxListItem { + public interface CheckboxListItem { /** * Returns the checkbox state. @@ -83,7 +85,7 @@ final class CheckBoxJList extends JLis /** * Construct a new JCheckBoxList. */ - CheckBoxJList() { + public CheckBoxJList() { initalize(); } @@ -134,12 +136,15 @@ final class CheckBoxJList extends JLis checkbox.setSelected(value.isChecked()); checkbox.setBackground(list.getBackground()); checkbox.setEnabled(list.isEnabled()); + checkbox.setOpaque(list.isOpaque()); label.setText(value.getDisplayName()); label.setEnabled(list.isEnabled()); + label.setOpaque(list.isOpaque()); if (value.hasIcon()) { label.setIcon(value.getIcon()); } + setOpaque(list.isOpaque()); setEnabled(list.isEnabled()); return this; } diff --git a/Core/src/org/sleuthkit/autopsy/images/yara_32.png b/Core/src/org/sleuthkit/autopsy/images/yara_32.png new file mode 100755 index 0000000000..9fdb23052a Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/yara_32.png differ diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED index bf61ad9be0..cd35320f71 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED @@ -1,6 +1,6 @@ DATExtractor_process_message=Processing DJI DAT file: %s DATFileExtractor_Extractor_Name=DAT File Extractor -DroneIngestModule_Description=Analyzes files generated by drones. -DroneIngestModule_Name=Drone Analyzer +DroneIngestModule_Description=Analyzes files generated by some DJI drones. +DroneIngestModule_Name=DJI Drone Analyzer # {0} - AbstractFileName DroneIngestModule_process_start=Started {0} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java index 1213ffedd5..0e8f7107a5 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java @@ -33,8 +33,8 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; public class DroneIngestModuleFactory extends IngestModuleFactoryAdapter { @Messages({ - "DroneIngestModule_Name=Drone Analyzer", - "DroneIngestModule_Description=Analyzes files generated by drones." + "DroneIngestModule_Name=DJI Drone Analyzer", + "DroneIngestModule_Description=Analyzes files generated by some DJI drones." }) /** diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties index 0036d4dd6f..15698d736c 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties @@ -1,3 +1,4 @@ ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}. ILeappAnalyzerIngestModule.processing.file=Processing file {0} -ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} \ No newline at end of file +ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} +ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED index b4f350f478..b4e8226a91 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED @@ -8,6 +8,7 @@ ILeappAnalyzerIngestModule.iLeapp.cancelled=iLeapp run was canceled ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}. ILeappAnalyzerIngestModule.processing.file=Processing file {0} ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} +ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem ILeappAnalyzerIngestModule.report.name=iLeapp Html Report ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows. ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java index f859919bae..abec90e6c0 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java @@ -18,8 +18,10 @@ */ package org.sleuthkit.autopsy.modules.ileappanalyzer; +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -32,14 +34,17 @@ import java.util.Locale; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.io.FilenameUtils; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.getCurrentCase; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; @@ -50,6 +55,7 @@ import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskCoreException; /** @@ -61,7 +67,9 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { private static final String MODULE_NAME = ILeappAnalyzerModuleFactory.getModuleName(); private static final String ILEAPP = "iLeapp"; //NON-NLS + private static final String ILEAPP_FS = "fs_"; //NON-NLS private static final String ILEAPP_EXECUTABLE = "ileapp.exe";//NON-NLS + private static final String ILEAPP_PATHS_FILE = "iLeapp_paths.txt"; //NON-NLS private File iLeappExecutable; @@ -87,7 +95,7 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { try { iLeappFileProcessor = new ILeappFileProcessor(); - } catch (IOException | IngestModuleException ex) { + } catch (IOException | IngestModuleException | NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.ILeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex); } @@ -112,58 +120,49 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { @Override public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) { - if (!(context.getDataSource() instanceof LocalFilesDataSource)) { - return ProcessResult.OK; + Case currentCase = Case.getCurrentCase(); + Path tempOutputPath = Paths.get(currentCase.getTempDirectory(), ILEAPP, ILEAPP_FS + dataSource.getId()); + try { + Files.createDirectories(tempOutputPath); + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", tempOutputPath.toString()), ex); + return ProcessResult.ERROR; + } + + List iLeappPathsToProcess = new ArrayList<>(); + ProcessBuilder iLeappCommand = buildiLeappListCommand(tempOutputPath); + try { + int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true)); + if (result != 0) { + logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program getting file paths to search for result is %d", result)); + return ProcessResult.ERROR; + } + iLeappPathsToProcess = loadIleappPathFile(tempOutputPath); + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program getting file paths to search"), ex); + return ProcessResult.ERROR; } statusHelper.progress(Bundle.ILeappAnalyzerIngestModule_starting_iLeapp(), 0); - List iLeappFilesToProcess = findiLeappFilesToProcess(dataSource); + List iLeappFilesToProcess = new ArrayList<>(); - statusHelper.switchToDeterminate(iLeappFilesToProcess.size()); + if (!(context.getDataSource() instanceof LocalFilesDataSource)) { + extractFilesFromImage(dataSource, iLeappPathsToProcess, tempOutputPath); + statusHelper.switchToDeterminate(iLeappFilesToProcess.size()); + processILeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString()); + } else { + iLeappFilesToProcess = findiLeappFilesToProcess(dataSource); + statusHelper.switchToDeterminate(iLeappFilesToProcess.size()); - Integer filesProcessedCount = 0; - - Case currentCase = Case.getCurrentCase(); - for (AbstractFile iLeappFile : iLeappFilesToProcess) { - - String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS - Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ILEAPP, currentTime); - try { - Files.createDirectories(moduleOutputPath); - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", moduleOutputPath.toString()), ex); - return ProcessResult.ERROR; + Integer filesProcessedCount = 0; + for (AbstractFile iLeappFile : iLeappFilesToProcess) { + processILeappFile(dataSource, currentCase, statusHelper, filesProcessedCount, iLeappFile); + filesProcessedCount++; } - - statusHelper.progress(NbBundle.getMessage(this.getClass(), "ILeappAnalyzerIngestModule.processing.file", iLeappFile.getName()), filesProcessedCount); - ProcessBuilder iLeappCommand = buildiLeappCommand(moduleOutputPath, iLeappFile.getLocalAbsPath(), iLeappFile.getNameExtension()); - try { - int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true)); - if (result != 0) { - // ignore if there is an error and continue to try and process the next file if there is one - continue; - } - - addILeappReportToReports(moduleOutputPath, currentCase); - - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program against file %s", iLeappFile.getLocalAbsPath()), ex); - return ProcessResult.ERROR; - } - - if (context.dataSourceIngestIsCancelled()) { - logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS - return ProcessResult.OK; - } - - ProcessResult fileProcessorResult = iLeappFileProcessor.processFiles(dataSource, moduleOutputPath, iLeappFile); - - if (fileProcessorResult == ProcessResult.ERROR) { - return ProcessResult.ERROR; - } - - filesProcessedCount++; + // Process the logical image as a fs in iLeapp to make sure this is not a logical fs that was added + extractFilesFromImage(dataSource, iLeappPathsToProcess, tempOutputPath); + processILeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString()); } IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, @@ -173,6 +172,99 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { return ProcessResult.OK; } + /** + * Process each tar/zip file that is found in a logical image that contains xLeapp data + * @param dataSource Datasource where the file has been found + * @param currentCase current case + * @param statusHelper Progress bar for messages to show user + * @param filesProcessedCount count that is incremented for progress bar + * @param iLeappFile abstract file that will be processed + */ + private void processILeappFile(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, int filesProcessedCount, + AbstractFile iLeappFile) { + String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS + Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ILEAPP, currentTime); + try { + Files.createDirectories(moduleOutputPath); + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", moduleOutputPath.toString()), ex); + return; + } + + statusHelper.progress(NbBundle.getMessage(this.getClass(), "ILeappAnalyzerIngestModule.processing.file", iLeappFile.getName()), filesProcessedCount); + ProcessBuilder iLeappCommand = buildiLeappCommand(moduleOutputPath, iLeappFile.getLocalAbsPath(), iLeappFile.getNameExtension()); + try { + int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true)); + if (result != 0) { + logger.log(Level.WARNING, String.format("Error when trying to execute iLeapp program getting file paths to search for result is %d", result)); + return; + } + + addILeappReportToReports(moduleOutputPath, currentCase); + + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program against file %s", iLeappFile.getLocalAbsPath()), ex); + return; + } + + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS + return; + } + + ProcessResult fileProcessorResult = iLeappFileProcessor.processFiles(dataSource, moduleOutputPath, iLeappFile); + + if (fileProcessorResult == ProcessResult.ERROR) { + return; + } + } + + /** + * Process extracted files from a disk image using xLeapp + * @param dataSource Datasource where the file has been found + * @param currentCase current case + * @param statusHelper Progress bar for messages to show user + * @param directoryToProcess + */ + private void processILeappFs(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, String directoryToProcess) { + String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS + Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ILEAPP, currentTime); + try { + Files.createDirectories(moduleOutputPath); + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", moduleOutputPath.toString()), ex); + return; + } + + statusHelper.progress(NbBundle.getMessage(this.getClass(), "ILeappAnalyzerIngestModule.processing.filesystem")); + ProcessBuilder iLeappCommand = buildiLeappCommand(moduleOutputPath, directoryToProcess, "fs"); + try { + int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true)); + if (result != 0) { + logger.log(Level.WARNING, String.format("Error when trying to execute iLeapp program getting file paths to search for result is %d", result)); + return; + } + + addILeappReportToReports(moduleOutputPath, currentCase); + + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program against file system"), ex); + return; + } + + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS + return; + } + + ProcessResult fileProcessorResult = iLeappFileProcessor.processFileSystem(dataSource, moduleOutputPath); + + if (fileProcessorResult == ProcessResult.ERROR) { + return; + } + + } + /** * Find the files that will be processed by the iLeapp program * @@ -197,17 +289,24 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { List iLeappFilesToProcess = new ArrayList<>(); for (AbstractFile iLeappFile : iLeappFiles) { if (((iLeappFile.getLocalAbsPath() != null) - && (!iLeappFile.getNameExtension().isEmpty() && (!iLeappFile.isVirtual()))) - && ((iLeappFile.getName().toLowerCase().contains(".zip") || (iLeappFile.getName().toLowerCase().contains(".tar"))) - || iLeappFile.getName().toLowerCase().contains(".tgz"))) { - iLeappFilesToProcess.add(iLeappFile); - + && (!iLeappFile.getNameExtension().isEmpty() && (!iLeappFile.isVirtual()))) + && ((iLeappFile.getName().toLowerCase().contains(".zip") || (iLeappFile.getName().toLowerCase().contains(".tar"))) + || iLeappFile.getName().toLowerCase().contains(".tgz"))) { + iLeappFilesToProcess.add(iLeappFile); + } } return iLeappFilesToProcess; } + /** + * Build the command to run xLeapp + * @param moduleOutputPath output path for xLeapp + * @param sourceFilePath path where the xLeapp file is + * @param iLeappFileSystem type of file to process tar/zip/fs + * @return process to run + */ private ProcessBuilder buildiLeappCommand(Path moduleOutputPath, String sourceFilePath, String iLeappFileSystemType) { ProcessBuilder processBuilder = buildProcessWithRunAsInvoker( @@ -221,10 +320,26 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { return processBuilder; } + /** + * Command to run xLeapp using the path option + * @param moduleOutputPath path where the file paths output will reside + * @return process to run + */ + private ProcessBuilder buildiLeappListCommand(Path moduleOutputPath) { + + ProcessBuilder processBuilder = buildProcessWithRunAsInvoker( + "\"" + iLeappExecutable + "\"", //NON-NLS + "-p" + ); + processBuilder.redirectError(moduleOutputPath.resolve("iLeapp_paths_error.txt").toFile()); //NON-NLS + processBuilder.redirectOutput(moduleOutputPath.resolve("iLeapp_paths.txt").toFile()); //NON-NLS + return processBuilder; + } + static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) { ProcessBuilder processBuilder = new ProcessBuilder(commandLine); /* - * Add an environment variable to force log2timeline/psort to run with + * Add an environment variable to force iLeapp to run with * the same permissions Autopsy uses. */ processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS @@ -248,13 +363,18 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { private void addILeappReportToReports(Path iLeappOutputDir, Case currentCase) { List allIndexFiles = new ArrayList<>(); - try (Stream walk = Files.walk(iLeappOutputDir)) { + try (Stream walk = Files.walk(iLeappOutputDir)) { allIndexFiles = walk.map(x -> x.toString()) .filter(f -> f.toLowerCase().endsWith("index.html")).collect(Collectors.toList()); if (!allIndexFiles.isEmpty()) { - currentCase.addReport(allIndexFiles.get(0), MODULE_NAME, Bundle.ILeappAnalyzerIngestModule_report_name()); + // Check for existance of directory that holds report data if does not exist then report contains no data + String filePath = FilenameUtils.getFullPathNoEndSeparator(allIndexFiles.get(0)); + File dataFilesDir = new File(Paths.get(filePath, "_TSV Exports").toString()); + if (dataFilesDir.exists()) { + currentCase.addReport(allIndexFiles.get(0), MODULE_NAME, Bundle.ILeappAnalyzerIngestModule_report_name()); + } } } catch (IOException | UncheckedIOException | TskCoreException ex) { @@ -264,4 +384,129 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { } + /* + * Reads the iLeapp paths file to get the paths that we want to extract + * + * @param moduleOutputPath path where the file paths output will reside + */ + private List loadIleappPathFile(Path moduleOutputPath) throws FileNotFoundException, IOException { + List iLeappPathsToProcess = new ArrayList<>(); + + Path filePath = Paths.get(moduleOutputPath.toString(), ILEAPP_PATHS_FILE); + + try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toString()))) { + String line = reader.readLine(); + while (line != null) { + if (line.contains("path list generation") || line.length() < 2) { + line = reader.readLine(); + continue; + } + iLeappPathsToProcess.add(line.trim()); + line = reader.readLine(); + } + } + + return iLeappPathsToProcess; + } + + /** + * Extract files from a disk image to process with xLeapp + * @param dataSource Datasource of the image + * @param iLeappPathsToProcess List of paths to extract content from + * @param moduleOutputPath path to write content to + */ + private void extractFilesFromImage(Content dataSource, List iLeappPathsToProcess, Path moduleOutputPath) { + FileManager fileManager = getCurrentCase().getServices().getFileManager(); + + for (String fullFilePath : iLeappPathsToProcess) { + + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS + break; + } + + String ffp = fullFilePath.replaceAll("\\*", "%"); + ffp = FilenameUtils.normalize(ffp, true); + String fileName = FilenameUtils.getName(ffp); + String filePath = FilenameUtils.getPath(ffp); + + List iLeappFiles = new ArrayList<>(); + try { + if (filePath.isEmpty()) { + iLeappFiles = fileManager.findFiles(dataSource, fileName); //NON-NLS + } else { + iLeappFiles = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "No files found to process"); //NON-NLS + return; + } + + for (AbstractFile iLeappFile : iLeappFiles) { + Path parentPath = Paths.get(moduleOutputPath.toString(), iLeappFile.getParentPath()); + File fileParentPath = new File(parentPath.toString()); + + extractFileToOutput(dataSource, iLeappFile, fileParentPath, parentPath); + } + } + } + + /** + * Create path and file from datasource in temp + * @param dataSource datasource of the image + * @param iLeappFile abstract file to write out + * @param fileParentPath parent file path + * @param parentPath parent file + */ + private void extractFileToOutput(Content dataSource, AbstractFile iLeappFile, File fileParentPath, Path parentPath) { + if (fileParentPath.exists()) { + if (!iLeappFile.isDir()) { + writeiLeappFile(dataSource, iLeappFile, fileParentPath.toString()); + } else { + try { + Files.createDirectories(Paths.get(parentPath.toString(), iLeappFile.getName())); + } catch (IOException ex) { + logger.log(Level.INFO, String.format("Error creating iLeapp output directory %s", parentPath.toString()), ex); + } + } + } else { + try { + Files.createDirectories(parentPath); + } catch (IOException ex) { + logger.log(Level.INFO, String.format("Error creating iLeapp output directory %s", parentPath.toString()), ex); + } + if (!iLeappFile.isDir()) { + writeiLeappFile(dataSource, iLeappFile, fileParentPath.toString()); + } else { + try { + Files.createDirectories(Paths.get(parentPath.toString(), iLeappFile.getName())); + } catch (IOException ex) { + logger.log(Level.INFO, String.format("Error creating iLeapp output directory %s", parentPath.toString()), ex); + } + } + } + } + + /** + * Write out file to output + * @param dataSource datasource of disk image + * @param iLeappFile acstract file to write out + * @param parentPath path to write file to + */ + private void writeiLeappFile(Content dataSource, AbstractFile iLeappFile, String parentPath) { + String fileName = iLeappFile.getName().replace(":", "-"); + if (!fileName.matches(".") && !fileName.matches("..") && !fileName.toLowerCase().endsWith("-slack")) { + Path filePath = Paths.get(parentPath, fileName); + File localFile = new File(filePath.toString()); + try { + ContentUtils.writeToFile(iLeappFile, localFile, context::dataSourceIngestIsCancelled); + } catch (ReadContentInputStream.ReadContentInputStreamException ex) { + logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).", + iLeappFile.getName(), iLeappFile.getId()), ex); //NON-NLS + } catch (IOException ex) { + logger.log(Level.WARNING, String.format("Error writing file local file '%s' (id=%d).", + filePath.toString(), iLeappFile.getId()), ex); //NON-NLS + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java index cb5ab516a6..058b0aa28d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java @@ -44,6 +44,7 @@ import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; @@ -76,12 +77,16 @@ public final class ILeappFileProcessor { private final Map tsvFileArtifactComments; private final Map>> tsvFileAttributes; - public ILeappFileProcessor() throws IOException, IngestModuleException { + Blackboard blkBoard; + + public ILeappFileProcessor() throws IOException, IngestModuleException, NoCurrentCaseException { this.tsvFiles = new HashMap<>(); this.tsvFileArtifacts = new HashMap<>(); this.tsvFileArtifactComments = new HashMap<>(); this.tsvFileAttributes = new HashMap<>(); + blkBoard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); + configExtractor(); loadConfigFile(); @@ -110,6 +115,19 @@ public final class ILeappFileProcessor { return ProcessResult.OK; } + public ProcessResult processFileSystem(Content dataSource, Path moduleOutputPath) { + + try { + List iLeappTsvOutputFiles = findTsvFiles(moduleOutputPath); + processiLeappFiles(iLeappTsvOutputFiles, dataSource); + } catch (IOException | IngestModuleException ex) { + logger.log(Level.SEVERE, String.format("Error trying to process iLeapp output files in directory %s. ", moduleOutputPath.toString()), ex); //NON-NLS + return ProcessResult.ERROR; + } + + return ProcessResult.OK; + } + /** * Find the tsv files in the iLeapp output directory and match them to files * we know we want to process and return the list to process those files. @@ -124,7 +142,7 @@ public final class ILeappFileProcessor { .filter(f -> f.toLowerCase().endsWith(".tsv")).collect(Collectors.toList()); for (String tsvFile : allTsvFiles) { - if (tsvFiles.containsKey(FilenameUtils.getName(tsvFile))) { + if (tsvFiles.containsKey(FilenameUtils.getName(tsvFile.toLowerCase()))) { foundTsvFiles.add(tsvFile); } } @@ -160,7 +178,41 @@ public final class ILeappFileProcessor { processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, iLeappImageFile); } catch (TskCoreException ex) { - // check this + throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex); + } + } + + } + + if (!bbartifacts.isEmpty()) { + postArtifacts(bbartifacts); + } + + } + + /** + * Process the iLeapp files that were found that match the xml mapping file + * + * @param iLeappFilesToProcess List of files to process + * @param iLeappImageFile Abstract file to create artifact for + * + * @throws FileNotFoundException + * @throws IOException + */ + private void processiLeappFiles(List iLeappFilesToProcess, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException { + List bbartifacts = new ArrayList<>(); + + for (String iLeappFileName : iLeappFilesToProcess) { + String fileName = FilenameUtils.getName(iLeappFileName); + File iLeappFile = new File(iLeappFileName); + if (tsvFileAttributes.containsKey(fileName)) { + List> attrList = tsvFileAttributes.get(fileName); + try { + BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName)); + + processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, dataSource); + + } catch (TskCoreException ex) { throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex); } } @@ -174,7 +226,8 @@ public final class ILeappFileProcessor { } private void processFile(File iLeappFile, List> attrList, String fileName, BlackboardArtifact.Type artifactType, - List bbartifacts, AbstractFile iLeappImageFile) throws FileNotFoundException, IOException, IngestModuleException { + List bbartifacts, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException, + TskCoreException { try (BufferedReader reader = new BufferedReader(new FileReader(iLeappFile))) { String line = reader.readLine(); // Check first line, if it is null then no heading so nothing to match to, close and go to next file. @@ -183,8 +236,8 @@ public final class ILeappFileProcessor { line = reader.readLine(); while (line != null) { Collection bbattributes = processReadLine(line, columnNumberToProcess, fileName); - if (!bbattributes.isEmpty()) { - BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), iLeappImageFile, bbattributes); + if (!bbattributes.isEmpty() && !blkBoard.artifactExists(dataSource, BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactType.getTypeID()), bbattributes)) { + BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes); if (bbartifact != null) { bbartifacts.add(bbartifact); } @@ -234,8 +287,8 @@ public final class ILeappFileProcessor { } - private void checkAttributeType(Collection bbattributes, String attrType, String[] columnValues, Integer columnNumber, BlackboardAttribute.Type attributeType, - String fileName) { + private void checkAttributeType(Collection bbattributes, String attrType, String[] columnValues, Integer columnNumber, BlackboardAttribute.Type attributeType, + String fileName) { if (attrType.matches("STRING")) { bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, columnValues[columnNumber])); } else if (attrType.matches("INTEGER")) { @@ -340,7 +393,7 @@ public final class ILeappFileProcessor { for (int i = 0; i < nlist.getLength(); i++) { NamedNodeMap nnm = nlist.item(i).getAttributes(); - tsvFiles.put(nnm.getNamedItem("filename").getNodeValue(), nnm.getNamedItem("description").getNodeValue()); + tsvFiles.put(nnm.getNamedItem("filename").getNodeValue().toLowerCase(), nnm.getNamedItem("description").getNodeValue()); } @@ -393,19 +446,20 @@ public final class ILeappFileProcessor { } } - /** - * Generic method for creating a blackboard artifact with attributes - * - * @param type is a blackboard.artifact_type enum to determine - * which type the artifact should be - * @param abstractFile is the AbstractFile object that needs to have the - * artifact added for it - * @param bbattributes is the collection of blackboard attributes that - * need to be added to the artifact after the - * artifact has been created - * - * @return The newly-created artifact, or null on error - */ + + /** + * Generic method for creating a blackboard artifact with attributes + * + * @param type is a blackboard.artifact_type enum to determine which + * type the artifact should be + * @param abstractFile is the AbstractFile object that needs to have the + * artifact added for it + * @param bbattributes is the collection of blackboard attributes that need + * to be added to the artifact after the artifact has + * been created + * + * @return The newly-created artifact, or null on error + */ private BlackboardArtifact createArtifactWithAttributes(int type, AbstractFile abstractFile, Collection bbattributes) { try { BlackboardArtifact bbart = abstractFile.newArtifact(type); @@ -417,6 +471,30 @@ public final class ILeappFileProcessor { return null; } + /** + * Generic method for creating a blackboard artifact with attributes + * + * @param type is a blackboard.artifact_type enum to determine which + * type the artifact should be + * @param datasource is the Content object that needs to have the artifact + * added for it + * @param bbattributes is the collection of blackboard attributes that need + * to be added to the artifact after the artifact has + * been created + * + * @return The newly-created artifact, or null on error + */ + private BlackboardArtifact createArtifactWithAttributes(int type, Content dataSource, Collection bbattributes) { + try { + BlackboardArtifact bbart = dataSource.newArtifact(type); + bbart.addAttributes(bbattributes); + return bbart; + } catch (TskException ex) { + logger.log(Level.WARNING, Bundle.ILeappFileProcessor_error_creating_new_artifacts(), ex); //NON-NLS + } + return null; + } + /** * Method to post a list of BlackboardArtifacts to the blackboard. * diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml index 0959cbdcc4..a4169395aa 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml @@ -47,6 +47,15 @@ + + + + + + + + + @@ -120,6 +129,13 @@ + + + + + + + @@ -189,6 +205,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -207,15 +253,61 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -248,6 +340,19 @@ + + + + + + + + + + + + + @@ -288,6 +393,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -302,6 +437,18 @@ + + + + + + + + + + + + @@ -317,6 +464,18 @@ + + + + + + + + + + + + @@ -433,6 +592,102 @@ --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/yara/Bundle.properties-MERGED new file mode 100755 index 0000000000..8f898ba127 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/Bundle.properties-MERGED @@ -0,0 +1,5 @@ +Yara_Module_Description=With the YARA ingest module you use YARA rule files to search files for textual or binary patterns. +Yara_Module_Name=YARA +YaraIngestModule_no_ruleSets=Unable to run YARA ingest, list of YARA rule sets was empty. +YaraIngestModule_windows_error_msg=The YARA ingest module is only available on 64bit Windows. +YaraIngestModule_yarac_not_found=Unable to compile YARA rules files. Unable to find executable at. diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java new file mode 100755 index 0000000000..4b71a62351 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java @@ -0,0 +1,258 @@ +/* + * 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.modules.yara; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.openide.modules.InstalledFileLocator; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ExecUtil; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSetManager; +import org.sleuthkit.autopsy.yara.YaraJNIWrapper; +import org.sleuthkit.autopsy.yara.YaraWrapperException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_YARA_HIT; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_RULE; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Methods for scanning files for yara rule matches. + */ +final class YaraIngestHelper { + + private static final String YARA_DIR = "yara"; + private static final String YARA_C_EXE = "yarac64.exe"; + private static final String MODULE_NAME = YaraIngestModuleFactory.getModuleName(); + + private YaraIngestHelper() { + } + + /** + * Uses the yarac tool to compile the rules in the given rule sets. + * + * @param ruleSetNames List of names of the selected rule sets. + * @param tempDir Path of the directory to put the compiled rule files. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + static void compileRules(List ruleSetNames, Path outputDir) throws IngestModuleException { + if (ruleSetNames == null || ruleSetNames.isEmpty()) { + throw new IngestModule.IngestModuleException(Bundle.YaraIngestModule_no_ruleSets()); + } + + // Find javac + File exeFile = InstalledFileLocator.getDefault().locate( + Paths.get(YARA_DIR, YARA_C_EXE).toString(), + YaraIngestModule.class.getPackage().getName(), false); + + if (exeFile == null) { + throw new IngestModuleException(Bundle.YaraIngestModule_yarac_not_found()); + } + + for (RuleSet set : getRuleSetsForNames(ruleSetNames)) { + compileRuleSet(set, outputDir, exeFile); + } + } + + /** + * Scan the given AbstractFile for yara rule matches from the rule sets in + * the given directory creating a blackboard artifact for each matching + * rule. + * + * The baseDirectory should contain a series of directories one for each + * rule set. + * + * @param file The file to scan. + * @param baseRuleSetDirectory Base directory for the compiled rule sets. + * + * @throws TskCoreException + */ + static List scanFileForMatches(AbstractFile file, File baseRuleSetDirectory, byte[] fileData, int fileDataSize, int timeout) throws TskCoreException, YaraWrapperException { + List artifacts = new ArrayList<>(); + + File[] ruleSetDirectories = baseRuleSetDirectory.listFiles(); + for (File ruleSetDirectory : ruleSetDirectories) { + + List ruleMatches = YaraIngestHelper.scanFileForMatches(fileData, fileDataSize, ruleSetDirectory, timeout); + if (!ruleMatches.isEmpty()) { + artifacts.addAll(YaraIngestHelper.createArtifact(file, ruleSetDirectory.getName(), ruleMatches)); + } + } + + return artifacts; + } + + /** + * + * @param file The Abstract File being processed. + * @param baseRuleSetDirectory Base directory of the compiled rule sets. + * @param localFile Local copy of file. + * @param timeout Yara file scan timeout in seconds. + * + * @return + * + * @throws TskCoreException + * @throws YaraWrapperException + */ + static List scanFileForMatches(AbstractFile file, File baseRuleSetDirectory, File localFile, int timeout) throws TskCoreException, YaraWrapperException { + List artifacts = new ArrayList<>(); + + File[] ruleSetDirectories = baseRuleSetDirectory.listFiles(); + for (File ruleSetDirectory : ruleSetDirectories) { + List ruleMatches = YaraIngestHelper.scanFileForMatch(localFile, ruleSetDirectory, timeout); + if (!ruleMatches.isEmpty()) { + artifacts.addAll(YaraIngestHelper.createArtifact(file, ruleSetDirectory.getName(), ruleMatches)); + } + } + + return artifacts; + } + + /** + * Scan the given file byte array for rule matches using the YaraJNIWrapper + * API. + * + * @param fileBytes + * @param ruleSetDirectory + * + * @return List of rules that match from the given file from the given rule + * set. Empty list is returned if no matches where found. + * + * @throws TskCoreException + */ + private static List scanFileForMatches(byte[] fileBytes, int fileSize, File ruleSetDirectory, int timeout) throws YaraWrapperException { + List matchingRules = new ArrayList<>(); + + File[] ruleSetCompiledFileList = ruleSetDirectory.listFiles(); + + for (File ruleFile : ruleSetCompiledFileList) { + matchingRules.addAll(YaraJNIWrapper.findRuleMatch(ruleFile.getAbsolutePath(), fileBytes, fileSize, timeout)); + } + + return matchingRules; + } + + private static List scanFileForMatch(File scanFile, File ruleSetDirectory, int timeout) throws YaraWrapperException { + List matchingRules = new ArrayList<>(); + + File[] ruleSetCompiledFileList = ruleSetDirectory.listFiles(); + + for (File ruleFile : ruleSetCompiledFileList) { + matchingRules.addAll(YaraJNIWrapper.findRuleMatchFile(ruleFile.getAbsolutePath(), scanFile.getAbsolutePath(), timeout)); + } + + return matchingRules; + } + + /** + * Create a list of Blackboard Artifacts, one for each matching rule. + * + * @param abstractFile File to add artifact to. + * @param ruleSetName Name rule set with matching rule. + * @param matchingRules Matching rule. + * + * @return List of artifacts or empty list if none were found. + * + * @throws TskCoreException + */ + private static List createArtifact(AbstractFile abstractFile, String ruleSetName, List matchingRules) throws TskCoreException { + List artifacts = new ArrayList<>(); + for (String rule : matchingRules) { + BlackboardArtifact artifact = abstractFile.newArtifact(TSK_YARA_HIT); + List attributes = new ArrayList<>(); + + attributes.add(new BlackboardAttribute(TSK_SET_NAME, MODULE_NAME, ruleSetName)); + attributes.add(new BlackboardAttribute(TSK_RULE, MODULE_NAME, rule)); + + artifact.addAttributes(attributes); + artifacts.add(artifact); + } + return artifacts; + } + + @NbBundle.Messages({ + "YaraIngestModule_yarac_not_found=Unable to compile YARA rules files. Unable to find executable at.", + "YaraIngestModule_no_ruleSets=Unable to run YARA ingest, list of YARA rule sets was empty." + }) + + /** + * Compiles the rule files in the given rule set. + * + * The compiled rule files are created in outputDir\RuleSetName. + * + * @param set RuleSet for which to compile files. + * @param outputDir Output directory for the compiled rule files. + * @param yarac yarac executeable file. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + static private void compileRuleSet(RuleSet set, Path outputDir, File yarac) throws IngestModuleException { + File tempFolder = Paths.get(outputDir.toString(), set.getName()).toFile(); + if (!tempFolder.exists()) { + tempFolder.mkdir(); + } + + List fileList = set.getRuleFiles(); + for (File file : fileList) { + List commandList = new ArrayList<>(); + commandList.add(String.format("\"%s\"", yarac.toString())); + commandList.add(String.format("\"%s\"", file.toString())); + commandList.add(String.format("\"%s\"", Paths.get(tempFolder.getAbsolutePath(), "compiled_" + file.getName()))); + + ProcessBuilder builder = new ProcessBuilder(commandList); + try { + ExecUtil.execute(builder); + } catch (SecurityException | IOException ex) { + throw new IngestModuleException(String.format("Failed to compile Yara rules file", file.toString()), ex); + } + + } + } + + /** + * Returns a list of RuleSet objects for the given list of RuleSet names. + * + * @param names List of RuleSet names. + * + * @return List of RuleSet or empty list if none of the names matched + * existing rules. + */ + private static List getRuleSetsForNames(List names) { + List ruleSetList = new ArrayList<>(); + + RuleSetManager manager = new RuleSetManager(); + for (RuleSet set : manager.getRuleSetList()) { + if (names.contains(set.getName())) { + ruleSetList.add(set); + } + } + + return ruleSetList; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestJobSettings.java new file mode 100755 index 0000000000..b115bba90f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestJobSettings.java @@ -0,0 +1,106 @@ +/* + * 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.modules.yara; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; + +/** + * IngestJobSettings for the YARA ingest module. + */ +public final class YaraIngestJobSettings implements IngestModuleIngestJobSettings { + + private static final long serialVersionUID = 1L; + + private List selectedRuleSetNames; + private boolean onlyExecutableFiles; + + // Default constructor. + YaraIngestJobSettings() { + onlyExecutableFiles = true; + selectedRuleSetNames = new ArrayList<>(); + } + + /** + * Constructor. + * + * @param selected List of selected rules. + * @param onlyExecutableFiles Process only executable files. + */ + public YaraIngestJobSettings(List selected, boolean onlyExecutableFiles) { + this.selectedRuleSetNames = new ArrayList<>(); + + for (RuleSet set : selected) { + selectedRuleSetNames.add(set.getName()); + } + + this.onlyExecutableFiles = onlyExecutableFiles; + } + + /** + * Return the list of rule name sets that were selected in the ingest + * settings panel. + * + * @return List of selected RuleSet names. + */ + public List getSelectedRuleSetNames() { + return Collections.unmodifiableList(selectedRuleSetNames); + } + + /** + * Set the list of selected rule names. + * + * @param selected List of selected rule Sets. + */ + void setSelectedRuleSetNames(List selected) { + this.selectedRuleSetNames = new ArrayList<>(); + for (RuleSet set : selected) { + selectedRuleSetNames.add(set.getName()); + } + } + + /** + * Process only executable Files. + * + * @return If true the ingest module should process only executable files, + * if false process all files. + */ + public boolean onlyExecutableFiles() { + return onlyExecutableFiles; + } + + /** + * Set whether to process only executable files or all files. + * + * @param onlyExecutableFiles True if the ingest module should only process + * executable files. + */ + void setOnlyExecuteableFile(boolean onlyExecutableFiles) { + this.onlyExecutableFiles = onlyExecutableFiles; + } + + @Override + public long getVersionNumber() { + return serialVersionUID; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModule.java new file mode 100755 index 0000000000..c8deff0936 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModule.java @@ -0,0 +1,224 @@ +/* + * 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.modules.yara; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import org.apache.commons.lang3.RandomStringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.yara.YaraWrapperException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * An ingest module that runs the yara against the given files. + * + */ +public class YaraIngestModule extends FileIngestModuleAdapter { + + // 15MB + private static final int FILE_SIZE_THRESHOLD_MB = 100; + private static final int FILE_SIZE_THRESHOLD_BYTE = FILE_SIZE_THRESHOLD_MB * 1024 * 1024; + private static final int YARA_SCAN_TIMEOUT_SEC = 30 * 60 * 60; // 30 minutes. + + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); + private final static Logger logger = Logger.getLogger(YaraIngestModule.class.getName()); + private static final String YARA_DIR = "yara"; + private static final Map pathsByJobId = new ConcurrentHashMap<>(); + private static final String RULESET_DIR = "RuleSets"; + + private final YaraIngestJobSettings settings; + + private IngestJobContext context = null; + private Long jobId; + + /** + * Constructor. + * + * @param settings + */ + YaraIngestModule(YaraIngestJobSettings settings) { + this.settings = settings; + } + + @Messages({ + "YaraIngestModule_windows_error_msg=The YARA ingest module is only available on 64bit Windows.",}) + + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + this.jobId = context.getJobId(); + + if (!PlatformUtil.isWindowsOS() || !PlatformUtil.is64BitOS()) { + throw new IngestModule.IngestModuleException(Bundle.YaraIngestModule_windows_error_msg()); + } + + if (refCounter.incrementAndGet(jobId) == 1) { + // compile the selected rules & put into temp folder based on jobID + Path tempDir = getTempDirectory(jobId); + Path tempRuleSetDir = Paths.get(tempDir.toString(), RULESET_DIR); + if(!tempRuleSetDir.toFile().exists()) { + tempRuleSetDir.toFile().mkdir(); + } + + YaraIngestHelper.compileRules(settings.getSelectedRuleSetNames(), tempRuleSetDir); + } + } + + @Override + public void shutDown() { + if (context != null && refCounter.decrementAndGet(jobId) == 0) { + // do some clean up. + Path jobPath = pathsByJobId.get(jobId); + if (jobPath != null) { + jobPath.toFile().delete(); + pathsByJobId.remove(jobId); + } + } + } + + @Override + public ProcessResult process(AbstractFile file) { + + if (settings.onlyExecutableFiles()) { + String extension = file.getNameExtension(); + if (!extension.equals("exe")) { + return ProcessResult.OK; + } + } + + // Skip the file if its 0 in length or a directory. + if (file.getSize() == 0 || + file.isDir() || + file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { + return ProcessResult.OK; + } + + try { + List artifacts = new ArrayList<>(); + File ruleSetsDir = Paths.get(getTempDirectory(jobId).toString(), RULESET_DIR).toFile(); + + // If the file size is less than FILE_SIZE_THRESHOLD_BYTE read the file + // into a buffer, else make a local copy of the file. + if(file.getSize() < FILE_SIZE_THRESHOLD_BYTE) { + byte[] fileBuffer = new byte[(int)file.getSize()]; + + int dataRead = file.read(fileBuffer, 0, file.getSize()); + if(dataRead != 0) { + artifacts.addAll( YaraIngestHelper.scanFileForMatches(file, ruleSetsDir, fileBuffer, dataRead, YARA_SCAN_TIMEOUT_SEC)); + } + } else { + File tempCopy = createLocalCopy(file); + artifacts.addAll( YaraIngestHelper.scanFileForMatches(file, ruleSetsDir, tempCopy, YARA_SCAN_TIMEOUT_SEC)); + tempCopy.delete(); + } + + if(!artifacts.isEmpty()) { + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); + blackboard.postArtifacts(artifacts, YaraIngestModuleFactory.getModuleName()); + } + + } catch (BlackboardException | NoCurrentCaseException | IngestModuleException | TskCoreException | YaraWrapperException ex) { + logger.log(Level.SEVERE, String.format("YARA ingest module failed to process file id %d", file.getId()), ex); + return ProcessResult.ERROR; + } catch(IOException ex) { + logger.log(Level.SEVERE, String.format("YARA ingest module failed to make a local copy of given file id %d", file.getId()), ex); + return ProcessResult.ERROR; + } + + return ProcessResult.OK; + } + + /** + * Return the temp directory for this jobId. If the folder does not exit it + * will be created. + * + * @param jobId The current jobId + * + * @return The path of the temporary directory for the given jobId. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + private synchronized Path getTempDirectory(long jobId) throws IngestModuleException { + Path jobPath = pathsByJobId.get(jobId); + if (jobPath != null) { + return jobPath; + } + + Path baseDir; + try { + baseDir = Paths.get(Case.getCurrentCaseThrows().getTempDirectory(), YARA_DIR); + } catch (NoCurrentCaseException ex) { + throw new IngestModuleException("Failed to create YARA ingest model temp directory, no open case.", ex); + } + + // Make the base yara directory, as needed + if (!baseDir.toFile().exists()) { + baseDir.toFile().mkdirs(); + } + + String randomDirName = String.format("%s_%d", RandomStringUtils.randomAlphabetic(8), jobId); + jobPath = Paths.get(baseDir.toString(), randomDirName); + jobPath.toFile().mkdir(); + + pathsByJobId.put(jobId, jobPath); + + return jobPath; + } + + /** + * Create a local copy of the given AbstractFile. + * + * @param file AbstractFile to make a copy of. + * + * @return A File object representation of the local copy. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + * @throws IOException + */ + protected File createLocalCopy(AbstractFile file) throws IngestModuleException, IOException { + String tempFileName = RandomStringUtils.randomAlphabetic(15) + file.getId() + ".temp"; + + File tempFile = Paths.get(getTempDirectory(context.getJobId()).toString(), tempFileName).toFile(); + ContentUtils.writeToFile(file, tempFile, context::dataSourceIngestIsCancelled); + + return tempFile; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java new file mode 100755 index 0000000000..1abf8aaa0f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java @@ -0,0 +1,92 @@ +/* + * 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.modules.yara; + +import java.util.ArrayList; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; +import org.sleuthkit.autopsy.modules.yara.ui.YaraIngestSettingsPanel; + +/** + * A factory that creates ingest modules that use the Yara rule set definitions + * to identify files that may be of interest to the user. + */ +@ServiceProvider(service = IngestModuleFactory.class) +public class YaraIngestModuleFactory extends IngestModuleFactoryAdapter { + + @Messages({ + "Yara_Module_Name=YARA", + "Yara_Module_Description=With the YARA ingest module you use YARA rule files to search files for textual or binary patterns." + }) + + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + @Override + public String getModuleDescription() { + return Bundle.Yara_Module_Description(); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean hasIngestJobSettingsPanel() { + return true; + } + + @Override + public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { + return new YaraIngestSettingsPanel((YaraIngestJobSettings)settings); + } + + @Override + public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { + return new YaraIngestJobSettings(new ArrayList<>(), true); + } + + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + return new YaraIngestModule((YaraIngestJobSettings) settings); + } + + /** + * Return the name of the ingest module. + * + * @return Ingest module name. + */ + static String getModuleName() { + return Bundle.Yara_Module_Name(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java new file mode 100755 index 0000000000..112cf9206c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java @@ -0,0 +1,84 @@ +/* + * 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.modules.yara.rules; + +import java.io.File; +import java.io.Serializable; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +/** + * Represents a yara rule set which is a collection of yara rule files. + */ +public class RuleSet implements Comparable, Serializable { + + private static final long serialVersionUID = 1L; + + private final String name; + private final Path path; + + /** + * Construct a new RuleSet. + * + * @param name Name of the rule set. + * @param path Directory path to the rule set. + */ + RuleSet(String name, Path path) { + this.name = name; + this.path = path; + } + + /** + * Returns the name of the rule set. + * + * @return Name of rule set. + */ + public String getName() { + return name; + } + + /** + * Returns location if the rule set files. + * + * @return The path for this rule set. + */ + public Path getPath() { + return path; + } + + /** + * Returns a list of the rule files in this rule set. + * + * @return List of Files in current directory. + */ + public List getRuleFiles() { + return Arrays.asList(path.toFile().listFiles()); + } + + @Override + public String toString() { + return getName(); + } + + @Override + public int compareTo(RuleSet ruleSet) { + return getName().compareTo(ruleSet.getName()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetException.java b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetException.java new file mode 100755 index 0000000000..7ae0e12c45 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetException.java @@ -0,0 +1,47 @@ +/* + * 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.modules.yara.rules; + +/** + * + * An exception class for yara rule sets. + */ +public class RuleSetException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Construct a RuleSetException with the given message. + * + * @param msg Exception message. + */ + RuleSetException(String msg) { + super(msg); + } + + /** + * Construct a RuleSetException with the given message and exception. + * + * @param msg Exception message. + * @param ex Exception. + */ + RuleSetException(String msg, Exception ex) { + super(msg, ex); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetManager.java b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetManager.java new file mode 100755 index 0000000000..eab2f80819 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetManager.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-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.modules.yara.rules; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * + * Yara Rule Set Manager. Manages the creation, deletion of yara rule sets. + */ +public class RuleSetManager { + + private final static String BASE_FOLDER = "yara"; + private final static String RULE_SET_FOLDER = "ruleSets"; + + /** + * Create a new Yara rule set with the given set name. + * + * @param name Name of new rule set + * + * @return Newly created RuleSet + * + * @throws RuleSetException RuleSet with given name already exists. + */ + public RuleSet createRuleSet(String name) throws RuleSetException { + + if (isRuleSetExists(name)) { + throw new RuleSetException(String.format("Yara rule set with name %s already exits.", name)); + } + + Path basePath = getRuleSetPath(); + Path setPath = Paths.get(basePath.toString(), name); + + setPath.toFile().mkdir(); + + return new RuleSet(name, setPath); + } + + /** + * Returns a list of all of the existing yara rule sets. + * + * @return + */ + public List getRuleSetList() { + List ruleSets = new ArrayList<>(); + Path basePath = getRuleSetPath(); + + String[] ruleSetNames = basePath.toFile().list(); + + for (String setName : ruleSetNames) { + ruleSets.add(new RuleSet(setName, Paths.get(basePath.toString(), setName))); + } + + return ruleSets; + } + + /** + * Check if a yara rule set of the given name exists. + * + * @param name Yara rule set name + * + * @return True if the rule set exist. + */ + public boolean isRuleSetExists(String name) { + Path basePath = getRuleSetPath(); + Path setPath = Paths.get(basePath.toString(), name); + + return setPath.toFile().exists(); + } + + /** + * Returns the Path to get RuleSet directory. If it does not exist it is + * created. + * + * @return Yara rule set directory path. + */ + private Path getRuleSetPath() { + Path basePath = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), BASE_FOLDER, RULE_SET_FOLDER); + File baseFile = basePath.toFile(); + + if (!baseFile.exists()) { + baseFile.mkdirs(); + } + + return basePath; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties new file mode 100755 index 0000000000..22f969f628 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties @@ -0,0 +1,15 @@ +OptionCategory_Name_YaraRuleSetOption=Yara Rule Sets +OptionCategory_Keywords_YaraRuleSetOption=YaraRuleSets +RuleSetPanel.newButton.text=New Set +RuleSetPanel.deleteButton.text=Delete Set +RuleSetPanel.ruleSetListLabel.text=Rule Sets: +RuleSetDetailsPanel.ruleSetListLabel.text=The folder currently contains the following rule files: +RuleSetDetailsPanel.setDetailsLabel.text=Set Details +RuleSetDetailsPanel.openFolderButton.text=Open Folder +RuleSetPanel.descriptionField.text=This module allows you to find files the match Yara rules. Each set has a list of Yara rule files. A file need only match one rule in the set to be found. +RuleSetDetailsPanel.openLabel.text=Place rule files in the set's folder. They will be compiled before use. +YaraIngestSettingsPanel.border.title=Select YARA rule sets to enable during ingest: +YaraIngestSettingsPanel.allFilesButton.text=All Files +YaraIngestSettingsPanel.allFilesButton.toolTipText= +YaraIngestSettingsPanel.executableFilesButton.text=Only Executable Files +RuleSetDetailsPanel.refreshButton.text=Refresh File List diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED new file mode 100755 index 0000000000..f152cf959b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED @@ -0,0 +1,22 @@ +OptionCategory_Name_YaraRuleSetOption=Yara Rule Sets +OptionCategory_Keywords_YaraRuleSetOption=YaraRuleSets +RuleSetDetailsPanel_failed_to_open_folder_msg=Failed to open new window for rule set file. +RuleSetDetailsPanel_failed_to_open_folder_title=Open Error +RuleSetPanel.newButton.text=New Set +RuleSetPanel.deleteButton.text=Delete Set +RuleSetPanel.ruleSetListLabel.text=Rule Sets: +RuleSetDetailsPanel.ruleSetListLabel.text=The folder currently contains the following rule files: +RuleSetDetailsPanel.setDetailsLabel.text=Set Details +RuleSetDetailsPanel.openFolderButton.text=Open Folder +RuleSetPanel.descriptionField.text=This module allows you to find files the match Yara rules. Each set has a list of Yara rule files. A file need only match one rule in the set to be found. +RuleSetDetailsPanel.openLabel.text=Place rule files in the set's folder. They will be compiled before use. +YaraIngestSettingsPanel.border.title=Select YARA rule sets to enable during ingest: +YaraIngestSettingsPanel.allFilesButton.text=All Files +YaraIngestSettingsPanel.allFilesButton.toolTipText= +YaraIngestSettingsPanel.executableFilesButton.text=Only Executable Files +RuleSetDetailsPanel.refreshButton.text=Refresh File List +# {0} - rule set name +YaraRuleSetOptionPanel_badName_msg=Rule set name {0} already exists.\nRule set names must be unique. +YaraRuleSetOptionPanel_badName_title=Create Rule Set +YaraRuleSetOptionPanel_new_rule_set_name_msg=Supply a new unique rule set name: +YaraRuleSetOptionPanel_new_rule_set_name_title=Rule Set Name diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.form new file mode 100755 index 0000000000..955199108a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.form @@ -0,0 +1,113 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.java new file mode 100755 index 0000000000..7e3e506948 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.java @@ -0,0 +1,228 @@ +/* + * 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.modules.yara.ui; + +import java.awt.Component; +import java.awt.Desktop; +import java.awt.Graphics; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; + +/** + * A panel for displaying the details of an individual yara rule set. + */ +public class RuleSetDetailsPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private static final Logger logger = Logger.getLogger(RuleSetDetailsPanel.class.getName()); + + private RuleSet currentRuleSet; + private final DefaultListModel fileListModel; + private final JList fileList; + + /** + * Creates new form RuleSetDetailsPanel + */ + public RuleSetDetailsPanel() { + initComponents(); + + fileListModel = new DefaultListModel<>(); + fileList = new JList<>(); + fileList.setModel(fileListModel); + fileList.setCellRenderer(new FileRenderer()); + openFolderButton.setEnabled(false); + scrollPane.setViewportView(fileList); + + } + + /** + * Update the panel to show the details of the given RuleSet. + * + * @param ruleSet New RuleSet to display + */ + void setRuleSet(RuleSet ruleSet) { + currentRuleSet = ruleSet; + + fileListModel.clear(); + + if (ruleSet != null) { + List files = currentRuleSet.getRuleFiles(); + + if(files != null) { + for (File file : files) { + fileListModel.addElement(file); + } + } + } + + openFolderButton.setEnabled(ruleSet != null); + } + + /** + * Simple ListCellRenderer for the file list. + */ + private final class FileRenderer extends DefaultListCellRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + if (value instanceof File) { + File file = (File) value; + setText(file.getName()); + setToolTipText(file.getAbsolutePath()); + } + + return this; + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + javax.swing.JLabel ruleSetListLabel = new javax.swing.JLabel(); + javax.swing.JLabel setDetailsLabel = new javax.swing.JLabel(); + openFolderButton = new javax.swing.JButton(); + openLabel = new javax.swing.JLabel(); + scrollPane = new javax.swing.JScrollPane(); + javax.swing.JButton refreshButton = new javax.swing.JButton(); + + setLayout(new java.awt.GridBagLayout()); + + org.openide.awt.Mnemonics.setLocalizedText(ruleSetListLabel, org.openide.util.NbBundle.getMessage(RuleSetDetailsPanel.class, "RuleSetDetailsPanel.ruleSetListLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(16, 0, 3, 0); + add(ruleSetListLabel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(setDetailsLabel, org.openide.util.NbBundle.getMessage(RuleSetDetailsPanel.class, "RuleSetDetailsPanel.setDetailsLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 10, 0); + add(setDetailsLabel, gridBagConstraints); + + openFolderButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/folder-icon-16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(openFolderButton, org.openide.util.NbBundle.getMessage(RuleSetDetailsPanel.class, "RuleSetDetailsPanel.openFolderButton.text")); // NOI18N + openFolderButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + openFolderButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 5; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(10, 0, 0, 0); + add(openFolderButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(openLabel, org.openide.util.NbBundle.getMessage(RuleSetDetailsPanel.class, "RuleSetDetailsPanel.openLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 5; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(10, 0, 0, 0); + add(openLabel, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 0); + add(scrollPane, gridBagConstraints); + + refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/arrow-circle-double-135.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(RuleSetDetailsPanel.class, "RuleSetDetailsPanel.refreshButton.text")); // NOI18N + refreshButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + refreshButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 5; + gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHEAST; + add(refreshButton, gridBagConstraints); + }// //GEN-END:initComponents + + @Messages({ + "RuleSetDetailsPanel_failed_to_open_folder_msg=Failed to open new window for rule set file.", + "RuleSetDetailsPanel_failed_to_open_folder_title=Open Error" + }) + private void openFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openFolderButtonActionPerformed + if (currentRuleSet != null) { + File file = currentRuleSet.getPath().toFile(); + if (file.exists()) { + try { + Desktop.getDesktop().open(file); + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, + Bundle.RuleSetDetailsPanel_failed_to_open_folder_msg(), + Bundle.RuleSetDetailsPanel_failed_to_open_folder_title(), + JOptionPane.ERROR_MESSAGE); + logger.log(Level.WARNING, String.format("Failed to open external file explorer for: %s", currentRuleSet.getPath().toString()), ex); + } + } + } + }//GEN-LAST:event_openFolderButtonActionPerformed + + private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed + if (currentRuleSet != null) { + fileListModel.clear(); + List files = currentRuleSet.getRuleFiles(); + + if(files != null) { + for (File file : files) { + fileListModel.addElement(file); + } + } + } + }//GEN-LAST:event_refreshButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton openFolderButton; + private javax.swing.JLabel openLabel; + private javax.swing.JScrollPane scrollPane; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetPanel.form new file mode 100755 index 0000000000..bd3e12390f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetPanel.form @@ -0,0 +1,110 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetPanel.java new file mode 100755 index 0000000000..63474a8adb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetPanel.java @@ -0,0 +1,231 @@ +/* + * 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.modules.yara.ui; + +import java.awt.Component; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; + +/** + * Panel for managing yara rule sets. + */ +public final class RuleSetPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private final DefaultListModel listModel; + private final JList ruleSetList; + + public RuleSetPanel() { + initComponents(); + + // Creating and initializing JList here to better take + // advantace of JList use of generics. + ruleSetList = new JList<>(); + listModel = new DefaultListModel<>(); + scrollPane.setViewportView(ruleSetList); + ruleSetList.setModel(listModel); + ruleSetList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + ruleSetList.setCellRenderer(new RuleSetRenderer()); + ruleSetList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + deleteButton.setEnabled(getSelectedRule() != null); + } + }); + + deleteButton.setEnabled(false); + } + + /** + * Add the list of rule sets to the list model. + * + * @param ruleSetList Rule sets to add. + */ + void addSetList(List newSetList) { + // Put the list into alphectical order. + List list = new ArrayList<>(); + list.addAll(newSetList); + Collections.sort(list); + + listModel.clear(); + + for (RuleSet set : list) { + listModel.addElement(set); + } + } + + /** + * Adds a new rule to the list. + * + * @param set + */ + void addRuleSet(RuleSet set) { + // This will assure that the new item + // appears in the correct location. + List list = Collections.list(listModel.elements()); + list.add(set); + + addSetList(list); + + ruleSetList.setSelectedValue(set, true); + } + + /** + * Removes a rule set from the list. + * + * @param set + */ + void removeRuleSet(RuleSet set) { + listModel.removeElement(set); + } + + /** + * Add a listener for the new rule set action. + * + * @param listener + */ + void addNewRuleListener(ActionListener listener) { + newButton.addActionListener(listener); + } + + /** + * Add a listener for the delete rule set action. + * + * @param listener + */ + void addDeleteRuleListener(ActionListener listener) { + deleteButton.addActionListener(listener); + } + + /** + * Add a listener for list selection change. + * + * @param listener + */ + void addListSelectionListener(ListSelectionListener listener) { + ruleSetList.addListSelectionListener(listener); + } + + /** + * Returns the current selected rule set. + * + * @return Currently selected rule set or null if one is not selected. + */ + RuleSet getSelectedRule() { + return ruleSetList.getSelectedValue(); + } + + /** + * Simple ListCellRenderer for a RuleSet. + */ + private final class RuleSetRenderer extends DefaultListCellRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + if (value instanceof RuleSet) { + RuleSet set = (RuleSet) value; + setText(set.getName()); + setToolTipText(set.getName()); + } + + return this; + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + javax.swing.JTextPane descriptionField = new javax.swing.JTextPane(); + javax.swing.JLabel ruleSetListLabel = new javax.swing.JLabel(); + scrollPane = new javax.swing.JScrollPane(); + javax.swing.JPanel buttonPanel = new javax.swing.JPanel(); + newButton = new javax.swing.JButton(); + deleteButton = new javax.swing.JButton(); + + setLayout(new java.awt.GridBagLayout()); + + descriptionField.setEditable(false); + descriptionField.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); + descriptionField.setText(org.openide.util.NbBundle.getMessage(RuleSetPanel.class, "RuleSetPanel.descriptionField.text")); // NOI18N + descriptionField.setOpaque(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1.0; + add(descriptionField, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(ruleSetListLabel, org.openide.util.NbBundle.getMessage(RuleSetPanel.class, "RuleSetPanel.ruleSetListLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 0); + add(ruleSetListLabel, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 0, 10, 0); + add(scrollPane, gridBagConstraints); + + buttonPanel.setLayout(new java.awt.GridLayout(1, 0, 5, 0)); + + newButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newButton, org.openide.util.NbBundle.getMessage(RuleSetPanel.class, "RuleSetPanel.newButton.text")); // NOI18N + buttonPanel.add(newButton); + + deleteButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteButton, org.openide.util.NbBundle.getMessage(RuleSetPanel.class, "RuleSetPanel.deleteButton.text")); // NOI18N + buttonPanel.add(deleteButton); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + add(buttonPanel, gridBagConstraints); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton deleteButton; + private javax.swing.JButton newButton; + private javax.swing.JScrollPane scrollPane; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.form new file mode 100755 index 0000000000..d4832c66a1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.form @@ -0,0 +1,87 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java new file mode 100755 index 0000000000..e68b11ccb2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java @@ -0,0 +1,182 @@ +/* + * 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.modules.yara.ui; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import javax.swing.DefaultListModel; +import org.sleuthkit.autopsy.guicomponeontutils.AbstractCheckboxListItem; +import org.sleuthkit.autopsy.guiutils.CheckBoxJList; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; +import org.sleuthkit.autopsy.modules.yara.YaraIngestJobSettings; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSetManager; + +/** + * Yara Ingest settings panel. + */ +public class YaraIngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { + + private static final long serialVersionUID = 1L; + + private final CheckBoxJList checkboxList; + private final DefaultListModel listModel; + + /** + * Creates new form YaraIngestSettingsPanel + */ + YaraIngestSettingsPanel() { + initComponents(); + listModel = new DefaultListModel<>(); + checkboxList = new CheckBoxJList<>(); + scrollPane.setViewportView(checkboxList); + } + + public YaraIngestSettingsPanel(YaraIngestJobSettings settings) { + this(); + + List setNames = settings.getSelectedRuleSetNames(); + + checkboxList.setModel(listModel); + checkboxList.setOpaque(false); + RuleSetManager manager = new RuleSetManager(); + List ruleSetList = manager.getRuleSetList(); + for (RuleSet set : ruleSetList) { + RuleSetListItem item = new RuleSetListItem(set); + item.setChecked(setNames.contains(set.getName())); + listModel.addElement(item); + } + + allFilesButton.setSelected(!settings.onlyExecutableFiles()); + executableFilesButton.setSelected(settings.onlyExecutableFiles()); + } + + @Override + public IngestModuleIngestJobSettings getSettings() { + List selectedRules = new ArrayList<>(); + + Enumeration enumeration = listModel.elements(); + while (enumeration.hasMoreElements()) { + RuleSetListItem item = enumeration.nextElement(); + if (item.isChecked()) { + selectedRules.add(item.getRuleSet()); + } + } + + return new YaraIngestJobSettings(selectedRules, executableFilesButton.isSelected()); + } + + /** + * RuleSet wrapper class for Checkbox JList model. + */ + private final class RuleSetListItem extends AbstractCheckboxListItem { + + private final RuleSet ruleSet; + + /** + * RuleSetListItem constructor. + * + * @param set RuleSet object to display in list. + */ + RuleSetListItem(RuleSet set) { + this.ruleSet = set; + } + + /** + * Returns the RuleSet. + * + * @return + */ + RuleSet getRuleSet() { + return ruleSet; + } + + @Override + public String getDisplayName() { + return ruleSet.getName(); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + buttonGroup = new javax.swing.ButtonGroup(); + scrollPane = new javax.swing.JScrollPane(); + buttonPanel = new javax.swing.JPanel(); + allFilesButton = new javax.swing.JRadioButton(); + executableFilesButton = new javax.swing.JRadioButton(); + + setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.border.title"))); // NOI18N + setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(scrollPane, gridBagConstraints); + + buttonPanel.setLayout(new java.awt.GridBagLayout()); + + buttonGroup.add(allFilesButton); + org.openide.awt.Mnemonics.setLocalizedText(allFilesButton, org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.allFilesButton.text")); // NOI18N + allFilesButton.setToolTipText(org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.allFilesButton.toolTipText")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + buttonPanel.add(allFilesButton, gridBagConstraints); + + buttonGroup.add(executableFilesButton); + executableFilesButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(executableFilesButton, org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.executableFilesButton.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + buttonPanel.add(executableFilesButton, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 1.0; + add(buttonPanel, gridBagConstraints); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton allFilesButton; + private javax.swing.ButtonGroup buttonGroup; + private javax.swing.JPanel buttonPanel; + private javax.swing.JRadioButton executableFilesButton; + private javax.swing.JScrollPane scrollPane; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.form new file mode 100755 index 0000000000..c7b05ede35 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.form @@ -0,0 +1,104 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.java new file mode 100755 index 0000000000..937dc504a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.java @@ -0,0 +1,207 @@ +/* + * 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.modules.yara.ui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JOptionPane; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSetException; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSetManager; + +/** + * + * Yara Rule Set Option Panel. + */ +public class YaraRuleSetOptionPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private static final Logger logger = Logger.getLogger(YaraRuleSetOptionPanel.class.getName()); + + private final RuleSetManager manager; + + /** + * Creates new form YaraRuleSetOptionPanel + */ + public YaraRuleSetOptionPanel() { + initComponents(); + + manager = new RuleSetManager(); + + ruleSetPanel.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + handleSelectionChange(); + } + }); + + ruleSetPanel.addDeleteRuleListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleDeleteRuleSet(); + } + }); + + ruleSetPanel.addNewRuleListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleNewRuleSet(); + } + }); + } + + /** + * Update the panel with the current rule set. + */ + void updatePanel() { + ruleSetPanel.addSetList(manager.getRuleSetList()); + } + + /** + * Handle the change in rule set selection. Update the detail panel with the + * selected rule. + */ + private void handleSelectionChange() { + ruleSetDetailsPanel.setRuleSet(ruleSetPanel.getSelectedRule()); + } + + @NbBundle.Messages({ + "YaraRuleSetOptionPanel_new_rule_set_name_msg=Supply a new unique rule set name:", + "YaraRuleSetOptionPanel_new_rule_set_name_title=Rule Set Name", + "# {0} - rule set name", + "YaraRuleSetOptionPanel_badName_msg=Rule set name {0} already exists.\nRule set names must be unique.", + "YaraRuleSetOptionPanel_badName_title=Create Rule Set" + }) + /** + * Handle the new rule set action. Prompt the user for a rule set name, + * create the new set and update the rule set list. + */ + private void handleNewRuleSet() { + String value = JOptionPane.showInputDialog(this, + Bundle.YaraRuleSetOptionPanel_new_rule_set_name_msg(), + Bundle.YaraRuleSetOptionPanel_new_rule_set_name_title()); + try { + ruleSetPanel.addRuleSet(manager.createRuleSet(value)); + } catch (RuleSetException ex) { + JOptionPane.showMessageDialog(this, + Bundle.YaraRuleSetOptionPanel_badName_msg(value), + Bundle.YaraRuleSetOptionPanel_badName_title(), + JOptionPane.ERROR_MESSAGE); + logger.log(Level.WARNING, "Failed to create new rule set, user provide existing name.", ex); + } + } + + /** + * Handle the delete rule action. Delete the rule set and update the the + * rule set list. + */ + private void handleDeleteRuleSet() { + RuleSet ruleSet = ruleSetPanel.getSelectedRule(); + ruleSetPanel.removeRuleSet(ruleSet); + deleteDirectory(ruleSet.getPath().toFile()); + } + + /** + * Recursively delete the given directory and its children. + * + * @param directoryToBeDeleted + * + * @return True if the delete was successful. + */ + private boolean deleteDirectory(File directoryToBeDeleted) { + File[] allContents = directoryToBeDeleted.listFiles(); + if (allContents != null) { + for (File file : allContents) { + deleteDirectory(file); + } + } + return directoryToBeDeleted.delete(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane(); + javax.swing.JPanel viewportPanel = new javax.swing.JPanel(); + javax.swing.JSeparator separator = new javax.swing.JSeparator(); + ruleSetDetailsPanel = new org.sleuthkit.autopsy.modules.yara.ui.RuleSetDetailsPanel(); + ruleSetPanel = new org.sleuthkit.autopsy.modules.yara.ui.RuleSetPanel(); + + setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); + setLayout(new java.awt.BorderLayout()); + + scrollPane.setBorder(null); + + viewportPanel.setLayout(new java.awt.GridBagLayout()); + + separator.setOrientation(javax.swing.SwingConstants.VERTICAL); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; + gridBagConstraints.weighty = 1.0; + viewportPanel.add(separator, gridBagConstraints); + + ruleSetDetailsPanel.setMinimumSize(new java.awt.Dimension(500, 97)); + ruleSetDetailsPanel.setPreferredSize(new java.awt.Dimension(500, 204)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(10, 10, 10, 10); + viewportPanel.add(ruleSetDetailsPanel, gridBagConstraints); + + ruleSetPanel.setMaximumSize(new java.awt.Dimension(400, 2147483647)); + ruleSetPanel.setMinimumSize(new java.awt.Dimension(400, 107)); + ruleSetPanel.setPreferredSize(new java.awt.Dimension(400, 214)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(10, 10, 10, 10); + viewportPanel.add(ruleSetPanel, gridBagConstraints); + + scrollPane.setViewportView(viewportPanel); + + add(scrollPane, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.modules.yara.ui.RuleSetDetailsPanel ruleSetDetailsPanel; + private org.sleuthkit.autopsy.modules.yara.ui.RuleSetPanel ruleSetPanel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetsOptionPanelController.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetsOptionPanelController.java new file mode 100755 index 0000000000..7619e23b4c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetsOptionPanelController.java @@ -0,0 +1,90 @@ +/* + * 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.modules.yara.ui; + +import java.beans.PropertyChangeListener; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +/** + * Yara rule set option panel controller. + * + */ +@OptionsPanelController.TopLevelRegistration( + categoryName = "#OptionCategory_Name_YaraRuleSetOption", + iconBase = "org/sleuthkit/autopsy/images/yara_32.png", + keywords = "#OptionCategory_Keywords_YaraRuleSetOption", + keywordsCategory = "YaraRuleSets", + position = 20 +) +public class YaraRuleSetsOptionPanelController extends OptionsPanelController { + + private YaraRuleSetOptionPanel panel = null; + + @Override + public void update() { + if (panel != null) { + panel.updatePanel(); + } + } + + @Override + public void applyChanges() { + + } + + @Override + public void cancel() { + + } + + @Override + public JComponent getComponent(Lookup masterLookup) { + panel = new YaraRuleSetOptionPanel(); + return panel; + } + + @Override + public HelpCtx getHelpCtx() { + return null; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener pl) { + + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pl) { + + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public boolean isChanged() { + return false; + } + +} 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 67b253a1fa..7221ee4a55 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -60,6 +60,7 @@ import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory; import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.caseuco.CaseUcoExporter; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -983,10 +984,10 @@ public class PortableCaseReportModule implements ReportModule { BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName()); try { - BlackboardArtifact.Type newCustomType = portableSkCase.addBlackboardArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName()); + BlackboardArtifact.Type newCustomType = portableSkCase.getBlackboard().getOrAddArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName()); oldArtTypeIdToNewArtTypeId.put(oldArtifact.getArtifactTypeID(), newCustomType.getTypeID()); return newCustomType.getTypeID(); - } catch (TskDataException ex) { + } catch (BlackboardException ex) { throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS } } @@ -1007,11 +1008,11 @@ public class PortableCaseReportModule implements ReportModule { } try { - BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(), + BlackboardAttribute.Type newCustomType = portableSkCase.getBlackboard().getOrAddAttributeType(oldAttrType.getTypeName(), oldAttrType.getValueType(), oldAttrType.getDisplayName()); oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType); return newCustomType; - } catch (TskDataException ex) { + } catch (BlackboardException ex) { throw new TskCoreException("Error creating new attribute type " + oldAttrType.getTypeName(), ex); // NON-NLS } } diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java index 068f0c3904..ff73c428b9 100644 --- a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java @@ -71,8 +71,8 @@ public class RecentFilesSummaryTest { * Means of acquiring data from a method in RecentFilesSummary. * * @param recentFilesSummary The RecentFilesSummary object. - * @param dataSource The datasource. - * @param count The number of items to retrieve. + * @param dataSource The datasource. + * @param count The number of items to retrieve. * * @return The method's return data. * @@ -95,7 +95,7 @@ public class RecentFilesSummaryTest { /** * If -1 count passed to method, should throw IllegalArgumentException. * - * @param method The method to call. + * @param method The method to call. * @param methodName The name of the metho * * @throws TskCoreException @@ -137,7 +137,7 @@ public class RecentFilesSummaryTest { * SleuthkitCase isn't called. * * @param recentFilesMethod The method to call. - * @param methodName The name of the method + * @param methodName The name of the method * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -175,7 +175,7 @@ public class RecentFilesSummaryTest { * If SleuthkitCase returns no results, an empty list is returned. * * @param recentFilesMethod The method to call. - * @param methodName The name of the method. + * @param methodName The name of the method. * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -220,11 +220,11 @@ public class RecentFilesSummaryTest { /** * Gets a mock BlackboardArtifact. * - * @param ds The data source to which the artifact belongs. - * @param artifactId The artifact id. - * @param artType The artifact type. + * @param ds The data source to which the artifact belongs. + * @param artifactId The artifact id. + * @param artType The artifact type. * @param attributeArgs The mapping of attribute type to value for each - * attribute in the artifact. + * attribute in the artifact. * * @return The mock artifact. */ @@ -247,10 +247,10 @@ public class RecentFilesSummaryTest { /** * Returns a mock artifact for getRecentlyOpenedDocuments. * - * @param ds The datasource for the artifact. + * @param ds The datasource for the artifact. * @param artifactId The artifact id. - * @param dateTime The time in seconds from epoch. - * @param path The path for the document. + * @param dateTime The time in seconds from epoch. + * @param path The path for the document. * * @return The mock artifact with pertinent attributes. */ @@ -292,13 +292,33 @@ public class RecentFilesSummaryTest { } } + @Test + public void getRecentlyOpenedDocuments_uniquePaths() throws SleuthkitCaseProviderException, TskCoreException { + DataSource dataSource = TskMockUtils.getDataSource(1); + + BlackboardArtifact item1 = getRecentDocumentArtifact(dataSource, 1001, DAY_SECONDS, "/a/path"); + BlackboardArtifact item2 = getRecentDocumentArtifact(dataSource, 1002, DAY_SECONDS + 1, "/a/path"); + BlackboardArtifact item3 = getRecentDocumentArtifact(dataSource, 1003, DAY_SECONDS + 2, "/a/path"); + List artifacts = Arrays.asList(item2, item3, item1); + + Pair casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts)); + RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft()); + List results = summary.getRecentlyOpenedDocuments(dataSource, 10); + + // verify results (only successItem) + Assert.assertNotNull(results); + Assert.assertEquals(1, results.size()); + Assert.assertEquals((Long) (DAY_SECONDS + 2), results.get(0).getDateAsLong()); + Assert.assertTrue("/a/path".equalsIgnoreCase(results.get(0).getPath())); + } + @Test public void getRecentlyOpenedDocuments_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException { DataSource dataSource = TskMockUtils.getDataSource(1); BlackboardArtifact successItem = getRecentDocumentArtifact(dataSource, 1001, DAY_SECONDS, "/a/path"); BlackboardArtifact nullTime = getRecentDocumentArtifact(dataSource, 1002, null, "/a/path2"); - BlackboardArtifact zeroTime = getRecentDocumentArtifact(dataSource, 10021, 0L, "/a/path2a"); + BlackboardArtifact zeroTime = getRecentDocumentArtifact(dataSource, 10021, 0L, "/a/path2a"); List artifacts = Arrays.asList(nullTime, zeroTime, successItem); Pair casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts)); @@ -315,11 +335,11 @@ public class RecentFilesSummaryTest { /** * Creates a mock blackboard artifact for getRecentDownloads. * - * @param ds The datasource. + * @param ds The datasource. * @param artifactId The artifact id. - * @param dateTime The time in seconds from epoch. - * @param domain The domain. - * @param path The path for the download. + * @param dateTime The time in seconds from epoch. + * @param domain The domain. + * @param path The path for the download. * * @return The mock artifact. */ @@ -368,6 +388,30 @@ public class RecentFilesSummaryTest { } } + @Test + public void getRecentDownloads_uniquePaths() throws SleuthkitCaseProviderException, TskCoreException { + DataSource dataSource = TskMockUtils.getDataSource(1); + + BlackboardArtifact item1 = getRecentDownloadArtifact(dataSource, 1001, DAY_SECONDS, "domain1.com", "/a/path1"); + BlackboardArtifact item1a = getRecentDownloadArtifact(dataSource, 10011, DAY_SECONDS + 1, "domain1.com", "/a/path1"); + BlackboardArtifact item2 = getRecentDownloadArtifact(dataSource, 1002, DAY_SECONDS + 2, "domain2.com", "/a/path1"); + BlackboardArtifact item3 = getRecentDownloadArtifact(dataSource, 1003, DAY_SECONDS + 3, "domain2a.com", "/a/path1"); + List artifacts = Arrays.asList(item2, item3, item1); + + Pair casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts)); + RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft()); + + // call method + List results = summary.getRecentDownloads(dataSource, 10); + + // verify results + Assert.assertNotNull(results); + Assert.assertEquals(1, results.size()); + Assert.assertEquals((Long) (DAY_SECONDS + 3), results.get(0).getDateAsLong()); + Assert.assertTrue("/a/path1".equalsIgnoreCase(results.get(0).getPath())); + Assert.assertTrue("domain2a.com".equalsIgnoreCase(results.get(0).getWebDomain())); + } + @Test public void getRecentDownloads_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException { DataSource dataSource = TskMockUtils.getDataSource(1); @@ -409,19 +453,17 @@ public class RecentFilesSummaryTest { * Constructor with all parameters. * * @param messageArtifactTypeId The type id for the artifact or null if - * no message artifact to be created. - * @param emailFrom Who the message is from or null not to - * include attribute. - * @param messageTime Time in seconds from epoch or null not - * to include attribute. - * @param fileParentPath The parent AbstractFile's path value. - * @param fileName The parent AbstractFile's filename - * value. - * @param associatedAttrFormed If false, the TSK_ASSOCIATED_OBJECT - * artifact has no attribute (even though - * it is required). - * @param hasParent Whether or not the artifact has a parent - * AbstractFile. + * no message artifact to be created. + * @param emailFrom Who the message is from or null not to include + * attribute. + * @param messageTime Time in seconds from epoch or null not to include + * attribute. + * @param fileParentPath The parent AbstractFile's path value. + * @param fileName The parent AbstractFile's filename value. + * @param associatedAttrFormed If false, the TSK_ASSOCIATED_OBJECT + * artifact has no attribute (even though it is required). + * @param hasParent Whether or not the artifact has a parent + * AbstractFile. */ AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName, @@ -441,14 +483,13 @@ public class RecentFilesSummaryTest { * SleuthkitCase assumed. * * @param messageArtifactTypeId The type id for the artifact or null if - * no message artifact to be created. - * @param emailFrom Who the message is from or null not to - * include attribute. - * @param messageTime Time in seconds from epoch or null not - * to include attribute. - * @param fileParentPath The parent AbstractFile's path value. - * @param fileName The parent AbstractFile's filename - * value. + * no message artifact to be created. + * @param emailFrom Who the message is from or null not to include + * attribute. + * @param messageTime Time in seconds from epoch or null not to include + * attribute. + * @param fileParentPath The parent AbstractFile's path value. + * @param fileName The parent AbstractFile's filename value. */ AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName) { this(messageArtifactTypeId, emailFrom, messageTime, fileParentPath, fileName, true, true); @@ -486,11 +527,11 @@ public class RecentFilesSummaryTest { /** * Sets up the associated artifact message for the TSK_ASSOCIATED_OBJECT. * - * @param artifacts The mapping of artifact id to artifact. - * @param item The record to setup. - * @param dataSource The datasource. + * @param artifacts The mapping of artifact id to artifact. + * @param item The record to setup. + * @param dataSource The datasource. * @param associatedId The associated attribute id. - * @param artifactId The artifact id. + * @param artifactId The artifact id. * * @return The associated Artifact blackboard attribute. * @@ -504,7 +545,7 @@ public class RecentFilesSummaryTest { if (item.getMessageArtifactTypeId() == null) { return associatedAttr; } - + // find the artifact type or null if not found ARTIFACT_TYPE messageType = Stream.of(ARTIFACT_TYPE.values()) .filter((artType) -> artType.getTypeID() == item.getMessageArtifactTypeId()) @@ -534,7 +575,7 @@ public class RecentFilesSummaryTest { * to return pertinent data. * * @param items Each attachment item where each item could represent a - * return result if fully formed. + * return result if fully formed. * * @return The mock SleuthkitCase and Blackboard. */ @@ -678,4 +719,34 @@ public class RecentFilesSummaryTest { .toString().equalsIgnoreCase(successItem2Details.getPath())); Assert.assertTrue(successItem2.getEmailFrom().equalsIgnoreCase(successItem2Details.getSender())); } + + @Test + public void getRecentAttachments_uniquePath() throws SleuthkitCaseProviderException, TskCoreException { + // setup data + DataSource dataSource = TskMockUtils.getDataSource(1); + + AttachmentArtifactItem item1 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(), + "person@sleuthkit.com", DAY_SECONDS, "/parent/path", "msg.pdf"); + AttachmentArtifactItem item2 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), + "person_on_skype", DAY_SECONDS + 1, "/parent/path", "msg.pdf"); + AttachmentArtifactItem item3 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(), + "person2@sleuthkit.com", DAY_SECONDS + 2, "/parent/path", "msg.pdf"); + + List items = Arrays.asList(item1, item2, item3); + + Pair casePair = getRecentAttachmentArtifactCase(items); + RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft()); + + // get data + List results = summary.getRecentAttachments(dataSource, 10); + + // verify results + Assert.assertNotNull(results); + Assert.assertEquals(1, results.size()); + + Assert.assertEquals(results.get(0).getDateAsLong(), (Long) (DAY_SECONDS + 2)); + Assert.assertTrue(Paths.get(item3.getFileParentPath(), item3.getFileName()) + .toString().equalsIgnoreCase(results.get(0).getPath())); + Assert.assertTrue(results.get(0).getSender().equalsIgnoreCase(item3.getEmailFrom())); + } } diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java index 0aff8a56b5..c7f3246a4d 100644 --- a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java @@ -66,18 +66,22 @@ import org.sleuthkit.datamodel.TskCoreException; * Tests for UserActivitySummary. */ public class UserActivitySummaryTest { + /** - * Function to retrieve data from UserActivitySummary with the provided arguments. + * Function to retrieve data from UserActivitySummary with the provided + * arguments. */ private interface DataFunction { + /** * A UserActivitySummary method encapsulated in a uniform manner. + * * @param userActivitySummary The UserActivitySummary class to use. * @param datasource The data source. * @param count The count. * @return The list of objects to return. * @throws SleuthkitCaseProviderException - * @throws TskCoreException + * @throws TskCoreException */ List retrieve(UserActivitySummary userActivitySummary, DataSource datasource, int count) throws SleuthkitCaseProviderException, TskCoreException; @@ -117,8 +121,8 @@ public class UserActivitySummaryTest { /** * Gets a UserActivitySummary class to test. * - * @param tskCase The SleuthkitCase. - * @param hasTranslation Whether the translation service is functional. + * @param tskCase The SleuthkitCase. + * @param hasTranslation Whether the translation service is functional. * @param translateFunction Function for translation. * * @return The UserActivitySummary class to use for testing. @@ -333,6 +337,28 @@ public class UserActivitySummaryTest { } } + @Test + public void getRecentDevices_uniqueByDeviceId() + throws TskCoreException, NoServiceProviderException, SleuthkitCaseProviderException, TskCoreException, TranslationException { + + long dataSourceId = 1L; + DataSource dataSource = TskMockUtils.getDataSource(dataSourceId); + BlackboardArtifact item1 = getRecentDeviceArtifact(1001, dataSource, "ID1", "MAKE1", "MODEL1", DAY_SECONDS); + BlackboardArtifact item2 = getRecentDeviceArtifact(1002, dataSource, "ID1", "MAKE1", "MODEL1", DAY_SECONDS + 1); + BlackboardArtifact item3 = getRecentDeviceArtifact(1003, dataSource, "ID1", "MAKE1", "MODEL1", DAY_SECONDS + 2); + + Pair tskPair = getArtifactsTSKMock(Arrays.asList(item1, item2, item3)); + UserActivitySummary summary = getTestClass(tskPair.getLeft(), false, null); + + List results = summary.getRecentDevices(dataSource, 10); + + Assert.assertEquals(1, results.size()); + Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getDateAccessed().getTime() / 1000); + Assert.assertTrue("ID1".equalsIgnoreCase(results.get(0).getDeviceId())); + Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake())); + Assert.assertTrue("MODEL1".equalsIgnoreCase(results.get(0).getDeviceModel())); + } + private static BlackboardArtifact getWebSearchArtifact(long artifactId, DataSource dataSource, String query, Long date) { try { return TskMockUtils.getArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY), artifactId, dataSource, @@ -708,8 +734,8 @@ public class UserActivitySummaryTest { * * @param artifactId The artifact id. * @param dataSource The datasource. - * @param dateRcvd The date received in seconds or null to exclude. - * @param dateSent The date sent in seconds or null to exclude. + * @param dateRcvd The date received in seconds or null to exclude. + * @param dateSent The date sent in seconds or null to exclude. * * @return The mock artifact. */ @@ -738,8 +764,8 @@ public class UserActivitySummaryTest { * * @param artifactId The artifact id. * @param dataSource The datasource. - * @param dateStart The date start in seconds or null to exclude. - * @param dateEnd The date end in seconds or null to exclude. + * @param dateStart The date start in seconds or null to exclude. + * @param dateEnd The date end in seconds or null to exclude. * * @return The mock artifact. */ @@ -768,8 +794,8 @@ public class UserActivitySummaryTest { * * @param artifactId The artifact id. * @param dataSource The datasource. - * @param type The account type. - * @param dateSent The date of the message in seconds. + * @param type The account type. + * @param dateSent The date of the message in seconds. */ private static BlackboardArtifact getMessageArtifact(long artifactId, DataSource dataSource, String type, Long dateTime) { List attributes = new ArrayList<>(); @@ -794,11 +820,11 @@ public class UserActivitySummaryTest { /** * Performs a test on UserActivitySummary.getRecentAccounts. * - * @param dataSource The datasource to use as parameter. - * @param count The count to use as a parameter. - * @param retArtifacts The artifacts to return from - * SleuthkitCase.getArtifacts. This method filters - * based on artifact type from the call. + * @param dataSource The datasource to use as parameter. + * @param count The count to use as a parameter. + * @param retArtifacts The artifacts to return from + * SleuthkitCase.getArtifacts. This method filters based on artifact type + * from the call. * @param expectedResults The expected results. * * @throws TskCoreException diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 56a675e256..823399e0d0 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -205,7 +205,9 @@ DeleteCaseTask.progress.parsingManifest=Parsing manifest file {0}... DeleteCaseTask.progress.releasingManifestLock=Releasing lock on the manifest file {0}... DeleteCaseTask.progress.startMessage=Starting deletion... DeleteOrphanCaseNodesAction.progressDisplayName=Cleanup Case Znodes +# {0} - item count DeleteOrphanCaseNodesDialog.additionalInit.lblNodeCount.text=Znodes found: {0} +# {0} - item count DeleteOrphanCaseNodesDialog.additionalInit.znodesTextArea.countMessage=ZNODES FOUND: {0} DeleteOrphanCaseNodesTask.progress.connectingToCoordSvc=Connecting to the coordination service # {0} - node path diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index fbbbd1999b..f7c9c6d8ff 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015 - 2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -151,6 +151,8 @@ public class SharedConfiguration { /** * Upload the current multi-user ingest settings to a shared folder. * + * @return + * * @throws SharedConfigurationException * @throws CoordinationServiceException * @throws InterruptedException @@ -208,6 +210,7 @@ public class SharedConfiguration { uploadCentralRepositorySettings(remoteFolder); uploadObjectDetectionClassifiers(remoteFolder); uploadPythonModules(remoteFolder); + uploadYARASetting(remoteFolder); try { Files.deleteIfExists(uploadInProgress.toPath()); @@ -222,6 +225,8 @@ public class SharedConfiguration { /** * Download the multi-user settings from a shared folder. * + * @return + * * @throws SharedConfigurationException * @throws InterruptedException */ @@ -252,13 +257,16 @@ public class SharedConfiguration { } try { - /* Make sure all recent changes are saved to the preference file. - This also releases open file handles to the preference files. If this - is not done, then occasionally downloading of shared configuration - fails silently, likely because Java/OS is still holding the file handle. - The problem manifests itself by some of the old/original configuration files - sticking around after shared configuration has seemingly been successfully - updated. */ + /* + * Make sure all recent changes are saved to the preference + * file. This also releases open file handles to the preference + * files. If this is not done, then occasionally downloading of + * shared configuration fails silently, likely because Java/OS + * is still holding the file handle. The problem manifests + * itself by some of the old/original configuration files + * sticking around after shared configuration has seemingly been + * successfully updated. + */ UserPreferences.saveToStorage(); } catch (BackingStoreException ex) { throw new SharedConfigurationException("Failed to save shared configuration settings", ex); @@ -275,6 +283,7 @@ public class SharedConfiguration { downloadCentralRepositorySettings(remoteFolder); downloadObjectDetectionClassifiers(remoteFolder); downloadPythonModules(remoteFolder); + downloadYARASettings(remoteFolder); // Download general settings, then restore the current // values for the unshared fields @@ -344,7 +353,7 @@ public class SharedConfiguration { private void saveNonSharedSettings() { sharedConfigMaster = AutoIngestUserPreferences.getSharedConfigMaster(); sharedConfigFolder = AutoIngestUserPreferences.getSharedConfigFolder(); - showToolsWarning = AutoIngestUserPreferences.getShowToolsWarning(); + showToolsWarning = AutoIngestUserPreferences.getShowToolsWarning(); displayLocalTime = UserPreferences.displayTimesInLocalTime(); hideKnownFilesInDataSource = UserPreferences.hideKnownFilesInDataSourcesTree(); hideKnownFilesInViews = UserPreferences.hideKnownFilesInViewsTree(); @@ -360,7 +369,7 @@ public class SharedConfiguration { private void restoreNonSharedSettings() { AutoIngestUserPreferences.setSharedConfigFolder(sharedConfigFolder); AutoIngestUserPreferences.setSharedConfigMaster(sharedConfigMaster); - AutoIngestUserPreferences.setShowToolsWarning(showToolsWarning); + AutoIngestUserPreferences.setShowToolsWarning(showToolsWarning); UserPreferences.setDisplayTimesInLocalTime(displayLocalTime); UserPreferences.setHideKnownFilesInDataSourcesTree(hideKnownFilesInDataSource); UserPreferences.setHideKnownFilesInViewsTree(hideKnownFilesInViews); @@ -515,21 +524,23 @@ public class SharedConfiguration { throw new SharedConfigurationException(String.format("Failed to copy %s to %s", remoteFile.getAbsolutePath(), localSettingsFolder.getAbsolutePath()), ex); } } - + /** - * Copy an entire local settings folder to the remote folder, deleting any existing files. - * + * Copy an entire local settings folder to the remote folder, deleting any + * existing files. + * * @param localFolder The local folder to copy - * @param remoteBaseFolder The remote folder that will hold a copy of the original folder - * - * @throws SharedConfigurationException + * @param remoteBaseFolder The remote folder that will hold a copy of the + * original folder + * + * @throws SharedConfigurationException */ private void copyLocalFolderToRemoteFolder(File localFolder, File remoteBaseFolder) throws SharedConfigurationException { logger.log(Level.INFO, "Uploading {0} to {1}", new Object[]{localFolder.getAbsolutePath(), remoteBaseFolder.getAbsolutePath()}); - + File newRemoteFolder = new File(remoteBaseFolder, localFolder.getName()); - - if(newRemoteFolder.exists()) { + + if (newRemoteFolder.exists()) { try { FileUtils.deleteDirectory(newRemoteFolder); } catch (IOException ex) { @@ -537,29 +548,30 @@ public class SharedConfiguration { throw new SharedConfigurationException(String.format("Failed to delete remote folder {0}", newRemoteFolder.getAbsolutePath()), ex); } } - + try { FileUtils.copyDirectoryToDirectory(localFolder, remoteBaseFolder); } catch (IOException ex) { throw new SharedConfigurationException(String.format("Failed to copy %s to %s", localFolder, remoteBaseFolder.getAbsolutePath()), ex); - } + } } - + /** - * Copy an entire remote settings folder to the local folder, deleting any existing files. - * No error if the remote folder does not exist. - * + * Copy an entire remote settings folder to the local folder, deleting any + * existing files. No error if the remote folder does not exist. + * * @param localFolder The local folder that will be overwritten. - * @param remoteBaseFolder The remote folder holding the folder that will be copied - * - * @throws SharedConfigurationException + * @param remoteBaseFolder The remote folder holding the folder that will be + * copied + * + * @throws SharedConfigurationException */ private void copyRemoteFolderToLocalFolder(File localFolder, File remoteBaseFolder) throws SharedConfigurationException { logger.log(Level.INFO, "Downloading {0} from {1}", new Object[]{localFolder.getAbsolutePath(), remoteBaseFolder.getAbsolutePath()}); - + // Clean out the local folder regardless of whether the remote version exists. leave the // folder in place since Autopsy expects it to exist. - if(localFolder.exists()) { + if (localFolder.exists()) { try { FileUtils.cleanDirectory(localFolder); } catch (IOException ex) { @@ -567,19 +579,19 @@ public class SharedConfiguration { throw new SharedConfigurationException(String.format("Failed to delete files from local folder {0}", localFolder.getAbsolutePath()), ex); } } - + File remoteSubFolder = new File(remoteBaseFolder, localFolder.getName()); - if(! remoteSubFolder.exists()) { + if (!remoteSubFolder.exists()) { logger.log(Level.INFO, "{0} does not exist", remoteSubFolder.getAbsolutePath()); return; } - + try { FileUtils.copyDirectory(remoteSubFolder, localFolder); } catch (IOException ex) { throw new SharedConfigurationException(String.format("Failed to copy %s from %s", localFolder, remoteBaseFolder.getAbsolutePath()), ex); - } - } + } + } /** * Upload the basic set of auto-ingest settings to the shared folder. @@ -899,56 +911,56 @@ public class SharedConfiguration { /** * Upload the object detection classifiers. - * + * * @param remoteFolder Shared settings folder - * - * @throws SharedConfigurationException + * + * @throws SharedConfigurationException */ private void uploadObjectDetectionClassifiers(File remoteFolder) throws SharedConfigurationException { publishTask("Uploading object detection classfiers"); File classifiersFolder = new File(PlatformUtil.getObjectDetectionClassifierPath()); copyLocalFolderToRemoteFolder(classifiersFolder, remoteFolder); } - + /** * Download the object detection classifiers. - * + * * @param remoteFolder Shared settings folder - * - * @throws SharedConfigurationException + * + * @throws SharedConfigurationException */ private void downloadObjectDetectionClassifiers(File remoteFolder) throws SharedConfigurationException { publishTask("Downloading object detection classfiers"); File classifiersFolder = new File(PlatformUtil.getObjectDetectionClassifierPath()); copyRemoteFolderToLocalFolder(classifiersFolder, remoteFolder); } - - /** + + /** * Upload the Python modules. - * + * * @param remoteFolder Shared settings folder - * - * @throws SharedConfigurationException + * + * @throws SharedConfigurationException */ private void uploadPythonModules(File remoteFolder) throws SharedConfigurationException { publishTask("Uploading python modules"); File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); copyLocalFolderToRemoteFolder(classifiersFolder, remoteFolder); } - + /** * Download the Python modules. - * + * * @param remoteFolder Shared settings folder - * - * @throws SharedConfigurationException + * + * @throws SharedConfigurationException */ private void downloadPythonModules(File remoteFolder) throws SharedConfigurationException { publishTask("Downloading python modules"); File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); copyRemoteFolderToLocalFolder(classifiersFolder, remoteFolder); } - + /** * Upload settings and hash databases to the shared folder. The general * algorithm is: - Copy the general settings in hashsets.xml - For each hash @@ -1093,12 +1105,10 @@ public class SharedConfiguration { Map remoteVersions = readVersionsFromFile(remoteVersionFile); /* - Iterate through remote list - If local needs it, download - - Download remote settings files to local - Download remote versions file to local - HashDbManager reload + * Iterate through remote list If local needs it, download + * + * Download remote settings files to local Download remote versions file + * to local HashDbManager reload */ File localDb = new File(""); File sharedDb = new File(""); @@ -1247,7 +1257,7 @@ public class SharedConfiguration { if (hashDb.getIndexPath().isEmpty() && hashDb.getDatabasePath().isEmpty()) { continue; } - + if (hashDb.hasIndexOnly()) { results.add(hashDb.getIndexPath()); } else { @@ -1356,4 +1366,41 @@ public class SharedConfiguration { throw new SharedConfigurationException(String.format("Failed to calculate CRC for %s", file.getAbsolutePath()), ex); } } + + /** + * Copy the YARA settings directory from the local directory to the remote + * directory. + * + * @param remoteFolder Shared settings folder + * + * @throws + * org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException + */ + private void uploadYARASetting(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading YARA module configuration"); + + File localYara = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "yara").toFile(); + + if (!localYara.exists()) { + return; + } + + copyLocalFolderToRemoteFolder(localYara, remoteFolder); + } + + /** + * Downloads the YARA settings folder from the remote directory to the local + * one. + * + * @param remoteFolder Shared settings folder + * + * @throws + * org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException + */ + private void downloadYARASettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading YARA module configuration"); + File localYara = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "yara").toFile(); + + copyRemoteFolderToLocalFolder(localYara, remoteFolder); + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java index 08ca0ab511..fd0f11ab74 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java @@ -187,7 +187,6 @@ class Chunker implements Iterator, Iterable { private static StringBuilder sanitize(String s) { String normStr = Normalizer.normalize(s, Normalizer.Form.NFKC); return sanitizeToUTF8(replaceInvalidUTF16(normStr)); - } @Override @@ -336,8 +335,9 @@ class Chunker implements Iterator, Iterable { String chunkSegment; if (Character.isHighSurrogate(ch)) { //read another char into the buffer. - charsRead = reader.read(tempChunkBuf, 1, 1); - if (charsRead == -1) { + int surrogateCharsRead = reader.read(tempChunkBuf, 1, 1); + charsRead += surrogateCharsRead; + if (surrogateCharsRead == -1) { //this is the last chunk, so just drop the unpaired surrogate endOfReaderReached = true; return; @@ -352,17 +352,32 @@ class Chunker implements Iterator, Iterable { //cleanup any invalid utf-16 sequences StringBuilder sanitizedChunkSegment = sanitize(chunkSegment); - //check for whitespace. - whitespaceFound = Character.isWhitespace(sanitizedChunkSegment.codePointAt(0)); - //add read chars to the chunk and update the length. - currentChunk.append(sanitizedChunkSegment); - chunkSizeBytes += sanitizedChunkSegment.toString().getBytes(UTF_8).length; - + //get the length in utf8 bytes of the read chars + int segmentSize = chunkSegment.getBytes(UTF_8).length; + // lower case the string and get it's size. NOTE: lower casing can // change the size of the string. String lowerCasedSegment = sanitizedChunkSegment.toString().toLowerCase(); - lowerCasedChunk.append(lowerCasedSegment); - lowerCasedChunkSizeBytes += lowerCasedSegment.getBytes(UTF_8).length; + int lowerCasedSegmentSize = lowerCasedSegment.getBytes(UTF_8).length; + + //if it will not put us past maxBytes + if ((chunkSizeBytes + segmentSize < maxBytes - MAX_CHAR_SIZE_INCREASE_IN_BYTES) + && (lowerCasedChunkSizeBytes + lowerCasedSegmentSize < maxBytes - MAX_CHAR_SIZE_INCREASE_IN_BYTES)) { + + //add read chars to the chunk and update the length. + currentChunk.append(sanitizedChunkSegment); + chunkSizeBytes += segmentSize; + + lowerCasedChunk.append(lowerCasedSegment); + lowerCasedChunkSizeBytes += lowerCasedSegmentSize; + + //check for whitespace. + whitespaceFound = Character.isWhitespace(sanitizedChunkSegment.codePointAt(0)); + } else { + //unread it, and break out of read loop. + reader.unread(tempChunkBuf, 0, charsRead); + return; + } } } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index a842d76f74..7927d43415 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -61,6 +61,7 @@ ExtractOS_progressMessage=Checking for OS ExtractPrefetch_errMsg_prefetchParsingFailed={0}: Error analyzing prefetch files ExtractPrefetch_module_name=Windows Prefetch Extractor ExtractRecycleBin_module_name=Recycle Bin +ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files. ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files @@ -84,16 +85,9 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module -Browser.name.Microsoft.Edge=Microsoft Edge -Browser.name.Yandex=Yandex -Browser.name.Opera=Opera -Browser.name.SalamWeb=SalamWeb -Browser.name.UC.Browser=UC Browser -Browser.name.Brave=Brave -Browser.name.Google.Chrome=Google Chrome Chrome.moduleName=Chromium Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files. Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files. diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java index adc7170482..175a47044e 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java @@ -45,6 +45,7 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -415,6 +416,9 @@ final class ExtractRecycleBin extends Extract { return artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID()))); } + @Messages({ + "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin" + }) /** * Create TSK_RECYCLE_BIN artifact type. * @@ -422,9 +426,9 @@ final class ExtractRecycleBin extends Extract { */ private void createRecycleBinArtifactType() throws TskCoreException { try { - tskCase.addBlackboardArtifactType(RECYCLE_BIN_ARTIFACT_NAME, "Recycle Bin"); //NON-NLS - } catch (TskDataException ex) { - logger.log(Level.INFO, String.format("%s may have already been defined for this case", RECYCLE_BIN_ARTIFACT_NAME)); + tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name()); //NON-NLS + } catch (BlackboardException ex) { + throw new TskCoreException(String.format("An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 9d232cdc84..13ede70c90 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -68,6 +68,7 @@ import java.util.Set; import java.util.HashSet; import static java.util.Locale.US; import static java.util.TimeZone.getTimeZone; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -77,6 +78,7 @@ import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.autopsy.recentactivity.ShellBagParser.ShellBag; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT; @@ -1961,18 +1963,11 @@ class ExtractRegistry extends Extract { */ private BlackboardArtifact.Type getShellBagArtifact() throws TskCoreException { if (shellBagArtifactType == null) { - shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME); - - if (shellBagArtifactType == null) { - try { - tskCase.addBlackboardArtifactType(SHELLBAG_ARTIFACT_NAME, Bundle.Shellbag_Artifact_Display_Name()); //NON-NLS - } catch (TskDataException ex) { - // Artifact already exists - logger.log(Level.INFO, String.format("%s may have already been defined for this case", SHELLBAG_ARTIFACT_NAME)); - } - - shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME); - } + try { + shellBagArtifactType = tskCase.getBlackboard().getOrAddArtifactType(SHELLBAG_ARTIFACT_NAME, Bundle.Shellbag_Artifact_Display_Name()); + } catch (BlackboardException ex) { + throw new TskCoreException(String.format("Failed to get shell bag artifact type", SHELLBAG_ARTIFACT_NAME), ex); + } } return shellBagArtifactType; @@ -1989,12 +1984,12 @@ class ExtractRegistry extends Extract { private BlackboardAttribute.Type getLastWriteAttribute() throws TskCoreException { if (shellBagLastWriteAttributeType == null) { try { - shellBagLastWriteAttributeType = tskCase.addArtifactAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE, + shellBagLastWriteAttributeType = tskCase.getBlackboard().getOrAddAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME, Bundle.Shellbag_Last_Write_Attribute_Display_Name()); - } catch (TskDataException ex) { + } catch (BlackboardException ex) { // Attribute already exists get it from the case - shellBagLastWriteAttributeType = tskCase.getAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE); + throw new TskCoreException(String.format("Failed to get custom attribute %s", SHELLBAG_ATTRIBUTE_LAST_WRITE), ex); } } return shellBagLastWriteAttributeType; @@ -2011,12 +2006,11 @@ class ExtractRegistry extends Extract { private BlackboardAttribute.Type getKeyAttribute() throws TskCoreException { if (shellBagKeyAttributeType == null) { try { - shellBagKeyAttributeType = tskCase.addArtifactAttributeType(SHELLBAG_ATTRIBUTE_KEY, + shellBagKeyAttributeType = tskCase.getBlackboard().getOrAddAttributeType(SHELLBAG_ATTRIBUTE_KEY, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, Bundle.Shellbag_Key_Attribute_Display_Name()); - } catch (TskDataException ex) { - // The attribute already exists get it from the case - shellBagKeyAttributeType = tskCase.getAttributeType(SHELLBAG_ATTRIBUTE_KEY); + } catch (BlackboardException ex) { + throw new TskCoreException(String.format("Failed to get key attribute %s", SHELLBAG_ATTRIBUTE_KEY), ex); } } return shellBagKeyAttributeType; diff --git a/Testing/build.xml b/Testing/build.xml index f0ba6531a4..f8eff4b821 100644 --- a/Testing/build.xml +++ b/Testing/build.xml @@ -76,6 +76,10 @@ + + + + diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index 6f6e04d7bd..f2aa476590 100644 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,9 @@ import junit.framework.Test; import junit.framework.TestCase; import org.netbeans.jemmy.Timeouts; import org.netbeans.junit.NbModuleSuite; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; /** * This test expects the following system properties to be set: img_path: The @@ -100,6 +103,21 @@ public class RegressionTest extends TestCase { public void setUp() { logger.info("######## " + AutopsyTestCases.getEscapedPath(System.getProperty("img_path")) + " #######"); Timeouts.setDefault("ComponentOperator.WaitComponentTimeout", 1000000); + + try { + if (Boolean.parseBoolean(System.getProperty("isMultiUser"))) { + // Set up a custom postgres CR using the configuration passed + // to system properties. + CentralRepoDbManager manager = new CentralRepoDbManager(); + manager.getDbSettingsPostgres().setHost(System.getProperty("crHost")); + manager.getDbSettingsPostgres().setPort(Integer.parseInt(System.getProperty("crPort"))); + manager.getDbSettingsPostgres().setUserName(System.getProperty("crUserName")); + manager.getDbSettingsPostgres().setPassword(System.getProperty("crPassword")); + manager.setupPostgresDb(CentralRepoDbChoice.POSTGRESQL_CUSTOM); + } + } catch (CentralRepoException ex) { + throw new RuntimeException("Error setting up multi user CR", ex); + } } /** diff --git a/appveyor.yml b/appveyor.yml index bc3c7fa82d..fe70c36850 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,14 +6,13 @@ cache: - C:\ProgramData\chocolatey\lib - '%APPVEYOR_BUILD_FOLDER%\Core\test\qa-functional\data' - image: Visual Studio 2015 platform: x64 environment: - global: - TSK_HOME: "C:\\sleuthkit" - JDK_HOME: C:\Program Files\Java\jdk1.8.0 - PYTHON: "C:\\Python36-x64" + global: + TSK_HOME: "C:\\sleuthkit" + JDK_HOME: C:\Program Files\Java\jdk1.8.0 + PYTHON: "C:\\Python36-x64" install: - ps: choco install nuget.commandline @@ -42,4 +41,6 @@ build_script: - cd %APPVEYOR_BUILD_FOLDER% - cmd: ant -q build -test: off +test_script: + - cd %APPVEYOR_BUILD_FOLDER% + - cmd: ant -q test-no-regression diff --git a/build.xml b/build.xml index 1cab40b475..fc25cba14d 100644 --- a/build.xml +++ b/build.xml @@ -24,7 +24,7 @@ + unless="supported-java-versions"/> @@ -68,22 +68,54 @@
- + - + - + + + + + + + + + + + + + + + + + + ZIP target and then opens it up and adds in any files that we want. This is where we customize the + version number. --> @@ -128,15 +160,15 @@ - - - - - - - - - + + + + + + + + + @@ -146,9 +178,9 @@ + shadow the files in the autopsy/modules/lib/ARCHITECTURE folder in the JAR. + These files are legacy from when we used to copy the dlls to this location. + This check should do away in the future. Added Sept '13--> @@ -188,9 +220,9 @@ + message="Enter the desired build type:" + validargs="DEVELOPMENT,RELEASE" + defaultvalue="DEVELOPMENT"/> @@ -228,21 +260,21 @@ ${app.name} branding + file="${branding.dir}/core/core.jar/org/netbeans/core/startup/Bundle.properties" + comment="Updated by build script"> + file="${branding.dir}/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties" + comment="Updated by build script"> + file="${basedir}/Core/src/org/sleuthkit/autopsy/coreutils/Version.properties" + comment="Updated by build script"> @@ -329,7 +361,7 @@ - + diff --git a/docs/doxygen-user/multi-user/installPostgres.dox b/docs/doxygen-user/multi-user/installPostgres.dox index fd7753281b..d56afe931b 100644 --- a/docs/doxygen-user/multi-user/installPostgres.dox +++ b/docs/doxygen-user/multi-user/installPostgres.dox @@ -13,7 +13,7 @@ You should ensure that the database folder is backed up. To install PostgreSQL, perform the following steps: -1. Download a 64-bit PostgreSQL installer from http://www.enterprisedb.com/products-services-training/pgdownload#windows Choose the one that says _Win X86-64_. Autopsy has been tested with PostgreSQL version 9.5. +1. Download a 64-bit PostgreSQL installer from https://www.enterprisedb.com/downloads/postgres-postgresql-downloads Choose one under Windows x86-64. Autopsy has been tested with PostgreSQL version 9.5. 2. Run the installer. The name will be similar to _postgresql-9.5.3-1-windows-x64.exe_. diff --git a/test/script/regression.py b/test/script/regression.py index 9999a5c563..94b50c244f 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -471,6 +471,10 @@ class TestRunner(object): test_data.ant.append("-DsolrPort=" + str(test_config.solrPort)) test_data.ant.append("-DmessageServiceHost=" + test_config.messageServiceHost) test_data.ant.append("-DmessageServicePort=" + str(test_config.messageServicePort)) + test_data.ant.append("-DcrHost=" + str(test_config.crHost)) + test_data.ant.append("-DcrPort=" + str(test_config.crPort)) + test_data.ant.append("-DcrUserName=" + str(test_config.crUserName)) + test_data.ant.append("-DcrPassword=" + str(test_config.crPassword)) if test_data.isMultiUser: test_data.ant.append("-DisMultiUser=true") # Note: test_data has autopys_version attribute, but we couldn't see it from here. It's set after run ingest. @@ -854,6 +858,14 @@ class TestConfiguration(object): self.messageServicePort = parsed_config.getElementsByTagName("messageServicePort")[0].getAttribute("value").encode().decode("utf_8") if parsed_config.getElementsByTagName("multiUser_outdir"): self.multiUser_outdir = parsed_config.getElementsByTagName("multiUser_outdir")[0].getAttribute("value").encode().decode("utf_8") + if parsed_config.getElementsByTagName("crHost"): + self.crHost = parsed_config.getElementsByTagName("crHost")[0].getAttribute("value").encode().decode("utf_8") + if parsed_config.getElementsByTagName("crPort"): + self.crPort = parsed_config.getElementsByTagName("crPort")[0].getAttribute("value").encode().decode("utf_8") + if parsed_config.getElementsByTagName("crUserName"): + self.crUserName = parsed_config.getElementsByTagName("crUserName")[0].getAttribute("value").encode().decode("utf_8") + if parsed_config.getElementsByTagName("crPassword"): + self.crPassword = parsed_config.getElementsByTagName("crPassword")[0].getAttribute("value").encode().decode("utf_8") self._init_imgs(parsed_config) self._init_build_info(parsed_config) diff --git a/test/script/regression_utils.py b/test/script/regression_utils.py index 51fa3eb1c4..0c0229beb2 100644 --- a/test/script/regression_utils.py +++ b/test/script/regression_utils.py @@ -27,7 +27,7 @@ def make_os_path(platform, *dirs): path += str(dir).replace('\\', '/') + '/' return path_fix(path) elif platform == "win32": - return make_path(dirs) + return make_path(*dirs) else: print("Couldn't make path, because we only support Windows and Cygwin at this time.") sys.exit(1) diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py index f152cd923a..e202c3e111 100644 --- a/test/script/tskdbdiff.py +++ b/test/script/tskdbdiff.py @@ -464,6 +464,16 @@ def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info # remove object ID if files_index: + + # Ignore TIFF size and hash if extracted from PDFs. + # See JIRA-6951 for more details. + # index -1 = last element in the list, which is extension + # index -3 = 3rd from the end, which is the parent path. + if fields_list[-1] == "'tif'" and fields_list[-3].endswith(".pdf/'"): + fields_list[15] = "'SIZE_IGNORED'" + fields_list[23] = "'MD5_IGNORED'" + fields_list[24] = "'SHA256_IGNORED'" + newLine = ('INSERT INTO "tsk_files" VALUES(' + ', '.join(fields_list[1:]) + ');') # Remove object ID from Unalloc file name newLine = re.sub('Unalloc_[0-9]+_', 'Unalloc_', newLine) diff --git a/thirdparty/iLeapp/ileapp.exe b/thirdparty/iLeapp/ileapp.exe index 8176b4f679..d17ab28f4d 100644 Binary files a/thirdparty/iLeapp/ileapp.exe and b/thirdparty/iLeapp/ileapp.exe differ diff --git a/thirdparty/rr-full/plugins/shellactivities.pl b/thirdparty/rr-full/plugins/shellactivities.pl index 8b16917536..5df9f5615f 100644 --- a/thirdparty/rr-full/plugins/shellactivities.pl +++ b/thirdparty/rr-full/plugins/shellactivities.pl @@ -70,6 +70,7 @@ sub processShellActivities { ::rptMsg(""); while ($offset < ($sz - 10)) { + # Code to locate the appropriate identifier $tag = 1; while ($tag) { @@ -78,9 +79,15 @@ sub processShellActivities { } else { $offset++; + # Check if at end of file and exit loop if it is + last if ($offset >= $sz ); } } - + + # Check if at end of file and exit loop if it is + last if ($offset >= $sz ); + + $offset += 2; $l = unpack("C",substr($data,$offset,1)); # ::rptMsg("String Length: ".sprintf "0x%x",$l); diff --git a/thirdparty/yara/ReadMe.txt b/thirdparty/yara/ReadMe.txt index 31f38633b4..98c356f1b6 100755 --- a/thirdparty/yara/ReadMe.txt +++ b/thirdparty/yara/ReadMe.txt @@ -1,7 +1,7 @@ This folder contains the projects you need for building and testing the yarabridge.dll and YaraJNIWrapper.jar. bin: -Contains the built dll and jar. +Contains the built jar and jarac64.exe. jarac64.exe is used to by the ingest module to compile the rule files. yarabridge: VS project to create the dll that wraps the the libyara library. @@ -18,7 +18,8 @@ Steps for building yarabridge, YaraJNIWrapper and YaraWrapperTest. - Build Release x64. 3. Open the yarabridge project and build Release x64. -If you have link issues, make sure you build release x64 in the previous step. - -This project will automatically copy the built dll to the bin folder. + -This project will automatically copy the built dll into the YaraJNIWrapper src\org\sleuthkit\autopsy\yara folder. + - This is where is needs to be so that its included into the jar file. 4. Build YaraJNIWrapper - Open in netbeans and select Build. - Manually move the newly build jar file to the bin folder. After building the jar file can be found in diff --git a/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml b/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml index 38dd8d0c87..d5569a48c3 100755 --- a/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml +++ b/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml @@ -179,9 +179,7 @@ is divided into following sections: - - - + @@ -289,7 +287,6 @@ is divided into following sections: Must set src.dir - Must set test.src.dir Must set build.dir Must set dist.dir Must set build.classes.dir @@ -588,9 +585,6 @@ is divided into following sections: - - - @@ -613,11 +607,7 @@ is divided into following sections: - - - - - + @@ -1544,14 +1534,14 @@ is divided into following sections: - - + + - + @@ -1592,17 +1582,15 @@ is divided into following sections: - + - + - - - + @@ -1616,14 +1604,12 @@ is divided into following sections: Must select some files in the IDE or set javac.includes - + - - - + diff --git a/thirdparty/yara/YaraJNIWrapper/nbproject/private/private.xml b/thirdparty/yara/YaraJNIWrapper/nbproject/private/private.xml index 475096252c..f6db5d6149 100755 --- a/thirdparty/yara/YaraJNIWrapper/nbproject/private/private.xml +++ b/thirdparty/yara/YaraJNIWrapper/nbproject/private/private.xml @@ -1,4 +1,9 @@ + + + file:/C:/Users/kelly/Workspace/autopsy/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java + + diff --git a/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties b/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties index a0ef4dac37..0af470a2bf 100755 --- a/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties +++ b/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties @@ -1,9 +1,10 @@ annotation.processing.enabled=true annotation.processing.enabled.in.editor=false -annotation.processing.processor.options= annotation.processing.processors.list= annotation.processing.run.all.processors=true annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=YaraJNIWrapper +application.vendor=kelly build.classes.dir=${build.dir}/classes build.classes.excludes=**/*.java,**/*.form # This directory is removed when the project is cleaned: @@ -32,10 +33,13 @@ dist.jar=${dist.dir}/YaraJNIWrapper.jar dist.javadoc.dir=${dist.dir}/javadoc dist.jlink.dir=${dist.dir}/jlink dist.jlink.output=${dist.jlink.dir}/YaraJNIWrapper +endorsed.classpath= excludes= +file.reference.yara-lib=src/org/sleuthkit/autopsy/yara/lib includes=** jar.compress=false -javac.classpath= +javac.classpath=\ + ${file.reference.yara-lib} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false @@ -90,4 +94,3 @@ run.test.modulepath=\ ${javac.test.modulepath} source.encoding=UTF-8 src.dir=src -test.src.dir=test diff --git a/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml b/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml index df43138d7e..89ae97a48b 100755 --- a/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml +++ b/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml @@ -7,9 +7,7 @@ - - - + diff --git a/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java index 0fc5e8f0f4..b14cea7fd3 100755 --- a/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java +++ b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java @@ -18,9 +18,11 @@ */ package org.sleuthkit.autopsy.yara; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,18 +33,12 @@ import java.util.logging.Logger; */ public class YaraJNIWrapper { - // Load the yarabridge.dll which should be located in the same directory as - // the jar file. If we need to use this code for debugging the dll this - // code will need to be modified to add that support. static { - Path directoryPath = null; try { - directoryPath = Paths.get(YaraJNIWrapper.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().toAbsolutePath(); - } catch (URISyntaxException ex) { + extractAndLoadDll(); + } catch (IOException | YaraWrapperException ex) { Logger.getLogger(YaraJNIWrapper.class.getName()).log(Level.SEVERE, null, ex); } - String libraryPath = Paths.get(directoryPath != null ? directoryPath.toString() : "", "yarabridge.dll").toAbsolutePath().toString(); - System.load(libraryPath); } /** @@ -50,19 +46,65 @@ public class YaraJNIWrapper { * * The rule path must be to a yara compile rule file. * - * @param compiledRulesPath - * @param byteBuffer + * @param compiledRulesPath Absolute path to a compiled YARA rule file. + * @param byteBuffer File buffer. + * @param bufferSize Size of the byte to read in the given buffer + * @param timeoutSec Scan timeout value in seconds. * * @return List of rules found rules. Null maybe returned if error occurred. * * @throws YaraWrapperException */ - static public native List findRuleMatch(String compiledRulesPath, byte[] byteBuffer) throws YaraWrapperException; + static public native List findRuleMatch(String compiledRulesPath, byte[] byteBuffer, int bufferSize, int timeoutSec) throws YaraWrapperException; + + /** + * Returns a list of matching YARA rules found in the given file. + * + * @param compiledRulePath Absolute path to a compiled YARA rule file. + * @param filePath Absolute path to the file to search. + * @param timeoutSec Scan timeout value in seconds. + * + * @return List of rules found rules. Null maybe returned if error occurred. + * + * + * @throws YaraWrapperException + */ + static public native List findRuleMatchFile(String compiledRulePath, String filePath, int timeoutSec) throws YaraWrapperException; + + /** + * Copy yarabridge.dll from inside the jar to a temp file that can be loaded + * with System.load. + * + * To make this work, the dll needs to be in the same folder as this source + * file. The dll needs to be located somewhere in the jar class path. + * + * @throws IOException + * @throws YaraWrapperException + */ + static private void extractAndLoadDll() throws IOException, YaraWrapperException { + File tempFile = File.createTempFile("lib", null); + tempFile.deleteOnExit(); + try (InputStream in = YaraJNIWrapper.class.getResourceAsStream("yarabridge.dll")) { + if (in == null) { + throw new YaraWrapperException("native library was not found in jar file."); + } + try (OutputStream out = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[1024]; + int lengthRead; + while ((lengthRead = in.read(buffer)) > 0) { + out.write(buffer, 0, lengthRead); + out.flush(); + } + } + } + + System.load(tempFile.getAbsolutePath()); + } /** * private constructor. */ private YaraJNIWrapper() { } - + } diff --git a/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/yarabridge.dll b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/yarabridge.dll new file mode 100755 index 0000000000..4be3a859e1 Binary files /dev/null and b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/yarabridge.dll differ diff --git a/thirdparty/yara/YaraWrapperTest/nbproject/project.properties b/thirdparty/yara/YaraWrapperTest/nbproject/project.properties index c0126ab42a..b7874aae82 100755 --- a/thirdparty/yara/YaraWrapperTest/nbproject/project.properties +++ b/thirdparty/yara/YaraWrapperTest/nbproject/project.properties @@ -35,7 +35,7 @@ dist.jlink.dir=${dist.dir}/jlink dist.jlink.output=${dist.jlink.dir}/YaraWrapperTest endorsed.classpath= excludes= -file.reference.YaraJNIWrapper.jar=../bin/YaraJNIWrapper.jar +file.reference.YaraJNIWrapper.jar=../YaraJNIWrapper/dist/YaraJNIWrapper.jar includes=** jar.compress=false javac.classpath=\ diff --git a/thirdparty/yara/YaraWrapperTest/src/org/sleuthkit/autopsy/yara/YaraWrapperTest.java b/thirdparty/yara/YaraWrapperTest/src/org/sleuthkit/autopsy/yara/YaraWrapperTest.java index 4a57abfef2..ee25676896 100755 --- a/thirdparty/yara/YaraWrapperTest/src/org/sleuthkit/autopsy/yara/YaraWrapperTest.java +++ b/thirdparty/yara/YaraWrapperTest/src/org/sleuthkit/autopsy/yara/YaraWrapperTest.java @@ -26,8 +26,6 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import org.sleuthkit.autopsy.yara.YaraJNIWrapper; -import org.sleuthkit.autopsy.yara.YaraWrapperException; /** * Tests the YaraJNIWrapper code. @@ -43,6 +41,7 @@ public class YaraWrapperTest { } testFileRuleMatch(args[0], args[1]); + testFileRuleMatchFile(args[0], args[1]); } /** @@ -58,7 +57,7 @@ public class YaraWrapperTest { try { byte[] data = Files.readAllBytes(path); - List list = YaraJNIWrapper.findRuleMatch(compiledRulePath, data); + List list = YaraJNIWrapper.findRuleMatch(compiledRulePath, data, data.length, 100); if (list != null) { if (list.isEmpty()) { @@ -77,5 +76,34 @@ public class YaraWrapperTest { logger.log(Level.SEVERE, "Error thrown from yarabridge", ex); } } + + /** + * Test the call to findRuleMatchFile which takes a compiled rule file + * path and a path to a file. + * + * @param compiledRulePath + * @param filePath + */ + private static void testFileRuleMatchFile(String compiledRulePath, String filePath) { + try { + List list = YaraJNIWrapper.findRuleMatchFile(compiledRulePath, filePath, 100); + + if (list != null) { + if (list.isEmpty()) { + System.out.println("FindRuleMatch return an empty list"); + } else { + System.out.println("Matching Rules:"); + for (String s : list) { + System.out.println(s); + } + } + } else { + logger.log(Level.SEVERE, "FindRuleMatch return a null list"); + } + + } catch (YaraWrapperException ex) { + logger.log(Level.SEVERE, "Error thrown from yarabridge", ex); + } + } } diff --git a/thirdparty/yara/bin/YaraJNIWrapper.jar b/thirdparty/yara/bin/YaraJNIWrapper.jar index 749d7a6ae7..21fe881a82 100755 Binary files a/thirdparty/yara/bin/YaraJNIWrapper.jar and b/thirdparty/yara/bin/YaraJNIWrapper.jar differ diff --git a/thirdparty/yara/bin/yarabridge.dll b/thirdparty/yara/bin/yarabridge.dll deleted file mode 100755 index c74062a626..0000000000 Binary files a/thirdparty/yara/bin/yarabridge.dll and /dev/null differ diff --git a/thirdparty/yara/bin/yarac64.exe b/thirdparty/yara/bin/yarac64.exe new file mode 100755 index 0000000000..bf94cc4462 Binary files /dev/null and b/thirdparty/yara/bin/yarac64.exe differ diff --git a/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.cpp b/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.cpp index 0d36d2a039..0fc7f63806 100755 --- a/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.cpp +++ b/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.cpp @@ -20,7 +20,6 @@ using std::string; using std::vector; - /* Callback method to be passed to yr_rules_scan_mem method. user_data is expected to be a pointer to a string vector. @@ -79,49 +78,85 @@ jobject createArrayList(JNIEnv *env, std::vector vector) { return list; } +/* + Loads the compiled rules file returning a YARA error code. + Throws a java exeception if there are any issues. +*/ +int loadRuleFile(JNIEnv * env, jstring compiledRulePath, YR_RULES **rules) { + char errorMessage[256]; + const char *nativeString = env->GetStringUTFChars(compiledRulePath, 0); + int result = yr_rules_load(nativeString, rules); + + if (result != ERROR_SUCCESS) { + sprintf_s(errorMessage, "Failed to load compiled yara rule %s (error code = %d)\n", nativeString, result); + throwException(env, errorMessage); + } + + env->ReleaseStringUTFChars(compiledRulePath, nativeString); + + return result; +} + +/* + Initalize the YARA library, if needed. yr_initialize only needs to be called once. +*/ +int initalizeYaraLibrary(JNIEnv * env) { + static int library_initalized = 0; + char errorMessage[256]; + int result = ERROR_SUCCESS; + if (library_initalized == 0) { + if ((result = yr_initialize()) != ERROR_SUCCESS) { + sprintf_s(errorMessage, "libyara initialization error (%d)\n", result); + throwException(env, errorMessage); + } + library_initalized = 1; + } + + return result; +} + /* * Class: org_sleuthkit_autopsy_yara_YaraJNIWrapper * Method: FindRuleMatch * Signature: (Ljava/lang/String;[B)Ljava/util/List; */ JNIEXPORT jobject JNICALL Java_org_sleuthkit_autopsy_yara_YaraJNIWrapper_findRuleMatch -(JNIEnv * env, jclass cls, jstring compiledRulePath, jbyteArray fileByteArray) { +(JNIEnv * env, jclass cls, jstring compiledRulePath, jbyteArray fileByteArray, jint byteArrayLength, jint timeoutSec) { char errorMessage[256]; - const char *nativeString = env->GetStringUTFChars(compiledRulePath, 0); jobject resultList = NULL; - int result; - if ((result = yr_initialize()) != ERROR_SUCCESS) { - sprintf_s(errorMessage, "libyara initialization error (%d)\n", result); - throwException(env, errorMessage); + YR_RULES *rules = NULL; + + if ((result = initalizeYaraLibrary(env)) != ERROR_SUCCESS) { return resultList; } + while (1) { - YR_RULES *rules = NULL; - if ((result = yr_rules_load(nativeString, &rules)) != ERROR_SUCCESS) { - sprintf_s(errorMessage, "Failed to load compiled yara rules (%d)\n", result); - throwException(env, errorMessage); + if((result = loadRuleFile(env, compiledRulePath, &rules)) != ERROR_SUCCESS) { break; } - boolean isCopy; - int byteArrayLength = env->GetArrayLength(fileByteArray); if (byteArrayLength == 0) { throwException(env, "Unable to scan for matches. File byte array size was 0."); break; } + boolean isCopy; jbyte* nativeByteArray = env->GetByteArrayElements(fileByteArray, &isCopy); - int flags = 0; std::vector scanResults; - result = yr_rules_scan_mem(rules, (unsigned char*)nativeByteArray, byteArrayLength, flags, callback, &scanResults, 1000000); + result = yr_rules_scan_mem(rules, (unsigned char*)nativeByteArray, byteArrayLength, 0, callback, &scanResults, timeoutSec); env->ReleaseByteArrayElements(fileByteArray, nativeByteArray, 0); if (result != ERROR_SUCCESS) { - sprintf_s(errorMessage, "Yara file scan failed (%d)\n", result); + if (result == ERROR_SCAN_TIMEOUT) { + sprintf_s(errorMessage, "Yara file scan timed out"); + } + else { + sprintf_s(errorMessage, "Yara file scan failed (%d)\n", result); + } throwException(env, errorMessage); break; } @@ -130,9 +165,60 @@ JNIEXPORT jobject JNICALL Java_org_sleuthkit_autopsy_yara_YaraJNIWrapper_findRul break; } - env->ReleaseStringUTFChars(compiledRulePath, nativeString); - yr_finalize(); + if (rules != NULL) { + yr_rules_destroy(rules); + } return resultList; +} + +/* +* Class: org_sleuthkit_autopsy_yara_YaraJNIWrapper +* Method: findRuleMatchFile +* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/util/List; +*/ +JNIEXPORT jobject JNICALL Java_org_sleuthkit_autopsy_yara_YaraJNIWrapper_findRuleMatchFile +(JNIEnv * env, jclass cls, jstring compiledRulePath, jstring filePath, jint timeoutSec) { + + char errorMessage[256]; + jobject resultList = NULL; + int result; + YR_RULES *rules = NULL; + + if ((result = initalizeYaraLibrary(env)) != ERROR_SUCCESS) { + return resultList; + } + + + while (1) { + if ((result = loadRuleFile(env, compiledRulePath, &rules)) != ERROR_SUCCESS) { + break; + } + + std::vector scanResults; + const char *nativeString = env->GetStringUTFChars(filePath, 0); + + result = yr_rules_scan_file(rules, nativeString, 0, callback, &scanResults, timeoutSec); + + if (result != ERROR_SUCCESS) { + if (result == ERROR_SCAN_TIMEOUT) { + sprintf_s(errorMessage, "Yara file scan timed out on file %s", nativeString); + } + else { + sprintf_s(errorMessage, "Yara file scan failed (%d)\n", result); + } + throwException(env, errorMessage); + break; + } + + resultList = createArrayList(env, scanResults); + break; + } + + if (rules != NULL) { + yr_rules_destroy(rules); + } + + return resultList; } \ No newline at end of file diff --git a/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.h b/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.h index 6c5f5f5d75..d93db2ac7a 100755 --- a/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.h +++ b/thirdparty/yara/yarabridge/yarabridge/YaraJNIWrapper.h @@ -13,7 +13,15 @@ extern "C" { * Signature: (Ljava/lang/String;[B)Ljava/util/List; */ JNIEXPORT jobject JNICALL Java_org_sleuthkit_autopsy_yara_YaraJNIWrapper_findRuleMatch - (JNIEnv *, jclass, jstring, jbyteArray); + (JNIEnv *, jclass, jstring, jbyteArray, jint, jint); + + /* + * Class: org_sleuthkit_autopsy_yara_YaraJNIWrapper + * Method: findRuleMatchFile + * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/util/List; + */ + JNIEXPORT jobject JNICALL Java_org_sleuthkit_autopsy_yara_YaraJNIWrapper_findRuleMatchFile + (JNIEnv *, jclass, jstring, jstring, jint); #ifdef __cplusplus } diff --git a/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj b/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj index ce5dd10c80..c049e81dc9 100755 --- a/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj +++ b/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj @@ -113,7 +113,7 @@ ws2_32.lib;crypt32.lib;libyara64.lib;%(AdditionalDependencies) - copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\bin\$(ProjectName).dll" + copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\YaraJNIWrapper\src\org\sleuthkit\autopsy\yara\$(ProjectName).dll" @@ -153,7 +153,7 @@ ws2_32.lib;crypt32.lib;libyara64.lib;%(AdditionalDependencies) - copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\bin\$(ProjectName).dll" + copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\YaraJNIWrapper\src\org\sleuthkit\autopsy\yara\$(ProjectName).dll" diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java index d662c5f5e6..886a3bc41f 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java @@ -53,6 +53,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.AccountFileInstance; import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; @@ -421,14 +422,16 @@ final class VcardParser { if (attributeType == null) { try{ // Add this attribute type to the case database. - attributeType = tskCase.addArtifactAttributeType(attributeTypeName, + attributeType = tskCase.getBlackboard().getOrAddAttributeType(attributeTypeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, String.format("Phone Number (%s)", StringUtils.capitalize(splitType.toLowerCase()))); - }catch (TskDataException ex) { - attributeType = tskCase.getAttributeType(attributeTypeName); + + ThunderbirdMboxFileIngestModule.addArtifactAttribute(telephoneText, attributeType, attributes); + }catch (BlackboardException ex) { + logger.log(Level.WARNING, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); } } - ThunderbirdMboxFileIngestModule.addArtifactAttribute(telephoneText, attributeType, attributes); + } catch (TskCoreException ex) { logger.log(Level.WARNING, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); } @@ -474,14 +477,14 @@ final class VcardParser { BlackboardAttribute.Type attributeType = tskCase.getAttributeType(attributeTypeName); if (attributeType == null) { // Add this attribute type to the case database. - attributeType = tskCase.addArtifactAttributeType(attributeTypeName, + attributeType = tskCase.getBlackboard().getOrAddAttributeType(attributeTypeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, String.format("Email (%s)", StringUtils.capitalize(splitType.toLowerCase()))); } ThunderbirdMboxFileIngestModule.addArtifactAttribute(email.getValue(), attributeType, attributes); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); - } catch (TskDataException ex) { + } catch (BlackboardException ex) { logger.log(Level.SEVERE, String.format("Unable to add custom attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); } } diff --git a/travis_build.sh b/travis_build.sh index 5f574d5726..d3753fd934 100755 --- a/travis_build.sh +++ b/travis_build.sh @@ -8,5 +8,19 @@ pushd bindings/java && ant -q dist && popd echo "Building Autopsy..." && echo -en 'travis_fold:start:script.build\\r' cd $TRAVIS_BUILD_DIR/ -ant build +ant -q build echo -en 'travis_fold:end:script.build\\r' + +echo "Testing Autopsy..." && echo -en 'travis_fold:start:script.tests\\r' +echo "Free Space:" +echo `df -h .` + +if [ "${TRAVIS_OS_NAME}" = "osx" ]; then + # if os x, just run it + ant -q test-no-regression +elif [ "${TRAVIS_OS_NAME}" = "linux" ]; then + # if linux use xvfb + xvfb-run ant -q test-no-regression +fi + +echo -en 'travis_fold:end:script.tests\\r'