diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index fbb1430cc4..30f782983e 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -52,8 +52,8 @@ Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}. Case.exceptionMessage.metadataUpdateError=Failed to update case metadata # {0} - exception message Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}. -Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case. -Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case. +Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case. +Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case. Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User. # {0} - image Case.openFileSystems.openingImage=Opening all filesystems for image: {0}... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 62dd5ddc3b..c2fd9d822f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2828,8 +2828,8 @@ public class Case { * @throws CaseActionException If the lock cannot be acquired. */ @Messages({ - "Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.", - "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case." + "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.", + "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case." }) private void acquireCaseLock(CaseLockType lockType) throws CaseActionException { String caseDir = metadata.getCaseDirectory(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED index 0fc6762cc3..329bcdd88f 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED @@ -39,9 +39,9 @@ GlobalSettingsPanel.askForCentralRepoDbChoice.sqliteChoice.text=Use SQLite GlobalSettingsPanel.onMultiUserChange.disabledMu.description=The Central Repository will be reconfigured to use a local SQLite database. GlobalSettingsPanel.onMultiUserChange.disabledMu.description2=Press Configure PostgreSQL to change to a PostgreSQL database. GlobalSettingsPanel.onMultiUserChange.disabledMu.title=Central Repository Change Necessary -GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL database? -GlobalSettingsPanel.onMultiUserChange.enable.description2=The Central Repository stores hash values and accounts from past cases. -GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository? +GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL server? +GlobalSettingsPanel.onMultiUserChange.enable.description2=Any data in an existing SQLite Central Repository will not be transferred to the new database. +GlobalSettingsPanel.onMultiUserChange.enable.title=Central Repository GlobalSettingsPanel.testCurrentConfiguration.dbDoesNotExist.message=Database does not exist. GlobalSettingsPanel.validationErrMsg.ingestRunning=You cannot change settings while ingest is running. GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 7cb1e566dd..d4dc547018 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -151,9 +151,9 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i * as of most recent change. */ @NbBundle.Messages({ - "GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository?", - "GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL database?", - "GlobalSettingsPanel.onMultiUserChange.enable.description2=The Central Repository stores hash values and accounts from past cases." + "GlobalSettingsPanel.onMultiUserChange.enable.title=Central Repository", + "GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL server?", + "GlobalSettingsPanel.onMultiUserChange.enable.description2=Any data in an existing SQLite Central Repository will not be transferred to the new database." }) public static void onMultiUserChange(Component parent, boolean muPreviouslySelected, boolean muCurrentlySelected) { boolean crEnabled = CentralRepoDbUtil.allowUseOfCentralRepository(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTPersonaCache.java b/Core/src/org/sleuthkit/autopsy/communications/CVTPersonaCache.java index 974d7ca299..ac81b15de6 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTPersonaCache.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTPersonaCache.java @@ -82,9 +82,9 @@ final public class CVTPersonaCache { } /** - * Returns the list of PersonaAccounts for the given Account typeSpecificId. + * Returns the list of PersonaAccounts for the given Account. * - * @param typeSpecificID Account typeSpecificId. + * @param account The account. * * @return List of PersonaAccounts for id or empty list if none were found. * diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.form index c2451e28cb..4a4133f5e4 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.form @@ -11,30 +11,32 @@ - - + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java index 6dbf8f34e5..1b07ecd754 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java @@ -37,7 +37,6 @@ 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.apache.commons.lang.StringUtils; @@ -82,6 +81,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i private final GridBagLayout gridBagLayout = new GridBagLayout(); private final GridBagConstraints gridBagConstraints = new GridBagConstraints(); private final Map orderingMap = new HashMap<>(); + private final javax.swing.JPanel detailsPanel = new javax.swing.JPanel(); /** * Creates new form GeneralPurposeArtifactViewer. @@ -90,6 +90,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i public GeneralPurposeArtifactViewer() { addOrderings(); initComponents(); + gridBagConstraints.anchor = GridBagConstraints.FIRST_LINE_START; detailsPanel.setLayout(gridBagLayout); } @@ -161,8 +162,9 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i } updateView(artifact, attributeMap, dataSourceName, sourceFileName); } + detailsScrollPane.setViewportView(detailsPanel); + detailsScrollPane.revalidate(); revalidate(); - repaint(); } /** @@ -172,7 +174,8 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i private void resetComponent() { // clear the panel detailsPanel.removeAll(); - gridBagConstraints.anchor = GridBagConstraints.FIRST_LINE_START; + detailsPanel.setLayout(gridBagLayout); + detailsPanel.revalidate(); gridBagConstraints.gridy = 0; gridBagConstraints.gridx = LABEL_COLUMN; gridBagConstraints.weighty = 0.0; @@ -183,14 +186,6 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i @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_AS_NEEDED); - } - - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - @Override - public boolean isSupported(BlackboardArtifact artifact) { return (artifact != null) && (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() @@ -218,22 +213,22 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i // //GEN-BEGIN:initComponents private void initComponents() { - detailsPanel = new javax.swing.JPanel(); + detailsScrollPane = new javax.swing.JScrollPane(); - setLayout(new java.awt.BorderLayout()); + detailsScrollPane.setPreferredSize(new java.awt.Dimension(300, 0)); - javax.swing.GroupLayout detailsPanelLayout = new javax.swing.GroupLayout(detailsPanel); - detailsPanel.setLayout(detailsPanelLayout); - detailsPanelLayout.setHorizontalGroup( - detailsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(detailsScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); - detailsPanelLayout.setVerticalGroup( - detailsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(detailsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(0, 0, 0)) ); - - add(detailsPanel, java.awt.BorderLayout.PAGE_START); }// //GEN-END:initComponents /** @@ -243,7 +238,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i * @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 + * @param sourceFilePath The path of the file that caused the creation of * the artifact. */ @NbBundle.Messages({"GeneralPurposeArtifactViewer.dates.created=Created", @@ -257,7 +252,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i private void updateView(BlackboardArtifact artifact, Map> attributeMap, String dataSourceName, String sourceFilePath) { final Integer artifactTypeId = artifact.getArtifactTypeID(); if (!(artifactTypeId < 1 || artifactTypeId >= Integer.MAX_VALUE)) { - addDetailsHeader(artifactTypeId); + JTextPane firstTextPane = addDetailsHeader(artifactTypeId); Integer[] orderingArray = orderingMap.get(artifactTypeId); if (orderingArray == null) { orderingArray = DEFAULT_ORDERING; @@ -282,7 +277,6 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i addNameValueRow(bba.getAttributeType().getDisplayName(), displayString); } else { addNameValueRow(bba.getAttributeType().getDisplayName(), bba.getDisplayString()); - } } } @@ -311,7 +305,11 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i addNameValueRow(Bundle.GeneralPurposeArtifactViewer_details_file(), sourceFilePath); // add veritcal glue at the end addPageEndGlue(); + if (firstTextPane != null) { + firstTextPane.setCaretPosition(0); + } } + detailsPanel.revalidate(); } /** @@ -353,7 +351,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i "GeneralPurposeArtifactViewer.details.searchHeader=Web Search", "GeneralPurposeArtifactViewer.details.cachedHeader=Cached File", "GeneralPurposeArtifactViewer.details.cookieHeader=Cookie Details",}) - private void addDetailsHeader(int artifactTypeId) { + private JTextPane addDetailsHeader(int artifactTypeId) { String header; if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()) { header = Bundle.GeneralPurposeArtifactViewer_details_historyHeader(); @@ -370,7 +368,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i } else { header = Bundle.GeneralPurposeArtifactViewer_details_attrHeader(); } - addHeader(header); + return addHeader(header); } /** @@ -381,16 +379,21 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i * @return JLabel Heading label added. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - private JLabel addHeader(String headerString) { + private JTextPane addHeader(String headerString) { // create label for heading - javax.swing.JLabel headingLabel = new javax.swing.JLabel(); + javax.swing.JTextPane headingLabel = new javax.swing.JTextPane(); + headingLabel.setOpaque(false); + headingLabel.setFocusable(false); + headingLabel.setEditable(false); // add a blank line before the start of new section, unless it's // the first section if (gridBagConstraints.gridy != 0) { gridBagConstraints.gridy++; - detailsPanel.add(new javax.swing.JLabel(" "), gridBagConstraints); + // add to panel + addToPanel(new javax.swing.JLabel(" ")); addLineEndGlue(); - } + headingLabel.setFocusable(false); + } gridBagConstraints.gridy++; gridBagConstraints.gridx = LABEL_COLUMN;; // let the header span all of the row @@ -401,7 +404,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i // make it large and bold headingLabel.setFont(headingLabel.getFont().deriveFont(Font.BOLD, headingLabel.getFont().getSize() + 2)); // add to panel - detailsPanel.add(headingLabel, gridBagConstraints); + addToPanel(headingLabel); // reset constraints to normal gridBagConstraints.gridwidth = LABEL_WIDTH; // add line end glue @@ -417,9 +420,9 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i * @param keyString Key name to display. * @param valueString Value string to display. */ - private void addNameValueRow(String keyString, String valueString) { + private JTextPane addNameValueRow(String keyString, String valueString) { addKeyAtCol(keyString); - addValueAtCol(valueString); + return addValueAtCol(valueString); } /** @@ -432,7 +435,8 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i 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)); - detailsPanel.add(horizontalFiller, gridBagConstraints); + // add to panel + addToPanel(horizontalFiller); // restore fill & weight gridBagConstraints.fill = GridBagConstraints.NONE; gridBagConstraints.weightx = TEXT_WEIGHT_X; @@ -446,7 +450,8 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i 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)); - detailsPanel.add(vertFiller, gridBagConstraints); + // add to panel + addToPanel(vertFiller); } /** @@ -459,16 +464,22 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i private JLabel addKeyAtCol(String keyString) { // create label javax.swing.JLabel keyLabel = new javax.swing.JLabel(); + keyLabel.setFocusable(false); gridBagConstraints.gridy++; gridBagConstraints.gridx = LABEL_COLUMN; gridBagConstraints.gridwidth = LABEL_WIDTH; // set text keyLabel.setText(keyString + ": "); // add to panel - detailsPanel.add(keyLabel, gridBagConstraints); + addToPanel(keyLabel); return keyLabel; } + private void addToPanel(Component comp) { + detailsPanel.add(comp, gridBagConstraints); + detailsPanel.revalidate(); + } + /** * Adds a value string to the panel at specified column. * @@ -479,6 +490,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i private JTextPane addValueAtCol(String valueString) { // create label, JTextPane valueField = new JTextPane(); + valueField.setFocusable(false); valueField.setEditable(false); valueField.setOpaque(false); gridBagConstraints.gridx = VALUE_COLUMN; @@ -488,8 +500,6 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i cloneConstraints.fill = GridBagConstraints.BOTH; // set text valueField.setText(valueString); - // scroll to start of text - valueField.setCaretPosition(0); // attach a right click menu with Copy option valueField.addMouseListener(new java.awt.event.MouseAdapter() { @Override @@ -497,8 +507,9 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i valueLabelMouseClicked(evt, valueField); } }); - // add label to panel + // add label to panel with cloned contraintsF detailsPanel.add(valueField, cloneConstraints); + revalidate(); // end the line addLineEndGlue(); return valueField; @@ -531,6 +542,6 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JPanel detailsPanel; + private javax.swing.JScrollPane detailsScrollPane; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java index 8cf4337163..fbf1ada856 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java @@ -586,6 +586,7 @@ final class MessageAccountPanel extends JPanel { // Set up each matching account. We don't know what type of account we have, so check all the types to // find any matches. try { + boolean showErrorMessage = true; for (CentralRepoAccount.CentralRepoAccountType type : CentralRepository.getInstance().getAllAccountTypes()) { try { // Try to load any matching accounts of this type. Throws an InvalidAccountIDException if the account is the @@ -593,13 +594,15 @@ final class MessageAccountPanel extends JPanel { CentralRepoAccount account = CentralRepository.getInstance().getAccount(type, accountContainer.getAccount().getTypeSpecificID()); if (account != null) { personaPanel.addAccount(account, Bundle.MessageAccountPanel_account_justification(), Persona.Confidence.HIGH); - } else { - createPersonaDialog.setStartupPopupMessage(Bundle.MessageAccountPanel_id_not_found_in_cr(accountContainer.getAccount().getTypeSpecificID())); - } + showErrorMessage = false; + } } catch (InvalidAccountIDException ex2) { // These are expected when the account identifier doesn't match the format of the account type. } } + if(showErrorMessage) { + createPersonaDialog.setStartupPopupMessage(Bundle.MessageAccountPanel_id_not_found_in_cr(accountContainer.getAccount().getTypeSpecificID())); + } } catch (CentralRepoException ex) { logger.log(Level.SEVERE, "Error looking up account types in the central repository", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/PersonaAccountFetcher.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/PersonaAccountFetcher.java index 0601c0893d..020a2645b6 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/PersonaAccountFetcher.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/PersonaAccountFetcher.java @@ -200,6 +200,7 @@ class PersonaAccountFetcher extends SwingWorker> // Set up each matching account. We don't know what type of account we have, so check all the types to // find any matches. try { + boolean showErrorMessage = true; for (CentralRepoAccount.CentralRepoAccountType type : CentralRepository.getInstance().getAllAccountTypes()) { try { // Try to load any matching accounts of this type. Throws an InvalidAccountIDException if the account is the @@ -207,16 +208,17 @@ class PersonaAccountFetcher extends SwingWorker> CentralRepoAccount account = CentralRepository.getInstance().getAccount(type, personaSearcherData.getAccountIdentifer()); if (account != null) { personaPanel.addAccount(account, Bundle.PersonaAccountFetcher_account_justification(), Persona.Confidence.HIGH); + showErrorMessage = false; } - if((personaSearcherData.getAccountIdentifer() != null && - !personaSearcherData.getAccountIdentifer().isEmpty()) && account == null) { - dialog.setStartupPopupMessage(Bundle.PersonaAccountFetcher_not_account_in_cr(personaSearcherData.getAccountIdentifer())); - } } catch (InvalidAccountIDException ex2) { // These are expected when the account identifier doesn't match the format of the account type. } } + if ((personaSearcherData.getAccountIdentifer() != null + && !personaSearcherData.getAccountIdentifer().isEmpty()) && showErrorMessage) { + dialog.setStartupPopupMessage(Bundle.PersonaAccountFetcher_not_account_in_cr(personaSearcherData.getAccountIdentifer())); + } } catch (CentralRepoException ex) { logger.log(Level.SEVERE, "Error looking up account types in the central repository", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/StringsTextViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/StringsTextViewer.java index 1d4a6aef03..666c38d070 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/StringsTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/StringsTextViewer.java @@ -25,6 +25,7 @@ import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.TextViewer; import org.sleuthkit.autopsy.corecomponents.DataContentViewerUtility; import org.sleuthkit.autopsy.datamodel.StringContent; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; /** @@ -81,7 +82,7 @@ public class StringsTextViewer implements TextViewer { return false; } Content content = DataContentViewerUtility.getDefaultContent(node); - return (content != null && content.getSize() > 0); + return (content != null && !(content instanceof BlackboardArtifact) && content.getSize() > 0); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 8f3c2e8e14..0d3fd0198a 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -84,6 +84,7 @@ public final class UserPreferences { private static final boolean DISPLAY_TRANSLATED_NAMES_DEFAULT = true; public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath"; public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize"; + private static final int DEFAULT_SOLR_HEAP_SIZE_MB = 2048; public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize"; private static final String GEO_TILE_OPTION = "GeolocationTileOption"; private static final String GEO_OSM_TILE_ZIP_PATH = "GeolocationOsmZipPath"; @@ -535,10 +536,10 @@ public final class UserPreferences { /** * Get the maximum JVM heap size (in MB) for the embedded Solr server. * - * @return Saved value or default (512) + * @return Saved value or default (2 GB) */ public static int getMaxSolrVMSize() { - return preferences.getInt(SOLR_MAX_JVM_SIZE, 512); + return preferences.getInt(SOLR_MAX_JVM_SIZE, DEFAULT_SOLR_HEAP_SIZE_MB); } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 7f4f8bf3a7..e7c0f9eaeb 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -82,6 +82,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private static final String CONFIG_FILE_EXTENSION = ".conf"; private static final long ONE_BILLION = 1000000000L; //used to roughly convert system memory from bytes to gigabytes private static final int MEGA_IN_GIGA = 1024; //used to convert memory settings saved as megabytes to gigabytes + private static final int DEFAULT_SOLR_HEAP_SIZE_MB = 2048; private static final int MIN_MEMORY_IN_GB = 2; //the enforced minimum memory in gigabytes private static final Logger logger = Logger.getLogger(AutopsyOptionsPanel.class.getName()); private String initialMemValue = Long.toString(Runtime.getRuntime().maxMemory() / ONE_BILLION); @@ -113,7 +114,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { // The cast to int in the following is to ensure that the correct SpinnerNumberModel // constructor is called. solrMaxHeapSpinner.setModel(new javax.swing.SpinnerNumberModel(UserPreferences.getMaxSolrVMSize(), - 512, ((int) getSystemMemoryInGB()) * MEGA_IN_GIGA, 512)); + DEFAULT_SOLR_HEAP_SIZE_MB, ((int) getSystemMemoryInGB()) * MEGA_IN_GIGA, DEFAULT_SOLR_HEAP_SIZE_MB)); textFieldListener = new TextFieldListener(); agencyLogoPathField.getDocument().addDocumentListener(textFieldListener); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java index be8f854b96..10188826b1 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java @@ -46,6 +46,7 @@ import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.DataConversion; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -606,7 +607,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont return false; } Content content = DataContentViewerUtility.getDefaultContent(node); - return content != null && content.getSize() > 0; + return content != null && !(content instanceof BlackboardArtifact) && content.getSize() > 0; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java index d460a16f03..2356a25a3b 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java @@ -32,9 +32,6 @@ public class DataContentViewerUtility { * preferring to return any Content object other than a BlackboardArtifact * object. * - * This method was written with the needs of the hex and strings content - * viewers in mind - the algorithm is exactly what those viewers require. - * * @param node A display Node object. * * @return If there are multiple Content objects associated with the Node, diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index efdfb12c48..5bfca75425 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import com.google.common.annotations.Beta; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.beans.PropertyChangeEvent; @@ -234,7 +235,7 @@ public class BlackboardArtifactNode extends AbstractContentNode artifact.getSleuthkitCase().getContentById(objectID)); if (content == null) { - content = contentCache.get(objectID, () -> artifact.getSleuthkitCase().getContentById(objectID)); + return Lookups.fixed(artifact); + } else { + return Lookups.fixed(artifact, content); } } catch (ExecutionException ex) { logger.log(Level.SEVERE, MessageFormat.format("Error getting source content (artifact objID={0}", artifact.getId()), ex); //NON-NLS - content = null; - } - if (content == null) { return Lookups.fixed(artifact); + } + } + + /** + * Creates a Lookup object for this node and populates it with both the + * artifact this node represents and its source content. + * + * @param artifact The artifact this node represents. + * @param lookupIsAssociatedFile True if the Content lookup should be made + * for the associated file instead of the + * parent file. + * + * @return The Lookup. + */ + private static Lookup createLookup(BlackboardArtifact artifact, boolean lookupIsAssociatedFile) { + Content content = null; + if (lookupIsAssociatedFile) { + try { + content = getPathIdFile(artifact); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting source content (artifact objID={0}", artifact.getId()), ex); //NON-NLS + content = null; + } + if (content == null) { + return Lookups.fixed(artifact); + } else { + return Lookups.fixed(artifact, content); + } } else { - return Lookups.fixed(artifact, content); + return createLookup(artifact); } } @@ -982,7 +1050,7 @@ public class BlackboardArtifactNode extends AbstractContentNode mostRecent)) { mostRecent = curTime; diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java deleted file mode 100644 index 3477dd5ff2..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.util.Map; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.IngestJobInfo; -import org.sleuthkit.datamodel.IngestModuleInfo; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Utilities for checking if an ingest module has been run on a datasource. - */ -@Messages({ - "IngestModuleCheckUtil_recentActivityModuleName=Recent Activity", - -}) -public class IngestModuleCheckUtil { - public static final String RECENT_ACTIVITY_FACTORY = "org.sleuthkit.autopsy.recentactivity.RecentActivityExtracterModuleFactory"; - public static final String RECENT_ACTIVITY_MODULE_NAME = Bundle.IngestModuleCheckUtil_recentActivityModuleName(); - - // IngestModuleInfo separator for unique_name - private static final String UNIQUE_NAME_SEPARATOR = "-"; - - private final SleuthkitCaseProvider caseProvider; - - /** - * Main constructor. - */ - public IngestModuleCheckUtil() { - this(SleuthkitCaseProvider.DEFAULT); - - } - - /** - * Main constructor with external dependencies specified. This constructor - * is designed with unit testing in mind since mocked dependencies can be - * utilized. - * - * @param provider The object providing the current SleuthkitCase. - */ - public IngestModuleCheckUtil(SleuthkitCaseProvider provider) { - - this.caseProvider = provider; - } - - - /** - * Gets the fully qualified factory from the IngestModuleInfo. - * @param info The IngestJobInfo. - * @return The fully qualified factory. - */ - private static String getFullyQualifiedFactory(IngestModuleInfo info) { - if (info == null) { - return null; - } - - String qualifiedName = info.getUniqueName(); - if (StringUtils.isBlank(qualifiedName)) { - return null; - } - - return qualifiedName.split(UNIQUE_NAME_SEPARATOR)[0]; - } - - - /** - * Whether or not the ingest job info contains the ingest modulename. - * @param info The IngestJobInfo. - * @param fullyQualifiedFactory The fully qualified classname of the relevant factory. - * @return True if the ingest module name is contained in the data. - */ - private static boolean hasIngestModule(IngestJobInfo info, String fullyQualifiedFactory) { - if (info == null || info.getIngestModuleInfo() == null || StringUtils.isBlank(fullyQualifiedFactory)) { - return false; - } - - return info.getIngestModuleInfo().stream() - .anyMatch((moduleInfo) -> { - String thisQualifiedFactory = getFullyQualifiedFactory(moduleInfo); - return fullyQualifiedFactory.equalsIgnoreCase(thisQualifiedFactory); - }); - } - - /** - * Whether or not a data source has been ingested with a particular ingest module. - * @param dataSource The datasource. - * @param fullyQualifiedFactory The fully qualified classname of the relevant factory. - * @return Whether or not a data source has been ingested with a particular ingest module. - * @throws TskCoreException - * @throws SleuthkitCaseProviderException - */ - public boolean isModuleIngested(DataSource dataSource, String fullyQualifiedFactory) - throws TskCoreException, SleuthkitCaseProviderException { - if (dataSource == null) { - return false; - } - - long dataSourceId = dataSource.getId(); - - return caseProvider.get().getIngestJobs().stream() - .anyMatch((ingestJob) -> { - return ingestJob != null - && ingestJob.getObjectId() == dataSourceId - && hasIngestModule(ingestJob, fullyQualifiedFactory); - }); - - } - - /** - * Get a mapping of fully qualified factory name to display name. - * @param skCase The SleuthkitCase. - * @return The mapping of fully qualified factory name to display name. - * @throws TskCoreException - */ - public static Map getFactoryDisplayNames(SleuthkitCase skCase) throws TskCoreException { - return skCase.getIngestJobs().stream() - .flatMap(ingestJob -> ingestJob.getIngestModuleInfo().stream()) - .collect(Collectors.toMap( - (moduleInfo) -> getFullyQualifiedFactory(moduleInfo), - (moduleInfo) -> moduleInfo.getDisplayName(), - (a,b) -> a)); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java index 34dfa97a13..d2839a7f54 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/AnalysisPanel.java @@ -29,8 +29,6 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel; -import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; -import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory; import org.sleuthkit.datamodel.DataSource; /** @@ -46,15 +44,6 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; - private static final String KEYWORD_SEARCH_MODULE_NAME = Bundle.AnalysisPanel_keywordSearchModuleName(); - private static final String KEYWORD_SEARCH_FACTORY = "org.sleuthkit.autopsy.keywordsearch.KeywordSearchModuleFactory"; - - private static final String INTERESTING_ITEM_MODULE_NAME = new InterestingItemsIngestModuleFactory().getModuleDisplayName(); - private static final String INTERESTING_ITEM_FACTORY = InterestingItemsIngestModuleFactory.class.getCanonicalName(); - - private static final String HASHSET_MODULE_NAME = HashLookupModuleFactory.getModuleName(); - private static final String HASHSET_FACTORY = HashLookupModuleFactory.class.getCanonicalName(); - /** * Default Column definitions for each table */ @@ -90,9 +79,8 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel { keywordHitsTable, interestingItemsTable ); - + private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel(); - /** * All of the components necessary for data fetch swing workers to load data @@ -115,28 +103,26 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel { // hashset hits loading components new DataFetchWorker.DataFetchComponents<>( (dataSource) -> analysisData.getHashsetCounts(dataSource), - (result) -> showResultWithModuleCheck(hashsetHitsTable, result, HASHSET_FACTORY, HASHSET_MODULE_NAME)), + (result) -> hashsetHitsTable.showDataFetchResult(result)), // keyword hits loading components new DataFetchWorker.DataFetchComponents<>( (dataSource) -> analysisData.getKeywordCounts(dataSource), - (result) -> showResultWithModuleCheck(keywordHitsTable, result, KEYWORD_SEARCH_FACTORY, KEYWORD_SEARCH_MODULE_NAME)), + (result) -> keywordHitsTable.showDataFetchResult(result)), // interesting item hits loading components new DataFetchWorker.DataFetchComponents<>( (dataSource) -> analysisData.getInterestingItemCounts(dataSource), - (result) -> showResultWithModuleCheck(interestingItemsTable, result, INTERESTING_ITEM_FACTORY, INTERESTING_ITEM_MODULE_NAME)) + (result) -> interestingItemsTable.showDataFetchResult(result)) ); initComponents(); } - @Override public void close() { ingestRunningLabel.unregister(); super.close(); } - - + @Override protected void fetchInformation(DataSource dataSource) { fetchInformation(dataFetchComponents, dataSource); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java index 96767fcb8f..4abb5bcc32 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java @@ -25,7 +25,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.function.Predicate; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -39,11 +38,8 @@ 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.datasourcesummary.datamodel.IngestModuleCheckUtil; -import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer; 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.EventUpdateHandler; @@ -73,7 +69,6 @@ abstract class BaseDataSourceSummaryPanel extends JPanel { private static final Logger logger = Logger.getLogger(BaseDataSourceSummaryPanel.class.getName()); private final SwingWorkerSequentialExecutor executor = new SwingWorkerSequentialExecutor(); - private final IngestModuleCheckUtil ingestModuleCheck = new IngestModuleCheckUtil(); private final EventUpdateHandler updateHandler; private final List governors; @@ -319,8 +314,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel { } /** - * Given the relevant file, navigates to the file in the - * tree and closes data source summary dialog if open. + * Given the relevant file, navigates to the file in the tree and closes + * data source summary dialog if open. * * @param file The file. * @return The menu item list for a go to artifact menu item. @@ -471,76 +466,4 @@ abstract class BaseDataSourceSummaryPanel extends JPanel { fetchInformation(dataSource); } } - - /** - * Get default message when there is a NotIngestedWithModuleException. - * - * @param moduleName The moduleName. - * - * @return Message specifying that the ingest module was not run. - */ - @Messages({ - "# {0} - module name", - "BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source." - }) - protected String getDefaultNoIngestMessage(String moduleName) { - return Bundle.BaseDataSourceSummaryPanel_defaultNotIngestMessage(moduleName); - } - - /** - * Utility method to return the IngestModuleCheckUtil. - * - * @return The IngestModuleCheckUtil. - */ - protected IngestModuleCheckUtil getIngestModuleCheckUtil() { - return this.ingestModuleCheck; - } - - /** - * Utility method that in the event of a) there are no results and b) a - * relevant ingest module has not been run on this datasource, then a - * message indicating the unrun ingest module will be shown. Otherwise, the - * default LoadableComponent.showDataFetchResult behavior will be used. - * - * @param component The component. - * @param result The data result. - * @param factoryClass The fully qualified class name of the relevant - * factory. - * @param moduleName The name of the ingest module (i.e. 'Keyword Search'). - */ - protected void showResultWithModuleCheck(LoadableComponent> component, DataFetchResult> result, String factoryClass, String moduleName) { - Predicate> hasResults = (lst) -> lst != null && !lst.isEmpty(); - showResultWithModuleCheck(component, result, hasResults, factoryClass, moduleName); - } - - /** - * Utility method that in the event of a) there are no results and b) a - * relevant ingest module has not been run on this datasource, then a - * message indicating the unrun ingest module will be shown. Otherwise, the - * default LoadableComponent.showDataFetchResult behavior will be used. - * - * @param component The component. - * @param result The data result. - * @param hasResults Given the data type, will provide whether or not the - * data contains any actual results. - * @param factoryClass The fully qualified class name of the relevant - * factory. - * @param moduleName The name of the ingest module (i.e. 'Keyword Search'). - */ - protected void showResultWithModuleCheck(LoadableComponent component, DataFetchResult result, - Predicate hasResults, String factoryClass, String moduleName) { - - if (result != null && result.getResultType() == ResultType.SUCCESS && !hasResults.test(result.getData())) { - try { - if (!ingestModuleCheck.isModuleIngested(getDataSource(), factoryClass)) { - component.showMessage(getDefaultNoIngestMessage(moduleName)); - return; - } - } catch (TskCoreException | SleuthkitCaseProviderException ex) { - logger.log(Level.WARNING, "There was an error while checking for ingest modules for datasource.", ex); - } - } - - component.showDataFetchResult(result); - } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/GeolocationPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/GeolocationPanel.java index 5bf4efceb4..a4692abbf4 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/GeolocationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/GeolocationPanel.java @@ -63,8 +63,6 @@ import org.sleuthkit.datamodel.DataSource; public class GeolocationPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; - private static final String GPX_FACTORY = "org.python.proxies.GPX_Parser_Module$GPXParserFileIngestModuleFactory"; - private static final String GPX_NAME = "GPX Parser"; private static final int DAYS_COUNT = 30; private static final int MAX_COUNT = 10; @@ -114,6 +112,8 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel { * @param whereUsedData The GeolocationSummary instance to use. */ public GeolocationPanel(GeolocationSummary whereUsedData) { + super(whereUsedData); + this.whereUsedData = whereUsedData; // set up data acquisition methods dataFetchComponents = Arrays.asList( @@ -145,11 +145,19 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel { return null; } - if (StringUtils.isBlank(record.getCountry())) { - return record.getCityName(); - } + List cityIdentifiers = Stream.of(record.getCityName(), record.getState(), record.getCountry()) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toList()); - return String.format("%s, %s", record.getCityName(), record.getCountry()); + if (cityIdentifiers.size() == 1) { + return cityIdentifiers.get(0); + } else if (cityIdentifiers.size() == 2) { + return String.format("%s, %s", cityIdentifiers.get(0), cityIdentifiers.get(1)); + } else if (cityIdentifiers.size() >= 3) { + return String.format("%s, %s; %s", cityIdentifiers.get(0), cityIdentifiers.get(1), cityIdentifiers.get(2)); + } + + return null; } /** @@ -211,7 +219,7 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel { goToGeolocation.setEnabled(true); } - showResultWithModuleCheck(table, convertedData, GPX_FACTORY, GPX_NAME); + table.showDataFetchResult(convertedData); } /** @@ -225,7 +233,7 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel { private void openGeolocationWindow(DataSource dataSource, Integer daysLimit) { // notify dialog (if in dialog) should close. notifyParentClose(); - + // set the filter TopComponent topComponent = WindowManager.getDefault().findTopComponent(GeolocationTopComponent.class.getSimpleName()); if (topComponent instanceof GeolocationTopComponent) { diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java index 2400334ee3..0181ced024 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.tuple.Pair; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory; import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; @@ -46,8 +45,6 @@ import org.sleuthkit.datamodel.DataSource; public class PastCasesPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; - private static final String CR_FACTORY = CentralRepoIngestModuleFactory.class.getName(); - private static final String CR_NAME = CentralRepoIngestModuleFactory.getModuleName(); private static final ColumnModel> CASE_COL = new ColumnModel<>( Bundle.PastCasesPanel_caseColumn_title(), @@ -84,6 +81,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel { * Creates new form PastCasesPanel */ public PastCasesPanel(PastCasesSummary pastCaseData) { + super(pastCaseData); + // set up data acquisition methods dataFetchComponents = Arrays.asList( new DataFetchWorker.DataFetchComponents<>( @@ -101,8 +100,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel { * @param result The result. */ private void handleResult(DataFetchResult result) { - showResultWithModuleCheck(notableFileTable, DataFetchResult.getSubResult(result, (res) -> res.getTaggedNotable()), CR_FACTORY, CR_NAME); - showResultWithModuleCheck(sameIdTable, DataFetchResult.getSubResult(result, (res) -> res.getSameIdsResults()), CR_FACTORY, CR_NAME); + notableFileTable.showDataFetchResult(DataFetchResult.getSubResult(result, (res) -> res.getTaggedNotable())); + sameIdTable.showDataFetchResult(DataFetchResult.getSubResult(result, (res) -> res.getSameIdsResults())); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java index 2f94f2d598..64fa4817c3 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails; @@ -44,8 +43,6 @@ import org.sleuthkit.datamodel.DataSource; public final class RecentFilesPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; - private static final String EMAIL_PARSER_FACTORY = "org.sleuthkit.autopsy.thunderbirdparser.EmailParserModuleFactory"; - private static final String EMAIL_PARSER_MODULE_NAME = Bundle.RecentFilePanel_emailParserModuleName(); private final List> tablePanelList = new ArrayList<>(); private final List> dataFetchComponents = new ArrayList<>(); @@ -167,11 +164,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel { DataFetchWorker.DataFetchComponents> worker = new DataFetchWorker.DataFetchComponents<>( (dataSource) -> dataHandler.getRecentlyOpenedDocuments(dataSource, 10), - (result) -> { - showResultWithModuleCheck(pane, result, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }); + (result) -> pane.showDataFetchResult(result)); dataFetchComponents.add(worker); } @@ -210,11 +203,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel { DataFetchWorker.DataFetchComponents> worker = new DataFetchWorker.DataFetchComponents<>( (dataSource) -> dataHandler.getRecentDownloads(dataSource, 10), - (result) -> { - showResultWithModuleCheck(pane, result, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }); + (result) -> pane.showDataFetchResult(result)); dataFetchComponents.add(worker); } @@ -253,7 +242,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel { DataFetchWorker.DataFetchComponents> worker = new DataFetchWorker.DataFetchComponents<>( (dataSource) -> dataHandler.getRecentAttachments(dataSource, 10), - (result) -> showResultWithModuleCheck(pane, result, EMAIL_PARSER_FACTORY, EMAIL_PARSER_MODULE_NAME) + (result) -> pane.showDataFetchResult(result) ); dataFetchComponents.add(worker); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java index b4c3194545..436f968aa8 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java @@ -103,6 +103,8 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { * Creates new form PastCasesPanel */ public TimelinePanel(TimelineSummary timelineData) { + super(timelineData); + // set up data acquisition methods dataFetchComponents = Arrays.asList( new DataFetchWorker.DataFetchComponents<>( diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java index 247aa0c304..f6c952d4b5 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java @@ -26,16 +26,13 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TypesSummary; 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.DataFetchResult; @@ -251,25 +248,11 @@ class TypesPanel extends BaseDataSourceSummaryPanel { // usage label worker new DataFetchWorker.DataFetchComponents<>( containerData::getDataSourceType, - (result) -> { - showResultWithModuleCheck( - usageLabel, - result, - StringUtils::isNotBlank, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }), + (result) -> usageLabel.showDataFetchResult(result)), // os label worker new DataFetchWorker.DataFetchComponents<>( containerData::getOperatingSystems, - (result) -> { - showResultWithModuleCheck( - osLabel, - result, - StringUtils::isNotBlank, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }), + (result) -> osLabel.showDataFetchResult(result)), // size label worker new DataFetchWorker.DataFetchComponents<>( (dataSource) -> { @@ -379,50 +362,22 @@ class TypesPanel extends BaseDataSourceSummaryPanel { * @param result The result to be shown. */ private void showMimeTypeCategories(DataFetchResult result) { - // if result is null check for ingest module and show empty results. if (result == null) { - showPieResultWithModuleCheck(null); + fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(null)); return; } // if error, show error if (result.getResultType() == ResultType.ERROR) { - this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getErrorResult(result.getException())); + fileMimeTypesChart.showDataFetchResult(DataFetchResult.getErrorResult(result.getException())); return; } TypesPieChartData data = result.getData(); if (data == null) { - // if no data, do an ingest module check with empty results - showPieResultWithModuleCheck(null); - } else if (!data.isUsefulContent()) { - // if no useful data, do an ingest module check and show data - showPieResultWithModuleCheck(data.getPieSlices()); + fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(null)); } else { - // otherwise, show the data - this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(data.getPieSlices())); - } - } - - /** - * Shows a message in the fileMimeTypesChart about the data source not being - * ingested with the file type ingest module if the data source has not been - * ingested with that module. Also shows data if present. - * - * @param items The list of items to show. - */ - private void showPieResultWithModuleCheck(List items) { - boolean hasBeenIngested = false; - try { - hasBeenIngested = this.getIngestModuleCheckUtil().isModuleIngested(getDataSource(), FILE_TYPE_FACTORY); - } catch (TskCoreException | SleuthkitCaseProviderException ex) { - logger.log(Level.WARNING, "There was an error fetching whether or not the current data source has been ingested with the file type ingest module.", ex); - } - - if (hasBeenIngested) { - this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(items)); - } else { - this.fileMimeTypesChart.showDataWithMessage(items, getDefaultNoIngestMessage(FILE_TYPE_MODULE_NAME)); + fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(data.getPieSlices())); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java index 63671e16f2..c1ac2b2c6b 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Locale; import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.LastAccessedArtifact; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult; @@ -287,43 +286,23 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel { // top programs query new DataFetchComponents>( (dataSource) -> userActivityData.getTopPrograms(dataSource, TOP_PROGS_COUNT), - (result) -> { - showResultWithModuleCheck(topProgramsTable, result, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }), + (result) -> topProgramsTable.showDataFetchResult(result)), // top domains query new DataFetchComponents>( (dataSource) -> userActivityData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT), - (result) -> { - showResultWithModuleCheck(recentDomainsTable, result, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }), + (result) -> recentDomainsTable.showDataFetchResult(result)), // top web searches query new DataFetchComponents>( (dataSource) -> userActivityData.getMostRecentWebSearches(dataSource, TOP_SEARCHES_COUNT), - (result) -> { - showResultWithModuleCheck(topWebSearchesTable, result, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }), + (result) -> topWebSearchesTable.showDataFetchResult(result)), // top devices query new DataFetchComponents>( (dataSource) -> userActivityData.getRecentDevices(dataSource, TOP_DEVICES_COUNT), - (result) -> { - showResultWithModuleCheck(topDevicesAttachedTable, result, - IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY, - IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME); - }), + (result) -> topDevicesAttachedTable.showDataFetchResult(result)), // top accounts query new DataFetchComponents>( (dataSource) -> userActivityData.getRecentAccounts(dataSource, TOP_ACCOUNTS_COUNT), - (result) -> { - showResultWithModuleCheck(topAccountsTable, result, - ANDROID_FACTORY, - ANDROID_MODULE_NAME); - }) + (result) -> topAccountsTable.showDataFetchResult(result)) ); initComponents(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java index 15171cf945..77f67899eb 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java @@ -140,9 +140,10 @@ public class DiscoveryAttributes { return new DiscoveryKeyUtils.FileTypeGroupKey(file); } } - + /** - * Attribute for grouping/sorting by domain category (TSK_WEB_CATEGORY artifacts). + * Attribute for grouping/sorting by domain category (TSK_WEB_CATEGORY + * artifacts). */ static class DomainCategoryAttribute extends AttributeType { @@ -150,7 +151,7 @@ public class DiscoveryAttributes { public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) { return new DiscoveryKeyUtils.DomainCategoryGroupKey(result); } - + @Override public void addAttributeToResults(List results, SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException { @@ -167,10 +168,11 @@ public class DiscoveryAttributes { throw new DiscoveryException("Error fetching TSK_WEB_CATEGORY artifacts from the database", ex); } } - + /** - * Loads all TSK_WEB_CATEGORY artifacts and maps the domain attribute to the category name attribute. - * Each ResultDomain is then parsed and matched against this map of values. + * Loads all TSK_WEB_CATEGORY artifacts and maps the domain attribute to + * the category name attribute. Each ResultDomain is then parsed and + * matched against this map of values. */ private Map getDomainsWithWebCategories(SleuthkitCase caseDb) throws TskCoreException, InterruptedException { Map domainToCategory = new HashMap<>(); @@ -190,7 +192,7 @@ public class DiscoveryAttributes { } } - return domainToCategory; + return domainToCategory; } } @@ -269,36 +271,36 @@ public class DiscoveryAttributes { } } } - + /** - * Organizes the domain instances by normalized domain value. - * This helps reduce the complexity of updating ResultDomain instances - * after the query has been executed. - * - * Example: query for notable status of google.com. Result: notable - * With this map, all domain instances that represent google.com can - * be updated after one simple lookup. + * Organizes the domain instances by normalized domain value. This helps + * reduce the complexity of updating ResultDomain instances after the query + * has been executed. + * + * Example: query for notable status of google.com. Result: notable With + * this map, all domain instances that represent google.com can be updated + * after one simple lookup. */ private static Map> organizeByValue(List domainsBatch, CorrelationAttributeInstance.Type attributeType) { - final Map> resultDomainTable = new HashMap<>(); - for (ResultDomain domainInstance : domainsBatch) { - try { - final String domainValue = domainInstance.getDomain(); - final String normalizedDomain = CorrelationAttributeNormalizer.normalize(attributeType, domainValue); - final List bucket = resultDomainTable.getOrDefault(normalizedDomain, new ArrayList<>()); - bucket.add(domainInstance); - resultDomainTable.put(normalizedDomain, bucket); - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.INFO, String.format("Domain [%s] failed normalization, skipping...", domainInstance.getDomain())); - } + final Map> resultDomainTable = new HashMap<>(); + for (ResultDomain domainInstance : domainsBatch) { + try { + final String domainValue = domainInstance.getDomain(); + final String normalizedDomain = CorrelationAttributeNormalizer.normalize(attributeType, domainValue); + final List bucket = resultDomainTable.getOrDefault(normalizedDomain, new ArrayList<>()); + bucket.add(domainInstance); + resultDomainTable.put(normalizedDomain, bucket); + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.INFO, String.format("Domain [%s] failed normalization, skipping...", domainInstance.getDomain())); } - return resultDomainTable; + } + return resultDomainTable; } /** - * Helper function to create a string of comma separated values. - * Each value is wrapped in `'`. This method is used to bundle up - * a collection of values for use in a SQL WHERE IN (...) clause. + * Helper function to create a string of comma separated values. Each value + * is wrapped in `'`. This method is used to bundle up a collection of + * values for use in a SQL WHERE IN (...) clause. */ private static String createCSV(Set values) { StringJoiner joiner = new StringJoiner(", "); @@ -307,30 +309,30 @@ public class DiscoveryAttributes { } return joiner.toString(); } - + /** * Attribute for grouping/sorting by notability in the CR. */ static class PreviouslyNotableAttribute extends AttributeType { - + static final int DOMAIN_BATCH_SIZE = 500; // Number of domains to look up at one time @Override public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) { return new DiscoveryKeyUtils.PreviouslyNotableGroupKey(result); } - + @Override public void addAttributeToResults(List results, SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException { - + if (centralRepoDb != null) { processFilesWithCr(results, centralRepoDb); - } + } } - + private void processFilesWithCr(List results, CentralRepository centralRepo) throws DiscoveryException { - + List domainsBatch = new ArrayList<>(); for (Result result : results) { if (result.getType() == SearchData.Type.DOMAIN) { @@ -341,15 +343,15 @@ public class DiscoveryAttributes { } } } - + queryPreviouslyNotable(domainsBatch, centralRepo); } - + private void queryPreviouslyNotable(List domainsBatch, CentralRepository centralRepo) throws DiscoveryException { if (domainsBatch.isEmpty()) { return; } - + try { final CorrelationAttributeInstance.Type attributeType = centralRepo.getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID); final Map> resultDomainTable = organizeByValue(domainsBatch, attributeType); @@ -371,16 +373,16 @@ public class DiscoveryAttributes { throw new DiscoveryException("Fatal exception encountered querying the CR.", ex); } } - + private static class DomainPreviouslyNotableCallback implements InstanceTableCallback { - + private final Map> domainLookup; private SQLException sqlCause; private DomainPreviouslyNotableCallback(Map> domainLookup) { this.domainLookup = domainLookup; } - + @Override public void process(ResultSet resultSet) { try { @@ -401,7 +403,7 @@ public class DiscoveryAttributes { */ SQLException getCause() { return this.sqlCause; - } + } } } @@ -499,12 +501,13 @@ public class DiscoveryAttributes { final CorrelationAttributeInstance.Type attributeType = centralRepository.getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID); final Map> resultDomainTable = organizeByValue(domainsToQuery, attributeType); final String values = createCSV(resultDomainTable.keySet()); - final String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType); - final String domainFrequencyQuery = " value AS domain_name, COUNT(*) AS frequency " - + "FROM " + tableName + " " - + "WHERE value IN (" + values + ") " - + "GROUP BY value"; + final String domainFrequencyQuery = " value AS domain_name, COUNT(value) AS frequency FROM" + + "(SELECT DISTINCT case_id, value FROM " + + tableName + + " WHERE value IN (" + + values + + ")) AS foo GROUP BY value"; final DomainFrequencyCallback frequencyCallback = new DomainFrequencyCallback(resultDomainTable); centralRepository.processSelectClause(domainFrequencyQuery, frequencyCallback); @@ -784,8 +787,8 @@ public class DiscoveryAttributes { } /** - * Attribute for grouping/sorting domains by number of page views. - * Page views is defined at the number of TSK_WEB_HISTORY artifacts. + * Attribute for grouping/sorting domains by number of page views. Page + * views is defined at the number of TSK_WEB_HISTORY artifacts. */ static class PageViewsAttribute extends AttributeType { @@ -1074,4 +1077,4 @@ public class DiscoveryAttributes { private DiscoveryAttributes() { // Class should not be instantiated } - } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java index 7dbfb8848c..e228ce5f21 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryEventUtils.java @@ -277,17 +277,20 @@ public final class DiscoveryEventUtils { public static final class MiniTimelineResultEvent { private final List results = new ArrayList<>(); + private final String domain; /** * Construct a new MiniTimelineResultEvent. * * @param results The list of MiniTimelineResults contained in this * event. + * @param domain The domain the results are for. */ - public MiniTimelineResultEvent(List results) { + public MiniTimelineResultEvent(List results, String domain) { if (results != null) { this.results.addAll(results); } + this.domain = domain; } /** @@ -298,6 +301,15 @@ public final class DiscoveryEventUtils { public List getResultList() { return Collections.unmodifiableList(results); } + + /** + * Get the domain this list of results is for. + * + * @return The domain the list of results is for. + */ + public String getDomain() { + return domain; + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java index 0ceda46203..61efc8cfbe 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java @@ -201,7 +201,7 @@ public class DomainSearch { * @throws DiscoveryException if unable to get the artifacts or the date * attributes from an artifact. */ - public List getAllArtifactsForDomain(SleuthkitCase sleuthkitCase, String domain) throws DiscoveryException, InterruptedException { + public List getAllArtifactsForDomain(SleuthkitCase sleuthkitCase, String domain) throws DiscoveryException { List artifacts = new ArrayList<>(); Map> dateMap = new HashMap<>(); if (!StringUtils.isBlank(domain)) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java index 9aaa375eba..31006d7e93 100755 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java @@ -63,7 +63,7 @@ class DomainSearchCacheLoader extends CacheLoader searchAttributes = new ArrayList<>(); + Set searchAttributes = new HashSet<>(); searchAttributes.add(key.getGroupAttributeType()); searchAttributes.addAll(key.getFileSortingMethod().getRequiredAttributes()); for (AttributeType attr : searchAttributes) { @@ -304,7 +304,7 @@ class DomainSearchCacheLoader extends CacheLoader { return compareStrings(first.getDomain().toLowerCase(), second.getDomain().toLowerCase()); }; } - + /** * Sorts domains by page view count. - * + * * This comparator sorts results in descending order (largest -> smallest). */ private static Comparator getPageViewComparator() { return (Result domain1, Result domain2) -> { - if (domain1.getType() != SearchData.Type.DOMAIN || - domain2.getType() != SearchData.Type.DOMAIN) { + if (domain1.getType() != SearchData.Type.DOMAIN + || domain2.getType() != SearchData.Type.DOMAIN) { return 0; } ResultDomain first = (ResultDomain) domain1; ResultDomain second = (ResultDomain) domain2; - + long firstPageViews = first.getTotalPageViews(); long secondPageViews = second.getTotalPageViews(); return Long.compare(secondPageViews, firstPageViews); }; } - + /** - * Sorts result domains by last activity date time. The results will be in + * Sorts result domains by last activity date time. The results will be in * descending order. */ private static Comparator getLastActivityDateTimeComparator() { return (Result domain1, Result domain2) -> { - if (domain1.getType() != SearchData.Type.DOMAIN || - domain2.getType() != SearchData.Type.DOMAIN) { + if (domain1.getType() != SearchData.Type.DOMAIN + || domain2.getType() != SearchData.Type.DOMAIN) { return 0; } ResultDomain first = (ResultDomain) domain1; ResultDomain second = (ResultDomain) domain2; - + long firstActivityEnd = first.getActivityEnd(); long secondActivityEnd = second.getActivityEnd(); return Long.compare(secondActivityEnd, firstActivityEnd); }; } - + /** - * Sorts result domains by most file downloads. The results will be in + * Sorts result domains by most file downloads. The results will be in * descending order. */ private static Comparator getWebDownloadsComparator() { return (Result domain1, Result domain2) -> { - if (domain1.getType() != SearchData.Type.DOMAIN || - domain2.getType() != SearchData.Type.DOMAIN) { + if (domain1.getType() != SearchData.Type.DOMAIN + || domain2.getType() != SearchData.Type.DOMAIN) { return 0; } ResultDomain first = (ResultDomain) domain1; ResultDomain second = (ResultDomain) domain2; - + long firstFilesDownloaded = first.getFilesDownloaded(); long secondFilesDownloaded = second.getFilesDownloaded(); return Long.compare(secondFilesDownloaded, firstFilesDownloaded); @@ -388,10 +388,10 @@ public class ResultsSorter implements Comparator { Bundle.FileSorter_SortingMethod_keywordlist_displayName()), // Sort alphabetically by list of keyword list names found BY_FULL_PATH(new ArrayList<>(), Bundle.FileSorter_SortingMethod_fullPath_displayName()), // Sort alphabetically by path - BY_DOMAIN_NAME(new ArrayList<>(),Bundle.FileSorter_SortingMethod_domain_displayName()), - BY_PAGE_VIEWS(new ArrayList<>(), Bundle.FileSorter_SortingMethod_pageViews_displayName()), - BY_DOWNLOADS(new ArrayList<>(), Bundle.FileSorter_SortingMethod_downloads_displayName()), - BY_LAST_ACTIVITY(new ArrayList<>(), Bundle.FileSorter_SortingMethod_activity_displayName()); + BY_DOMAIN_NAME(Arrays.asList(new DiscoveryAttributes.DomainCategoryAttribute()), Bundle.FileSorter_SortingMethod_domain_displayName()), + BY_PAGE_VIEWS(Arrays.asList(new DiscoveryAttributes.DomainCategoryAttribute()), Bundle.FileSorter_SortingMethod_pageViews_displayName()), + BY_DOWNLOADS(Arrays.asList(new DiscoveryAttributes.DomainCategoryAttribute()), Bundle.FileSorter_SortingMethod_downloads_displayName()), + BY_LAST_ACTIVITY(Arrays.asList(new DiscoveryAttributes.DomainCategoryAttribute()), Bundle.FileSorter_SortingMethod_activity_displayName()); private final String displayName; private final List requiredAttributes; diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java index b56e60aa8f..93ae57db36 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractArtifactDetailsPanel.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.discovery.ui; import java.awt.Component; import javax.swing.JPanel; -import javax.swing.JScrollPane; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -30,14 +29,13 @@ import org.sleuthkit.datamodel.BlackboardArtifact; */ public abstract class AbstractArtifactDetailsPanel extends JPanel { + private static final long serialVersionUID = 1L; + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public Component getComponent() { - // Slap a vertical scrollbar on the panel. - return new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + return this; } - private static final long serialVersionUID = 1L; - /** * Called to display the contents of the given artifact. * diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form index 3b9f9f4c7a..002baadab7 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.form @@ -4,7 +4,7 @@ - + @@ -22,12 +22,12 @@ - + - + @@ -37,9 +37,13 @@ + + + + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java index 6cce1aeeb9..33e8b1c8f8 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsListPanel.java @@ -25,6 +25,7 @@ import java.util.logging.Level; import javax.swing.JPopupMenu; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellRenderer; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle; @@ -32,6 +33,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.guiutils.SimpleTableCellRenderer; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TimeUtilities; @@ -56,6 +58,11 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { ArtifactsListPanel(BlackboardArtifact.ARTIFACT_TYPE artifactType) { tableModel = new DomainArtifactTableModel(artifactType); initComponents(); + // add the cell renderer to all columns + TableCellRenderer renderer = new SimpleTableCellRenderer(); + for (int i = 0; i < tableModel.getColumnCount(); ++i) { + artifactsTable.getColumnModel().getColumn(i).setCellRenderer(renderer); + } artifactsTable.getRowSorter().toggleSortOrder(0); artifactsTable.getRowSorter().toggleSortOrder(0); } @@ -94,11 +101,15 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { @Override BlackboardArtifact getSelectedArtifact() { - int selectedIndex = artifactsTable.getSelectionModel().getLeadSelectionIndex(); - if (selectedIndex < artifactsTable.getSelectionModel().getMinSelectionIndex() || artifactsTable.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > artifactsTable.getSelectionModel().getMaxSelectionIndex()) { + if (artifactsTable.getModel() instanceof DomainArtifactTableModel) { + int selectedIndex = artifactsTable.getSelectionModel().getLeadSelectionIndex(); + if (selectedIndex < artifactsTable.getSelectionModel().getMinSelectionIndex() || artifactsTable.getSelectionModel().getMaxSelectionIndex() < 0 || selectedIndex > artifactsTable.getSelectionModel().getMaxSelectionIndex()) { + return null; + } + return tableModel.getArtifactByRow(artifactsTable.convertRowIndexToModel(selectedIndex)); + } else { return null; } - return tableModel.getArtifactByRow(artifactsTable.convertRowIndexToModel(selectedIndex)); } @Override @@ -124,7 +135,12 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override void addArtifacts(List artifactList) { - tableModel.setContents(artifactList); + if (!artifactList.isEmpty()) { + artifactsTable.setModel(tableModel); + tableModel.setContents(artifactList); + } else { + artifactsTable.setModel(new EmptyTableModel()); + } artifactsTable.validate(); artifactsTable.repaint(); tableModel.fireTableDataChanged(); @@ -152,10 +168,12 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { artifactsTable = new javax.swing.JTable(); setOpaque(false); - setPreferredSize(new java.awt.Dimension(300, 0)); + setPreferredSize(new java.awt.Dimension(350, 10)); jScrollPane1.setBorder(null); + jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 0)); + jScrollPane1.setPreferredSize(new java.awt.Dimension(350, 10)); artifactsTable.setAutoCreateRowSorter(true); artifactsTable.setModel(tableModel); @@ -166,11 +184,11 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -357,6 +375,46 @@ final class ArtifactsListPanel extends AbstractArtifactListPanel { } } } + + /** + * Table model which displays only that no results were found. + */ + private class EmptyTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + @Override + public int getRowCount() { + return 1; + } + + @Override + public int getColumnCount() { + return 1; + } + + @NbBundle.Messages({"ArtifactsListPanel.noResultsFound.text=No results found"}) + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return Bundle.ArtifactsListPanel_noResultsFound_text(); + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return Bundle.ArtifactsListPanel_dateColumn_name(); + case 1: + 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 artifactsTable; // 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 index 2c919f371f..1202f20647 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactsWorker.java @@ -61,7 +61,8 @@ class ArtifactsWorker extends SwingWorker, Void> { return domainSearch.getArtifacts(new DomainSearchArtifactsRequest(Case.getCurrentCase().getSleuthkitCase(), domain, artifactType)); } catch (DiscoveryException ex) { if (ex.getCause() instanceof InterruptedException) { - logger.log(Level.INFO, "MiniTimeline search was cancelled or interrupted for domain: {0}", domain); + this.cancel(true); + //ignore the exception as it was cancelled while the cache was performing its get and we support cancellation } else { throw ex; } @@ -84,6 +85,6 @@ class ArtifactsWorker extends SwingWorker, Void> { //Worker was cancelled after previously finishing its background work, exception ignored to cut down on non-helpful logging } } - + } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties index e6ce6cfda7..5a2bc066ba 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties @@ -53,12 +53,11 @@ DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which ObjectDetectedFilterPanel.text=Object Detected: 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= PreviouslyNotableFilterPanel.text_1=Include only previously notable domains -KnownAccountTypeFilterPanel.text_1=Include only domains with a known account type \ No newline at end of file +KnownAccountTypeFilterPanel.text_1=Include only domains with a known account type +LoadingPanel.detailsLabel.AccessibleContext.accessibleName=detailsLabel 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 3fcce54c93..98deeee12f 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED @@ -3,6 +3,7 @@ ArtifactMenuMouseAdapter_label=Extract Files ArtifactsListPanel.dateColumn.name=Date/Time ArtifactsListPanel.fileNameColumn.name=Name ArtifactsListPanel.mimeTypeColumn.name=MIME Type +ArtifactsListPanel.noResultsFound.text=No results found ArtifactsListPanel.termColumn.name=Term ArtifactsListPanel.titleColumn.name=Title ArtifactsListPanel.urlColumn.name=URL @@ -52,14 +53,20 @@ DocumentPanel.numberOfImages.noImages=No images # {0} - numberOfImages DocumentPanel.numberOfImages.text=1 of {0} images DocumentWrapper.previewInitialValue=Preview not generated yet. -DomainDetailsPanel.miniTimelineTitle.text=Mini Timeline +DomainDetailsPanel.miniTimelineTitle.text=Timeline # {0} - startDate # {1} - endDate DomainSummaryPanel.activity.text=Activity: {0} to {1} +DomainSummaryPanel.category.text=Category: DomainSummaryPanel.downloads.text=Files downloaded: +DomainSummaryPanel.known.text=User role: Known account type(s) DomainSummaryPanel.loadingImages.text=Loading thumbnail... +DomainSummaryPanel.no.text=No +DomainSummaryPanel.notability.text=Previously tagged as notable: DomainSummaryPanel.pages.text=Page views in past 60 days: DomainSummaryPanel.totalPages.text=Total page views: +DomainSummaryPanel.unknown.text=User role: Unknown +DomainSummaryPanel.yes.text=Yes GroupsListPanel.noDomainResults.message.text=No domains were found for the selected filters.\n\nReminder:\n -The Recent Activity module must be run on each data source you want to find results in.\n -The Central Repository module must be run on each data source if you want to filter or sort by past occurrences.\n -The iOS Analyzer (iLEAPP) module must be run on each data source which contains data from an iOS device.\n GroupsListPanel.noFileResults.message.text=No files were found for the selected filters.\n\nReminder:\n -The File Type Identification module must be run on each data source you want to find results in.\n -The Hash Lookup module must be run on each data source if you want to filter by past occurrence.\n -The Picture Analyzer module must be run on each data source if you are filtering by User Created content. GroupsListPanel.noResults.title.text=No results found @@ -68,12 +75,16 @@ ImageThumbnailPanel.isDeleted.text=All instances of file are deleted. # {0} - otherInstanceCount ImageThumbnailPanel.nameLabel.more.text=\ and {0} more InterestingItemsFilterPanel.error.text=At least one interesting file set name must be selected. +LoadingPanel.loading.text=Loading, please wait. +# {0} - resultInfo +LoadingPanel.retrieving.text=Retrieving results for {0}. MiniTimelineArtifactListPanel.descriptionColumn.name=\ Description MiniTimelineArtifactListPanel.typeColumn.name=Result Type MiniTimelineArtifactListPanel.value.noValue=No value available. MiniTimelineDateListPanel.countColumn.name=Count MiniTimelineDateListPanel.dateColumn.name=Date MiniTimelineDateListPanel.value.noValue=No value available. +MiniTimelinePanel.loadingPanel.details=the Timeline view MonthAbbreviation.aprilAbbrev=Apr MonthAbbreviation.augustAbbrev=Aug MonthAbbreviation.decemberAbbrev=Dec @@ -156,15 +167,14 @@ DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which ObjectDetectedFilterPanel.text=Object Detected: 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= PreviouslyNotableFilterPanel.text_1=Include only previously notable domains KnownAccountTypeFilterPanel.text_1=Include only domains with a known account type +LoadingPanel.detailsLabel.AccessibleContext.accessibleName=detailsLabel 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 index 9281965410..f3718bad99 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.form @@ -3,7 +3,7 @@
- + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java index 9667ff94d8..4132947918 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ContentViewerDetailsPanel.java @@ -50,7 +50,7 @@ final class ContentViewerDetailsPanel extends AbstractArtifactDetailsPanel { // //GEN-BEGIN:initComponents private void initComponents() { - setPreferredSize(new java.awt.Dimension(500, 0)); + setPreferredSize(new java.awt.Dimension(300, 0)); setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents @@ -59,7 +59,9 @@ final class ContentViewerDetailsPanel extends AbstractArtifactDetailsPanel { public void setArtifact(BlackboardArtifact artifact) { Node node = Node.EMPTY; if (artifact != null) { - node = new BlackboardArtifactNode(artifact); + boolean useAssociatedFile = artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() + || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID(); + node = new BlackboardArtifactNode(artifact, useAssociatedFile); } contentViewer.setNode(node); } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java index 299e1285df..a74b32ca26 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryTopComponent.java @@ -60,9 +60,11 @@ public final class DiscoveryTopComponent extends TopComponent { private volatile static int previousDividerLocation = 250; private final GroupListPanel groupListPanel; private final ResultsPanel resultsPanel; + private JPanel detailsPanel = new JPanel(); private String selectedDomainTabName; private Type searchType; private int dividerLocation = JSplitPane.UNDEFINED_CONDITION; + private SwingAnimator animator = null; /** @@ -83,25 +85,25 @@ public final class DiscoveryTopComponent extends TopComponent { } }); + rightSplitPane.setTopComponent(resultsPanel); + resetBottomComponent(); rightSplitPane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equalsIgnoreCase(JSplitPane.DIVIDER_LOCATION_PROPERTY)) { + if (evt.getPropertyName().equalsIgnoreCase(JSplitPane.DIVIDER_LOCATION_PROPERTY) + && ((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))) { //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 - && 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(); - - } + previousDividerLocation = (int) evt.getNewValue(); } } - }); - rightSplitPane.setTopComponent(resultsPanel); - rightSplitPane.setBottomComponent(new JPanel()); + } + ); + } /** @@ -131,7 +133,8 @@ public final class DiscoveryTopComponent extends TopComponent { * @return The open DiscoveryTopComponent or null if it has not been opened. */ public static DiscoveryTopComponent getTopComponent() { - return (DiscoveryTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + DiscoveryTopComponent discoveryTopComp = (DiscoveryTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + return discoveryTopComp; } /** @@ -153,6 +156,11 @@ public final class DiscoveryTopComponent extends TopComponent { DiscoveryEventUtils.getDiscoveryEventBus().register(groupListPanel); } + private void resetBottomComponent() { + rightSplitPane.setBottomComponent(new JPanel()); + rightSplitPane.setDividerLocation(JSplitPane.UNDEFINED_CONDITION); + } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override protected void componentClosed() { @@ -162,11 +170,12 @@ public final class DiscoveryTopComponent extends TopComponent { DiscoveryEventUtils.getDiscoveryEventBus().unregister(this); DiscoveryEventUtils.getDiscoveryEventBus().unregister(groupListPanel); DiscoveryEventUtils.getDiscoveryEventBus().unregister(resultsPanel); - DiscoveryEventUtils.getDiscoveryEventBus().unregister(rightSplitPane.getBottomComponent()); - if (rightSplitPane.getBottomComponent() instanceof DomainDetailsPanel) { - selectedDomainTabName = ((DomainDetailsPanel) rightSplitPane.getBottomComponent()).getSelectedTabName(); + DiscoveryEventUtils.getDiscoveryEventBus().unregister(detailsPanel); + if (detailsPanel instanceof DomainDetailsPanel) { + ((DomainDetailsPanel) detailsPanel).unregister(); + selectedDomainTabName = ((DomainDetailsPanel) detailsPanel).getSelectedTabName(); } - rightSplitPane.setDividerLocation(JSplitPane.UNDEFINED_CONDITION); + resetBottomComponent(); super.componentClosed(); } @@ -336,12 +345,13 @@ public final class DiscoveryTopComponent extends TopComponent { } selectedDomainTabName = validateLastSelectedType(searchCompleteEvent); DomainDetailsPanel domainDetailsPanel = new DomainDetailsPanel(); - rightSplitPane.setBottomComponent(domainDetailsPanel); domainDetailsPanel.configureArtifactTabs(selectedDomainTabName); + detailsPanel = domainDetailsPanel; } else { - rightSplitPane.setBottomComponent(new FileDetailsPanel()); + detailsPanel = new FileDetailsPanel(); } - DiscoveryEventUtils.getDiscoveryEventBus().register(rightSplitPane.getBottomComponent()); + rightSplitPane.setBottomComponent(detailsPanel); + DiscoveryEventUtils.getDiscoveryEventBus().register(detailsPanel); descriptionText += searchCompleteEvent.getFilters().stream().map(AbstractFilter::getDesc).collect(Collectors.joining("; ")); progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchComplete_text(descriptionText)); progressMessageTextArea.setCaretPosition(0); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.form index fbd55756e9..bdef0da96f 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.form @@ -1,11 +1,21 @@ + + + + + + + + + + - - + + - + @@ -23,23 +33,4 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java index 3bc5ae14a7..1cc29aef1d 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainArtifactsTabPanel.java @@ -22,7 +22,6 @@ import org.sleuthkit.autopsy.contentviewers.artifactviewers.GeneralPurposeArtifa 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; @@ -64,7 +63,8 @@ final class DomainArtifactsTabPanel extends JPanel { this.artifactType = type; listPanel = new ArtifactsListPanel(artifactType); listPanel.addMouseListener(new ArtifactMenuMouseAdapter(listPanel)); - jSplitPane1.setLeftComponent(listPanel); + mainSplitPane.setLeftComponent(listPanel); + add(mainSplitPane); setRightComponent(); listPanel.addSelectionListener(listener); } @@ -91,7 +91,7 @@ final class DomainArtifactsTabPanel extends JPanel { break; } if (rightPanel != null) { - jSplitPane1.setRightComponent(rightPanel.getComponent()); + mainSplitPane.setRightComponent(rightPanel.getComponent()); } } @@ -115,10 +115,14 @@ final class DomainArtifactsTabPanel extends JPanel { this.status = status; if (status == ArtifactRetrievalStatus.UNPOPULATED) { listPanel.clearList(); - if (rightPanel != null){ + removeAll(); + add(mainSplitPane); + if (rightPanel != null) { rightPanel.setArtifact(null); } - + } else if (status == ArtifactRetrievalStatus.POPULATING) { + removeAll(); + add(new LoadingPanel(artifactType.getDisplayName())); } } @@ -130,7 +134,7 @@ final class DomainArtifactsTabPanel extends JPanel { */ @Subscribe void handleArtifactSearchResultEvent(DiscoveryEventUtils.ArtifactSearchResultEvent artifactresultEvent) { - if (artifactType == artifactresultEvent.getArtifactType()) { + if (artifactType == artifactresultEvent.getArtifactType() && status == ArtifactRetrievalStatus.POPULATING) { SwingUtilities.invokeLater(() -> { listPanel.removeSelectionListener(listener); listPanel.addArtifacts(artifactresultEvent.getListOfArtifacts()); @@ -138,6 +142,8 @@ final class DomainArtifactsTabPanel extends JPanel { setEnabled(!listPanel.isEmpty()); listPanel.addSelectionListener(listener); listPanel.selectFirst(); + removeAll(); + add(mainSplitPane); revalidate(); repaint(); try { @@ -169,20 +175,19 @@ final class DomainArtifactsTabPanel extends JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - jSplitPane1 = new javax.swing.JSplitPane(); + mainSplitPane = new javax.swing.JSplitPane(); + mainSplitPane.setDividerLocation(350); + mainSplitPane.setResizeWeight(0.1); + + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); 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; + private javax.swing.JSplitPane mainSplitPane; // End of variables declaration//GEN-END:variables /** diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java index b7c372aa64..df77f939fb 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainDetailsPanel.java @@ -39,7 +39,6 @@ final class DomainDetailsPanel extends JPanel { private static final long serialVersionUID = 1L; private ArtifactsWorker singleArtifactDomainWorker; - private MiniTimelineWorker miniTimelineWorker; private String domain; private String selectedTabName = null; @@ -51,7 +50,9 @@ final class DomainDetailsPanel extends JPanel { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) DomainDetailsPanel() { initComponents(); - jTabbedPane1.add(Bundle.DomainDetailsPanel_miniTimelineTitle_text(), new MiniTimelinePanel()); + MiniTimelinePanel timelinePanel = new MiniTimelinePanel(); + DiscoveryEventUtils.getDiscoveryEventBus().register(timelinePanel); + jTabbedPane1.add(Bundle.DomainDetailsPanel_miniTimelineTitle_text(), timelinePanel); for (BlackboardArtifact.ARTIFACT_TYPE type : SearchData.Type.DOMAIN.getArtifactTypes()) { jTabbedPane1.add(type.getDisplayName(), new DomainArtifactsTabPanel(type)); } @@ -63,7 +64,7 @@ final class DomainDetailsPanel extends JPanel { * * @param tabName The name of the tab to select initially. */ - @NbBundle.Messages({"DomainDetailsPanel.miniTimelineTitle.text=Mini Timeline"}) + @NbBundle.Messages({"DomainDetailsPanel.miniTimelineTitle.text=Timeline"}) @ThreadConfined(type = ThreadConfined.ThreadType.AWT) void configureArtifactTabs(String tabName) { selectedTabName = tabName; @@ -104,6 +105,20 @@ final class DomainDetailsPanel extends JPanel { } } + /** + * Get the status of the currently selected tab. + * + * @return The loading status of the currently selected tab. + */ + DomainArtifactsTabPanel.ArtifactRetrievalStatus getCurrentTabStatus() { + if (jTabbedPane1.getSelectedComponent() instanceof MiniTimelinePanel) { + return ((MiniTimelinePanel) jTabbedPane1.getSelectedComponent()).getStatus(); + } else if (jTabbedPane1.getSelectedComponent() instanceof DomainArtifactsTabPanel) { + return ((DomainArtifactsTabPanel) jTabbedPane1.getSelectedComponent()).getStatus(); + } + return null; + } + /** * Run the worker which retrieves the list of artifacts for the domain to * populate the details area. @@ -127,14 +142,9 @@ final class DomainDetailsPanel extends JPanel { * mini timeline view to populate. */ private void runMiniTimelineWorker(MiniTimelinePanel miniTimelinePanel) { - if (miniTimelineWorker != null && !miniTimelineWorker.isDone()) { - miniTimelineWorker.cancel(true); - } if (miniTimelinePanel.getStatus() == DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED) { - DiscoveryEventUtils.getDiscoveryEventBus().register(miniTimelinePanel); - miniTimelinePanel.setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATING); - miniTimelineWorker = new MiniTimelineWorker(domain); - miniTimelineWorker.execute(); + miniTimelinePanel.setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATING, domain); + new MiniTimelineWorker(domain).execute(); } } @@ -146,20 +156,19 @@ final class DomainDetailsPanel extends JPanel { */ @Subscribe void handlePopulateDomainTabsEvent(DiscoveryEventUtils.PopulateDomainTabsEvent populateEvent) { - domain = populateEvent.getDomain(); SwingUtilities.invokeLater(() -> { - resetTabsStatus(); - selectTab(); - Component selectedComponent = jTabbedPane1.getSelectedComponent(); - if (selectedComponent instanceof DomainArtifactsTabPanel) { - runDomainWorker((DomainArtifactsTabPanel) selectedComponent); - } else if (selectedComponent instanceof MiniTimelinePanel) { - runMiniTimelineWorker((MiniTimelinePanel) selectedComponent); - } - if (StringUtils.isBlank(domain)) { + if (StringUtils.isBlank(populateEvent.getDomain())) { + resetTabsStatus(); //send fade out event DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.DetailsVisibleEvent(false)); } else { + domain = populateEvent.getDomain(); + Component selectedComponent = jTabbedPane1.getSelectedComponent(); + if (selectedComponent instanceof DomainArtifactsTabPanel) { + runDomainWorker((DomainArtifactsTabPanel) selectedComponent); + } else if (selectedComponent instanceof MiniTimelinePanel) { + runMiniTimelineWorker((MiniTimelinePanel) selectedComponent); + } //send fade in event DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.DetailsVisibleEvent(true)); } @@ -176,7 +185,7 @@ final class DomainDetailsPanel extends JPanel { if (comp instanceof DomainArtifactsTabPanel) { ((DomainArtifactsTabPanel) comp).setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED); } else if (comp instanceof MiniTimelinePanel) { - ((MiniTimelinePanel) comp).setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED); + ((MiniTimelinePanel) comp).setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED, domain); } } } @@ -215,4 +224,15 @@ final class DomainDetailsPanel extends JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTabbedPane jTabbedPane1; // End of variables declaration//GEN-END:variables + + /* + * Unregister the MiniTimelinePanel from the event bus. + */ + void unregister() { + for (Component comp : jTabbedPane1.getComponents()) { + if (comp instanceof MiniTimelinePanel) { + DiscoveryEventUtils.getDiscoveryEventBus().unregister(comp); + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.form index 19ecc82ca2..9e7fd5fd3c 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.form @@ -24,18 +24,25 @@ - + + - - + + + + + + + + + + - - - - + + - + @@ -51,20 +58,26 @@ - + - + + + + + + - - - - - - - - + + + + + + + + + @@ -97,11 +110,6 @@ - - - - - @@ -118,11 +126,10 @@ - - - - - + + + + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java index ef3e4967f1..bcf004b89b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java @@ -32,6 +32,7 @@ import javax.swing.ListCellRenderer; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.discovery.search.SearchData; /** * Class which displays a preview and details about a domain. @@ -66,6 +67,8 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< pagesLabel = new javax.swing.JLabel(); filesDownloadedLabel = new javax.swing.JLabel(); totalVisitsLabel = new javax.swing.JLabel(); + domainNotabilityLabel = new javax.swing.JLabel(); + categoryLabel = new javax.swing.JLabel(); setBorder(javax.swing.BorderFactory.createEtchedBorder()); @@ -75,14 +78,10 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< sampleImageLabel.setMinimumSize(new java.awt.Dimension(100, 100)); sampleImageLabel.setPreferredSize(new java.awt.Dimension(100, 100)); - org.openide.awt.Mnemonics.setLocalizedText(activityLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.activityLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(pagesLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.pagesLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(filesDownloadedLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.filesDownloadedLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(totalVisitsLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.totalVisitsLabel.text")); // NOI18N - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -90,12 +89,17 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(domainNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addComponent(domainNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 539, Short.MAX_VALUE) - .addGap(47, 47, 47)) - .addComponent(activityLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(filesDownloadedLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + .addComponent(activityLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(domainNotabilityLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + .addComponent(categoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(4, 4, 4)) .addComponent(pagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(filesDownloadedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(totalVisitsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) @@ -103,6 +107,9 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< .addComponent(sampleImageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {activityLabel, filesDownloadedLabel}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() @@ -110,27 +117,38 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(numberOfImagesLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE) .addComponent(sampleImageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() - .addComponent(domainNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(domainNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(activityLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(domainNotabilityLabel, javax.swing.GroupLayout.Alignment.TRAILING)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(activityLabel) - .addGap(11, 11, 11) - .addComponent(totalVisitsLabel) - .addGap(11, 11, 11) - .addComponent(pagesLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(filesDownloadedLabel) - .addGap(0, 0, Short.MAX_VALUE))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(filesDownloadedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 14, Short.MAX_VALUE) + .addComponent(categoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(totalVisitsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 14, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pagesLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(1, 1, 1))) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {domainNotabilityLabel, totalVisitsLabel}); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {activityLabel, filesDownloadedLabel}); + }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel activityLabel; + private javax.swing.JLabel categoryLabel; private javax.swing.JLabel domainNameLabel; + private javax.swing.JLabel domainNotabilityLabel; private javax.swing.JLabel filesDownloadedLabel; private javax.swing.JLabel numberOfImagesLabel; private javax.swing.JLabel pagesLabel; @@ -145,13 +163,27 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< "DomainSummaryPanel.pages.text=Page views in past 60 days: ", "DomainSummaryPanel.totalPages.text=Total page views: ", "DomainSummaryPanel.downloads.text=Files downloaded: ", - "DomainSummaryPanel.loadingImages.text=Loading thumbnail..."}) + "DomainSummaryPanel.notability.text=Previously tagged as notable: ", + "DomainSummaryPanel.unknown.text=User role: Unknown", + "DomainSummaryPanel.known.text=User role: Known account type(s)", + "DomainSummaryPanel.category.text=Category: ", + "DomainSummaryPanel.loadingImages.text=Loading thumbnail...", + "DomainSummaryPanel.no.text=No", + "DomainSummaryPanel.yes.text=Yes"}) @Override public Component getListCellRendererComponent(JList list, DomainWrapper value, int index, boolean isSelected, boolean cellHasFocus) { domainNameLabel.setText(value.getResultDomain().getDomain()); TimeZone timeZone = ContentUtils.getTimeZone(value.getResultDomain().getDataSource()); String startDate = formatDate(value.getResultDomain().getActivityStart(), timeZone); String endDate = formatDate(value.getResultDomain().getActivityEnd(), timeZone); + String notability = Bundle.DomainSummaryPanel_notability_text(); + if (value.getResultDomain().getPreviouslyNotableInCR() == SearchData.PreviouslyNotable.PREVIOUSLY_NOTABLE) { + notability += Bundle.DomainSummaryPanel_yes_text(); + } else { + notability += Bundle.DomainSummaryPanel_no_text(); + } + domainNotabilityLabel.setText(notability); + categoryLabel.setText(Bundle.DomainSummaryPanel_category_text() + value.getResultDomain().getWebCategory()); activityLabel.setText(Bundle.DomainSummaryPanel_activity_text(startDate, endDate)); totalVisitsLabel.setText(Bundle.DomainSummaryPanel_totalPages_text() + value.getResultDomain().getTotalPageViews()); pagesLabel.setText(Bundle.DomainSummaryPanel_pages_text() + value.getResultDomain().getPageViewsInLast60Days()); @@ -166,20 +198,21 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< setBackground(isSelected ? SELECTION_COLOR : list.getBackground()); return this; } - + /** * Formats an epoch time in a given time zone using the following pattern - * - * MMM dd YYYY - * - * The pattern below is formatted manually to reuse the MonthAbbreviation utility. + * + * MMM dd YYYY + * + * The pattern below is formatted manually to reuse the MonthAbbreviation + * utility. */ private String formatDate(long epochSeconds, TimeZone timeZone) { Instant epochSecondsAsInstant = Instant.ofEpochSecond(epochSeconds); ZonedDateTime dateTime = ZonedDateTime.ofInstant(epochSecondsAsInstant, timeZone.toZoneId()); MonthAbbreviation currentCutOffMonth = MonthAbbreviation.fromMonthValue(dateTime.getMonthValue()); - return String.format("%s %02d %04d", - currentCutOffMonth.toString(), + return String.format("%s %02d %04d", + currentCutOffMonth.toString(), dateTime.getDayOfMonth(), dateTime.getYear()); } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form index 9f1e3516b9..ee52e40121 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form @@ -33,6 +33,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java index 66125e36d0..ef3c7d6ad6 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java @@ -64,6 +64,7 @@ public class DomainSummaryViewer extends javax.swing.JPanel { setLayout(new java.awt.BorderLayout()); domainList.setModel(domainListModel); + domainList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); domainList.setCellRenderer(new DomainSummaryPanel()); domainScrollPane.setViewportView(domainList); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/LoadingPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/LoadingPanel.form new file mode 100644 index 0000000000..649519a353 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/LoadingPanel.form @@ -0,0 +1,60 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/LoadingPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/LoadingPanel.java new file mode 100644 index 0000000000..1b92fe67cf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/LoadingPanel.java @@ -0,0 +1,97 @@ +/* + * 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.apache.commons.lang3.StringUtils; +import org.openide.util.NbBundle; + +/** + * Panel to let user know that the information is loading. + */ +class LoadingPanel extends JPanel { + + private static final long serialVersionUID = 1L; + + /** + * Creates new form LoadingPanel + */ + LoadingPanel() { + this(null); + } + + @NbBundle.Messages({"LoadingPanel.loading.text=Loading, please wait.", + "# {0} - resultInfo", + "LoadingPanel.retrieving.text=Retrieving results for {0}."}) + + LoadingPanel(String details) { + initComponents(); + loadingLabel.setText(Bundle.LoadingPanel_loading_text()); + if (!StringUtils.isBlank(details)) { + detailsLabel.setText(Bundle.LoadingPanel_retrieving_text(details)); + } + } + + /** + * 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() { + + loadingLabel = new javax.swing.JLabel(); + detailsLabel = new javax.swing.JLabel(); + + loadingLabel.setFont(loadingLabel.getFont().deriveFont(loadingLabel.getFont().getSize()+4f)); + loadingLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + loadingLabel.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + + detailsLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + detailsLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP); + detailsLabel.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + detailsLabel.setVerticalTextPosition(javax.swing.SwingConstants.TOP); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(loadingLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(detailsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 390, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(loadingLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(detailsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(30, Short.MAX_VALUE)) + ); + + detailsLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(LoadingPanel.class, "LoadingPanel.detailsLabel.AccessibleContext.accessibleName")); // NOI18N + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel detailsLabel; + private javax.swing.JLabel loadingLabel; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java index 01f3d67183..87a59edcf4 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineArtifactListPanel.java @@ -25,10 +25,12 @@ import java.util.logging.Level; import javax.swing.JPopupMenu; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellRenderer; import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.guiutils.SimpleTableCellRenderer; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.Type; @@ -54,6 +56,11 @@ class MiniTimelineArtifactListPanel extends AbstractArtifactListPanel { MiniTimelineArtifactListPanel() { tableModel = new TypeDescriptionTableModel(); initComponents(); + // add the cell renderer to all columns + TableCellRenderer renderer = new SimpleTableCellRenderer(); + for (int i = 0; i < tableModel.getColumnCount(); ++i) { + artifactsTable.getColumnModel().getColumn(i).setCellRenderer(renderer); + } artifactsTable.getRowSorter().toggleSortOrder(0); artifactsTable.getRowSorter().toggleSortOrder(0); } @@ -138,8 +145,8 @@ class MiniTimelineArtifactListPanel extends AbstractArtifactListPanel { artifactsTable = new javax.swing.JTable(); setOpaque(false); - setPreferredSize(new java.awt.Dimension(300, 0)); - + setPreferredSize(new java.awt.Dimension(200, 10)); + jScrollPane1.setPreferredSize(new java.awt.Dimension(200, 10)); jScrollPane1.setBorder(null); jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 0)); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineDateListPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineDateListPanel.java index 6bff7eb150..39f5f28813 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineDateListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineDateListPanel.java @@ -23,9 +23,11 @@ import java.util.List; import javax.swing.JPanel; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellRenderer; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.MiniTimelineResult; +import org.sleuthkit.autopsy.guiutils.SimpleTableCellRenderer; import org.sleuthkit.datamodel.BlackboardArtifact; /** @@ -42,6 +44,11 @@ class MiniTimelineDateListPanel extends JPanel { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) MiniTimelineDateListPanel() { initComponents(); + // add the cell renderer to all columns + TableCellRenderer renderer = new SimpleTableCellRenderer(); + for (int i = 0; i < tableModel.getColumnCount(); ++i) { + jTable1.getColumnModel().getColumn(i).setCellRenderer(renderer); + } jTable1.getRowSorter().toggleSortOrder(0); jTable1.getRowSorter().toggleSortOrder(0); } @@ -139,8 +146,8 @@ class MiniTimelineDateListPanel extends JPanel { jTable1 = new javax.swing.JTable(); setOpaque(false); - setPreferredSize(new java.awt.Dimension(200, 0)); - + setPreferredSize(new java.awt.Dimension(200, 10)); + jScrollPane1.setPreferredSize(new java.awt.Dimension(200, 10)); jScrollPane1.setBorder(null); jScrollPane1.setMinimumSize(new java.awt.Dimension(0, 0)); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.form index 3e133fcf77..0f0598a148 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.form @@ -1,13 +1,42 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - @@ -23,45 +52,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java index f577622a36..32c9e6bf6c 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelinePanel.java @@ -19,12 +19,11 @@ package org.sleuthkit.autopsy.discovery.ui; import com.google.common.eventbus.Subscribe; -import java.util.logging.Level; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.contentviewers.artifactviewers.GeneralPurposeArtifactViewer; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -32,7 +31,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; /** * Panel to display the entire mini timeline feature. */ -class MiniTimelinePanel extends javax.swing.JPanel { +final class MiniTimelinePanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; @@ -40,10 +39,11 @@ class MiniTimelinePanel extends javax.swing.JPanel { private final MiniTimelineArtifactListPanel artifactListPanel = new MiniTimelineArtifactListPanel(); private DomainArtifactsTabPanel.ArtifactRetrievalStatus status = DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED; private AbstractArtifactDetailsPanel rightPanel = new GeneralPurposeArtifactViewer(); - private static final Logger logger = Logger.getLogger(MiniTimelinePanel.class.getName()); + private String selectedDomain = null; private final ListSelectionListener artifactListener; private final ListSelectionListener dateListener; + @NbBundle.Messages({"MiniTimelinePanel.loadingPanel.details=the Timeline view"}) /** * Creates new form MiniTimelinePanel. */ @@ -62,8 +62,8 @@ class MiniTimelinePanel extends javax.swing.JPanel { } else { rightPanel = new GeneralPurposeArtifactViewer(); } - rightPanel.setArtifact(artifact); mainSplitPane.setRightComponent(rightPanel.getComponent()); + rightPanel.setArtifact(artifact); validate(); repaint(); } @@ -88,6 +88,7 @@ class MiniTimelinePanel extends javax.swing.JPanel { leftSplitPane.setLeftComponent(dateListPanel); leftSplitPane.setRightComponent(artifactListPanel); mainSplitPane.setRightComponent(rightPanel.getComponent()); + add(mainSplitPane); } /** @@ -103,17 +104,24 @@ class MiniTimelinePanel extends javax.swing.JPanel { /** * Manually set the status of the panel. * - * @param status The ArtifactRetrievalStatus of the panel. + * @param status The ArtifactRetrievalStatus of the panel + * @param domain The domain the panel is currently reflecting. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - void setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus status) { + void setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus status, String domain) { this.status = status; + this.selectedDomain = domain; if (status == DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED) { artifactListPanel.clearList(); dateListPanel.clearList(); + removeAll(); + add(mainSplitPane); if (rightPanel != null) { rightPanel.setArtifact(null); } + } else if (status == DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATING) { + removeAll(); + add(new LoadingPanel(Bundle.MiniTimelinePanel_loadingPanel_details())); } } @@ -126,21 +134,19 @@ class MiniTimelinePanel extends javax.swing.JPanel { @Subscribe void handleMiniTimelineResultEvent(DiscoveryEventUtils.MiniTimelineResultEvent miniTimelineResultEvent) { SwingUtilities.invokeLater(() -> { - dateListPanel.removeListSelectionListener(dateListener); - artifactListPanel.removeSelectionListener(artifactListener); - dateListPanel.addArtifacts(miniTimelineResultEvent.getResultList()); - status = DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATED; - setEnabled(!dateListPanel.isEmpty()); - dateListPanel.addSelectionListener(dateListener); - artifactListPanel.addSelectionListener(artifactListener); - dateListPanel.selectFirst(); - revalidate(); - repaint(); - try { - DiscoveryEventUtils.getDiscoveryEventBus().unregister(this); - } catch (IllegalArgumentException notRegistered) { - logger.log(Level.INFO, "Attempting to unregister mini timeline view which was not registered"); - // attempting to remove a tab that was never registered + if (miniTimelineResultEvent.getDomain().equals(selectedDomain)) { + dateListPanel.removeListSelectionListener(dateListener); + artifactListPanel.removeSelectionListener(artifactListener); + dateListPanel.addArtifacts(miniTimelineResultEvent.getResultList()); + status = DomainArtifactsTabPanel.ArtifactRetrievalStatus.POPULATED; + setEnabled(!dateListPanel.isEmpty()); + dateListPanel.addSelectionListener(dateListener); + artifactListPanel.addSelectionListener(artifactListener); + dateListPanel.selectFirst(); + removeAll(); + add(mainSplitPane); + revalidate(); + repaint(); } }); } @@ -157,21 +163,18 @@ class MiniTimelinePanel extends javax.swing.JPanel { mainSplitPane = new javax.swing.JSplitPane(); leftSplitPane = new javax.swing.JSplitPane(); - setMinimumSize(new java.awt.Dimension(0, 0)); - setPreferredSize(new java.awt.Dimension(0, 0)); - setLayout(new java.awt.BorderLayout()); - - mainSplitPane.setResizeWeight(0.4); + mainSplitPane.setDividerLocation(400); + mainSplitPane.setResizeWeight(0.1); mainSplitPane.setToolTipText(""); mainSplitPane.setMinimumSize(new java.awt.Dimension(0, 0)); - mainSplitPane.setPreferredSize(new java.awt.Dimension(0, 0)); + leftSplitPane.setDividerLocation(198); leftSplitPane.setResizeWeight(0.5); leftSplitPane.setMinimumSize(new java.awt.Dimension(0, 0)); - leftSplitPane.setPreferredSize(new java.awt.Dimension(300, 0)); mainSplitPane.setLeftComponent(leftSplitPane); - add(mainSplitPane, java.awt.BorderLayout.CENTER); + setMinimumSize(new java.awt.Dimension(0, 0)); + setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java index 5df30ba3f6..7ef49ca5d1 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MiniTimelineWorker.java @@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.discovery.search.DomainSearch; /** * SwingWorker to retrieve a list of artifacts for a specified type and domain. */ -class MiniTimelineWorker extends SwingWorker, Void> { +class MiniTimelineWorker extends SwingWorker { private final static Logger logger = Logger.getLogger(MiniTimelineWorker.class.getName()); private final String domain; @@ -51,37 +51,35 @@ class MiniTimelineWorker extends SwingWorker, Void> { } @Override - protected List doInBackground() throws Exception { + protected Void doInBackground() throws Exception { List results = new ArrayList<>(); if (!StringUtils.isBlank(domain)) { DomainSearch domainSearch = new DomainSearch(); try { results.addAll(domainSearch.getAllArtifactsForDomain(Case.getCurrentCase().getSleuthkitCase(), domain)); - + DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.MiniTimelineResultEvent(results, domain)); } catch (DiscoveryException ex) { if (ex.getCause() instanceof InterruptedException) { - logger.log(Level.INFO, "MiniTimeline search was cancelled or interrupted for domain: {0}", domain); + this.cancel(true); + //ignore the exception as it was cancelled while the cache was performing its get and we support cancellation } else { throw ex; } } } - return results; + return null; } @Override protected void done() { - List results = new ArrayList<>(); if (!isCancelled()) { try { - results.addAll(get()); - DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.MiniTimelineResultEvent(results)); + get(); } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, "Exception while trying to get list of artifacts for Domain details for mini timeline view for domain: " + domain, ex); } catch (CancellationException ignored) { //Worker was cancelled after previously finishing its background work, exception ignored to cut down on non-helpful logging } } - } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index f0ec1e50d3..223b1e9cb3 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -198,8 +198,13 @@ public class Waypoint { try { List attributeList = artifact.getAttributes(); for (BlackboardAttribute attribute : attributeList) { - BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID()); - attributeMap.put(type, attribute); + try{ + BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID()); + attributeMap.put(type, attribute); + } catch(IllegalArgumentException ex) { + // This was thrown due to a custom attribute that geolocation + // does not currently support. + } } } catch (TskCoreException ex) { throw new GeoLocationDataException("Unable to get attributes from artifact", ex); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java index 0a8ccfd7aa..e46c707eec 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java @@ -1281,7 +1281,7 @@ public final class HealthMonitor implements PropertyChangeListener { } /** - * Get an shared lock for the health monitor database. Acquire this before + * Get a shared lock for the health monitor database. Acquire this before * database reads or writes. * * @return The lock diff --git a/Core/src/org/sleuthkit/autopsy/images/yara_16.png b/Core/src/org/sleuthkit/autopsy/images/yara_16.png new file mode 100644 index 0000000000..2ac1321691 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/yara_16.png differ diff --git a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ALeappAnalyzerIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ALeappAnalyzerIngestModule.java index 705468ae15..1492670ada 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ALeappAnalyzerIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ALeappAnalyzerIngestModule.java @@ -155,7 +155,7 @@ public class ALeappAnalyzerIngestModule implements DataSourceIngestModule { statusHelper.switchToDeterminate(aLeappFilesToProcess.size()); processALeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString()); } else { - aLeappFilesToProcess = findaLeappFilesToProcess(dataSource); + aLeappFilesToProcess = LeappFileProcessor.findLeappFilesToProcess(dataSource); statusHelper.switchToDeterminate(aLeappFilesToProcess.size()); Integer filesProcessedCount = 0; @@ -268,40 +268,7 @@ public class ALeappAnalyzerIngestModule implements DataSourceIngestModule { } - /** - * Find the files that will be processed by the aLeapp program - * - * @param dataSource - * - * @return List of abstract files to process. - */ - private List findaLeappFilesToProcess(Content dataSource) { - List aLeappFiles = new ArrayList<>(); - - FileManager fileManager = getCurrentCase().getServices().getFileManager(); - - // findFiles use the SQL wildcard % in the file name - try { - aLeappFiles = fileManager.findFiles(dataSource, "%", "/"); //NON-NLS - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "No files found to process"); //NON-NLS - return aLeappFiles; - } - - List aLeappFilesToProcess = new ArrayList<>(); - for (AbstractFile aLeappFile : aLeappFiles) { - if (((aLeappFile.getLocalAbsPath() != null) - && (!aLeappFile.getNameExtension().isEmpty() && (!aLeappFile.isVirtual()))) - && ((aLeappFile.getName().toLowerCase().contains(".zip") || (aLeappFile.getName().toLowerCase().contains(".tar"))) - || aLeappFile.getName().toLowerCase().contains(".tgz"))) { - aLeappFilesToProcess.add(aLeappFile); - - } - } - - return aLeappFilesToProcess; - } /** * Build the aLeapp command to run diff --git a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ILeappAnalyzerIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ILeappAnalyzerIngestModule.java index 19cf344942..d124801046 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ILeappAnalyzerIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ILeappAnalyzerIngestModule.java @@ -155,7 +155,7 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { statusHelper.switchToDeterminate(iLeappFilesToProcess.size()); processILeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString()); } else { - iLeappFilesToProcess = findiLeappFilesToProcess(dataSource); + iLeappFilesToProcess = LeappFileProcessor.findLeappFilesToProcess(dataSource); statusHelper.switchToDeterminate(iLeappFilesToProcess.size()); Integer filesProcessedCount = 0; @@ -268,41 +268,6 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule { } - /** - * Find the files that will be processed by the iLeapp program - * - * @param dataSource - * - * @return List of abstract files to process. - */ - private List findiLeappFilesToProcess(Content dataSource) { - - List iLeappFiles = new ArrayList<>(); - - FileManager fileManager = getCurrentCase().getServices().getFileManager(); - - // findFiles use the SQL wildcard % in the file name - try { - iLeappFiles = fileManager.findFiles(dataSource, "%", "/"); //NON-NLS - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "No files found to process"); //NON-NLS - return iLeappFiles; - } - - 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); - - } - } - - return iLeappFilesToProcess; - } - /** * Build the command to run xLeapp * @param moduleOutputPath output path for xLeapp diff --git a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/LeappFileProcessor.java b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/LeappFileProcessor.java index 9f7418f8f9..3e992f0c05 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/LeappFileProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/LeappFileProcessor.java @@ -35,18 +35,24 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import static java.util.Locale.US; import java.util.Map; +import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.FilenameUtils; 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.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; @@ -69,6 +75,52 @@ import org.xml.sax.SAXException; */ public final class LeappFileProcessor { + /** + * Represents metadata for a particular column in a tsv file. + */ + private static class TsvColumn { + + private final String attributeName; + private final String columnName; + private final boolean required; + + /** + * Main constructor. + * + * @param attributeName The BlackboardAttribute name or null if not + * used. + * @param columnName The name of the column in the tsv file. + * @param required Whether or not this attribute is required to be + * present. + */ + TsvColumn(String attributeName, String columnName, boolean required) { + this.attributeName = attributeName; + this.columnName = columnName; + this.required = required; + } + + /** + * @return The BlackboardAttribute name or null if not used. + */ + String getAttributeName() { + return attributeName; + } + + /** + * @return The name of the column in the tsv file. + */ + String getColumnName() { + return columnName; + } + + /** + * @return Whether or not this attribute is required to be present. + */ + boolean isRequired() { + return required; + } + } + private static final Logger logger = Logger.getLogger(LeappFileProcessor.class.getName()); private static final String MODULE_NAME = ILeappAnalyzerModuleFactory.getModuleName(); @@ -77,7 +129,7 @@ public final class LeappFileProcessor { private final Map tsvFiles; private final Map tsvFileArtifacts; private final Map tsvFileArtifactComments; - private final Map>> tsvFileAttributes; + private final Map> tsvFileAttributes; Blackboard blkBoard; @@ -104,9 +156,7 @@ public final class LeappFileProcessor { "LeappFileProcessor.Leapp.cancelled=Leapp run was canceled", "LeappFileProcessor.completed=Leapp Processing Completed", "LeappFileProcessor.error.reading.Leapp.directory=Error reading Leapp Output Directory"}) - public ProcessResult processFiles(Content dataSource, Path moduleOutputPath, AbstractFile LeappFile) { - try { List LeappTsvOutputFiles = findTsvFiles(moduleOutputPath); processLeappFiles(LeappTsvOutputFiles, LeappFile); @@ -123,7 +173,7 @@ public final class LeappFileProcessor { try { List LeappTsvOutputFiles = findTsvFiles(moduleOutputPath); processLeappFiles(LeappTsvOutputFiles, dataSource); - } catch (IOException | IngestModuleException ex) { + } catch (IngestModuleException ex) { logger.log(Level.SEVERE, String.format("Error trying to process Leapp output files in directory %s. ", moduleOutputPath.toString()), ex); //NON-NLS return ProcessResult.ERROR; } @@ -162,7 +212,7 @@ public final class LeappFileProcessor { * Process the Leapp files that were found that match the xml mapping file * * @param LeappFilesToProcess List of files to process - * @param LeappImageFile Abstract file to create artifact for + * @param LeappImageFile Abstract file to create artifact for * * @throws FileNotFoundException * @throws IOException @@ -174,7 +224,7 @@ public final class LeappFileProcessor { String fileName = FilenameUtils.getName(LeappFileName); File LeappFile = new File(LeappFileName); if (tsvFileAttributes.containsKey(fileName)) { - List> attrList = tsvFileAttributes.get(fileName); + List attrList = tsvFileAttributes.get(fileName); try { BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName)); @@ -197,26 +247,34 @@ public final class LeappFileProcessor { * Process the Leapp files that were found that match the xml mapping file * * @param LeappFilesToProcess List of files to process - * @param dataSource The data source. + * @param dataSource The data source. * * @throws FileNotFoundException * @throws IOException */ - private void processLeappFiles(List LeappFilesToProcess, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException { + private void processLeappFiles(List LeappFilesToProcess, Content dataSource) throws IngestModuleException { List bbartifacts = new ArrayList<>(); for (String LeappFileName : LeappFilesToProcess) { String fileName = FilenameUtils.getName(LeappFileName); File LeappFile = new File(LeappFileName); if (tsvFileAttributes.containsKey(fileName)) { - List> attrList = tsvFileAttributes.get(fileName); + List attrList = tsvFileAttributes.get(fileName); + BlackboardArtifact.Type artifactType = null; try { - BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName)); - - processFile(LeappFile, attrList, fileName, artifactType, bbartifacts, dataSource); - + artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName)); } catch (TskCoreException ex) { - throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex); + logger.log(Level.SEVERE, String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex); + } + + if (artifactType == null) { + continue; + } + + try { + processFile(LeappFile, attrList, fileName, artifactType, bbartifacts, dataSource); + } catch (TskCoreException | IOException ex) { + logger.log(Level.SEVERE, String.format("Error processing file at %s", LeappFile.toString()), ex); } } @@ -228,26 +286,34 @@ public final class LeappFileProcessor { } - private void processFile(File LeappFile, List> attrList, String fileName, BlackboardArtifact.Type artifactType, + private void processFile(File LeappFile, List attrList, String fileName, BlackboardArtifact.Type artifactType, List bbartifacts, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException, TskCoreException { + + if (LeappFile == null || !LeappFile.exists() || fileName == null) { + logger.log(Level.WARNING, String.format("Leap file: %s is null or does not exist", LeappFile == null ? LeappFile.toString() : "")); + return; + } else if (attrList == null || artifactType == null || dataSource == null) { + logger.log(Level.WARNING, String.format("attribute list, artifact type or dataSource not provided for %s", LeappFile == null ? LeappFile.toString() : "")); + return; + } + try (BufferedReader reader = new BufferedReader(new FileReader(LeappFile))) { - String line = reader.readLine(); + String header = reader.readLine(); // Check first line, if it is null then no heading so nothing to match to, close and go to next file. - if (line != null) { - Map columnNumberToProcess = findColumnsToProcess(line, attrList); - line = reader.readLine(); + if (header != null) { + Map columnNumberToProcess = findColumnsToProcess(fileName, header, attrList); + String line = reader.readLine(); while (line != null) { Collection bbattributes = processReadLine(line, columnNumberToProcess, fileName); - if (artifactType == null) { - logger.log(Level.SEVERE, "Error trying to process Leapp output files in directory . "); //NON-NLS - } + 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); } } + line = reader.readLine(); } } @@ -258,20 +324,28 @@ public final class LeappFileProcessor { /** * Process the line read and create the necessary attributes for it * - * @param line a tsv line to process that was read + * @param line a tsv line to process that was read * @param columnNumberToProcess Which columns to process in the tsv line - * @param fileName name of file begin processed + * @param fileName name of file begin processed * * @return */ private Collection processReadLine(String line, Map columnNumberToProcess, String fileName) throws IngestModuleException { - + if (MapUtils.isEmpty(columnNumberToProcess)) { + return Collections.emptyList(); + } else if (line == null) { + logger.log(Level.WARNING, "Line is null. Returning empty list for attributes."); + return Collections.emptyList(); + } + String[] columnValues; - + // Check to see if the 2 values are equal, they may not be equal if there is no corresponding data in the line. + // or if the size of the line to split is not equal to the column numbers we are looking to process. This + // can happen when the last value of the tsv line has no data in it. // If this happens then adding an empty value(s) for each columnValue where data does not exist Integer maxColumnNumber = Collections.max(columnNumberToProcess.keySet()); - if (maxColumnNumber > line.split("\\t").length) { + if ((maxColumnNumber > line.split("\\t").length) || (columnNumberToProcess.size() > line.split("\\t").length)) { columnValues = Arrays.copyOf(line.split("\\t"), maxColumnNumber + 1); } else { columnValues = line.split("\\t"); @@ -283,15 +357,17 @@ public final class LeappFileProcessor { Integer columnNumber = columnToProcess.getKey(); String attributeName = columnToProcess.getValue(); - try { - BlackboardAttribute.Type attributeType = Case.getCurrentCase().getSleuthkitCase().getAttributeType(attributeName.toUpperCase()); - if (attributeType == null) { - break; + if (columnValues[columnNumber] != null) { + try { + BlackboardAttribute.Type attributeType = Case.getCurrentCase().getSleuthkitCase().getAttributeType(attributeName.toUpperCase()); + if (attributeType == null) { + continue; + } + String attrType = attributeType.getValueType().getLabel().toUpperCase(); + checkAttributeType(bbattributes, attrType, columnValues, columnNumber, attributeType, fileName); + } catch (TskCoreException ex) { + throw new IngestModuleException(String.format("Error getting Attribute type for Attribute Name %s", attributeName), ex); //NON-NLS } - String attrType = attributeType.getValueType().getLabel().toUpperCase(); - checkAttributeType(bbattributes, attrType, columnValues, columnNumber, attributeType, fileName); - } catch (TskCoreException ex) { - throw new IngestModuleException(String.format("Error getting Attribute type for Attribute Name %s", attributeName), ex); //NON-NLS } } @@ -303,34 +379,60 @@ public final class LeappFileProcessor { } - private void checkAttributeType(Collection bbattributes, String attrType, String[] columnValues, Integer columnNumber, BlackboardAttribute.Type attributeType, + private void checkAttributeType(Collection bbattributes, String attrType, String[] columnValues, int columnNumber, BlackboardAttribute.Type attributeType, String fileName) { + + if (columnValues == null || columnNumber < 0 || columnNumber > columnValues.length || columnValues[columnNumber] == null) { + logger.log(Level.WARNING, String.format("Unable to determine column value at index %d in columnValues: %s", + columnNumber, + columnValues == null ? "" : "[" + String.join(", ", columnValues) + "]")); + return; + } + + String columnValue = columnValues[columnNumber]; + if (attrType.matches("STRING")) { - bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, columnValues[columnNumber])); + bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, columnValue)); } else if (attrType.matches("INTEGER")) { - bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Integer.valueOf(columnValues[columnNumber]))); + try { + bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Integer.valueOf(columnValue))); + } catch (NumberFormatException ex) { + logger.log(Level.WARNING, String.format("Unable to format %s as an integer.", columnValue), ex); + } } else if (attrType.matches("LONG")) { - bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Long.valueOf(columnValues[columnNumber]))); + try { + bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Long.valueOf(columnValue))); + } catch (NumberFormatException ex) { + logger.log(Level.WARNING, String.format("Unable to format %s as an long.", columnValue), ex); + } } else if (attrType.matches("DOUBLE")) { - bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Double.valueOf(columnValues[columnNumber]))); + try { + bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Double.valueOf(columnValue))); + } catch (NumberFormatException ex) { + logger.log(Level.WARNING, String.format("Unable to format %s as an double.", columnValue), ex); + } } else if (attrType.matches("BYTE")) { - bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Byte.valueOf(columnValues[columnNumber]))); + try { + bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, Byte.valueOf(columnValue))); + } catch (NumberFormatException ex) { + logger.log(Level.WARNING, String.format("Unable to format %s as an byte.", columnValue), ex); + } } else if (attrType.matches("DATETIME")) { // format of data should be the same in all the data and the format is 2020-03-28 01:00:17 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-d HH:mm:ss", US); Long dateLong = Long.valueOf(0); try { - Date newDate = dateFormat.parse(columnValues[columnNumber]); + Date newDate = dateFormat.parse(columnValue); dateLong = newDate.getTime() / 1000; bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, dateLong)); } catch (ParseException ex) { // catching error and displaying date that could not be parsed // we set the timestamp to 0 and continue on processing - logger.log(Level.WARNING, String.format("Failed to parse date/time %s for attribute type %s in file %s.", columnValues[columnNumber], attributeType.getDisplayName(), fileName)); //NON-NLS + logger.log(Level.WARNING, String.format("Failed to parse date/time %s for attribute type %s in file %s.", columnValue, attributeType.getDisplayName(), fileName)); //NON-NLS } } else if (attrType.matches("JSON")) { - bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, columnValues[columnNumber])); + bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, columnValue)); } else { // Log this and continue on with processing logger.log(Level.WARNING, String.format("Attribute Type %s not defined.", attrType)); //NON-NLS @@ -343,29 +445,43 @@ public final class LeappFileProcessor { * headings to the columns in the XML mapping file so we know which columns * to process. * - * @param line a tsv heading line of the columns in the file + * @param fileName The name of the file in which these column headers exist. + * @param line a tsv heading line of the columns in the file * @param attrList the list of headings we want to process * * @return the numbered column(s) and attribute(s) we want to use for the - * column(s) + * column(s) */ - private Map findColumnsToProcess(String line, List> attrList) { + private Map findColumnsToProcess(String fileName, String line, List attrList) { String[] columnNames = line.split("\\t"); HashMap columnsToProcess = new HashMap<>(); Integer columnPosition = 0; for (String columnName : columnNames) { // for some reason the first column of the line has unprintable characters so removing them - String cleanColumnName = columnName.replaceAll("[^\\n\\r\\t\\p{Print}]", ""); - for (List atList : attrList) { - if (atList.contains(cleanColumnName.toLowerCase())) { - columnsToProcess.put(columnPosition, atList.get(0)); + String cleanColumnName = columnName.trim().replaceAll("[^\\n\\r\\t\\p{Print}]", ""); + for (TsvColumn tsvColumn : attrList) { + if (cleanColumnName.equalsIgnoreCase(tsvColumn.getColumnName())) { + columnsToProcess.put(columnPosition, tsvColumn.getAttributeName()); break; } } columnPosition++; } + if (columnsToProcess.size() != attrList.size()) { + String missingColumns = IntStream.range(0, attrList.size()) + .filter((idx) -> !columnsToProcess.containsKey(attrList.get(idx).getAttributeName())) + .mapToObj((idx) -> String.format("'%s'", attrList.get(idx).getColumnName() == null ? "" : attrList.get(idx).getColumnName())) + .collect(Collectors.joining(", ")); + + logger.log(Level.WARNING, String.format("Columns size expected not found in file %s based on xml from %s. Column Keys Missing = [%s]; Header Line = '%s'.", + this.xmlFile == null ? "" : this.xmlFile, + fileName, + missingColumns, + line)); + } + return columnsToProcess; } @@ -424,6 +540,18 @@ public final class LeappFileProcessor { String comment = nnm.getNamedItem("comment").getNodeValue(); String parentName = artifactNlist.item(k).getParentNode().getAttributes().getNamedItem("filename").getNodeValue(); + BlackboardArtifact.Type foundArtifactType = null; + try { + foundArtifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(artifactName); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("There was an issue that arose while trying to fetch artifact type for %s.", artifactName), ex); + } + + if (foundArtifactType == null) { + logger.log(Level.SEVERE, String.format("No known artifact mapping found for [artifact: %s, %s]", + artifactName, getXmlFileIdentifier(parentName))); + } + tsvFileArtifacts.put(parentName, artifactName); if (!comment.toLowerCase().matches("null")) { @@ -433,29 +561,66 @@ public final class LeappFileProcessor { } + private String getXmlFileIdentifier(String fileName) { + return String.format("file: %s, filename: %s", + this.xmlFile == null ? "" : this.xmlFile, + fileName == null ? "" : fileName); + } + + private String getXmlAttrIdentifier(String fileName, String attributeName) { + return String.format("attribute: %s %s", + attributeName == null ? "" : attributeName, + getXmlFileIdentifier(fileName)); + } + private void getAttributeNodes(Document xmlinput) { NodeList attributeNlist = xmlinput.getElementsByTagName("AttributeName"); //NON-NLS for (int k = 0; k < attributeNlist.getLength(); k++) { - List attributeList = new ArrayList<>(); NamedNodeMap nnm = attributeNlist.item(k).getAttributes(); String attributeName = nnm.getNamedItem("attributename").getNodeValue(); + if (!attributeName.toLowerCase().matches("null")) { String columnName = nnm.getNamedItem("columnName").getNodeValue(); String required = nnm.getNamedItem("required").getNodeValue(); String parentName = attributeNlist.item(k).getParentNode().getParentNode().getAttributes().getNamedItem("filename").getNodeValue(); - attributeList.add(attributeName.toLowerCase()); - attributeList.add(columnName.toLowerCase()); - attributeList.add(required.toLowerCase()); + BlackboardAttribute.Type foundAttrType = null; + try { + foundAttrType = Case.getCurrentCase().getSleuthkitCase().getAttributeType(attributeName.toUpperCase()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("There was an issue that arose while trying to fetch attribute type for %s.", attributeName), ex); + } + + if (foundAttrType == null) { + logger.log(Level.SEVERE, String.format("No known attribute mapping found for [%s]", getXmlAttrIdentifier(parentName, attributeName))); + } + + if (required != null && required.compareToIgnoreCase("yes") != 0 && required.compareToIgnoreCase("no") != 0) { + logger.log(Level.SEVERE, String.format("Required value %s did not match 'yes' or 'no' for [%s]", + required, getXmlAttrIdentifier(parentName, attributeName))); + } + + if (columnName == null) { + logger.log(Level.SEVERE, String.format("No column name provided for [%s]", getXmlAttrIdentifier(parentName, attributeName))); + } else if (columnName.trim().length() != columnName.length()) { + logger.log(Level.SEVERE, String.format("Column name '%s' starts or ends with whitespace for [%s]", columnName, getXmlAttrIdentifier(parentName, attributeName))); + } else if (columnName.matches("[^ \\S]")) { + logger.log(Level.SEVERE, String.format("Column name '%s' contains invalid characters [%s]", columnName, getXmlAttrIdentifier(parentName, attributeName))); + } + + TsvColumn thisCol = new TsvColumn( + attributeName.toLowerCase(), + columnName.toLowerCase(), + "yes".compareToIgnoreCase(required) == 0); if (tsvFileAttributes.containsKey(parentName)) { - List> attrList = tsvFileAttributes.get(parentName); - attrList.add(attributeList); + List attrList = tsvFileAttributes.get(parentName); + attrList.add(thisCol); tsvFileAttributes.replace(parentName, attrList); } else { - List> attrList = new ArrayList<>(); - attrList.add(attributeList); + List attrList = new ArrayList<>(); + attrList.add(thisCol); tsvFileAttributes.put(parentName, attrList); } } @@ -466,13 +631,12 @@ public final class LeappFileProcessor { /** * 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 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 + * 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 + * to be added to the artifact after the artifact has been created * * @return The newly-created artifact, or null on error */ @@ -490,13 +654,12 @@ public final class LeappFileProcessor { /** * 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 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 + * to be added to the artifact after the artifact has been created * * @return The newly-created artifact, or null on error */ @@ -515,7 +678,7 @@ public final class LeappFileProcessor { * Method to post a list of BlackboardArtifacts to the blackboard. * * @param artifacts A list of artifacts. IF list is empty or null, the - * function will return. + * function will return. */ void postArtifacts(Collection artifacts) { if (artifacts == null || artifacts.isEmpty()) { @@ -535,7 +698,44 @@ public final class LeappFileProcessor { * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException */ private void configExtractor() throws IOException { - PlatformUtil.extractResourceToUserConfigDir(LeappFileProcessor.class, xmlFile, true); + PlatformUtil.extractResourceToUserConfigDir(LeappFileProcessor.class, + xmlFile, true); } + + private static final Set ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList("zip", "tar", "tgz")); + + /** + * Find the files that will be processed by the iLeapp program + * + * @param dataSource + * + * @return List of abstract files to process. + */ + static List findLeappFilesToProcess(Content dataSource) { + + List leappFiles = new ArrayList<>(); + + FileManager fileManager = getCurrentCase().getServices().getFileManager(); + + // findFiles use the SQL wildcard % in the file name + try { + leappFiles = fileManager.findFiles(dataSource, "%", "/"); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "No files found to process"); //NON-NLS + return leappFiles; + } + + List leappFilesToProcess = new ArrayList<>(); + for (AbstractFile leappFile : leappFiles) { + if (((leappFile.getLocalAbsPath() != null) + && !leappFile.isVirtual()) + && leappFile.getNameExtension() != null + && ALLOWED_EXTENSIONS.contains(leappFile.getNameExtension().toLowerCase())) { + leappFilesToProcess.add(leappFile); + } + } + + return leappFilesToProcess; + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/aleap-artifact-attribute-reference.xml b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/aleap-artifact-attribute-reference.xml index 6a573e0abb..784ccefa18 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/aleap-artifact-attribute-reference.xml +++ b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/aleap-artifact-attribute-reference.xml @@ -31,18 +31,18 @@ - - + + - - + + - + @@ -56,17 +56,17 @@ - - - - - + + + + + - + @@ -108,11 +108,11 @@ - - - - - + + + + + @@ -153,17 +153,17 @@ - - - - - + + + + + - + @@ -197,11 +197,11 @@ - - - - - + + + + + @@ -224,6 +224,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -233,10 +306,11 @@ - - + + - + + @@ -256,12 +330,12 @@ - + - + - + diff --git a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ileap-artifact-attribute-reference.xml b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ileap-artifact-attribute-reference.xml index a4169395aa..c0c1b43985 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ileap-artifact-attribute-reference.xml +++ b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/ileap-artifact-attribute-reference.xml @@ -42,8 +42,8 @@ - - + + @@ -84,7 +84,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -143,13 +143,13 @@ - - - - - - - + + + + + + + @@ -160,7 +160,7 @@ - + @@ -209,7 +209,7 @@ - + @@ -226,12 +226,12 @@ - + - + @@ -255,7 +255,7 @@ - + @@ -271,7 +271,7 @@ - + @@ -286,7 +286,7 @@ - + @@ -301,7 +301,7 @@ - + @@ -344,12 +344,12 @@ - + - + @@ -362,7 +362,7 @@ - + @@ -388,7 +388,7 @@ - + @@ -397,7 +397,7 @@ - + @@ -412,14 +412,14 @@ - + - + @@ -528,8 +528,8 @@ - - + + @@ -584,11 +584,11 @@ - - - - - + + + + + --> @@ -596,10 +596,10 @@ - - - - + + + + @@ -651,7 +651,7 @@ - + @@ -680,7 +680,7 @@ - + @@ -705,7 +705,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java index e8f57657f9..e13f41fe7b 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java @@ -109,7 +109,10 @@ final class YaraIngestHelper { } /** - * + * Scan the given AbstractFile for yara rule matches from the rule sets in + * the given directory creating a blackboard artifact for each matching + * rule. + * * @param file The Abstract File being processed. * @param baseRuleSetDirectory Base directory of the compiled rule sets. * @param localFile Local copy of file. @@ -138,8 +141,8 @@ final class YaraIngestHelper { * Scan the given file byte array for rule matches using the YaraJNIWrapper * API. * - * @param fileBytes - * @param ruleSetDirectory + * @param fileBytes An array of the file data. + * @param ruleSetDirectory Base directory of the compiled rule sets. * * @return List of rules that match from the given file from the given rule * set. Empty list is returned if no matches where found. @@ -158,6 +161,17 @@ final class YaraIngestHelper { return matchingRules; } + /** + * Scan the given file for rules that match from the given rule set directory. + * + * @param scanFile Locally stored file to scan. + * @param ruleSetDirectory Base directory of the compiled rule sets. + * @param timeout YARA Scanner timeout value. + * + * @return List of matching rules, if none were found the list will be empty. + * + * @throws YaraWrapperException + */ private static List scanFileForMatch(File scanFile, File ruleSetDirectory, int timeout) throws YaraWrapperException { List matchingRules = new ArrayList<>(); @@ -228,7 +242,7 @@ final class YaraIngestHelper { ProcessBuilder builder = new ProcessBuilder(commandList); try { int result = ExecUtil.execute(builder); - if(result != 0) { + if (result != 0) { throw new IngestModuleException(String.format("Failed to compile Yara rules file %s. Compile error %d", file.toString(), result)); } } catch (SecurityException | IOException ex) { @@ -249,7 +263,7 @@ final class YaraIngestHelper { private static List getRuleSetsForNames(List names) { List ruleSetList = new ArrayList<>(); - RuleSetManager manager = new RuleSetManager(); + RuleSetManager manager = RuleSetManager.getInstance(); for (RuleSet set : manager.getRuleSetList()) { if (names.contains(set.getName())) { ruleSetList.add(set); diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java index 2330f5f448..8924c9bace 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java @@ -25,8 +25,10 @@ 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.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; +import org.sleuthkit.autopsy.modules.yara.ui.YaraGlobalSettingsPanel; import org.sleuthkit.autopsy.modules.yara.ui.YaraIngestSettingsPanel; /** @@ -63,7 +65,7 @@ public class YaraIngestModuleFactory extends IngestModuleFactoryAdapter { @Override public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { - return new YaraIngestSettingsPanel((YaraIngestJobSettings)settings); + return new YaraIngestSettingsPanel((YaraIngestJobSettings) settings); } @Override @@ -89,4 +91,16 @@ public class YaraIngestModuleFactory extends IngestModuleFactoryAdapter { static String getModuleName() { return Bundle.Yara_Module_Name(); } + + @Override + public boolean hasGlobalSettingsPanel() { + return true; + } + + @Override + public IngestModuleGlobalSettingsPanel getGlobalSettingsPanel() { + YaraGlobalSettingsPanel globalOptionsPanel = new YaraGlobalSettingsPanel(); + globalOptionsPanel.load(); + return globalOptionsPanel; + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java index 112cf9206c..3ab248f12f 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.modules.yara.rules; import java.io.File; import java.io.Serializable; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -69,7 +70,15 @@ public class RuleSet implements Comparable, Serializable { * @return List of Files in current directory. */ public List getRuleFiles() { - return Arrays.asList(path.toFile().listFiles()); + List fileList = new ArrayList<>(); + if(path.toFile().exists()) { + File[] fileArray = path.toFile().listFiles(); + if(fileArray != null) { + fileList.addAll(Arrays.asList(fileArray)); + } + } + + return fileList; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetManager.java b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetManager.java index a40d69a07b..ae0e3f6617 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSetManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,14 @@ */ package org.sleuthkit.autopsy.modules.yara.rules; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import javax.swing.SwingUtilities; import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** @@ -34,6 +37,54 @@ public class RuleSetManager { private final static String BASE_FOLDER = "yara"; private final static String RULE_SET_FOLDER = "ruleSets"; + /** + * Rule Set Property names. + */ + public final static String RULE_SET_ADDED = "YARARuleSetAdded"; + public final static String RULE_SET_DELETED = "YARARuleSetDeleted"; + + private final PropertyChangeSupport changeSupport; + + private static RuleSetManager instance; + + /** + * Private constructor for this singleton. + */ + private RuleSetManager() { + changeSupport = new PropertyChangeSupport(this); + } + + /** + * Returns the instance of this manager class. + * + * @return + */ + public synchronized static RuleSetManager getInstance() { + if (instance == null) { + instance = new RuleSetManager(); + } + + return instance; + } + + /** + * Adds a property change listener to the manager. + * + * @param listener Listener to be added. + */ + public static void addPropertyChangeListener(PropertyChangeListener listener) { + getInstance().getChangeSupport().addPropertyChangeListener(listener); + } + + /** + * Remove a property change listener from this manager. + * + * @param listener Listener to be added. + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + getInstance().getChangeSupport().removePropertyChangeListener(listener); + } + /** * Create a new Yara rule set with the given set name. * @@ -43,12 +94,11 @@ public class RuleSetManager { * * @throws RuleSetException RuleSet with given name already exists. */ - public RuleSet createRuleSet(String name) throws RuleSetException { - - if(name == null || name.isEmpty()) { - throw new RuleSetException("YARA rule set name cannot be null or empty string" ); + public synchronized RuleSet createRuleSet(String name) throws RuleSetException { + if (name == null || name.isEmpty()) { + throw new RuleSetException("YARA rule set name cannot be null or empty string"); } - + if (isRuleSetExists(name)) { throw new RuleSetException(String.format("Yara rule set with name %s already exits.", name)); } @@ -58,7 +108,42 @@ public class RuleSetManager { setPath.toFile().mkdir(); - return new RuleSet(name, setPath); + RuleSet newSet = new RuleSet(name, setPath); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + getChangeSupport().firePropertyChange(RULE_SET_ADDED, null, newSet); + } + }); + + return newSet; + } + + /** + * Deletes an existing RuleSet. + * + * @param ruleSet RuleSet to be deleted. + * + * @throws RuleSetException + */ + public synchronized void deleteRuleSet(RuleSet ruleSet) throws RuleSetException { + if (ruleSet == null) { + throw new RuleSetException("YARA rule set name cannot be null or empty string"); + } + + if (!isRuleSetExists(ruleSet.getName())) { + throw new RuleSetException(String.format("A YARA rule set with name %s does not exits.", ruleSet.getName())); + } + + deleteDirectory(ruleSet.getPath().toFile()); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + getChangeSupport().firePropertyChange(RULE_SET_DELETED, ruleSet, null); + } + }); } /** @@ -66,7 +151,7 @@ public class RuleSetManager { * * @return */ - public List getRuleSetList() { + public synchronized List getRuleSetList() { List ruleSets = new ArrayList<>(); Path basePath = getRuleSetPath(); @@ -86,7 +171,7 @@ public class RuleSetManager { * * @return True if the rule set exist. */ - public boolean isRuleSetExists(String name) { + public synchronized boolean isRuleSetExists(String name) { Path basePath = getRuleSetPath(); Path setPath = Paths.get(basePath.toString(), name); @@ -110,4 +195,30 @@ public class RuleSetManager { return basePath; } + /** + * Returns the PropertyChangeSupport instance. + * + * @return PropertyChangeSupport instance. + */ + private PropertyChangeSupport getChangeSupport() { + return changeSupport; + } + + /** + * 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(); + } + } 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 index 6b8d6f9b7b..0a00a19a58 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED @@ -10,6 +10,7 @@ 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. +YARA_Global_Settings_Panel_Title=YARA Options YaraIngestSettingsPanel.border.title=Select YARA rule sets to enable during ingest: YaraIngestSettingsPanel.allFilesButton.text=All Files YaraIngestSettingsPanel.allFilesButton.toolTipText= @@ -21,3 +22,8 @@ YaraRuleSetOptionPanel_badName_msg=Rule set name {0} already exists.\nRule set n 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 +# {0} - rule set name +YaraRuleSetOptionPanel_rule_set_delete=Unable to delete the selected YARA rule set {0}.\nRule set may have already been removed. +# {0} - rule set name +YaraRuleSetOptionPanel_RuleSet_Missing=The folder for the selected YARA rule set, {0}, no longer exists. +YaraRuleSetOptionPanel_RuleSet_Missing_title=Folder removed diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.form index 955199108a..7501a73224 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.form @@ -99,10 +99,6 @@ - - - - diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.java index 7e3e506948..ef6a347921 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/RuleSetDetailsPanel.java @@ -20,7 +20,6 @@ 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; @@ -58,7 +57,7 @@ public class RuleSetDetailsPanel extends javax.swing.JPanel { fileList.setCellRenderer(new FileRenderer()); openFolderButton.setEnabled(false); scrollPane.setViewportView(fileList); - + refreshButton.setEnabled(false); } /** @@ -82,6 +81,7 @@ public class RuleSetDetailsPanel extends javax.swing.JPanel { } openFolderButton.setEnabled(ruleSet != null); + refreshButton.setEnabled(ruleSet != null); } /** @@ -120,7 +120,7 @@ public class RuleSetDetailsPanel extends javax.swing.JPanel { openFolderButton = new javax.swing.JButton(); openLabel = new javax.swing.JLabel(); scrollPane = new javax.swing.JScrollPane(); - javax.swing.JButton refreshButton = new javax.swing.JButton(); + refreshButton = new javax.swing.JButton(); setLayout(new java.awt.GridBagLayout()); @@ -223,6 +223,7 @@ public class RuleSetDetailsPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton openFolderButton; private javax.swing.JLabel openLabel; + private javax.swing.JButton refreshButton; private javax.swing.JScrollPane scrollPane; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraGlobalSettingsPanel.form new file mode 100755 index 0000000000..2c7924e2a4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraGlobalSettingsPanel.form @@ -0,0 +1,18 @@ + + +
+ + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraGlobalSettingsPanel.java new file mode 100755 index 0000000000..6257a91b58 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraGlobalSettingsPanel.java @@ -0,0 +1,85 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.yara.ui; + +import java.awt.BorderLayout; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; + +/** + * YARA global settings panel. + */ +public class YaraGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { + + private static final long serialVersionUID = 1L; + + private final YaraRuleSetOptionPanel panel = new YaraRuleSetOptionPanel(); + + @Messages({ + "YARA_Global_Settings_Panel_Title=YARA Options" + }) + /** + * Creates new form YaraGlobalSettingsPanel + */ + public YaraGlobalSettingsPanel() { + initComponents(); + addOptionPanel(); + } + + @Override + public void saveSettings() { + + } + + @Override + public void store() { + + } + + @Override + public void load() { + if (panel != null) { + panel.updatePanel(); + } + } + + /** + * Add the YaraRuleSetOptionPanel to this panel. + */ + private void addOptionPanel() { + add(panel, BorderLayout.CENTER); + setName(Bundle.YARA_Global_Settings_Panel_Title()); + } + + /** + * 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 + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java index e68b11ccb2..9261d779b1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.modules.yara.ui; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -49,24 +51,42 @@ public class YaraIngestSettingsPanel extends IngestModuleIngestJobSettingsPanel checkboxList = new CheckBoxJList<>(); scrollPane.setViewportView(checkboxList); } - + + /** + * Constructs a new panel with the given JobSetting objects. + * + * @param settings Ingest job settings. + */ public YaraIngestSettingsPanel(YaraIngestJobSettings settings) { this(); - + List setNames = settings.getSelectedRuleSetNames(); - + checkboxList.setModel(listModel); checkboxList.setOpaque(false); - RuleSetManager manager = new RuleSetManager(); - List ruleSetList = manager.getRuleSetList(); + List ruleSetList = RuleSetManager.getInstance().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()); + + RuleSetManager.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + switch (evt.getPropertyName()) { + case RuleSetManager.RULE_SET_ADDED: + handleRuleSetAdded((RuleSet) evt.getNewValue()); + break; + case RuleSetManager.RULE_SET_DELETED: + handleRuleSetDeleted((RuleSet) evt.getOldValue()); + break; + } + } + }); } @Override @@ -84,6 +104,36 @@ public class YaraIngestSettingsPanel extends IngestModuleIngestJobSettingsPanel return new YaraIngestJobSettings(selectedRules, executableFilesButton.isSelected()); } + /** + * Handle the addition of a new Rule Set. + * + * @param ruleSet + */ + private void handleRuleSetAdded(RuleSet ruleSet) { + if (ruleSet == null) { + return; + } + + RuleSetListItem item = new RuleSetListItem(ruleSet); + listModel.addElement(item); + } + + /** + * Handle the removal of the rule set. + * + * @param ruleSet + */ + private void handleRuleSetDeleted(RuleSet ruleSet) { + Enumeration enumeration = listModel.elements(); + while (enumeration.hasMoreElements()) { + RuleSetListItem item = enumeration.nextElement(); + if (item.getDisplayName().equals(ruleSet.getName())) { + listModel.removeElement(item); + return; + } + } + } + /** * RuleSet wrapper class for Checkbox JList model. */ diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.form index c7b05ede35..41be11b2d7 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.form @@ -42,6 +42,14 @@ + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.java index 71d4749cae..885ca9b824 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraRuleSetOptionPanel.java @@ -20,13 +20,12 @@ 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.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; import org.sleuthkit.autopsy.modules.yara.rules.RuleSetException; import org.sleuthkit.autopsy.modules.yara.rules.RuleSetManager; @@ -41,16 +40,12 @@ public class YaraRuleSetOptionPanel extends javax.swing.JPanel { 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) { @@ -77,25 +72,41 @@ public class YaraRuleSetOptionPanel extends javax.swing.JPanel { * Update the panel with the current rule set. */ void updatePanel() { - ruleSetPanel.addSetList(manager.getRuleSetList()); + ruleSetPanel.addSetList(RuleSetManager.getInstance().getRuleSetList()); } + + @Messages({ + "# {0} - rule set name", + "YaraRuleSetOptionPanel_RuleSet_Missing=The folder for the selected YARA rule set, {0}, no longer exists.", + "YaraRuleSetOptionPanel_RuleSet_Missing_title=Folder removed", + }) /** * Handle the change in rule set selection. Update the detail panel with the * selected rule. */ private void handleSelectionChange() { - ruleSetDetailsPanel.setRuleSet(ruleSetPanel.getSelectedRule()); + RuleSet ruleSet = ruleSetPanel.getSelectedRule(); + + if(ruleSet != null && !ruleSet.getPath().toFile().exists()) { + ruleSetDetailsPanel.setRuleSet(null); + ruleSetPanel.removeRuleSet(ruleSet); + JOptionPane.showMessageDialog(this, + Bundle.YaraRuleSetOptionPanel_RuleSet_Missing(ruleSet.getName()), + Bundle.YaraRuleSetOptionPanel_RuleSet_Missing_title(), + JOptionPane.ERROR_MESSAGE); + } else { + ruleSetDetailsPanel.setRuleSet(ruleSet); + } } - @NbBundle.Messages({ + @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", - "YaraRuleSetOptionPanel_badName2_msg=Rule set is invalid.\nRule set names must be non-empty string and unique.", - }) + "YaraRuleSetOptionPanel_badName2_msg=Rule set is invalid.\nRule set names must be non-empty string and unique.",}) /** * Handle the new rule set action. Prompt the user for a rule set name, * create the new set and update the rule set list. @@ -105,16 +116,21 @@ public class YaraRuleSetOptionPanel extends javax.swing.JPanel { Bundle.YaraRuleSetOptionPanel_new_rule_set_name_msg(), Bundle.YaraRuleSetOptionPanel_new_rule_set_name_title()); - if(value == null || value.isEmpty()) { + // User hit cancel. + if(value == null) { + return; + } + + if (value.isEmpty()) { JOptionPane.showMessageDialog(this, Bundle.YaraRuleSetOptionPanel_badName2_msg(), Bundle.YaraRuleSetOptionPanel_badName_title(), JOptionPane.ERROR_MESSAGE); return; } - + try { - ruleSetPanel.addRuleSet(manager.createRuleSet(value)); + ruleSetPanel.addRuleSet(RuleSetManager.getInstance().createRuleSet(value)); } catch (RuleSetException ex) { JOptionPane.showMessageDialog(this, Bundle.YaraRuleSetOptionPanel_badName_msg(value), @@ -124,31 +140,29 @@ public class YaraRuleSetOptionPanel extends javax.swing.JPanel { } } + @Messages({ + "# {0} - rule set name", + "YaraRuleSetOptionPanel_rule_set_delete=Unable to delete the selected YARA rule set {0}.\nRule set may have already been removed." + }) + /** * 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); + if (ruleSet != null) { + try { + RuleSetManager.getInstance().deleteRuleSet(ruleSet); + } catch (RuleSetException ex) { + JOptionPane.showMessageDialog(this, + Bundle.YaraRuleSetOptionPanel_rule_set_delete(ruleSet.getName()), + Bundle.YaraRuleSetOptionPanel_badName_title(), + JOptionPane.ERROR_MESSAGE); + logger.log(Level.WARNING, String.format("Failed to delete YARA rule set %s", ruleSet.getName()), ex); } + ruleSetPanel.removeRuleSet(ruleSet); } - return directoryToBeDeleted.delete(); } /** @@ -172,6 +186,8 @@ public class YaraRuleSetOptionPanel extends javax.swing.JPanel { scrollPane.setBorder(null); + viewportPanel.setMinimumSize(new java.awt.Dimension(1000, 127)); + viewportPanel.setPreferredSize(new java.awt.Dimension(1020, 400)); viewportPanel.setLayout(new java.awt.GridBagLayout()); separator.setOrientation(javax.swing.SwingConstants.VERTICAL); diff --git a/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java index 0e2d9dc976..ca6722911e 100644 --- a/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java @@ -1348,7 +1348,7 @@ class TableReportGenerator { new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH))); columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED ))); attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID)); } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID() == artifactTypeId) { diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java b/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java index c99939377d..d1008f110b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java @@ -390,6 +390,9 @@ public class HTMLReport implements TableReportModule { case TSK_WEB_CATEGORIZATION: in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/domain-16.png"); //NON-NLS break; + case TSK_YARA_HIT: + in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/yara_16.png"); //NON-NLS + break; default: logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/threadutils/TaskRetryUtil.java b/Core/src/org/sleuthkit/autopsy/threadutils/TaskRetryUtil.java index 764208e628..e00cedab83 100755 --- a/Core/src/org/sleuthkit/autopsy/threadutils/TaskRetryUtil.java +++ b/Core/src/org/sleuthkit/autopsy/threadutils/TaskRetryUtil.java @@ -56,11 +56,6 @@ public class TaskRetryUtil { * Constructs an object that encapsulates the specification of a task * attempt for the attemptTask() utility. The attempt will have neither * a delay nor a time out. - * - * @param delay The delay before the task should be attempted, - * may be zero or any positive integer. - * @param delayTimeUnit The time unit for the delay before the task - * should be attempted. */ public TaskAttempt() { this.delay = 0L; @@ -168,7 +163,7 @@ public class TaskRetryUtil { * each attempt and an optional timeout for each attempt. If an attempt * times out, that particular attempt task will be cancelled. * - * @param The return type of the task. + * @tparam T The return type of the task. * @param task The task. * @param attempts The defining details for each attempt of the task. * @param executor The scheduled task executor to be used to attempt the diff --git a/Core/src/org/sleuthkit/autopsy/url/analytics/DomainCategorizer.java b/Core/src/org/sleuthkit/autopsy/url/analytics/DomainCategorizer.java index 513fd3bb08..4bce3c58d7 100644 --- a/Core/src/org/sleuthkit/autopsy/url/analytics/DomainCategorizer.java +++ b/Core/src/org/sleuthkit/autopsy/url/analytics/DomainCategorizer.java @@ -26,7 +26,7 @@ import com.google.common.annotations.Beta; * and should have a class annotation of '(at)ServiceProvider(service = * DomainCategoryProvider.class)'. * - * NOTE: The @SuppressWarnings("try") on the class is to suppress warnings + * NOTE: The (at)SuppressWarnings("try") on the class is to suppress warnings * relating to the fact that the close method can throw an InterruptedException * since Exception can encompass the InterruptedException. See the following * github issue and bugs for more information: 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 ff73c428b9..0acda3954a 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 @@ -256,7 +256,7 @@ public class RecentFilesSummaryTest { */ private BlackboardArtifact getRecentDocumentArtifact(DataSource ds, long artifactId, Long dateTime, String path) { return getArtifact(ds, artifactId, ARTIFACT_TYPE.TSK_RECENT_OBJECT, Arrays.asList( - Pair.of(ATTRIBUTE_TYPE.TSK_DATETIME, dateTime), + Pair.of(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED , dateTime), Pair.of(ATTRIBUTE_TYPE.TSK_PATH, path) )); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index feeea64957..24a42041dd 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -284,7 +284,11 @@ class Ingester { //Make a SolrInputDocument out of the field map SolrInputDocument updateDoc = new SolrInputDocument(); for (String key : fields.keySet()) { - updateDoc.addField(key, Chunker.sanitize((String)fields.get(key)).toString()); + if (fields.get(key).getClass() == String.class) { + updateDoc.addField(key, Chunker.sanitize((String)fields.get(key)).toString()); + } else { + updateDoc.addField(key, fields.get(key)); + } } try { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LanguageSpecificContentIndexingHelper.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LanguageSpecificContentIndexingHelper.java index 38fcfd429e..387399d7ae 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LanguageSpecificContentIndexingHelper.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LanguageSpecificContentIndexingHelper.java @@ -62,8 +62,12 @@ class LanguageSpecificContentIndexingHelper { //Make a SolrInputDocument out of the field map SolrInputDocument updateDoc = new SolrInputDocument(); for (String key : fields.keySet()) { - updateDoc.addField(key, Chunker.sanitize((String)fields.get(key)).toString()); - } + if (fields.get(key).getClass() == String.class) { + updateDoc.addField(key, Chunker.sanitize((String)fields.get(key)).toString()); + } else { + updateDoc.addField(key, fields.get(key)); + } + } try { updateDoc.setField(Server.Schema.ID.toString(), Chunker.sanitize(MiniChunkHelper.getChunkIdString(baseChunkID)).toString()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 2645e9f60a..b6b6590f4e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -1013,16 +1013,34 @@ public class Server { "# {0} - colelction name", "Server.deleteCore.exception.msg=Failed to delete Solr colelction {0}",}) void deleteCollection(String coreName, CaseMetadata metadata) throws KeywordSearchServiceException, KeywordSearchModuleException { try { - IndexingServerProperties properties = getMultiUserServerProperties(metadata.getCaseDirectory()); - HttpSolrClient solrServer = getSolrClient("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); - connectToSolrServer(solrServer); - - CollectionAdminRequest.Delete deleteCollectionRequest = CollectionAdminRequest.deleteCollection(coreName); - CollectionAdminResponse response = deleteCollectionRequest.process(solrServer); - if (response.isSuccess()) { - logger.log(Level.INFO, "Deleted collection {0}", coreName); //NON-NLS + HttpSolrClient solrServer; + if (metadata.getCaseType() == CaseType.SINGLE_USER_CASE) { + solrServer = getSolrClient("http://localhost:" + localSolrServerPort + "/solr"); //NON-NLS + CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, solrServer); + if (null != response.getCoreStatus(coreName).get("instanceDir")) { //NON-NLS + /* + * Send a core unload request to the Solr server, with the + * parameter set that request deleting the index and the + * instance directory (deleteInstanceDir = true). Note that + * this removes everything related to the core on the server + * (the index directory, the configuration files, etc.), but + * does not delete the actual Solr text index because it is + * currently stored in the case directory. + */ + org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(coreName, true, true, solrServer); + } } else { - logger.log(Level.WARNING, "Unable to delete collection {0}", coreName); //NON-NLS + IndexingServerProperties properties = getMultiUserServerProperties(metadata.getCaseDirectory()); + solrServer = getSolrClient("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); + connectToSolrServer(solrServer); + + CollectionAdminRequest.Delete deleteCollectionRequest = CollectionAdminRequest.deleteCollection(coreName); + CollectionAdminResponse response = deleteCollectionRequest.process(solrServer); + if (response.isSuccess()) { + logger.log(Level.INFO, "Deleted collection {0}", coreName); //NON-NLS + } else { + logger.log(Level.WARNING, "Unable to delete collection {0}", coreName); //NON-NLS + } } } catch (SolrServerException | IOException ex) { // We will get a RemoteSolrException with cause == null and detailsMessage diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index df44b2347a..920ac1aaba 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -238,8 +238,11 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { } catch (KeywordSearchModuleException ex) { throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_unableToDeleteCollection(index.getIndexName()), ex); } - if (!FileUtil.deleteDir(new File(index.getIndexPath()).getParentFile())) { - throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath())); + File indexDir = new File(index.getIndexPath()).getParentFile(); + if (indexDir.exists()) { + if (!FileUtil.deleteDir(indexDir)) { + throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath())); + } } } } diff --git a/NEWS.txt b/NEWS.txt index 7f37faa9c8..b51c0948de 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,36 +1,76 @@ ----------------- VERSION 4.17.0 -------------- -GUI: -Expanded the Data Source Summary panel to show recent activity, past cases, analysis results, etc. Also made this available from the main UI when a data source is selected. -Expanded Discovery UI to support searching for and basic display of web domains. It collapses the various web artifacts into a single view. +---------------- VERSION 4.18.0 -------------- +Keyword Search: +- A major upgrade from Solr 4 to Solr 8.6.3. Single user cases continue to use the embedded server. +Multi-user clusters need to install a new Solr 8 server and can now create a Solr cloud with multiple servers. +-- NOTE: Cases created with Autopsy 4.18 cannot be opened by previous versions of Autopsy. Autopsy 4.18 can open older cases though. +- Improved text indexing speed by not doing language detection on unknown file formats and unallocated space. + +Domain Discovery: +- Added details view to Domain Discovery to show what web-based artifacts are associated with the selected domain. +- Updated the Domain Discovery grouping and sorting by options. +- Added basic domain categorization for webmail-based domains. + +Content Viewers: +- Built more specialized viewers for web-based artifacts. + +Data Source Summary: +- Added a “Geolocations” tab that shows what cities the data source was near (based on geolocation data). +- Added a “Timeline” tab that shows counts of events from the last 30 days the data source was used. +- Added navigation buttons to jump from the summary view to the main Autopsy UI (for example to go to the map). Ingest Modules: -Added iOS Analyzer module based on iLEAPP and a subset of its artifacts. -New Picture Analyzer module that does EXIF extraction and HEIC conversion. HEIC/HEIF images are converted to JPEGs that retain EXIF using ImageMagick (replaces the previous EXIF ingest module). -Added support for the latest version of Edge browser that is based on Chromium into Recent Activity. Other Chromium-based browsers are also supported. -Updated the rules that search Web History artifacts for search queries. Expanded module to support multiple search engines for ambiguous URLs. -Bluetooth pairing artifacts are created based on RegRipper output. -Prefetch artifacts record the full path of exes. -PhotoRec module allows you to include or exclude specific file types. -Upgraded to Tika 1.23. +- New YARA ingest module to flag files based on regular expression patterns. +- New “Android Analyzer (aLEAPP)” module based on aLEAPP. Previous “Android Analyzer” also still exists. +- Updated “iOS Analyzer (iLEAPP)” module to create more artifacts and work on disk images. +- Hash Database module will calculate SHA-256 hash in addition to MD5. +- Removed Interesting Item rule that flagged existence of Bitlocker (since it ships with Windows). +- Fixed a major bug in the PhotoRec module that could result in an incorrect file layout if the carved file spanned non-contiguous sectors. +- Fixed MBOX detection bug in Email module. -Performance: -Documents are added to Solr in batches instead of one by one. -More efficient queries to find WAL files for SQLite databases. -Use a local drive for temp files for multi-user cases instead of the shared folder. - -Command Line -Command line support for report profiles. -Restored support for Windows file type association for opening a case in Autopsy by double clicking case metadata (.aut) file. -Better feedback for command line argument errors. +Reporting: +- Attachments from tagged messages are now included in a Portable Case. Misc: -Updated versions of libvmdk, libvhdi, and libewf. -Persona UI fixes: Pre-populate account and changed order of New Persona dialog. -Streaming ingest support added to auto ingest. -Recent Activity module processes now use the global timeout. -Option to include Autopsy executable in portable case (Windows only.) -Upgraded to NetBeans 11 Rich Client Platform. -Added debug feature to save the stack trace on all threads. +- Added support for Ext4 inline data and sparse blocks (via TSK fix). +- Updated PostgreSQL JDBC driver to support any recent version of PostgreSQL for multi-user cases and PostgreSQL Central Repository. +- Added personas to the summary viewer in CVT. +- Handling of bad characters in auto ingest manifest files. +- Assorted small bug fixes. + + +---------------- VERSION 4.17.0 -------------- +GUI: +- Expanded the Data Source Summary panel to show recent activity, past cases, analysis results, etc. Also made this available from the main UI when a data source is selected. +- Expanded Discovery UI to support searching for and basic display of web domains. It collapses the various web artifacts into a single view. + +Ingest Modules: +- Added iOS Analyzer module based on iLEAPP and a subset of its artifacts. +- New Picture Analyzer module that does EXIF extraction and HEIC conversion. HEIC/HEIF images are converted to JPEGs that retain EXIF using ImageMagick (replaces the previous EXIF ingest module). +- Added support for the latest version of Edge browser that is based on Chromium into Recent Activity. Other Chromium-based browsers are also supported. +- Updated the rules that search Web History artifacts for search queries. Expanded module to support multiple search engines for ambiguous URLs. +- Bluetooth pairing artifacts are created based on RegRipper output. +- Prefetch artifacts record the full path of exes. +- PhotoRec module allows you to include or exclude specific file types. +- Upgraded to Tika 1.23. + +Performance: +- Documents are added to Solr in batches instead of one by one. +- More efficient queries to find WAL files for SQLite databases. +- Use a local drive for temp files for multi-user cases instead of the shared folder. + +Command Line +- Command line support for report profiles. +- Restored support for Windows file type association for opening a case in Autopsy by double clicking case metadata (.aut) file. +- Better feedback for command line argument errors. + +Misc: +- Updated versions of libvmdk, libvhdi, and libewf. +- Persona UI fixes: Pre-populate account and changed order of New Persona dialog. +- Streaming ingest support added to auto ingest. +- Recent Activity module processes now use the global timeout. +- Option to include Autopsy executable in portable case (Windows only.) +- Upgraded to NetBeans 11 Rich Client Platform. +- Added debug feature to save the stack trace on all threads. ---------------- VERSION 4.16.0 -------------- diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java index 058b639091..0d64661f6c 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java @@ -45,7 +45,7 @@ import org.sleuthkit.autopsy.url.analytics.DomainCategory; * messaging: * https://www.raymond.cc/blog/list-of-web-messengers-for-your-convenience/ * - * NOTE: The @SuppressWarnings("try") on the class is to suppress warnings + * NOTE: The (at)SuppressWarnings("try") on the class is to suppress warnings * relating to the fact that the close method can throw an InterruptedException * since Exception can encompass the InterruptedException. See the following * github issue and bugs for more information: diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index bdcd9fbe30..38552dfd2e 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -68,7 +68,6 @@ 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; @@ -98,7 +97,6 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskDataException; /** * Extract windows registry data using regripper. Runs two versions of @@ -539,7 +537,7 @@ class ExtractRegistry extends Extract { //sometimes etime will be an empty string and therefore can not be parsed into a date if (etime != null && !etime.isEmpty()) { try { - mtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy").parse(etime).getTime(); + mtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US).parse(etime).getTime(); String Tempdate = mtime.toString(); mtime = Long.valueOf(Tempdate) / MS_IN_SEC; } catch (ParseException ex) { @@ -604,7 +602,7 @@ class ExtractRegistry extends Extract { case "InstallDate": //NON-NLS if (value != null && !value.isEmpty()) { try { - installtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy").parse(value).getTime(); + installtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US).parse(value).getTime(); String Tempdate = installtime.toString(); installtime = Long.valueOf(Tempdate) / MS_IN_SEC; } catch (ParseException e) { @@ -783,7 +781,7 @@ class ExtractRegistry extends Extract { try { String mTimeAttr = artnode.getAttribute("mtime"); if (mTimeAttr != null && !mTimeAttr.isEmpty()) { - itemMtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy").parse(mTimeAttr).getTime(); //NON-NLS + itemMtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US).parse(mTimeAttr).getTime(); //NON-NLS itemMtime /= MS_IN_SEC; } } catch (ParseException ex) { @@ -866,6 +864,8 @@ class ExtractRegistry extends Extract { parentModuleName, sid)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, parentModuleName, homeDir)); + + newArtifacts.add(bbart); } else { //add attributes to existing artifact BlackboardAttribute bbattr = bbart.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_USER_NAME)); @@ -878,10 +878,10 @@ class ExtractRegistry extends Extract { if (bbattr == null) { bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, parentModuleName, homeDir)); - } + } } bbart.addAttributes(bbattributes); - newArtifacts.add(bbart); + } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding account artifact to blackboard.", ex); //NON-NLS } @@ -1163,7 +1163,7 @@ class ExtractRegistry extends Extract { Collection getAttributesForAccount(Map userInfo, List groupList, boolean existingUser, AbstractFile regAbstractFile) throws ParseException { Collection bbattributes = new ArrayList<>(); - SimpleDateFormat regRipperTimeFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy 'Z'"); + SimpleDateFormat regRipperTimeFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy 'Z'", US); regRipperTimeFormat.setTimeZone(getTimeZone("GMT")); if (!existingUser) { @@ -1501,7 +1501,7 @@ class ExtractRegistry extends Extract { } Collection attributes = new ArrayList<>(); attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName)); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, getName(), adobeUsedTime)); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, getName(), adobeUsedTime)); attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment)); BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes); if (bba != null) { @@ -1742,7 +1742,7 @@ class ExtractRegistry extends Extract { String fileName = fileNameTokens[1]; Collection attributes = new ArrayList<>(); attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName)); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, getName(), docDate)); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, getName(), docDate)); attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment)); BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes); if (bba != null) { @@ -1805,7 +1805,7 @@ class ExtractRegistry extends Extract { } Collection attributes = new ArrayList<>(); attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName)); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, getName(), usedTime)); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, getName(), usedTime)); attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment)); BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes); if (bba != null) { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java index 64619518fd..367c68e3c4 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java @@ -294,6 +294,7 @@ final class ExtractZoneIdentifier extends Extract { private static final String REFERRER_URL = "ReferrerUrl"; //NON-NLS private static final String HOST_URL = "HostUrl"; //NON-NLS private static final String FAMILY_NAME = "LastWriterPackageFamilyName"; //NON-NLS + private static String fileName; private final Properties properties = new Properties(null); @@ -307,6 +308,7 @@ final class ExtractZoneIdentifier extends Extract { * @throws IOException */ ZoneIdentifierInfo(AbstractFile zoneFile) throws IOException { + fileName = zoneFile.getName(); properties.load(new ReadContentInputStream(zoneFile)); } @@ -318,8 +320,13 @@ final class ExtractZoneIdentifier extends Extract { private int getZoneId() { int zoneValue = -1; String value = properties.getProperty(ZONE_ID); - if (value != null) { - zoneValue = Integer.parseInt(value); + try { + if (value != null) { + zoneValue = Integer.parseInt(value); + } + } catch (NumberFormatException ex) { + String message = String.format("Unable to parse Zone Id for File %s", fileName); //NON-NLS + LOG.log(Level.WARNING, message); } return zoneValue; diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java index 2b93720fcd..7b65b0c824 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java @@ -119,7 +119,7 @@ class RecentDocumentsByLnk extends Extract { NbBundle.getMessage(this.getClass(), "RecentDocumentsByLnk.parentModuleName.noSpace"), Util.findID(dataSource, path))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, NbBundle.getMessage(this.getClass(), "RecentDocumentsByLnk.parentModuleName.noSpace"), recentFile.getCrtime())); diff --git a/docs/doxygen-dev/footer.html b/docs/doxygen-dev/footer.html index b874c74742..abc63ecc26 100755 --- a/docs/doxygen-dev/footer.html +++ b/docs/doxygen-dev/footer.html @@ -1,5 +1,5 @@
-

Copyright © 2012-2020 Basis Technology. Generated on $date
+

Copyright © 2012-2021 Basis Technology. Generated on $date
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

diff --git a/docs/doxygen-user/aleapp.dox b/docs/doxygen-user/aleapp.dox index d62a6edc6d..220b07de51 100644 --- a/docs/doxygen-user/aleapp.dox +++ b/docs/doxygen-user/aleapp.dox @@ -8,7 +8,7 @@ The Android Analyzer ingest module runs aLEAPP (https://github.com/abrignoni/aLE \section aleapp_config Using the Module -Select the checkbox in the Ingest Modules settings screen to enable the Android Analzyer (ALEAPP) module. The module will run on .tar/.zip files found in a \ref ds_log "logical files data source" or a \ref ds_img disk image. +Select the checkbox in the Ingest Modules settings screen to enable the Android Analzyer (ALEAPP) module. The module will run on .tar/.zip files found in a \ref ds_log "logical files data source" or a \ref ds_img "disk image". \section aleapp_results Seeing Results diff --git a/docs/doxygen-user/communications.dox b/docs/doxygen-user/communications.dox index fb2031f4b4..929661fe19 100644 --- a/docs/doxygen-user/communications.dox +++ b/docs/doxygen-user/communications.dox @@ -24,7 +24,7 @@ The middle column displays each account, its device and type, and the number of Selecting an account in the middle column will bring up the data for that account in the right hand column. There are four tabs that show information about the selected account.
    -
  • The Summary tab displays counts of how many times the account has appeared in different data types in the top section. In the middle it displays the files this account was found in. If the \ref central_repo_page is enabled, the bottom section will show any other cases that contained this account. +
  • The Summary tab displays counts of how many times the account has appeared in different data types in the top section. In the middle it displays the files this account was found in. If the \ref central_repo_page is enabled, you can see if any \ref personas_page "personas" are associated with this account and whether any other cases that contained this account. \image html cvt_summary_tab.png diff --git a/docs/doxygen-user/data_source_summary.dox b/docs/doxygen-user/data_source_summary.dox index f656e5a6ab..459dd7f611 100644 --- a/docs/doxygen-user/data_source_summary.dox +++ b/docs/doxygen-user/data_source_summary.dox @@ -32,7 +32,7 @@ The Types tab shows counts of different file types found in the data source. \subsection ds_summary_user_activity User Activity -The User Activity tab shows the most recent results found in the data source. +The User Activity tab shows the most recent results found in the data source. You can right click on a row to navigate directly to the corresponding result. \image html ds_summary_user_activity.png @@ -44,7 +44,7 @@ The Analysis tab shows the sets with the most results from the \ref hash_db_page \subsection ds_summary_recent_files Recent Files -The Recent Files tab shows information on the most recent files opened and downloaded. +The Recent Files tab shows information on the most recent files opened and downloaded. You can right click on a row to navigate directly to the corresponding file or result. \image html ds_summary_recent_files.png @@ -56,6 +56,18 @@ The Past Cases tab shows which cases had results or notable files in common with Note that because these entries are based on the Interesting Items results created during ingest and not querying the central repository, they will not reflect any matches in cases processed after this case. For example, suppose we create Case A and ingest a data source with Device Z. If we make a new case Case B afterward and ingest a data source that also has Device Z, we would see Case A listed in this tab for Case B, but if we reopened Case A we would not see Case B listed unless ingest was run again. +\subsection ds_summary_geo Geolocation + +The Geolocation tab uses the coordinates from geolocation results to find the nearest city for each and displays the most recent cities and most common cities. If the location is more than 150 km from a city then it will be displayed as "Unknown". The "View in Map" button under the recent cities table will open the \ref geolocation_page "Geolocation window" showing all waypoints for this data source with timestamps in the last 30 days. The "View in Map" button under the most common cities will show all waypoints for this data source. + +\image html ds_summary_geo.png + +\subsection ds_summary_timeline Timeline + +The Timeline tab shows a simplified version of the \ref timeline_page "Timeline Viewer" for the selected data source. It will show events for the last 30 days of activity in the data source and give the first and last dates of activity. "File events" represent file creation, modification, access, and change. "Result events" represent the results from running ingest, such as the time a message was sent or when a URL was accessed. The "View in Timeline" button will open the main \ref timeline_page "Timeline Viewer". + +\image html ds_summary_timeline.png + \subsection ds_summary_ingest_history Ingest History The Ingest History tab shows which ingest modules have been run on the data source and the version of each module. diff --git a/docs/doxygen-user/drone.dox b/docs/doxygen-user/drone.dox index 978e199917..baf2366a72 100755 --- a/docs/doxygen-user/drone.dox +++ b/docs/doxygen-user/drone.dox @@ -1,13 +1,13 @@ -/*! \page drone_page Drone Analyzer +/*! \page drone_page DJI Drone Analyzer [TOC] \section drone_overview Overview -The Drone Analyzer module allows you to analyze files from a drone. +The DJI Drone Analyzer module allows you to analyze files from a drone. -Currently, the Drone Analyzer module works on images obtained from the internal SD card found in the following DJI drone models: +Currently, the DJI Drone Analyzer module works on images obtained from the internal SD card found in the following DJI drone models: - Phantom 3 - Phantom 4 - Phantom 4 Pro @@ -20,7 +20,7 @@ The module will find DAT files and process them using DatCon (https://datfile.ne \section drone_config Running the Module -To enable the Drone Analyzer ingest module select the checkbox in the \ref ingest_configure "Ingest Modules configuration screen". +To enable the DJI Drone Analyzer ingest module select the checkbox in the \ref ingest_configure "Ingest Modules configuration screen". \section drone_results Viewing Results diff --git a/docs/doxygen-user/footer.html b/docs/doxygen-user/footer.html index b874c74742..abc63ecc26 100644 --- a/docs/doxygen-user/footer.html +++ b/docs/doxygen-user/footer.html @@ -1,5 +1,5 @@
    -

    Copyright © 2012-2020 Basis Technology. Generated on $date
    +

    Copyright © 2012-2021 Basis Technology. Generated on $date
    This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

    diff --git a/docs/doxygen-user/hashdb_lookup.dox b/docs/doxygen-user/hashdb_lookup.dox index c32e4adc74..29fc6f459d 100644 --- a/docs/doxygen-user/hashdb_lookup.dox +++ b/docs/doxygen-user/hashdb_lookup.dox @@ -6,7 +6,7 @@ What Does It Do ======== -The Hash Lookup Module calculates MD5 hash values for files and looks up hash values in a database to determine if the file is notable, known (in general), included in a specific set of files, or unknown. +The Hash Lookup Module calculates MD5 hash values for files and looks up hash values in a database to determine if the file is notable, known (in general), included in a specific set of files, or unknown. SHA-256 hashes are also calculated, though these will not be used in hash set lookups. Configuration diff --git a/docs/doxygen-user/ileapp.dox b/docs/doxygen-user/ileapp.dox index 21a314e0ac..5abae340d9 100644 --- a/docs/doxygen-user/ileapp.dox +++ b/docs/doxygen-user/ileapp.dox @@ -8,7 +8,7 @@ The iOS Analyzer ingest module runs iLEAPP (https://github.com/abrignoni/iLEAPP) \section ileapp_config Using the Module -Select the checkbox in the Ingest Modules settings screen to enable the IOS Analzyer (iLEAPP) module. In Autopsy 4.17.0 the module only runs on .tar/.zip files found in a \ref ds_log "logical files data source". +Select the checkbox in the Ingest Modules settings screen to enable the IOS Analzyer (iLEAPP) module. The module will run on .tar/.zip files found in a \ref ds_log "logical files data source" or a \ref ds_img "disk image". \section ileapp_results Seeing Results diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png index 15957a0712..a1e6bfb842 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png index 3adf7ec40d..3d51efee73 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_geo.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_geo.png new file mode 100644 index 0000000000..4ab4440b55 Binary files /dev/null and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_geo.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png index 203e9c44ee..d3a1a970a9 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png index 591cb66867..3b47578b0e 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png index af63e103f7..3eeaeffd89 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_result_viewer.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_result_viewer.png index ad16f2e031..8f2b4d3f47 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_result_viewer.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_result_viewer.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_timeline.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_timeline.png new file mode 100644 index 0000000000..1a376723d6 Binary files /dev/null and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_timeline.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png index db3f4a14f9..2bf93d3f90 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png index 2ce9dcaf16..7b6d4d09f5 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png differ diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png index 745c3b1d97..a9d61e9084 100644 Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png differ diff --git a/docs/doxygen-user/images/aleapp_main.jpg b/docs/doxygen-user/images/aleapp_main.jpg index 82d8d2c778..d318748a6d 100644 Binary files a/docs/doxygen-user/images/aleapp_main.jpg and b/docs/doxygen-user/images/aleapp_main.jpg differ diff --git a/docs/doxygen-user/images/cvt_summary_tab.png b/docs/doxygen-user/images/cvt_summary_tab.png index 53f4ee148c..405448d31e 100644 Binary files a/docs/doxygen-user/images/cvt_summary_tab.png and b/docs/doxygen-user/images/cvt_summary_tab.png differ diff --git a/docs/doxygen-user/images/yara_ingest_settings.png b/docs/doxygen-user/images/yara_ingest_settings.png new file mode 100644 index 0000000000..0917a73bd0 Binary files /dev/null and b/docs/doxygen-user/images/yara_ingest_settings.png differ diff --git a/docs/doxygen-user/images/yara_new_rule_set.png b/docs/doxygen-user/images/yara_new_rule_set.png new file mode 100644 index 0000000000..1470f8f45c Binary files /dev/null and b/docs/doxygen-user/images/yara_new_rule_set.png differ diff --git a/docs/doxygen-user/images/yara_options.png b/docs/doxygen-user/images/yara_options.png new file mode 100644 index 0000000000..e04a1e62f4 Binary files /dev/null and b/docs/doxygen-user/images/yara_options.png differ diff --git a/docs/doxygen-user/images/yara_results.png b/docs/doxygen-user/images/yara_results.png new file mode 100644 index 0000000000..5e1d447935 Binary files /dev/null and b/docs/doxygen-user/images/yara_results.png differ diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index c9649aeef6..b8778cd36b 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -51,6 +51,8 @@ The following topics are available here: - \subpage drone_page - \subpage gpx_page - \subpage ileapp_page + - \subpage aleapp_page + - \subpage yara_page - Reviewing the Results - \subpage uilayout_page diff --git a/docs/doxygen-user/yara.dox b/docs/doxygen-user/yara.dox new file mode 100644 index 0000000000..19bba86384 --- /dev/null +++ b/docs/doxygen-user/yara.dox @@ -0,0 +1,50 @@ +/*! \page yara_page YARA Analyzer + +[TOC] + + +\section yara_overview Overview + +The YARA Analyzer module uses a set of rules to search files for textual or binary patterns. YARA was designed for malware analysis but can be used to search for any type of files. For more information on YARA see https://virustotal.github.io/yara/. + +\section yara_config Configuration + +To create and edit your rule sets, go to "Tools", "Options" and then select the "YARA" tab. + +\image html yara_options.png + +YARA rule sets are stored in folders in the user's Autopsy folder. To create a new rule set, click the "New Set" button in the lower left and enter the name for your new set. + +\image html yara_new_rule_set.png + +With your new rule set selected, click the "Open Folder" button to go to the newly created rules folder. You can now copy existing YARA files into this folder to include them in the rule set. Information on writing YARA rules can be found here and many existing YARA rules can be found through a web search. As a very simple example, we will add this rule to the sample rule set to find files that contain the words "hello" and "world": + +\verbatim +rule HelloWorldRule +{ + strings: + $part1 = "hello" nocase + $part2 = "world" nocase + + condition: + $part1 and $part2 +} +\endverbatim + +Once you've added your rules to the folder, click the "Refresh File List" button to show them in the options panel. + +\section yara_running Running the Module + +To enable the YARA Analyzer ingest module select the checkbox in the \ref ingest_configure "Ingest Modules configuration screen". + +\image html yara_ingest_settings.png + +Make sure all rule sets you want to run are checked. You can also choose between running on all files or only running on executable files. + +\section yara_results Viewing Results + +Results are show in the Results tree under "Extracted Content". + +\image html yara_results.png + +*/ diff --git a/docs/doxygen/footer.html b/docs/doxygen/footer.html index f703eb2a5e..65eaf1d7dd 100644 --- a/docs/doxygen/footer.html +++ b/docs/doxygen/footer.html @@ -1,5 +1,5 @@
    -

    Copyright © 2012-2020 Basis Technology. Generated on: $date
    +

    Copyright © 2012-2021 Basis Technology. Generated on: $date
    This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

    diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py index e202c3e111..c6c6dfacdc 100644 --- a/test/script/tskdbdiff.py +++ b/test/script/tskdbdiff.py @@ -627,10 +627,24 @@ def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info fields_list[4] = files_table[object_id] if legacy_artifact_id != 'NULL' and legacy_artifact_id in artifact_table.keys(): fields_list[6] = artifact_table[legacy_artifact_id] + + + if fields_list[1] == fields_list[2] and fields_list[1] == fields_list[3]: + fields_list[1] = cleanupEventDescription(fields_list[1]) + fields_list[2] = cleanupEventDescription(fields_list[2]) + fields_list[3] = cleanupEventDescription(fields_list[3]) + newLine = ('INSERT INTO "tsk_event_descriptions" VALUES(' + ','.join(fields_list[1:]) + ');') # remove report_id return newLine else: return line + +def cleanupEventDescription(description): + test = re.search("^'\D+:\d+'$", description) + if test is not None: + return re.sub(":\d+", ":", description) + else: + return description def getAssociatedArtifactType(cur, artifact_id, isMultiUser): if isMultiUser: diff --git a/thirdparty/aLeapp/aleapp.exe b/thirdparty/aLeapp/aleapp.exe index 52fab109ea..179d7b6331 100644 Binary files a/thirdparty/aLeapp/aleapp.exe and b/thirdparty/aLeapp/aleapp.exe differ