diff --git a/Core/manifest.mf b/Core/manifest.mf index 5eb077ef30..e3a95f497e 100644 --- a/Core/manifest.mf +++ b/Core/manifest.mf @@ -2,7 +2,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.core/10 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml -OpenIDE-Module-Implementation-Version: 24 +OpenIDE-Module-Implementation-Version: 25 OpenIDE-Module-Requires: org.openide.windows.WindowManager AutoUpdate-Show-In-Client: true AutoUpdate-Essential-Module: true diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index ed92afe884..1b0a695edd 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -47,5 +47,5 @@ nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar -spec.version.base=10.12 +spec.version.base=10.13 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index 2fb294cdfb..30d539e87f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -127,7 +127,10 @@ public class EamArtifactUtil { || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() == artifactTypeID)) { // Lower-case this to normalize domains - value = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN)).getValueString(); + BlackboardAttribute attribute = bbArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN)); + if (attribute != null) { + value = attribute.getValueString(); + } } else if (correlationType.getId() == CorrelationAttributeInstance.PHONE_TYPE_ID && (BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() == artifactTypeID || BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() == artifactTypeID @@ -171,7 +174,7 @@ public class EamArtifactUtil { return null; } - if (null != value) { + if ((null != value) && (value.isEmpty() == false)) { return makeCorrelationAttributeInstanceUsingTypeValue(bbArtifact, correlationType, value); } else { return null; diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java index 97a41b27be..92d4b1a2d9 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java @@ -1,16 +1,16 @@ /* - * + * * Autopsy Forensic Browser - * + * * Copyright 2018 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. @@ -29,14 +29,13 @@ import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.corecomponents.DelayedLoadChildNodesOnTreeExpansion; /** - * DataResultViewerTable which overrides the default column - * header width calculations. The CommonAttributesSearchResultsViewerTable - * presents multiple tiers of data which are not always present and it may not - * make sense to try to calculate the column widths for such tables by sampling - * rows and looking for wide cells. Rather, we just pick some reasonable values. + * DataResultViewerTable which overrides the default column header + * width calculations. The CommonAttributesSearchResultsViewerTable + * presents multiple tiers of data which are not always present and it may not + * make sense to try to calculate the column widths for such tables by sampling + * rows and looking for wide cells. Rather, we just pick some reasonable values. */ public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTable { @@ -44,7 +43,7 @@ public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTa private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(CommonAttributesSearchResultsViewerTable.class.getName()); - + private static final int DEFAULT_WIDTH = 100; static { @@ -60,19 +59,20 @@ public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTa COLUMN_WIDTHS = Collections.unmodifiableMap(map); } + /** - * Implements a DataResultViewerTable which constructs a tabular result viewer that - * displays the children of the given root node using an OutlineView. The explorer - * manager will be discovered at runtime. - * - * Adds a TreeExpansionsListener to the outlineView to receive tree expansion events - * which dynamically loads children nodes when requested. + * Implements a DataResultViewerTable which constructs a tabular result + * viewer that displays the children of the given root node using an + * OutlineView. The explorer manager will be discovered at runtime. + * + * Adds a TreeExpansionsListener to the outlineView to receive tree + * expansion events which dynamically loads children nodes when requested. */ public CommonAttributesSearchResultsViewerTable() { super(); - outlineView.addTreeExpansionListener(new DelayedLoadChildNodesOnTreeExpansion()); + addTreeExpansionListener(new InstanceCountNodeTreeExpansionListener()); } - + @NbBundle.Messages({ "CommonFilesSearchResultsViewerTable.noDescText= ", "CommonFilesSearchResultsViewerTable.filesColLbl=Files", @@ -96,8 +96,8 @@ public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTa final String headerValue = column.getHeaderValue().toString(); final Integer defaultWidth = COLUMN_WIDTHS.get(headerValue); - - if(defaultWidth == null){ + + if (defaultWidth == null) { column.setPreferredWidth(DEFAULT_WIDTH); LOGGER.log(Level.SEVERE, String.format("Tried to set width on a column not supported by the CommonFilesSearchResultsViewerTable: %s", headerValue)); } else { diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index fa298c121d..93de3267d0 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -1,16 +1,16 @@ /* - * + * * Autopsy Forensic Browser - * + * * Copyright 2018 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. @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; * Node used to indicate the number of matches found with the MD5 children of * this Node. */ -final public class InstanceCountNode extends DisplayableItemNode { +public final class InstanceCountNode extends DisplayableItemNode { private static final Logger logger = Logger.getLogger(InstanceCountNode.class.getName()); @@ -74,11 +74,10 @@ final public class InstanceCountNode extends DisplayableItemNode { } /** - * Refresh the node, by dynamically loading in the children when called, and - * calling the CommonAttributeValueNodeFactory to generate nodes for the - * children in attributeValues. + * Creates the Children of this node. By doing this here instead of in the + * constructor, lazy creation of the Children is made possible. */ - public void refresh() { + void createChildren() { attributeValues.displayDelayedMetadata(); setChildren(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), true)); } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNodeTreeExpansionListener.java similarity index 52% rename from Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNodeTreeExpansionListener.java index 6e6d28af2a..3de4d44b7f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNodeTreeExpansionListener.java @@ -1,54 +1,54 @@ /* - * + * * Autopsy Forensic Browser - * + * * Copyright 2018 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.corecomponents; +package org.sleuthkit.autopsy.commonfilesearch; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import org.openide.explorer.view.Visualizer; import org.openide.nodes.Node; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** - * A tree expansion listener that will trigger a recreation of childs through - * its child factory on re-expansion of a node (causes to recreate the - * ChildFactory for this purpose.). + * A tree expansion listener used to do lazy creation of the Childfren of an + * InstanceCountNode when the node is expanded. */ -public final class DelayedLoadChildNodesOnTreeExpansion implements TreeExpansionListener { - - /** - * A flag for avoiding endless recursion inside the expansion listener that - * could trigger collapsing and (re-)expanding nodes again. - * @param event - */ +final class InstanceCountNodeTreeExpansionListener implements TreeExpansionListener { @Override public synchronized void treeCollapsed(final TreeExpansionEvent event) { - // Do nothing on collapse. Netbeans should manage nodes falling out of scope and GC. } @Override public synchronized void treeExpanded(final TreeExpansionEvent event) { - Node eventNode = Visualizer.findNode(event.getPath().getLastPathComponent()); + final Node eventNode = Visualizer.findNode(event.getPath().getLastPathComponent()); if (eventNode instanceof TableFilterNode) { - final TableFilterNode node = (TableFilterNode) eventNode; - node.refresh(); + final TableFilterNode tableFilterNode = (TableFilterNode) eventNode; + final DataResultFilterNode dataResultFilterNode = tableFilterNode.getLookup().lookup(DataResultFilterNode.class); + if (dataResultFilterNode != null) { + final InstanceCountNode instanceCountNode = dataResultFilterNode.getLookup().lookup(InstanceCountNode.class); + if (instanceCountNode != null) { + instanceCountNode.createChildren(); + } + } } - } + } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 576dc4f94e..9c09ca01ca 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -53,7 +53,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.coreutils.SqliteUtil; /** * A file content viewer for SQLite database files. diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/SqliteUtil.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SqliteUtil.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/coreutils/SqliteUtil.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/SqliteUtil.java index 4250487298..4fc220cf0d 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/SqliteUtil.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SqliteUtil.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.coreutils; +package org.sleuthkit.autopsy.contentviewers; import java.io.File; import java.io.IOException; @@ -34,7 +34,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Sqlite utility class. Find and copy metafiles, write sqlite abstract files to * temp directory, and generate unique temp directory paths. */ -public final class SqliteUtil { +final class SqliteUtil { private SqliteUtil() { diff --git a/Core/src/org/sleuthkit/autopsy/core/AutopsyOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/core/AutopsyOptionProcessor.java index 41961bf4f3..dd4ba98ae6 100644 --- a/Core/src/org/sleuthkit/autopsy/core/AutopsyOptionProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/core/AutopsyOptionProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2017 Basis Technology Corp. + * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index de261f16d2..12ba493380 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -190,3 +190,5 @@ ViewPreferencesPanel.hideRejectedResultsCheckbox.text=Hide rejected results ViewPreferencesPanel.hideOtherUsersTagsLabel.text=Hide other users' tags in the: ViewPreferencesPanel.centralRepoLabel.text=Do not use Central Repository for: ViewPreferencesPanel.commentsOccurencesColumnsCheckbox.text=C(omments) and O(ccurences) columns to reduce loading times +ViewPreferencesPanel.deletedFilesLimitCheckbox.text=Limit to 10,000 +ViewPreferencesPanel.deletedFilesLimitLabel.text=Limit number of deleted files displayed: diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index 8ad47b32c7..d6c32623a4 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -29,7 +29,6 @@ - diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 1679d0296a..bf18c4b831 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -46,6 +46,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; +import javax.swing.event.TreeExpansionListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; @@ -265,6 +266,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } } + /** + * Adds a tree expansion listener to the OutlineView of this tabular results + * viewer. + * + * @param listener The listener + */ + protected void addTreeExpansionListener(TreeExpansionListener listener) { + outlineView.addTreeExpansionListener(listener); + } + /** * Sets up the Outline view of this tabular result viewer by creating column * headers based on the children of the current root node. The persisted @@ -1036,7 +1047,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - protected org.openide.explorer.view.OutlineView outlineView; + private org.openide.explorer.view.OutlineView outlineView; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java index 2ba02f694a..eb36cf2e87 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java @@ -128,16 +128,6 @@ public class TableFilterNode extends FilterNode { return null; } } - - /** - * Refreshes the inner node, which depending on the actual node type that was wrapped - * could trigger a dynamic refresh of the children, if supported. - */ - void refresh() { - DataResultFilterNode innerNode = getLookup().lookup(DataResultFilterNode.class); - innerNode.refresh(); - - } /** * @return the column order key, which allows custom column ordering to be diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form index 5ab351c170..ddeb57bd74 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form @@ -79,65 +79,72 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -174,6 +181,11 @@ + + + + + @@ -327,6 +339,23 @@ + + + + + + + + + + + + + + + + + @@ -388,7 +417,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java index 7ab3bbc84e..51d4120449 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java @@ -25,6 +25,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.deletedFiles.DeletedFilePreferences; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; /** @@ -61,10 +62,12 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { dataSourcesHideSlackCheckbox.setSelected(UserPreferences.hideSlackFilesInDataSourcesTree()); viewsHideSlackCheckbox.setSelected(UserPreferences.hideSlackFilesInViewsTree()); - + commentsOccurencesColumnsCheckbox.setEnabled(EamDbUtil.useCentralRepo()); commentsOccurencesColumnsCheckbox.setSelected(UserPreferences.hideCentralRepoCommentsAndOccurrences()); + deletedFilesLimitCheckbox.setSelected(DeletedFilePreferences.getDefault().getShouldLimitDeletedFiles()); + // Current Case Settings boolean caseIsOpen = Case.isCaseOpen(); currentCaseSettingsPanel.setEnabled(caseIsOpen); @@ -91,6 +94,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { storeGroupItemsInTreeByDataSource(); DirectoryTreeTopComponent.getDefault().setShowRejectedResults(hideRejectedResultsCheckbox.isSelected() == false); + + DeletedFilePreferences.getDefault().setShouldLimitDeletedFiles(deletedFilesLimitCheckbox.isSelected()); } /** @@ -135,6 +140,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { hideOtherUsersTagsLabel = new javax.swing.JLabel(); commentsOccurencesColumnsCheckbox = new javax.swing.JCheckBox(); centralRepoLabel = new javax.swing.JLabel(); + deletedFilesLimitCheckbox = new javax.swing.JCheckBox(); + deletedFilesLimitLabel = new javax.swing.JLabel(); currentCaseSettingsPanel = new javax.swing.JPanel(); groupByDataSourceCheckbox = new javax.swing.JCheckBox(); currentSessionSettingsPanel = new javax.swing.JPanel(); @@ -228,6 +235,15 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { org.openide.awt.Mnemonics.setLocalizedText(centralRepoLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.centralRepoLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deletedFilesLimitCheckbox, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.deletedFilesLimitCheckbox.text")); // NOI18N + deletedFilesLimitCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deletedFilesLimitCheckboxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(deletedFilesLimitLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.deletedFilesLimitLabel.text")); // NOI18N + javax.swing.GroupLayout globalSettingsPanelLayout = new javax.swing.GroupLayout(globalSettingsPanel); globalSettingsPanel.setLayout(globalSettingsPanelLayout); globalSettingsPanelLayout.setHorizontalGroup( @@ -235,46 +251,51 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { .addGroup(globalSettingsPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(globalSettingsPanelLayout.createSequentialGroup() - .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(hideKnownFilesLabel) - .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(globalSettingsPanelLayout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataSourcesHideSlackCheckbox) - .addComponent(viewsHideSlackCheckbox))) - .addComponent(hideSlackFilesLabel)) - .addGroup(globalSettingsPanelLayout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataSourcesHideKnownCheckbox) - .addComponent(viewsHideKnownCheckbox))))) - .addGap(18, 18, 18) - .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(displayTimeLabel) - .addGroup(globalSettingsPanelLayout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(keepCurrentViewerRadioButton) - .addComponent(useBestViewerRadioButton) - .addComponent(useGMTTimeRadioButton) - .addComponent(useLocalTimeRadioButton))) - .addComponent(selectFileLabel))) - .addComponent(hideOtherUsersTagsLabel) - .addComponent(centralRepoLabel) .addGroup(globalSettingsPanelLayout.createSequentialGroup() .addGap(10, 10, 10) .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(commentsOccurencesColumnsCheckbox) - .addComponent(hideOtherUsersTagsCheckbox)))) - .addContainerGap(16, Short.MAX_VALUE)) + .addComponent(hideOtherUsersTagsCheckbox) + .addComponent(deletedFilesLimitCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(globalSettingsPanelLayout.createSequentialGroup() + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(globalSettingsPanelLayout.createSequentialGroup() + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(hideKnownFilesLabel) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(globalSettingsPanelLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataSourcesHideSlackCheckbox) + .addComponent(viewsHideSlackCheckbox))) + .addComponent(hideSlackFilesLabel)) + .addGroup(globalSettingsPanelLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataSourcesHideKnownCheckbox) + .addComponent(viewsHideKnownCheckbox))))) + .addGap(18, 18, 18) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(displayTimeLabel) + .addGroup(globalSettingsPanelLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(keepCurrentViewerRadioButton) + .addComponent(useBestViewerRadioButton) + .addComponent(useGMTTimeRadioButton) + .addComponent(useLocalTimeRadioButton))) + .addComponent(selectFileLabel))) + .addComponent(hideOtherUsersTagsLabel) + .addComponent(centralRepoLabel) + .addComponent(deletedFilesLimitLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 10, Short.MAX_VALUE))) + .addContainerGap()) ); globalSettingsPanelLayout.setVerticalGroup( globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(globalSettingsPanelLayout.createSequentialGroup() - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap() .addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(globalSettingsPanelLayout.createSequentialGroup() .addComponent(hideKnownFilesLabel) @@ -307,7 +328,12 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(centralRepoLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(commentsOccurencesColumnsCheckbox)) + .addComponent(commentsOccurencesColumnsCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(deletedFilesLimitLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deletedFilesLimitCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) ); currentCaseSettingsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.currentCaseSettingsPanel.border.title"))); // NOI18N @@ -350,7 +376,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { currentSessionSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(currentSessionSettingsPanelLayout.createSequentialGroup() .addContainerGap() - .addComponent(hideRejectedResultsCheckbox) + .addComponent(hideRejectedResultsCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 259, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); currentSessionSettingsPanelLayout.setVerticalGroup( @@ -501,6 +527,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { } }//GEN-LAST:event_commentsOccurencesColumnsCheckboxActionPerformed + private void deletedFilesLimitCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deletedFilesLimitCheckboxActionPerformed + if (immediateUpdates) { + DeletedFilePreferences.getDefault().setShouldLimitDeletedFiles(deletedFilesLimitCheckbox.isSelected()); + } else { + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + }//GEN-LAST:event_deletedFilesLimitCheckboxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel centralRepoLabel; @@ -509,6 +543,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { private javax.swing.JPanel currentSessionSettingsPanel; private javax.swing.JCheckBox dataSourcesHideKnownCheckbox; private javax.swing.JCheckBox dataSourcesHideSlackCheckbox; + private javax.swing.JCheckBox deletedFilesLimitCheckbox; + private javax.swing.JLabel deletedFilesLimitLabel; private javax.swing.JLabel displayTimeLabel; private javax.swing.JPanel globalSettingsPanel; private javax.swing.JCheckBox groupByDataSourceCheckbox; @@ -527,4 +563,4 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel { private javax.swing.JCheckBox viewsHideKnownCheckbox; private javax.swing.JCheckBox viewsHideSlackCheckbox; // End of variables declaration//GEN-END:variables -} \ No newline at end of file +} diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/History.java b/Core/src/org/sleuthkit/autopsy/coreutils/History.java index 746ac9f710..7a75529c2b 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/History.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/History.java @@ -43,18 +43,23 @@ import javax.annotation.concurrent.ThreadSafe; @ThreadSafe public class History { + // Stack of things that were previously shown before an 'advance' was done @GuardedBy("this") private final ObservableStack historyStack = new ObservableStack<>(); + // stack of things that were previously shown before a 'retreat' (i.e. a back) was done @GuardedBy("this") private final ObservableStack forwardStack = new ObservableStack<>(); + // what is currently being shown @GuardedBy("this") private final ReadOnlyObjectWrapper currentState = new ReadOnlyObjectWrapper<>(); + // Is the forward stack empty? @GuardedBy("this") private final ReadOnlyBooleanWrapper canAdvance = new ReadOnlyBooleanWrapper(); + // is the historyStack empty? @GuardedBy("this") private final ReadOnlyBooleanWrapper canRetreat = new ReadOnlyBooleanWrapper(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 57bd68911c..55384344bc 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -45,6 +45,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; +import org.sleuthkit.autopsy.deletedFiles.DeletedFilePreferences; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -111,11 +112,11 @@ public class DeletedContent implements AutopsyVisitableItem { this.skCase = skCase; this.datasourceObjId = dsObjId; } - + long filteringDataSourceObjId() { return this.datasourceObjId; } - + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -191,9 +192,10 @@ public class DeletedContent implements AutopsyVisitableItem { * fired. Other nodes are listening to this for changes. */ private static final class DeletedContentsChildrenObservable extends Observable { + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of( - Case.Events.DATA_SOURCE_ADDED, - Case.Events.CURRENT_CASE + Case.Events.DATA_SOURCE_ADDED, + Case.Events.CURRENT_CASE ); DeletedContentsChildrenObservable() { @@ -213,12 +215,11 @@ public class DeletedContent implements AutopsyVisitableItem { String eventType = evt.getPropertyName(); if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) { /** - * + // @@@ COULD CHECK If the new file is deleted - * before notifying... Checking for a current case is a - * stop gap measure + update(); until a different way of - * handling the closing of cases is worked out. - * Currently, remote events may be received for a case - * that is already closed. + * + // @@@ COULD CHECK If the new file is deleted before + * notifying... Checking for a current case is a stop gap + * measure + update(); until a different way of handling the + * closing of cases is worked out. Currently, remote events + * may be received for a case that is already closed. */ try { Case.getCurrentCaseThrows(); @@ -234,10 +235,10 @@ public class DeletedContent implements AutopsyVisitableItem { || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { /** - * Checking for a current case is a stop gap measure - * until a different way of handling the closing of - * cases is worked out. Currently, remote events may be - * received for a case that is already closed. + * Checking for a current case is a stop gap measure until a + * different way of handling the closing of cases is worked + * out. Currently, remote events may be received for a case + * that is already closed. */ try { Case.getCurrentCaseThrows(); @@ -282,7 +283,7 @@ public class DeletedContent implements AutopsyVisitableItem { // Use version that has observer for updates @Deprecated DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, long dsObjId) { - super(Children.create(new DeletedContentChildren(filter, skCase, null, dsObjId ), true), Lookups.singleton(filter.getDisplayName())); + super(Children.create(new DeletedContentChildren(filter, skCase, null, dsObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; this.datasourceObjId = dsObjId; init(); @@ -366,7 +367,7 @@ public class DeletedContent implements AutopsyVisitableItem { private final SleuthkitCase skCase; private final DeletedContent.DeletedContentFilter filter; private static final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName()); - private static final int MAX_OBJECTS = 10001; + private final Observable notifier; private final long datasourceObjId; @@ -385,7 +386,7 @@ public class DeletedContent implements AutopsyVisitableItem { @Override public void update(Observable o, Object arg) { refresh(true); - } + } } @Override @@ -408,18 +409,19 @@ public class DeletedContent implements AutopsyVisitableItem { + "There are more Deleted Files than can be displayed." + " Only the first {0} Deleted Files will be shown."}) protected boolean createKeys(List list) { + DeletedFilePreferences deletedPreferences = DeletedFilePreferences.getDefault(); List queryList = runFsQuery(); - if (queryList.size() == MAX_OBJECTS) { + if (deletedPreferences.getShouldLimitDeletedFiles() && queryList.size() == deletedPreferences.getDeletedFilesLimit()) { queryList.remove(queryList.size() - 1); // only show the dialog once - not each time we refresh if (maxFilesDialogShown == false) { maxFilesDialogShown = true; SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - DeletedContent_createKeys_maxObjects_msg(MAX_OBJECTS - 1)) + DeletedContent_createKeys_maxObjects_msg(deletedPreferences.getDeletedFilesLimit() - 1)) ); } - } + } list.addAll(queryList); return true; } @@ -463,10 +465,12 @@ public class DeletedContent implements AutopsyVisitableItem { } if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { - query += " AND data_source_obj_id = " + filteringDSObjId; + query += " AND data_source_obj_id = " + filteringDSObjId; + } + DeletedFilePreferences deletedPreferences = DeletedFilePreferences.getDefault(); + if (deletedPreferences.getShouldLimitDeletedFiles()) { + query += " LIMIT " + deletedPreferences.getDeletedFilesLimit(); //NON-NLS } - - query += " LIMIT " + MAX_OBJECTS; //NON-NLS return query; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 6e3c138fcd..8782b0fb89 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -44,7 +44,6 @@ import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -476,11 +475,13 @@ public class InterestingHits implements AutopsyVisitableItem { BlackboardArtifact art = skCase.getBlackboardArtifact(id); artifactHits.put(id, art); } - list.add(id); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS } }); + + list.addAll(artifactHits.keySet()); + return true; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java index e57febe1bf..7dc18a7394 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.datamodel; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.swing.Action; import org.openide.util.NbBundle; @@ -27,6 +28,7 @@ import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.FileSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SpecialDirectory; /** @@ -60,7 +62,11 @@ public abstract class SpecialDirectoryNode extends AbstractAbstractFileNodesingletonList(content))); + } else { + actions.add(new RunIngestModulesAction(content)); + } actions.addAll(ContextMenuExtensionPoint.getActions()); return actions.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/deletedFiles/DeletedFilePreferences.java b/Core/src/org/sleuthkit/autopsy/deletedFiles/DeletedFilePreferences.java new file mode 100644 index 0000000000..4eadd3611d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/deletedFiles/DeletedFilePreferences.java @@ -0,0 +1,171 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.deletedFiles; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.CasePreferences; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; + +/** + * Class to store settings related to the display of deleted files. + */ +public class DeletedFilePreferences { + + private static final String SETTINGS_FILE = "DeletedFilePreferences.properties"; //NON-NLS + private static final String KEY_LIMIT_DELETED_FILES = "limitDeletedFiles"; //NON-NLS + private static final String KEY_LIMIT_VALUE = "limitValue"; + private static final String VALUE_TRUE = "true"; //NON-NLS + private static final String VALUE_FALSE = "false"; //NON-NLS + private static final int DEFAULT_MAX_OBJECTS = 10001; + private static final Logger logger = Logger.getLogger(CasePreferences.class.getName()); + private static DeletedFilePreferences defaultInstance; + private static boolean limitDeletedFiles = true; + private static int deletedFilesLimit = DEFAULT_MAX_OBJECTS; + + /** + * Get the settings for the display of deleted files. + * + * @return defaultInstance with freshly loaded + */ + public static synchronized DeletedFilePreferences getDefault() { + if (defaultInstance == null) { + defaultInstance = new DeletedFilePreferences(); + } + defaultInstance.loadFromStorage(); + return defaultInstance; + } + + /** + * Prevent instantiation. + */ + private DeletedFilePreferences() { + } + + /** + * Get the 'limitDeletedFiles' value. This can be true or false. It will + * default to true if it was not saved correctly previously.s + * + * @return true if the number of deleted files displayed should be limied, + * false if it should not be limited. + */ + public boolean getShouldLimitDeletedFiles() { + return limitDeletedFiles; + } + + /** + * Set the 'limitDeletedFiles' value to true or false. + * + * @param value true if the number of deleted files displayed should be + * limied, false if it should not be limited. + */ + public void setShouldLimitDeletedFiles(boolean value) { + limitDeletedFiles = value; + saveToStorage(); + DirectoryTreeTopComponent.getDefault().refreshContentTreeSafe(); + } + + /** + * Get the 'limitValue' value. This is an interger value and will default to + * DEFAULT_MAX_OBJECTS if it was not previously saved correctly. + * + * @return an integer representing the max number of deleted files to display. + */ + public int getDeletedFilesLimit() { + return deletedFilesLimit; + } + + /** + * Set the 'limitValue' for max number of deleted files to display. + * + * @param value an integer representing the max number of deleted files to display. + */ + public void setDeletedFilesLimit(int value) { + deletedFilesLimit = value; + saveToStorage(); + DirectoryTreeTopComponent.getDefault().refreshContentTreeSafe(); + + } + + /** + * Load deleted file preferences from the settings file. + */ + private void loadFromStorage() { + Path settingsFile = Paths.get(PlatformUtil.getUserConfigDirectory(), SETTINGS_FILE); //NON-NLS + if (settingsFile.toFile().exists()) { + // Read the settings + try (InputStream inputStream = Files.newInputStream(settingsFile)) { + Properties props = new Properties(); + props.load(inputStream); + String limitDeletedFilesValue = props.getProperty(KEY_LIMIT_DELETED_FILES); + if (limitDeletedFilesValue != null) { + switch (limitDeletedFilesValue) { + case VALUE_TRUE: + limitDeletedFiles = true; + break; + case VALUE_FALSE: + limitDeletedFiles = false; + break; + default: + logger.log(Level.WARNING, String.format("Unexpected value '%s' for limit deleted files using value of true instead", + limitDeletedFilesValue)); + limitDeletedFiles = true; + break; + } + } + String limitValue = props.getProperty(KEY_LIMIT_VALUE); + try { + if (limitValue != null) { + deletedFilesLimit = Integer.valueOf(limitValue); + + } + } catch (NumberFormatException ex) { + logger.log(Level.INFO, String.format("Unexpected value '%s' for limit, expected an integer using default of 10,001 instead", + limitValue)); + deletedFilesLimit = DEFAULT_MAX_OBJECTS; + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error reading deletedFilesPreferences file", ex); + } + } + } + + /** + * Store deleted file preferences in the settings file. + */ + private void saveToStorage() { + Path settingsFile = Paths.get(PlatformUtil.getUserConfigDirectory(), SETTINGS_FILE); //NON-NLS + Properties props = new Properties(); + props.setProperty(KEY_LIMIT_DELETED_FILES, (limitDeletedFiles ? VALUE_TRUE : VALUE_FALSE)); + props.setProperty(KEY_LIMIT_VALUE, String.valueOf(deletedFilesLimit)); + try (OutputStream fos = Files.newOutputStream(settingsFile)) { + props.store(fos, ""); //NON-NLS + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error writing deletedFilesPreferences file", ex); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index c94ad57094..3a27f9fa9b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -140,18 +140,6 @@ public class DataResultFilterNode extends FilterNode { this.sourceEm = em; } - /** - * Refreshes the inner node. If the actual underlying node is an InstanceCountNode, - * refresh() that node, which refreshes the children. - * - */ - public void refresh() { - if (getOriginal() instanceof InstanceCountNode) { - InstanceCountNode innerNode = getLookup().lookup(InstanceCountNode.class); - innerNode.refresh(); - } - } - /** * Constructs a node used to wrap another node before passing it to the * result viewers. The wrapper node defines the actions associated with the diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index c0a18a9922..cb0d6bd6cb 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -135,7 +135,7 @@ 10 - 10.12 + 10.13 diff --git a/ImageGallery/manifest.mf b/ImageGallery/manifest.mf index 52bf6cfe1e..38081388f4 100644 --- a/ImageGallery/manifest.mf +++ b/ImageGallery/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.imagegallery/2 -OpenIDE-Module-Implementation-Version: 3 +OpenIDE-Module-Implementation-Version: 4 OpenIDE-Module-Layer: org/sleuthkit/autopsy/imagegallery/layer.xml OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/imagegallery/Bundle.properties diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml index dcaa641e75..8c7226ef37 100644 --- a/ImageGallery/nbproject/project.xml +++ b/ImageGallery/nbproject/project.xml @@ -127,7 +127,7 @@ 10 - 10.12 + 10.13 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index ee46376aed..356ac5bdd1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -136,18 +136,10 @@ public final class ImageGalleryController { return thumbnailSizeProp; } - public GroupViewState getViewState() { - return historyManager.getCurrentState(); - } - public ReadOnlyBooleanProperty regroupDisabledProperty() { return regroupDisabled.getReadOnlyProperty(); } - public ReadOnlyObjectProperty viewStateProperty() { - return historyManager.currentState(); - } - public FileIDSelectionModel getSelectionModel() { return selectionModel; } @@ -240,24 +232,66 @@ public final class ImageGalleryController { dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled()); } + + /** + * @return Currently displayed group or null if nothing is being displayed + */ + public GroupViewState getViewState() { + return historyManager.getCurrentState(); + } + + /** + * Get observable property of the current group. The UI currently changes + * based on this property changing, which happens when other actions and + * threads call advance(). + * + * @return Currently displayed group (as a property that can be observed) + */ + public ReadOnlyObjectProperty viewStateProperty() { + return historyManager.currentState(); + } + /** + * Should the "forward" button on the history be enabled? + * @return + */ public ReadOnlyBooleanProperty getCanAdvance() { return historyManager.getCanAdvance(); } + /** + * Should the "Back" button on the history be enabled? + * @return + */ public ReadOnlyBooleanProperty getCanRetreat() { return historyManager.getCanRetreat(); } + /** + * Display the passed in group. Causes this group to + * get recorded in the history queue and observers of the + * current state will be notified and update their panels/widgets + * appropriately. + * + * @param newState + */ @ThreadConfined(type = ThreadConfined.ThreadType.ANY) public void advance(GroupViewState newState) { historyManager.advance(newState); } + /** + * Display the next group in the "forward" history stack + * @return + */ public GroupViewState advance() { return historyManager.advance(); } + /** + * Display the previous group in the "back" history stack + * @return + */ public GroupViewState retreat() { return historyManager.retreat(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 74168e9e13..994ec7b3cd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; +import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.util.Optional; import javafx.application.Platform; @@ -32,6 +33,7 @@ import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; +import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; /** * Marks the currently displayed group as "seen" and advances to the next unseen @@ -56,6 +58,11 @@ public class NextUnseenGroup extends Action { private final ImageGalleryController controller; private final ObservableList unSeenGroups; private final GroupManager groupManager; + + private boolean isLoading = false; // set to true when we are marking current group as seen and loading new + + private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(NextUnseenGroup.class); + public NextUnseenGroup(ImageGalleryController controller) { super(NEXT_UNSEEN_GROUP); @@ -63,56 +70,98 @@ public class NextUnseenGroup extends Action { this.controller = controller; groupManager = controller.getGroupManager(); + + // Get reference to the list of unseen groups, that GroupManager will continue to manage unSeenGroups = groupManager.getUnSeenGroups(); - unSeenGroups.addListener((Observable observable) -> updateButton()); + unSeenGroups.addListener((Observable observable) -> unSeenGroupListener()); controller.viewStateProperty().addListener((Observable observable) -> updateButton()); setEventHandler(event -> { //on fx-thread - //if there is a group assigned to the view, mark it as seen - Optional.ofNullable(controller.getViewState()) - .flatMap(GroupViewState::getGroup) - .ifPresent(group -> { - setDisabled(true); - groupManager.markGroupSeen(group, true) - .addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService()); - }); + isLoading = true; // make sure button stays disabled until we are done loading + setDisabled(true); + + //if there is a group assigned to the view, mark it as seen and move on to the next one + GroupViewState viewState = controller.getViewState(); + if (viewState != null) { + Optional group = viewState.getGroup(); + + if (group.isPresent()) { + // NOTE: We need to wait for current group to be marked as seen because the 'advance' + // method grabs the top of the unseen list + groupManager.markGroupSeen(group.get(), true) + .addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService()); + return; + } + } + + // otherwise, just move on to the next one + exec.submit(this::advanceToNextUnseenGroup); }); + + // initial button state updateButton(); } + /** + * Listener that updates UI based on changes to the unseen group list + */ + private void unSeenGroupListener() { + // set the group if there is no visible group. + // NOTE: it could be argued that this should be done in another listner + if (controller.getViewState() == null) { + advanceToNextUnseenGroup(); + // do not update the button if it is supposed to be disabled during loading of the next group + } else if (isLoading == false) { + // NOTE: should we get a lock on groupManager here like advanceToNextUnseenGroup does? + updateButton(); + } + } + + // update UI based on button being pressed private void advanceToNextUnseenGroup() { synchronized (groupManager) { if (CollectionUtils.isNotEmpty(unSeenGroups)) { - controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + // NOTE: We keep the group in the unSeenGroup list until the user presses the + // button again mark it as seen + controller.advance(GroupViewState.createTile(unSeenGroups.get(0))); } - + updateButton(); } } + /** + * Update button based on currently displayed group and queues. + */ private void updateButton() { - int size = unSeenGroups.size(); - if (size < 1) { - //there are no unseen groups. + isLoading = false; + + int unSeenSize = unSeenGroups.size(); + + // NOTE: The currently displayed group is still in the unSeenGroups list until the user presses + // the button again and then we'll mark it as seen. + + // disable button if no unseen groups + if (unSeenSize < 1) { Platform.runLater(() -> { setDisabled(true); setText(ALL_GROUPS_SEEN); setGraphic(null); }); } else { - DrawableGroup get = unSeenGroups.get(0); - DrawableGroup orElse = Optional.ofNullable(controller.getViewState()).flatMap(GroupViewState::getGroup).orElse(null); - boolean equals = get.equals(orElse); - if (size == 1 & equals) { - //The only unseen group is the one that is being viewed. + DrawableGroup groupOnList = unSeenGroups.get(0); + DrawableGroup groupInView = Optional.ofNullable(controller.getViewState()).flatMap(GroupViewState::getGroup).orElse(null); + + //The only unseen group is the one that is being viewed. + if (unSeenSize == 1 & groupOnList.equals(groupInView)) { Platform.runLater(() -> { - setDisabled(false); + setDisabled(true); setText(MARK_GROUP_SEEN); setGraphic(new ImageView(END_IMAGE)); }); } else { - //there are more unseen groups. + //there are more unseen groups after this one Platform.runLater(() -> { setDisabled(false); setText(NEXT_UNSEEN_GROUP); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 7e2588bc65..9911b1fb58 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -110,6 +110,8 @@ public final class DrawableDB { private final PreparedStatement insertHashHitStmt; + private final PreparedStatement removeHashHitStmt; + private final PreparedStatement updateDataSourceStmt; private final PreparedStatement updateFileStmt; @@ -263,6 +265,7 @@ public final class DrawableDB { selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS + removeHashHitStmt = prepareStatement("DELETE FROM hash_set_hits WHERE obj_id = ?"); //NON-NLS CaseDbTransaction caseDbTransaction = null; try { @@ -1408,7 +1411,7 @@ public final class DrawableDB { ds_obj_id, value, groupBy.attrName.toString()); if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { - insertSQL += "ON CONFLICT DO NOTHING"; + insertSQL += " ON CONFLICT DO NOTHING"; } tskCase.getCaseDbAccessManager().insert(GROUPS_TABLENAME, insertSQL, caseDbTransaction); groupCache.put(cacheKey, Boolean.TRUE); @@ -1517,12 +1520,15 @@ public final class DrawableDB { // Update the list of file IDs in memory removeImageFileFromList(id); + //"delete from hash_set_hits where (obj_id = " + id + ")" + removeHashHitStmt.setLong(1, id); + removeHashHitStmt.executeUpdate(); + //"delete from drawable_files where (obj_id = " + id + ")" removeFileStmt.setLong(1, id); removeFileStmt.executeUpdate(); tr.addRemovedFile(id); - //TODO: delete from hash_set_hits table also... } catch (SQLException ex) { logger.log(Level.WARNING, "failed to delete row for obj_id = " + id, ex); //NON-NLS } finally { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java index 8b1f6eb31a..2a6102ff27 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java @@ -99,7 +99,17 @@ public class GroupKey> implements Comparable if (!Objects.equals(this.attr, other.attr)) { return false; } - return this.dataSource.getId() == other.dataSource.getId(); + // Check datasource, if available + if (this.dataSource != null && other.dataSource != null) { + return this.dataSource.getId() == other.dataSource.getId(); + } else if (this.dataSource == null && other.dataSource == null) { + // neither group has a datasource + return true; + } else { + // one group has a datasource, other doesn't + return false; + } + } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index c60da0357b..263e189b7d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -449,7 +449,7 @@ public class GroupManager { * * @param dataSource Data source to display or null to display all of them */ - synchronized void setDataSource(DataSource dataSource) { + public synchronized void setDataSource(DataSource dataSource) { dataSourceProp.set(dataSource); } @@ -785,12 +785,12 @@ public class GroupManager { //the current group should not be visible so ... if (isNotEmpty(unSeenGroups)) { // show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + controller.advance(GroupViewState.createTile(unSeenGroups.get(0))); } else if (isNotEmpty(analyzedGroups)) { //show the first analyzed group. - controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + controller.advance(GroupViewState.createTile(analyzedGroups.get(0))); } else { //there are no groups, clear the group area. - controller.advance(GroupViewState.tile(null)); + controller.advance(GroupViewState.createTile(null)); } } } finally { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java index fa578021ef..87f1971d84 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java @@ -26,12 +26,28 @@ import java.util.Optional; */ public final class GroupViewState { + // what group is being represented private final DrawableGroup group; + // Tile, Slide show, etc. private final GroupViewMode mode; private final Optional slideShowfileID; + private GroupViewState(DrawableGroup group, GroupViewMode mode, Long slideShowfileID) { + this.group = group; + this.mode = mode; + this.slideShowfileID = Optional.ofNullable(slideShowfileID); + } + + public static GroupViewState createTile(DrawableGroup group) { + return new GroupViewState(group, GroupViewMode.TILE, null); + } + + public static GroupViewState createSlideShow(DrawableGroup group, Long fileID) { + return new GroupViewState(group, GroupViewMode.SLIDE_SHOW, fileID); + } + public Optional getGroup() { return Optional.ofNullable(group); } @@ -44,19 +60,7 @@ public final class GroupViewState { return slideShowfileID; } - private GroupViewState(DrawableGroup group, GroupViewMode mode, Long slideShowfileID) { - this.group = group; - this.mode = mode; - this.slideShowfileID = Optional.ofNullable(slideShowfileID); - } - - public static GroupViewState tile(DrawableGroup group) { - return new GroupViewState(group, GroupViewMode.TILE, null); - } - - public static GroupViewState slideShow(DrawableGroup group, Long fileID) { - return new GroupViewState(group, GroupViewMode.SLIDE_SHOW, fileID); - } + @Override public int hashCode() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 6ce152bfdb..bc0f5e5d84 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -185,6 +185,9 @@ public class Toolbar extends ToolBar { alert.initOwner(getScene().getWindow()); GuiUtils.setDialogIcons(alert); if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { + // Set the datasource selection to 'All', before switching group + controller.getGroupManager().setDataSource(null); + queryInvalidationListener.invalidated(observable); } else { Platform.runLater(() -> groupByBox.getSelectionModel().select(DrawableAttribute.PATH)); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index 0aa0c287eb..1ce67cd030 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -107,7 +107,7 @@ abstract class NavPanel extends Tab { .addListener((observable, oldItem, newSelectedItem) -> { Optional.ofNullable(newSelectedItem) .map(getDataItemMapper()) - .ifPresent(group -> controller.advance(GroupViewState.tile(group))); + .ifPresent(group -> controller.advance(GroupViewState.createTile(group))); }); } diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml index 343dc691e7..f6ca42f988 100644 --- a/KeywordSearch/nbproject/project.xml +++ b/KeywordSearch/nbproject/project.xml @@ -119,7 +119,7 @@ 10 - 10.12 + 10.13 diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SqliteTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SqliteTextExtractor.java index f5cebb42d3..c8bbe289e4 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SqliteTextExtractor.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SqliteTextExtractor.java @@ -33,7 +33,6 @@ import java.util.LinkedList; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.SqliteUtil; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SqliteUtil.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SqliteUtil.java new file mode 100755 index 0000000000..08eefe7232 --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SqliteUtil.java @@ -0,0 +1,130 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018-2018 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.keywordsearch; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.casemodule.services.Services; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Sqlite utility class. Find and copy metafiles, write sqlite abstract files to + * temp directory, and generate unique temp directory paths. + */ +final class SqliteUtil { + + private SqliteUtil() { + + } + + /** + * Overloaded implementation of + * {@link #findAndCopySQLiteMetaFile(AbstractFile, String) findAndCopySQLiteMetaFile} + * , automatically tries to copy -wal and -shm files without needing to know + * their existence. + * + * @param sqliteFile file which has -wal and -shm meta files + * + * @throws NoCurrentCaseException Case has been closed. + * @throws TskCoreException fileManager cannot find AbstractFile + * files. + * @throws IOException Issue during writing to file. + */ + public static void findAndCopySQLiteMetaFile(AbstractFile sqliteFile) + throws NoCurrentCaseException, TskCoreException, IOException { + + findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-wal"); + findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-shm"); + } + + /** + * Searches for a meta file associated with the give SQLite database. If + * found, it copies this file into the temp directory of the current case. + * + * @param sqliteFile file being processed + * @param metaFileName name of meta file to look for + * + * @throws NoCurrentCaseException Case has been closed. + * @throws TskCoreException fileManager cannot find AbstractFile + * files. + * @throws IOException Issue during writing to file. + */ + public static void findAndCopySQLiteMetaFile(AbstractFile sqliteFile, + String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException { + + Case openCase = Case.getCurrentCaseThrows(); + SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase(); + Services services = new Services(sleuthkitCase); + FileManager fileManager = services.getFileManager(); + + List metaFiles = fileManager.findFiles( + sqliteFile.getDataSource(), metaFileName, + sqliteFile.getParent().getName()); + + if (metaFiles != null) { + for (AbstractFile metaFile : metaFiles) { + writeAbstractFileToLocalDisk(metaFile); + } + } + } + + /** + * Copies the file contents into a unique path in the current case temp + * directory. + * + * @param file AbstractFile from the data source + * + * @return The path of the file on disk + * + * @throws IOException Exception writing file contents + * @throws NoCurrentCaseException Current case closed during file copying + */ + public static String writeAbstractFileToLocalDisk(AbstractFile file) + throws IOException, NoCurrentCaseException { + + String localDiskPath = getUniqueTempDirectoryPath(file); + File localDatabaseFile = new File(localDiskPath); + if (!localDatabaseFile.exists()) { + ContentUtils.writeToFile(file, localDatabaseFile); + } + return localDiskPath; + } + + /** + * Generates a unique local disk path that resides in the temp directory of + * the current case. + * + * @param file The database abstract file + * + * @return Unique local disk path living in the temp directory of the case + * + * @throws org.sleuthkit.autopsy.casemodule.NoCurrentCaseException + */ + public static String getUniqueTempDirectoryPath(AbstractFile file) throws NoCurrentCaseException { + return Case.getCurrentCaseThrows().getTempDirectory() + + File.separator + file.getId() + file.getName(); + } +} diff --git a/NEWS.txt b/NEWS.txt index 3fcbff5b40..f0414d48ba 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,9 +1,39 @@ +---------------- VERSION 4.9.0 -------------- + +New Features: +- Removed data from table that are time intensive and can be found in content viewers (such as hash set hits) +- Added ability to find common items (files, emails, etc.) between current case and past cases using the Central Repository. +- Added ability to ignore common items that exist in a large number of cases by using Central Repository data. +- Data is validated and normalized before being entered into the Central Repository. +- Allow users to specify that an ad-hoc keyword search should not be saved to database +- New “Annotations” content viewer that shows all tags and comments associated with an item +- Added 2 icons to the table to show the item’s score (if it is notable or suspicious) and if it has a comment. +- Added column to the table to show previous number of occurrences. +- Tags are now associated with the user (in a multi-user environment) and you can hide other people’s tags +- New Display options area that unifies various new settings. +- Hash sets can be copied into the user’s config folder (AppData), which makes it easier to run Autopsy from a Live Triage USB and not care about what drive letter it gets. +- Image Gallery stores its groups and seen status in Case DB instead of its own. +- Image Gallery works better in multi-user setups and reloads the database when other nodes add data sources. +- Image Gallery saves which user saw a group and gives user option of seeing only their unseen groups or all unseen groups. +- Saves last export location and pre-populates that in the file picker +- Provide feedback about why some right click options are disabled (ingest is running, not file content, etc.) + +Bug Fixes: +- Substring keyword search is more accurate (now uses regular expression) +- New text extractor for SQLite that better deals with full text search tables +- Better deal with Unicode text files that do not have Byte Order Marker +- Embedded file extractor module is now faster because it uses a different 7ZIP API. +- Fixed various HTML report bugs +- Duplicate hash set hits are not created when you run the Hash Ingest Module twice. +- Auto ingest (in Experimental) scan times of input folders is faster. + + ---------------- VERSION 4.8.0 -------------- New Features: - Data Source Grouping: -- The case tree view can now be grouped by data source. -- Keyword and file search can now be restricted to a data source. -- Central Repository / Corrrelation: +- Central Repository / Correlation: -- New common files search feature that finds files that exist in multiple devices in the same case. -- The Other Occurrences content viewer now shows matches in the current case (in addition to central repository). -- Central repository options panel now shows cases that are in repo. @@ -31,7 +61,7 @@ New Features: - A graph visualization was added to the Communications tool to make it easier to find messages and relationships. - A new "Application" content viewer (lower right) that will contain file-type specific viewers (to reduce number of tabs). - New viewer for SQLite databases (in Application content viewer) -- New viewer for binary PLists (in Appilcation content viewer) +- New viewer for binary PLists (in Application content viewer) - L01 files can be imported as data sources. - Ingest filters can now use date range conditions for triage. - Passwords to open password protected archive files can be entered (by right clicking on the file). diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml index 4d85f94e9b..7b7ae18347 100644 --- a/RecentActivity/nbproject/project.xml +++ b/RecentActivity/nbproject/project.xml @@ -60,7 +60,7 @@ 10 - 10.12 + 10.13 diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java index 1af8761144..3e6a1e6348 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java @@ -69,6 +69,7 @@ class ExtractIE extends Extract { private final String moduleTempResultsDir; private String PASCO_LIB_PATH; private final String JAVA_PATH; + private static final String RESOURCE_URL_PREFIX = "res://"; private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); private Content dataSource; private IngestJobContext context; @@ -473,8 +474,8 @@ class ExtractIE extends Extract { String actime = lineBuff[3]; Long ftime = (long) 0; - String user; - String realurl; + String user = null; + String realurl = null; String domain; /* @@ -494,6 +495,9 @@ class ExtractIE extends Extract { realurl = realurl.replace(":Host:", ""); //NON-NLS realurl = realurl.trim(); } else { + /* + * Use the entire input for the URL. + */ user = ""; realurl = lineBuff[1].trim(); } @@ -532,9 +536,12 @@ class ExtractIE extends Extract { "ExtractIE.parentModuleName.noSpace"), NbBundle.getMessage(this.getClass(), "ExtractIE.moduleName.text"))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, - NbBundle.getMessage(this.getClass(), - "ExtractIE.parentModuleName.noSpace"), domain)); + + if (isIgnoredUrl(lineBuff[1]) == false) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, + NbBundle.getMessage(this.getClass(), + "ExtractIE.parentModuleName.noSpace"), domain)); + } bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME, NbBundle.getMessage(this.getClass(), "ExtractIE.parentModuleName.noSpace"), user)); @@ -562,4 +569,26 @@ class ExtractIE extends Extract { fileScanner.close(); return bbartifacts; } + + /** + * Determine if the URL should be ignored. + * + * @param url The URL to test. + * + * @return True if the URL should be ignored; otherwise false. + */ + private boolean isIgnoredUrl(String url) { + if (url == null || url.isEmpty()) { + return true; + } + + if (url.toLowerCase().startsWith(RESOURCE_URL_PREFIX)) { + /* + * Ignore URLs that begin with the matched text. + */ + return true; + } + + return false; + } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index 22459f4cf6..6d0ae54a53 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -54,6 +54,7 @@ import org.sleuthkit.datamodel.TskCoreException; class Firefox extends Extract { private static final Logger logger = Logger.getLogger(Firefox.class.getName()); + private static final String PLACE_URL_PREFIX = "place:"; private static final String HISTORY_QUERY = "SELECT moz_historyvisits.id,url,title,visit_count,(visit_date/1000000) AS visit_date,from_visit,(SELECT url FROM moz_places WHERE id=moz_historyvisits.from_visit) as ref FROM moz_places, moz_historyvisits WHERE moz_places.id = moz_historyvisits.place_id AND hidden = 0"; //NON-NLS private static final String COOKIE_QUERY = "SELECT name,value,host,expiry,(lastAccessed/1000000) AS lastAccessed,(creationTime/1000000) AS creationTime FROM moz_cookies"; //NON-NLS private static final String COOKIE_QUERY_V3 = "SELECT name,value,host,expiry,(lastAccessed/1000000) AS lastAccessed FROM moz_cookies"; //NON-NLS @@ -132,11 +133,13 @@ class Firefox extends Extract { List> tempList = this.dbConnect(temps, HISTORY_QUERY); logger.log(Level.INFO, "{0} - Now getting history from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS for (HashMap result : tempList) { + String url = result.get("url").toString(); + Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), - ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS + ((url != null) ? url : ""))); //NON-NLS //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("url").toString() != null) ? EscapeUtil.decodeURL(result.get("url").toString()) : ""))); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, NbBundle.getMessage(this.getClass(), @@ -154,10 +157,12 @@ class Firefox extends Extract { NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), NbBundle.getMessage(this.getClass(), "Firefox.moduleName"))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, - NbBundle.getMessage(this.getClass(), - "Firefox.parentModuleName.noSpace"), (Util.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS + if (isIgnoredUrl(url) == false) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, + NbBundle.getMessage(this.getClass(), + "Firefox.parentModuleName.noSpace"), Util.extractDomain(url))); //NON-NLS + } BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY, historyFile, bbattributes); if (bbart != null) { bbartifacts.add(bbart); @@ -226,12 +231,13 @@ class Firefox extends Extract { List> tempList = this.dbConnect(temps, BOOKMARK_QUERY); logger.log(Level.INFO, "{0} - Now getting bookmarks from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS for (HashMap result : tempList) { + String url = result.get("url").toString(); Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), - ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS + ((url != null) ? url : ""))); //NON-NLS bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), @@ -246,10 +252,12 @@ class Firefox extends Extract { NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), NbBundle.getMessage(this.getClass(), "Firefox.moduleName"))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, - NbBundle.getMessage(this.getClass(), - "Firefox.parentModuleName.noSpace"), - (Util.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS + if (isIgnoredUrl(url) == false) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, + NbBundle.getMessage(this.getClass(), + "Firefox.parentModuleName.noSpace"), + Util.extractDomain(url))); //NON-NLS + } BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bookmarkFile, bbattributes); if (bbart != null) { @@ -327,12 +335,13 @@ class Firefox extends Extract { List> tempList = this.dbConnect(temps, query); logger.log(Level.INFO, "{0} - Now getting cookies from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS for (HashMap result : tempList) { + String host = result.get("host").toString(); Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), - ((result.get("host").toString() != null) ? result.get("host").toString() : ""))); //NON-NLS + ((host != null) ? host : ""))); //NON-NLS bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), @@ -356,11 +365,13 @@ class Firefox extends Extract { "Firefox.parentModuleName.noSpace"), (Long.valueOf(result.get("creationTime").toString())))); //NON-NLS } - String domain = Util.extractDomain(result.get("host").toString()); //NON-NLS - domain = domain.replaceFirst("^\\.+(?!$)", ""); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, - NbBundle.getMessage(this.getClass(), - "Firefox.parentModuleName.noSpace"), domain)); + if (isIgnoredUrl(host) == false) { + String domain = Util.extractDomain(host); //NON-NLS + domain = domain.replaceFirst("^\\.+(?!$)", ""); + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, + NbBundle.getMessage(this.getClass(), + "Firefox.parentModuleName.noSpace"), domain)); + } BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_COOKIE, cookiesFile, bbattributes); if (bbart != null) { @@ -442,13 +453,14 @@ class Firefox extends Extract { List> tempList = this.dbConnect(temps, DOWNLOAD_QUERY); logger.log(Level.INFO, "{0}- Now getting downloads from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS for (HashMap result : tempList) { + String source = result.get("source").toString(); Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), - ((result.get("source").toString() != null) ? result.get("source").toString() : ""))); //NON-NLS + source)); //NON-NLS //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : ""))); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, NbBundle.getMessage(this.getClass(), @@ -481,10 +493,12 @@ class Firefox extends Extract { NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), NbBundle.getMessage(this.getClass(), "Firefox.moduleName"))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, - NbBundle.getMessage(this.getClass(), - "Firefox.parentModuleName.noSpace"), - (Util.extractDomain((result.get("source").toString() != null) ? result.get("source").toString() : "")))); //NON-NLS + if (isIgnoredUrl(source) == false) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, + NbBundle.getMessage(this.getClass(), + "Firefox.parentModuleName.noSpace"), + Util.extractDomain(source))); //NON-NLS + } BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes); if (bbart != null) { @@ -565,13 +579,14 @@ class Firefox extends Extract { logger.log(Level.INFO, "{0} - Now getting downloads from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS for (HashMap result : tempList) { - + String url = result.get("url").toString(); + Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), - ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS + url)); //NON-NLS //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : ""))); //TODO Revisit usage of deprecated constructor as per TSK-583 //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "RecentActivity", "Last Visited", (Long.valueOf(result.get("startTime").toString())))); @@ -604,10 +619,12 @@ class Firefox extends Extract { NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName.noSpace"), NbBundle.getMessage(this.getClass(), "Firefox.moduleName"))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, - NbBundle.getMessage(this.getClass(), - "Firefox.parentModuleName.noSpace"), - (Util.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS + if (isIgnoredUrl(url) == false) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, + NbBundle.getMessage(this.getClass(), + "Firefox.parentModuleName.noSpace"), + Util.extractDomain(url))); //NON-NLS + } BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes); if (bbart != null) { @@ -627,4 +644,26 @@ class Firefox extends Extract { NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"), BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts)); } + + /** + * Determine if the URL should be ignored. + * + * @param url The URL to test. + * + * @return True if the URL should be ignored; otherwise false. + */ + private boolean isIgnoredUrl(String url) { + if (url == null || url.isEmpty()) { + return true; + } + + if (url.toLowerCase().startsWith(PLACE_URL_PREFIX)) { + /* + * Ignore URLs that begin with the matched text. + */ + return true; + } + + return false; + } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java index 8b246b05aa..9bff067394 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java @@ -84,7 +84,12 @@ class Util { } } - public static String getBaseDomain(String url) { + /** + * + * @param url + * @return empty string if no domain could be found + */ + private static String getBaseDomain(String url) { String host = null; //strip protocol @@ -113,10 +118,21 @@ class Util { hostB.append("."); } } - - return hostB.toString(); + + + String base = hostB.toString(); + // verify there are no special characters in there + if (base.matches(".*[~`!@#$%^&\\*\\(\\)\\+={}\\[\\];:\\?<>,/ ].*")) { + return ""; + } + return base; } + /** + * + * @param value + * @return empty string if no domain name was found + */ public static String extractDomain(String value) { if (value == null) { return ""; diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 088cafd41a..91571da9bf 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 05 Oct 2018 09:58:28 -0400 +#Sat, 13 Oct 2018 21:02:18 -0400 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 5678704094..90fb6cf276 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Fri, 05 Oct 2018 09:58:28 -0400 +#Sat, 13 Oct 2018 21:02:18 -0400 CTL_MainWindow_Title=Autopsy 4.9.0 CTL_MainWindow_Title_No_Project=Autopsy 4.9.0 diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index d4c0a0b53d..10437cbd97 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -36,7 +36,7 @@ 10 - 10.12 + 10.13