diff --git a/Core/build.xml b/Core/build.xml index ee1701a48d883c12749ae25ebaeb02fee4e4e999..5b8a4aec61c50b3873e021eba329e75aa13275b8 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -116,10 +116,6 @@ <fileset dir="${thirdparty.dir}/yara/bin"/> </copy> <copy file="${thirdparty.dir}/yara/bin/YaraJNIWrapper.jar" todir="${ext.dir}" /> - <!--Copy ffmpeg to release--> - <copy todir="${basedir}/release/ffmpeg" > - <fileset dir="${thirdparty.dir}/ffmpeg"/> - </copy> </target> diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 2afd79de976157d9cf3e140b0f96122f35daaa84..0553a915ca279a4833b7f03ebad6378ceb400f11 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -356,11 +356,9 @@ <package>org.sleuthkit.autopsy.modules.encryptiondetection</package> <package>org.sleuthkit.autopsy.modules.filetypeid</package> <package>org.sleuthkit.autopsy.modules.hashdatabase</package> - <package>org.sleuthkit.autopsy.modules.interestingitems</package> <package>org.sleuthkit.autopsy.modules.vmextractor</package> <package>org.sleuthkit.autopsy.progress</package> <package>org.sleuthkit.autopsy.report</package> - <package>org.sleuthkit.autopsy.testutils</package> <package>org.sleuthkit.autopsy.textextractors</package> <package>org.sleuthkit.autopsy.textextractors.configs</package> <package>org.sleuthkit.autopsy.textsummarizer</package> diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED index c96f183648ff109373a365220a24dfb46d08155a..5c9a0ea3ac61cd0bd2a723af69b2b5bc2236013d 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED @@ -32,15 +32,6 @@ DeleteFileBlackboardArtifactTagAction.deleteTags.alert=Unable to untag artifact DeleteFileContentTagAction.deleteTag=Remove File Tag # {0} - fileID DeleteFileContentTagAction.deleteTag.alert=Unable to untag file {0}. -DeleteReportAction.actionDisplayName.multipleReports=Delete Reports -DeleteReportAction.actionDisplayName.singleReport=Delete Report -# {0} - reportNum -DeleteReportAction.actionPerformed.showConfirmDialog.multiple.msg=Do you want to delete {0} reports from the case? -DeleteReportAction.actionPerformed.showConfirmDialog.single.msg=Do you want to delete 1 report from the case? -DeleteReportAction.actionPerformed.showConfirmDialog.title=Confirm Deletion -DeleteReportAction.showConfirmDialog.errorMsg=An error occurred while deleting the reports. -DeleteReportAction.showConfirmDialog.multiple.explanation=The reports will remain on disk. -DeleteReportAction.showConfirmDialog.single.explanation=The report will remain on disk. ExitAction.confirmationDialog.message=Ingest is running, are you sure you want to exit? ExitAction.confirmationDialog.title=Ingest is Running # {0} - exception message @@ -93,7 +84,6 @@ CTL_OpenOutputFolder=Open Case Folder OpenOutputFolder.error1=Case Folder Not Found: {0} OpenOutputFolder.noCaseOpen=No open case, therefore no current case folder available. OpenOutputFolder.CouldNotOpenOutputFolder=Could not open case folder -OpenReportAction_actionDisplayName=Open Report # {0} - old tag name # {1} - artifactID ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}. diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteReportAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteReportAction.java deleted file mode 100644 index 5c0dc80579c572641c7004c6ccbc9aa89dbe97ee..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteReportAction.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.actions; - -import java.awt.event.ActionEvent; -import java.util.Collection; -import java.util.logging.Level; -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.JOptionPane; -import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; -import org.openide.util.Utilities; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.datamodel.Report; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Action for deleting selected reports. - */ -@Messages({ - "DeleteReportAction.actionDisplayName.singleReport=Delete Report", - "DeleteReportAction.actionDisplayName.multipleReports=Delete Reports" -}) -public class DeleteReportAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - - private static DeleteReportAction instance = null; - - /** - * @return Singleton instance of this class. - */ - public static DeleteReportAction getInstance() { - if (instance == null) { - instance = new DeleteReportAction(); - } - - if (Utilities.actionsGlobalContext().lookupAll(Report.class).size() == 1) { - instance.putValue(Action.NAME, Bundle.DeleteReportAction_actionDisplayName_singleReport()); - } else { - instance.putValue(Action.NAME, Bundle.DeleteReportAction_actionDisplayName_multipleReports()); - } - return instance; - } - - /** - * Do not instantiate directly. Use DeleteReportAction.getInstance(), - * instead. - */ - private DeleteReportAction() { - } - - @NbBundle.Messages({ - "DeleteReportAction.showConfirmDialog.single.explanation=The report will remain on disk.", - "DeleteReportAction.showConfirmDialog.multiple.explanation=The reports will remain on disk.", - "DeleteReportAction.showConfirmDialog.errorMsg=An error occurred while deleting the reports.", - "DeleteReportAction.actionPerformed.showConfirmDialog.title=Confirm Deletion", - "DeleteReportAction.actionPerformed.showConfirmDialog.single.msg=Do you want to delete 1 report from the case?", - "# {0} - reportNum", - "DeleteReportAction.actionPerformed.showConfirmDialog.multiple.msg=Do you want to delete {0} reports from the case?"}) - @Override - public void actionPerformed(ActionEvent e) { - Collection<? extends Report> selectedReportsCollection = Utilities.actionsGlobalContext().lookupAll(Report.class); - String message = selectedReportsCollection.size() > 1 - ? Bundle.DeleteReportAction_actionPerformed_showConfirmDialog_multiple_msg(selectedReportsCollection.size()) - : Bundle.DeleteReportAction_actionPerformed_showConfirmDialog_single_msg(); - String explanation = selectedReportsCollection.size() > 1 - ? Bundle.DeleteReportAction_showConfirmDialog_multiple_explanation() - : Bundle.DeleteReportAction_showConfirmDialog_single_explanation(); - Object[] jOptionPaneContent = {message, explanation}; - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(null, jOptionPaneContent, - Bundle.DeleteReportAction_actionPerformed_showConfirmDialog_title(), - JOptionPane.YES_NO_OPTION)) { - try { - Case.getCurrentCaseThrows().deleteReports(selectedReportsCollection); - } catch (TskCoreException | NoCurrentCaseException ex) { - Logger.getLogger(DeleteReportAction.class.getName()).log(Level.SEVERE, "Error deleting reports", ex); // NON-NLS - MessageNotifyUtil.Message.error(Bundle.DeleteReportAction_showConfirmDialog_errorMsg()); - } - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenReportAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenReportAction.java deleted file mode 100644 index fa5886aedcfd6340935ef2a513241d8ab9d04a65..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenReportAction.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.actions; - -import java.awt.event.ActionEvent; -import java.io.File; -import javax.swing.AbstractAction; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; - -/** - * Action to open report. - */ -@Messages({ - "OpenReportAction_actionDisplayName=Open Report" -}) -public class OpenReportAction extends AbstractAction { - - private static final long serialVersionUID = 1L; - private final String reportPath; - - /** - * Constructor. - * - * @param reportPath Path to report.s - */ - public OpenReportAction(String reportPath) { - super(Bundle.OpenReportAction_actionDisplayName()); - this.reportPath = reportPath; - } - - @Override - public void actionPerformed(ActionEvent e) { - if (reportPath.toLowerCase().startsWith("http")) { - ExternalViewerAction.openURL(reportPath); - } else { - String extension = ""; - int extPosition = reportPath.lastIndexOf('.'); - if (extPosition != -1) { - extension = reportPath.substring(extPosition, reportPath.length()).toLowerCase(); - } - - ExternalViewerAction.openFile("", extension, new File(reportPath)); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java index 13d89f501f59d8d3f15a94af1a1f258b399374fa..6da2b55862a10dd300ab20c823f8557bfb5812ec 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java @@ -35,7 +35,6 @@ public class ViewArtifactAction extends AbstractAction { private static final Logger logger = Logger.getLogger(ViewArtifactAction.class.getName()); - private static final long serialVersionUID = 1L; private final BlackboardArtifact artifact; /** @@ -51,15 +50,12 @@ public ViewArtifactAction(BlackboardArtifact artifact, String displayName) { @Override public void actionPerformed(ActionEvent e) { - // Moved this call outside the swingworker to prevent exceptions. - final DirectoryTreeTopComponent comp = DirectoryTreeTopComponent.findInstance(); - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { - comp.viewArtifact(artifact); + DirectoryTreeTopComponent.findInstance().viewArtifact(artifact); return null; } diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java index 1f5d046704f22be59708a9940c7fe62b18d0ea3e..200759fbf5b4c09fb31f928fafe699ea7c1ad0ac 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java @@ -52,12 +52,11 @@ public ViewOsAccountAction(OsAccount osAccount, String displayName) { @Override public void actionPerformed(ActionEvent e) { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - final DirectoryTreeTopComponent topComp = DirectoryTreeTopComponent.findInstance(); new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { - topComp.viewOsAccount(osAccount); + DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount); return null; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 6f248e52aeaf68f120ac7f3f6112ed9bc96a6933..6ece795e35547c8b59b54e1d2b086b4a75efff29 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -76,7 +76,6 @@ import org.sleuthkit.autopsy.datasourcesummary.ui.DataSourceSummaryAction; import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent; import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent; -import org.sleuthkit.autopsy.casemodule.events.AnalysisResultDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent; @@ -508,9 +507,7 @@ public enum Events { /** * One or more TagSets have been removed. */ - TAG_SETS_DELETED, - - ANALYSIS_RESULT_DELETED; + TAG_SETS_DELETED; }; @@ -631,17 +628,17 @@ public void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent e } @Subscribe - public void publishHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) { + public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) { eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds())); } @Subscribe - public void publishTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) { + public void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) { eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames())); } @Subscribe - public void publishTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) { + public void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) { eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames())); } @@ -651,51 +648,14 @@ public void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event) { } @Subscribe - public void publishTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) { + public void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) { eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets())); } @Subscribe - public void publishTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) { + public void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) { eventPublisher.publish(new TagSetsDeletedEvent(event.getTagSetIds())); } - - @Subscribe - public void publishAnalysisResultDeleted(TskEvent.AnalysisResultsDeletedTskEvent event) { - eventPublisher.publish(new AnalysisResultDeletedEvent(event.getAnalysisResultObjectIds())); - } - - @Subscribe - public void publishBlackboardArtifactTagDeleted(TskEvent.BlackboardArtifactTagsDeletedTskEvent event) { - List<BlackboardArtifactTag> tags = event.getTags(); - for(BlackboardArtifactTag tag: tags) { - eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(tag)); - } - } - - @Subscribe - public void publishBlackboardTagAdded(TskEvent.BlackboardArtifactTagsAddedTskEvent event) { - List<BlackboardArtifactTag> tags = event.getTags(); - for(BlackboardArtifactTag tag: tags) { - eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(tag)); - } - } - - @Subscribe - public void publishContentTagAdded(TskEvent.ContentTagsAddedTskEvent event) { - List<ContentTag> tags = event.getTags(); - for(ContentTag tag: tags) { - eventPublisher.publish(new ContentTagAddedEvent(tag)); - } - } - - @Subscribe - public void publishContentTagDeleted(TskEvent.ContentTagsDeletedTskEvent event) { - List<ContentTag> tags = event.getTags(); - for(ContentTag tag: tags) { - eventPublisher.publish(new ContentTagDeletedEvent(tag)); - } - } } /** @@ -1845,6 +1805,41 @@ public void notifyDataSourceNameChanged(Content dataSource, String newName) { eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName)); } + /** + * Notifies case event subscribers that a content tag has been added. + * + * This should not be called from the event dispatch thread (EDT) + * + * @param newTag new ContentTag added + */ + public void notifyContentTagAdded(ContentTag newTag) { + notifyContentTagAdded(newTag, null); + } + + /** + * Notifies case event subscribers that a content tag has been added. + * + * This should not be called from the event dispatch thread (EDT) + * + * @param newTag The added ContentTag. + * @param deletedTagList List of ContentTags that were removed as a result + * of the addition of newTag. + */ + public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) { + eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList)); + } + + /** + * Notifies case event subscribers that a content tag has been deleted. + * + * This should not be called from the event dispatch thread (EDT) + * + * @param deletedTag ContentTag deleted + */ + public void notifyContentTagDeleted(ContentTag deletedTag) { + eventPublisher.publish(new ContentTagDeletedEvent(deletedTag)); + } + /** * Notifies case event subscribers that a tag definition has changed. * @@ -1875,6 +1870,41 @@ public void notifyCentralRepoCommentChanged(long contentId, String newComment) { } } + /** + * Notifies case event subscribers that an artifact tag has been added. + * + * This should not be called from the event dispatch thread (EDT) + * + * @param newTag new BlackboardArtifactTag added + */ + public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) { + notifyBlackBoardArtifactTagAdded(newTag, null); + } + + /** + * Notifies case event subscribers that an artifact tag has been added. + * + * This should not be called from the event dispatch thread (EDT) + * + * @param newTag The added ContentTag. + * @param removedTagList List of ContentTags that were removed as a result + * of the addition of newTag. + */ + public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) { + eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList)); + } + + /** + * Notifies case event subscribers that an artifact tag has been deleted. + * + * This should not be called from the event dispatch thread (EDT) + * + * @param deletedTag BlackboardArtifactTag deleted + */ + public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) { + eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag)); + } + /** * Adds a report to the case. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/AnalysisResultDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/AnalysisResultDeletedEvent.java deleted file mode 100755 index 91c38e910452d989fc4daa887fc8617c5049e137..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/AnalysisResultDeletedEvent.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.casemodule.events; - -import java.util.List; -import org.sleuthkit.autopsy.casemodule.Case; - -public class AnalysisResultDeletedEvent extends TskDataModelObjectsDeletedEvent{ - - private static final long serialVersionUID = 1L; - - public AnalysisResultDeletedEvent(List<Long> analysisResultIds) { - super(Case.Events.ANALYSIS_RESULT_DELETED.name(), analysisResultIds); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 53c695cf9d3bad1b1bc545bb90a475dc9572125c..a4bd51bcce6b11d8e6e031d5e406acde2ed842cc 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -66,9 +66,9 @@ public class TagsManager implements Closeable { private static String PROJECT_VIC_TAG_SET_NAME = "Project VIC"; private static final Object lock = new Object(); - + private final Map<String, TagName> allTagNameMap = Collections.synchronizedMap(new HashMap<>()); - + private final PropertyChangeListener listener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -95,7 +95,7 @@ public void propertyChange(PropertyChangeEvent evt) { } } }; - + private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null); static { @@ -300,12 +300,12 @@ public static void addTagSetDefinition(TagSetDefinition tagSetDef) throws IOExce try { List<TagSet> tagSetsInCase = taggingMgr.getTagSets(); if (tagSetsInCase.isEmpty()) { - + // add the standard tag names for (TagNameDefinition def : TagNameDefinition.getStandardTagNameDefinitions()) { taggingMgr.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus()); } - + //Assume new case and add all tag sets for (TagSetDefinition setDef : TagSetDefinition.readTagSetDefinitions()) { List<TagName> tagNamesInSet = new ArrayList<>(); @@ -317,12 +317,12 @@ public static void addTagSetDefinition(TagSetDefinition tagSetDef) throws IOExce taggingMgr.addTagSet(setDef.getName(), tagNamesInSet); } } - } + } - for (TagName tagName : caseDb.getAllTagNames()) { + for(TagName tagName: caseDb.getAllTagNames()) { allTagNameMap.put(tagName.getDisplayName(), tagName); } - + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error updating standard tag name and tag set definitions", ex); } catch (IOException ex) { @@ -332,7 +332,7 @@ public static void addTagSetDefinition(TagSetDefinition tagSetDef) throws IOExce for (TagNameDefinition tagName : TagNameDefinition.getTagNameDefinitions()) { tagName.saveToCase(caseDb); } - + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_UPDATED), weakListener); Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_ADDED), weakListener); Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_DELETED), weakListener); @@ -359,7 +359,7 @@ public List<TagSet> getAllTagSets() throws TskCoreException { * @throws TskCoreException If there is an error querying the case database. */ public TagSet getTagSet(TagName tagName) throws TskCoreException { - return caseDb.getTaggingManager().getTagSet(tagName); + return caseDb.getTaggingManager().getTagSet(tagName); } /** @@ -383,7 +383,7 @@ public TagSet addTagSet(String name, List<TagName> tagNameList) throws TskCoreEx * @return A list, possibly empty, of TagName objects. */ public synchronized List<TagName> getAllTagNames() { - + List<TagName> tagNames = new ArrayList<>(); tagNames.addAll(allTagNameMap.values()); return tagNames; @@ -636,6 +636,14 @@ public ContentTag addContentTag(Content content, TagName tagName, String comment */ public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws TskCoreException { TaggingManager.ContentTagChange tagChange = caseDb.getTaggingManager().addContentTag(content, tagName, comment, beginByteOffset, endByteOffset); + try { + Case currentCase = Case.getCurrentCaseThrows(); + + currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags()); + + } catch (NoCurrentCaseException ex) { + throw new TskCoreException("Added a tag to a closed case", ex); + } return tagChange.getAddedTag(); } @@ -649,6 +657,11 @@ public ContentTag addContentTag(Content content, TagName tagName, String comment */ public void deleteContentTag(ContentTag tag) throws TskCoreException { caseDb.deleteContentTag(tag); + try { + Case.getCurrentCaseThrows().notifyContentTagDeleted(tag); + } catch (NoCurrentCaseException ex) { + throw new TskCoreException("Deleted a tag from a closed case", ex); + } } /** @@ -844,6 +857,12 @@ public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifac */ public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { TaggingManager.BlackboardArtifactTagChange tagChange = caseDb.getTaggingManager().addArtifactTag(artifact, tagName, comment); + try { + Case currentCase = Case.getCurrentCaseThrows(); + currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags()); + } catch (NoCurrentCaseException ex) { + throw new TskCoreException("Added a tag to a closed case", ex); + } return tagChange.getAddedTag(); } @@ -857,6 +876,11 @@ public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifac */ public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { caseDb.deleteBlackboardArtifactTag(tag); + try { + Case.getCurrentCaseThrows().notifyBlackBoardArtifactTagDeleted(tag); + } catch (NoCurrentCaseException ex) { + throw new TskCoreException("Deleted a tag from a closed case", ex); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java index abc44d3ad669c3c803e2f602eb9939cc2fc2a99c..0540fb2a81a2e5f97301c4ca4c308bb84750e554 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java @@ -28,7 +28,6 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; -import org.sleuthkit.datamodel.DataArtifact; /** * This creates a single context menu item for adding or editing a Central @@ -40,23 +39,19 @@ public class CentralRepoContextMenuActionsProvider implements ContextMenuActions @Override public List<Action> getActions() { ArrayList<Action> actionsList = new ArrayList<>(); - - Collection<? extends DataArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(DataArtifact.class); - if(artifacts.isEmpty()) { - Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); - if (selectedFiles.size() != 1) { - return actionsList; - } + if (selectedFiles.size() != 1) { + return actionsList; + } - for (AbstractFile file : selectedFiles) { - if (CentralRepository.isEnabled() && CorrelationAttributeUtil.isSupportedAbstractFileType(file) && file.isFile()) { - AddEditCentralRepoCommentAction action = new AddEditCentralRepoCommentAction(file); - if (action.getCorrelationAttribute() == null) { - action.setEnabled(false); - } - actionsList.add(action); + for (AbstractFile file : selectedFiles) { + if (CentralRepository.isEnabled() && CorrelationAttributeUtil.isSupportedAbstractFileType(file) && file.isFile()) { + AddEditCentralRepoCommentAction action = new AddEditCentralRepoCommentAction(file); + if (action.getCorrelationAttribute() == null) { + action.setEnabled(false); } + actionsList.add(action); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java index fb889a92bfcb77ade8c7b18fd2738402b10a3fb1..883d979c908673d1861f06a74bc239a9e9bafb41 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java @@ -30,11 +30,11 @@ public class CentralRepositoryNotificationDialog { /** - * This dialog should display if the mode is RELEASE and the application is - * running with a GUI. + * This dialog should display iff the mode is RELEASE and the + * application is running with a GUI. */ static boolean shouldDisplay() { - return Version.getBuildType() == Version.Type.RELEASE + return Version.getBuildType() == Version.Type.RELEASE && RuntimeProperties.runningWithGUI(); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java index 81f2c0484153846316394ff0d7266f9777d53f41..f15dec3029b7fc15b61b85caad0aa839ada5ba52 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -178,12 +178,7 @@ private void setupDefaultCentralRepository() { return; // Nothing to do } - - // Some projects built on top of Autopsy platform set this environment - // variable to make sure there are no UI popups. This is necessary because - // Installer classes run before we have been able to determine - // whether we are running headless or not. See JIRA-8422. - if (System.getenv("AUTOPSY_HEADLESS") == null && CentralRepositoryNotificationDialog.shouldDisplay()) { + if (CentralRepositoryNotificationDialog.shouldDisplay()) { CentralRepositoryNotificationDialog.display(); } diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java index 665ce36a913dadc7c7fd04a41f972b9d32afa6e4..7bfdd2ad023f990a503a5aebb0bab1c325b09dc1 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java @@ -300,7 +300,7 @@ protected void done() { } Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata, correlationType); DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this)); - CommonAttributeTableFilterNode tableFilterWithDescendantsNode = new CommonAttributeTableFilterNode(dataResultFilterNode, 3); + TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3); DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable(); Collection<DataResultViewer> viewers = new ArrayList<>(1); viewers.add(table); @@ -402,7 +402,7 @@ protected void done() { // -3969 Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata); DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this)); - CommonAttributeTableFilterNode tableFilterWithDescendantsNode = new CommonAttributeTableFilterNode(dataResultFilterNode, 3); + TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3); DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable(); Collection<DataResultViewer> viewers = new ArrayList<>(1); viewers.add(table); diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeTableFilterNode.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeTableFilterNode.java deleted file mode 100644 index 9bf7788dc317cce0693c450dcd2caf71e68891df..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeTableFilterNode.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.commonpropertiessearch; - -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.corecomponents.TableFilterNode; - -/** - * Wrapper class used for the common attribute search results that - * should display descendants to ensure - * they are handled correctly in the result viewer. - */ -public class CommonAttributeTableFilterNode extends TableFilterNode { - - public CommonAttributeTableFilterNode(Node node, int childLayerDepth){ - super(node, childLayerDepth); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/Utils.java b/Core/src/org/sleuthkit/autopsy/communications/Utils.java index d57e74aceed68f84d735217641032f80ef85111b..3309bab92ecdec2e84d677d33cda1b7cdac1c698 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Utils.java +++ b/Core/src/org/sleuthkit/autopsy/communications/Utils.java @@ -25,7 +25,7 @@ import javax.swing.table.TableCellRenderer; import org.netbeans.swing.outline.Outline; import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; +import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.datamodel.Account; /** @@ -48,7 +48,7 @@ static public ZoneId getUserPreferredZoneId() { * @return The path of the icon for the given Account Type. */ static public final String getIconFilePath(Account.Type type) { - return IconsUtil.getIconFilePath(type); + return Accounts.getIconFilePath(type); } static public void setColumnWidths(Outline outline) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 54c6d96fe4c12ad51b669fb36558940d82be8100..840704e39ef57bfafca53775601d2e049e15273e 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -85,7 +85,7 @@ MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= HtmlPanel.showImagesToggleButton.text=Download Images -MediaViewImagePanel.tagsMenu.text_1=Image Tagging +MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 MediaPlayerPanel.audioSlider.toolTipText= MediaPlayerPanel.rewindButton.text= @@ -940,5 +940,3 @@ manager.properties.brokenProperty = Broken default property {0} value: {1} manager.properties.missingProperty = Missing default property {0} value: {1} -MediaViewImagePanel.tagsMenu.toolTipText=Create and modify tags specific to selected areas in the image. -MediaViewImagePanel.tagsMenu.actionCommand=Image Tagging diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 8d34bd46a090615b0107add42ae141604c1775bb..925e77c27c3cee0b065b1fe34bde4cfd3747a66e 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -60,13 +60,8 @@ MediaViewImagePanel.exportTagOption=Export MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E MediaViewImagePanel.fileChooserTitle=Choose a save location MediaViewImagePanel.hideTagOption=Hide -MediaViewImagePanel.showTagOption=Show MediaViewImagePanel.successfulExport=Tagged image was successfully saved. MediaViewImagePanel.unsuccessfulExport=Unable to export tagged image to disk. -MediaViewImagePanel_createMenu_tooltip_text=<html>Create an image area specific tag by right clicking<br>and dragging to select the area in the image to be tagged.</html> -MediaViewImagePanel_deleteMenu_tooltip_text=Delete the selected image area tag. -MediaViewImagePanel_exportMenu_tooltip_text=Export the image including the tagged areas. -MediaViewImagePanel_hideMenu_tooltip_text=Hide or show the tagged image areas. MediaViewVideoPanel.pauseButton.text=\u25ba MediaViewVideoPanel.progressLabel.text=00:00 MediaViewVideoPanel.infoLabel.text=info @@ -156,7 +151,7 @@ MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= HtmlPanel.showImagesToggleButton.text=Download Images -MediaViewImagePanel.tagsMenu.text_1=Image Tagging +MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 MediaPlayerPanel.audioSlider.toolTipText= MediaPlayerPanel.rewindButton.text= @@ -958,5 +953,3 @@ manager.properties.brokenProperty=Broken default property {0} value: {1} manager.properties.missingProperty=Missing default property {0} value: {1} -MediaViewImagePanel.tagsMenu.toolTipText=Create and modify tags specific to selected areas in the image. -MediaViewImagePanel.tagsMenu.actionCommand=Image Tagging diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form index 77149bbde44b26908f6551b31c591f24d114e12a..b157f7a0339320987e7e8dace0aa975003b498db 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.form @@ -221,14 +221,17 @@ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaViewImagePanel.tagsMenu.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> - <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaViewImagePanel.tagsMenu.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - <Property name="actionCommand" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaViewImagePanel.tagsMenu.actionCommand" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> <Property name="focusable" type="boolean" value="false"/> <Property name="horizontalTextPosition" type="int" value="0"/> + <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[75, 21]"/> + </Property> + <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[75, 21]"/> + </Property> + <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[75, 21]"/> + </Property> <Property name="verticalTextPosition" type="int" value="3"/> </Properties> <Events> diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index db9ac2234bc334f9a29fd472806e39d32f588be0..a388a284321757263245cf178907a8ad3c805a26 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -121,8 +121,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private static final Image openInExternalViewerButtonImage = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm()); //NOI18N private final boolean jfxIsInited = org.sleuthkit.autopsy.core.Installer.isJavaFxInited(); private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); - - private static final int TAG_POPUP_WIDTH = 200; /* * Threading policy: JFX UI components, must be accessed in JFX thread only. @@ -210,13 +208,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan "MediaViewImagePanel.createTagOption=Create", "MediaViewImagePanel.deleteTagOption=Delete", "MediaViewImagePanel.hideTagOption=Hide", - "MediaViewImagePanel.exportTagOption=Export", - "MediaViewImagePanel.showTagOption=Show", - "MediaViewImagePanel_createMenu_tooltip_text=<html>Create an image area specific tag by right clicking<br>and dragging to select the area in the image to be tagged.</html>", - "MediaViewImagePanel_deleteMenu_tooltip_text=Delete the selected image area tag.", - "MediaViewImagePanel_exportMenu_tooltip_text=Export the image including the tagged areas.", - "MediaViewImagePanel_hideMenu_tooltip_text=Hide or show the tagged image areas.", - + "MediaViewImagePanel.exportTagOption=Export" }) MediaViewImagePanel() { initComponents(); @@ -230,7 +222,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan imageTaggingOptions = new JPopupMenu(); createTagMenuItem = new JMenuItem(Bundle.MediaViewImagePanel_createTagOption()); createTagMenuItem.addActionListener((event) -> createTag()); - createTagMenuItem.setToolTipText(Bundle.MediaViewImagePanel_createMenu_tooltip_text()); imageTaggingOptions.add(createTagMenuItem); imageTaggingOptions.add(new JSeparator()); @@ -238,23 +229,20 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan deleteTagMenuItem = new JMenuItem(Bundle.MediaViewImagePanel_deleteTagOption()); deleteTagMenuItem.addActionListener((event) -> deleteTag()); imageTaggingOptions.add(deleteTagMenuItem); - deleteTagMenuItem.setToolTipText(Bundle.MediaViewImagePanel_deleteMenu_tooltip_text()); imageTaggingOptions.add(new JSeparator()); hideTagsMenuItem = new JMenuItem(Bundle.MediaViewImagePanel_hideTagOption()); hideTagsMenuItem.addActionListener((event) -> showOrHideTags()); imageTaggingOptions.add(hideTagsMenuItem); - hideTagsMenuItem.setToolTipText(Bundle.MediaViewImagePanel_hideMenu_tooltip_text()); imageTaggingOptions.add(new JSeparator()); exportTagsMenuItem = new JMenuItem(Bundle.MediaViewImagePanel_exportTagOption()); exportTagsMenuItem.addActionListener((event) -> exportTags()); imageTaggingOptions.add(exportTagsMenuItem); - exportTagsMenuItem.setToolTipText(Bundle.MediaViewImagePanel_exportMenu_tooltip_text()); - imageTaggingOptions.setPopupSize(TAG_POPUP_WIDTH, 150); + imageTaggingOptions.setPopupSize(300, 150); //Disable image tagging for non-windows users or upon failure to load OpenCV. if (!PlatformUtil.isWindowsOS() || !OpenCvLoader.openCvIsLoaded()) { @@ -469,14 +457,10 @@ final void loadFile(final AbstractFile file) { if (!isInited()) { return; } - + + final double panelWidth = fxPanel.getWidth(); + final double panelHeight = fxPanel.getHeight(); Platform.runLater(() -> { - - if (imageTagCreator != null) { - imageTagCreator.disconnect(); - masterGroup.getChildren().remove(imageTagCreator); - } - /* * Set up a new task to get the contents of the image file in * displayable form and cancel any previous task in progress. @@ -486,8 +470,6 @@ final void loadFile(final AbstractFile file) { } readImageFileTask = ImageUtils.newReadImageTask(file); readImageFileTask.setOnSucceeded(succeeded -> { - final double panelWidth = fxPanel.getWidth(); - final double panelHeight = fxPanel.getHeight(); onReadImageTaskSucceeded(file, panelWidth, panelHeight); }); readImageFileTask.setOnFailed(failed -> { @@ -554,9 +536,6 @@ private void onReadImageTaskSucceeded(AbstractFile file, double panelWidth, doub if (!tagsGroup.getChildren().isEmpty()) { pcs.firePropertyChange(new PropertyChangeEvent(this, "state", null, State.NONEMPTY)); - } else { - pcs.firePropertyChange(new PropertyChangeEvent(this, - "state", null, State.EMPTY)); } } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.WARNING, "Could not retrieve image tags for file in case db", ex); //NON-NLS @@ -811,10 +790,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { toolbar.add(jPanel1); org.openide.awt.Mnemonics.setLocalizedText(tagsMenu, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.tagsMenu.text_1")); // NOI18N - tagsMenu.setToolTipText(org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.tagsMenu.toolTipText")); // NOI18N - tagsMenu.setActionCommand(org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.tagsMenu.actionCommand")); // NOI18N tagsMenu.setFocusable(false); tagsMenu.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + tagsMenu.setMaximumSize(new java.awt.Dimension(75, 21)); + tagsMenu.setMinimumSize(new java.awt.Dimension(75, 21)); + tagsMenu.setPreferredSize(new java.awt.Dimension(75, 21)); tagsMenu.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); tagsMenu.addMouseListener(new java.awt.event.MouseAdapter() { public void mousePressed(java.awt.event.MouseEvent evt) { @@ -1103,7 +1083,7 @@ protected Void doInBackground() { FilenameUtils.getBaseName(file.getName()) + "-with_tags.png"); //NON-NLS ImageIO.write(taggedImage, "png", output.toFile()); - JOptionPane.showMessageDialog(MediaViewImagePanel.this, Bundle.MediaViewImagePanel_successfulExport()); + JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport()); } catch (Exception ex) { //Runtime exceptions may spill out of ImageTagsUtil from JavaFX. //This ensures we (devs and users) have something when it doesn't work. logger.log(Level.WARNING, "Unable to export tagged image to disk", ex); //NON-NLS @@ -1119,7 +1099,7 @@ protected Void doInBackground() { private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tagsMenuMousePressed if (imageTaggingOptions.isEnabled()) { - imageTaggingOptions.show(tagsMenu, -TAG_POPUP_WIDTH + tagsMenu.getWidth(), tagsMenu.getHeight() + 3); + imageTaggingOptions.show(tagsMenu, -300 + tagsMenu.getWidth(), tagsMenu.getHeight() + 3); } }//GEN-LAST:event_tagsMenuMousePressed @@ -1127,8 +1107,8 @@ private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:ev * Display states for the show/hide tags button. */ private enum DisplayOptions { - HIDE_TAGS(Bundle.MediaViewImagePanel_hideTagOption()), - SHOW_TAGS(Bundle.MediaViewImagePanel_showTagOption()); + HIDE_TAGS("Hide"), + SHOW_TAGS("Show"); private final String name; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java index 45bb97cc574dcba5d863b64b884c6113e0a674e3..53dc304b27b6fa3f1b2c30ad4b09667e6482290e 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java @@ -29,12 +29,13 @@ import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; import javax.imageio.ImageIO; +import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.core.MatOfByte; import org.opencv.core.Point; import org.opencv.core.Scalar; import org.opencv.core.Size; -import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.highgui.Highgui; import org.opencv.imgproc.Imgproc; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.datamodel.AbstractFile; @@ -123,7 +124,7 @@ private static Mat getImageMatFromFile(AbstractFile file) throws InterruptedExce byte[] imageBytes = outStream.toByteArray(); MatOfByte rawSourceBytes = new MatOfByte(imageBytes); - Mat sourceImage = Imgcodecs.imdecode(rawSourceBytes, Imgcodecs.IMREAD_COLOR); + Mat sourceImage = Highgui.imdecode(rawSourceBytes, Highgui.IMREAD_COLOR); rawSourceBytes.release(); return sourceImage; @@ -149,12 +150,12 @@ private static MatOfByte getTaggedImageMatrix(Mat sourceImage, Collection<ImageT int rectangleBorderWidth = (int) Math.rint(region.getStrokeThickness()); - Imgproc.rectangle(sourceImage, topLeft, bottomRight, + Core.rectangle(sourceImage, topLeft, bottomRight, rectangleBorderColor, rectangleBorderWidth); } MatOfByte taggedMatrix = new MatOfByte(); - Imgcodecs.imencode(OPENCV_PNG, sourceImage, taggedMatrix); + Highgui.imencode(OPENCV_PNG, sourceImage, taggedMatrix); return taggedMatrix; } @@ -199,13 +200,13 @@ public static BufferedImage getThumbnailWithTags(AbstractFile file, Collection<I */ private static MatOfByte getResizedMatrix(MatOfByte taggedMatrix, IconSize size) { Size resizeDimensions = new Size(size.getSize(), size.getSize()); - Mat taggedImage = Imgcodecs.imdecode(taggedMatrix, Imgcodecs.IMREAD_COLOR); + Mat taggedImage = Highgui.imdecode(taggedMatrix, Highgui.IMREAD_COLOR); Mat thumbnailImage = new Mat(); Imgproc.resize(taggedImage, thumbnailImage, resizeDimensions); MatOfByte thumbnailMatrix = new MatOfByte(); - Imgcodecs.imencode(OPENCV_PNG, thumbnailImage, thumbnailMatrix); + Highgui.imencode(OPENCV_PNG, thumbnailImage, thumbnailMatrix); thumbnailImage.release(); taggedImage.release(); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/Bundle.properties-MERGED index 17353df2d40a107304087f9cbeaa6cb173f7773a..d74307642cee562d57dd54f5b7fc8c01c9f944fc 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/Bundle.properties-MERGED @@ -4,7 +4,6 @@ OsAccountDataPanel_basic_admin=Administrator OsAccountDataPanel_basic_creationDate=Creation Date OsAccountDataPanel_basic_fullname=Full Name OsAccountDataPanel_basic_login=Login -OsAccountDataPanel_basic_objId=Object ID OsAccountDataPanel_basic_title=Basic Properties OsAccountDataPanel_basic_type=Type OsAccountDataPanel_data_accessed_title=Last Login diff --git a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java index ab25f1f6ce09ff93dc66ac7d9a955ff84e954dc6..4f5a697ba80e6e03f4056586e8034b85c643e29c 100644 --- a/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java +++ b/Core/src/org/sleuthkit/autopsy/core/RuntimeProperties.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2022 Basis Technology Corp. + * Copyright 2013-2017 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,7 +49,7 @@ public synchronized static void setRunningWithGUI(boolean runningWithGUI) throws /** * Sets or unsets a flag indicating whether or not the application is running in a target system. - * The flag can only be set once per application invocation + * The flag can only be set once per application innvocation * * @param runningInTarget * diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index ae299cf8d1cad714e3122e0421eb026f6418669b..cee09547a30468b837882105371c7376d35dfe6c 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -764,7 +764,7 @@ public static void setMaxSolrVMSize(int maxSize) { * @return Saved value or default (10,000). */ public static int getResultsTablePageSize() { - return viewPreferences.getInt(RESULTS_TABLE_PAGE_SIZE, 1000); + return viewPreferences.getInt(RESULTS_TABLE_PAGE_SIZE, 10_000); } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java index a833f32e572b8bce6f546a0ca9573a7c90ef3f20..c9d305f53ec92978a435f9ba26246b204472e5bf 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java @@ -18,11 +18,8 @@ */ package org.sleuthkit.autopsy.corecomponentinterfaces; -import com.google.common.annotations.Beta; import java.awt.Component; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.corecomponents.DataResultPanel.PagingControls; /** * An interface for result viewers. A result viewer uses a Swing Component to @@ -78,18 +75,6 @@ public interface DataResultViewer { */ public void setNode(Node node); - /** - * Sets the node for the result viewer but also includes the search results - * represented by the children of the node. - * - * @param node The node. - * @param searchResults The search results for the node. - */ - @Beta - default public void setNode(Node node, SearchResultsDTO searchResults) { - setNode(node, null); - } - /** * Requests selection of the given child nodes of the node passed to * setNode. This method should be implemented as a no-op for result viewers @@ -128,16 +113,6 @@ default public void resetComponent() { */ default public void clearComponent() { } - - /** - * Controls top level result paging in DataResultPanel. Allows result - * viewers to access and configure top level result paging. - * - * @param pagingControls - */ - @Beta - default public void setPagingControls(PagingControls pagingControls) { - } /** * Sets the node for which this result viewer should provide a view of the diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 9c23abe893aa82e1d7c9bf446502442a26f7600c..d11006a6feeb0d83b1da745f0105cdd00262e55f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -34,9 +34,16 @@ DataArtifactContentViewer.pageLabel.text=Result: AdvancedConfigurationDialog.applyButton.text=OK DataContentViewerHex.goToPageTextField.text= DataContentViewerHex.goToPageLabel.text=Go to Page: +DataResultViewerThumbnail.pageLabel.text=Page: +DataResultViewerThumbnail.pagesLabel.text=Pages: +DataResultViewerThumbnail.pagePrevButton.text= +DataResultViewerThumbnail.pageNextButton.text= DataResultViewerThumbnail.imagesLabel.text=Images: DataResultViewerThumbnail.imagesRangeLabel.text=- +DataResultViewerThumbnail.pageNumLabel.text=- DataResultViewerThumbnail.filePathLabel.text=\ \ \ +DataResultViewerThumbnail.goToPageLabel.text=Go to Page: +DataResultViewerThumbnail.goToPageField.text= AdvancedConfigurationDialog.cancelButton.text=Cancel DataArtifactContentViewer.waitText=Retrieving and preparing data, please wait... DataArtifactContentViewer.errorText=Error retrieving result @@ -126,6 +133,8 @@ DataResultPanel.descriptionLabel.text=directoryPath ViewPreferencesPanel.currentCaseSettingsPanel.border.title=Current Case Settings OptionsCategory_Name_View=View OptionsCategory_Keywords_View=View +ViewPreferencesPanel.currentSessionSettingsPanel.border.title=Current Session Settings +ViewPreferencesPanel.hideRejectedResultsCheckbox.text=Hide rejected results DataContentViewerHex.launchHxDButton.text=Launch in HxD ExternalViewerGlobalSettingsPanel.jButton2.text=jButton2 ExternalViewerGlobalSettingsPanel.newRuleButton1.text=New Rule @@ -157,7 +166,14 @@ ExternalViewerGlobalSettingsPanel.deleteRuleButton.text_1=Delete Rule ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text_1=Set aplication viewer to use for files with specific mime types/extensions: ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title1_1=Application ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extension +DataResultViewerTable.gotoPageTextField.text= DataResultViewerTable.gotoPageLabel.AccessibleContext.accessibleName= +DataResultViewerTable.gotoPageLabel.text=Go to Page: +DataResultViewerTable.pageNextButton.text= +DataResultViewerTable.pagePrevButton.text= +DataResultViewerTable.pagesLabel.text=Pages: +DataResultViewerTable.pageNumLabel.text= +DataResultViewerTable.pageLabel.text=Page: DataResultViewerTable.exportCSVButton.text=Save Table as CSV MultiUserSettingsPanel.tbSolr4Hostname.toolTipText=Solr 4 Hostname or IP Address MultiUserSettingsPanel.tbSolr4Port.toolTipText=Solr 4 Port Number @@ -239,18 +255,3 @@ AutopsyOptionsPanel.heapDumpFileField.text= AutopsyOptionsPanel.heapDumpBrowseButton.text=Browse AutopsyOptionsPanel.heapFileLabel.text=Custom Heap Dump Location: AutopsyOptionsPanel.heapFieldValidationLabel.text= -DataResultPanel.gotoPageTextField.text= -DataResultPanel.gotoPageLabel.text=Go to Page: -DataResultPanel.pageLabel.text=Page: -DataResultPanel.pagesLabel.text=Pages: -DataResultPanel.pageNumLabel.text= -DataResultPanel.pageNextButton.text= -DataResultPanel.pagePrevButton.text= - -DataResultViewerThumbnail.pageLabel.text=Page: -DataResultViewerThumbnail.pagesLabel.text=Pages: -DataResultViewerThumbnail.pagePrevButton.text= -DataResultViewerThumbnail.pageNextButton.text= -DataResultViewerThumbnail.pageNumLabel.text=- -DataResultViewerThumbnail.goToPageLabel.text=Go to Page: -DataResultViewerThumbnail.goToPageField.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index f91a8b74b2eb0df475902b32adc2ffeb95dbc8cb..0636340b0bd5268b40e6db2cdd1477a89264b5cd 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -35,9 +35,6 @@ DataArtifactContentViewer.failedToGetSourcePath.message=Failed to get source fil DataContentViewerHex.copyingFile=Copying file to open in HxD... DataContentViewerHex.launchError=Unable to launch HxD Editor. Please specify the HxD install location in Tools -> Options -> External Viewer DataContentViewerHex_loading_text=Loading hex from file... -# {0} - pageNumber -# {1} - pageCount -DataResultPanel_pageIdxOfCount={0} of {1} DataResultViewerTable.commentRender.name=C DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s) @@ -48,6 +45,12 @@ DataResultViewerTable.countRender.name=O DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export DataResultViewerTable.firstColLbl=Name +DataResultViewerTable.goToPageTextField.err=Invalid page number +# {0} - totalPages +DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0} +# {0} - currentPage +# {1} - totalPages +DataResultViewerTable.pageNumbers.curOfTotal={0} of {1} DataResultViewerTable.scoreRender.name=S DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable DataResultViewerTable.title=Table @@ -95,9 +98,16 @@ DataArtifactContentViewer.pageLabel.text=Result: AdvancedConfigurationDialog.applyButton.text=OK DataContentViewerHex.goToPageTextField.text= DataContentViewerHex.goToPageLabel.text=Go to Page: +DataResultViewerThumbnail.pageLabel.text=Page: +DataResultViewerThumbnail.pagesLabel.text=Pages: +DataResultViewerThumbnail.pagePrevButton.text= +DataResultViewerThumbnail.pageNextButton.text= DataResultViewerThumbnail.imagesLabel.text=Images: DataResultViewerThumbnail.imagesRangeLabel.text=- +DataResultViewerThumbnail.pageNumLabel.text=- DataResultViewerThumbnail.filePathLabel.text=\ \ \ +DataResultViewerThumbnail.goToPageLabel.text=Go to Page: +DataResultViewerThumbnail.goToPageField.text= AdvancedConfigurationDialog.cancelButton.text=Cancel DataArtifactContentViewer.waitText=Retrieving and preparing data, please wait... DataArtifactContentViewer.errorText=Error retrieving result @@ -189,6 +199,8 @@ ViewOptionsController.moduleErr.msg=Value change processing failed. ViewPreferencesPanel.currentCaseSettingsPanel.border.title=Current Case Settings OptionsCategory_Name_View=View OptionsCategory_Keywords_View=View +ViewPreferencesPanel.currentSessionSettingsPanel.border.title=Current Session Settings +ViewPreferencesPanel.hideRejectedResultsCheckbox.text=Hide rejected results DataContentViewerHex.launchHxDButton.text=Launch in HxD ExternalViewerGlobalSettingsPanel.jButton2.text=jButton2 ExternalViewerGlobalSettingsPanel.newRuleButton1.text=New Rule @@ -220,7 +232,14 @@ ExternalViewerGlobalSettingsPanel.deleteRuleButton.text_1=Delete Rule ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text_1=Set aplication viewer to use for files with specific mime types/extensions: ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title1_1=Application ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extension +DataResultViewerTable.gotoPageTextField.text= DataResultViewerTable.gotoPageLabel.AccessibleContext.accessibleName= +DataResultViewerTable.gotoPageLabel.text=Go to Page: +DataResultViewerTable.pageNextButton.text= +DataResultViewerTable.pagePrevButton.text= +DataResultViewerTable.pagesLabel.text=Pages: +DataResultViewerTable.pageNumLabel.text= +DataResultViewerTable.pageLabel.text=Page: DataResultViewerTable.exportCSVButton.text=Save Table as CSV MultiUserSettingsPanel.tbSolr4Hostname.toolTipText=Solr 4 Hostname or IP Address MultiUserSettingsPanel.tbSolr4Port.toolTipText=Solr 4 Port Number @@ -302,18 +321,3 @@ AutopsyOptionsPanel.heapDumpFileField.text= AutopsyOptionsPanel.heapDumpBrowseButton.text=Browse AutopsyOptionsPanel.heapFileLabel.text=Custom Heap Dump Location: AutopsyOptionsPanel.heapFieldValidationLabel.text= -DataResultPanel.gotoPageTextField.text= -DataResultPanel.gotoPageLabel.text=Go to Page: -DataResultPanel.pageLabel.text=Page: -DataResultPanel.pagesLabel.text=Pages: -DataResultPanel.pageNumLabel.text= -DataResultPanel.pageNextButton.text= -DataResultPanel.pagePrevButton.text= - -DataResultViewerThumbnail.pageLabel.text=Page: -DataResultViewerThumbnail.pagesLabel.text=Pages: -DataResultViewerThumbnail.pagePrevButton.text= -DataResultViewerThumbnail.pageNextButton.text= -DataResultViewerThumbnail.pageNumLabel.text=- -DataResultViewerThumbnail.goToPageLabel.text=Go to Page: -DataResultViewerThumbnail.goToPageField.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties index 9b59efab556f16c71c91190a26f8f0c22783881e..34a5c11a70973d35edd4d9d1730f1d5a754a54ff 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties @@ -274,6 +274,7 @@ URL_ON_IMG=http\://www.sleuthkit.org/ ViewOptionsController.moduleErr=\u5024\u306e\u5909\u66f4\u3092\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 ViewOptionsController.moduleErr.msg=\u5024\u306e\u5909\u66f4\u306e\u51e6\u7406\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 ViewPreferencesPanel.currentCaseSettingsPanel.border.title=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u8a2d\u5b9a +ViewPreferencesPanel.currentSessionSettingsPanel.border.title=\u73fe\u5728\u306e\u30bb\u30c3\u30b7\u30e7\u30f3\u8a2d\u5b9a ViewPreferencesPanel.dataSourcesHideKnownCheckbox.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u9818\u57df(\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u968e\u5c64) ViewPreferencesPanel.dataSourcesHideSlackCheckbox.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u9818\u57df(\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u968e\u5c64) ViewPreferencesPanel.displayTimeLabel.text=\u6642\u523b\u8868\u793a\u6642\: @@ -282,6 +283,7 @@ ViewPreferencesPanel.globalSettingsPanel.border.title=\u30b0\u30ed\u30fc\u30d0\u ViewPreferencesPanel.hideKnownFilesLabel.text=\u6b21\u306e\u65e2\u77e5\u306e\u30d5\u30a1\u30a4\u30eb(NIST NSRL\u5185\u306e\u30d5\u30a1\u30a4\u30eb)\u3092\u975e\u8868\u793a\u306b\u3059\u308b\: ViewPreferencesPanel.hideOtherUsersTagsCheckbox.text=\u30c4\u30ea\u30fc\u5185\u306e\u30bf\u30b0\u9818\u57df ViewPreferencesPanel.hideOtherUsersTagsLabel.text=\u6b21\u306e\u305d\u306e\u4ed6\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30bf\u30b0\u3092\u975e\u8868\u793a\u306b\u3059\u308b\: +ViewPreferencesPanel.hideRejectedResultsCheckbox.text=\u62d2\u5426\u3055\u308c\u305f\u7d50\u679c\u3092\u975e\u8868\u793a\u306b\u3059\u308b ViewPreferencesPanel.hideSlackFilesLabel.text=\u6b21\u306e\u30b9\u30e9\u30c3\u30af\u30d5\u30a1\u30a4\u30eb\u3092\u975e\u8868\u793a\u306b\u3059\u308b\: ViewPreferencesPanel.keepCurrentViewerRadioButton.text=\u540c\u3058\u30d5\u30a1\u30a4\u30eb\u30d3\u30e5\u30fc\u306e\u307e\u307e\u306b\u3059\u308b ViewPreferencesPanel.keepCurrentViewerRadioButton.toolTipText=\u305f\u3068\u3048\u3070\u3001JPEG\u9078\u629e\u6642\u306f16\u9032\u30d3\u30e5\u30fc\u306e\u307e\u307e\u306b\u3057\u307e\u3059\u3002 @@ -300,10 +302,3 @@ ViewPreferencesPanel.useBestViewerRadioButton.toolTipText=\u305f\u3068\u3048\u30 ViewPreferencesPanel.useLocalTimeRadioButton.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3\u3092\u4f7f\u7528 ViewPreferencesPanel.viewsHideKnownCheckbox.text=\u30d3\u30e5\u30fc\u9818\u57df ViewPreferencesPanel.viewsHideSlackCheckbox.text=\u30d3\u30e5\u30fc\u9818\u57df -DataResultPanel.gotoPageTextField.text= -DataResultPanel.gotoPageLabel.text=\u30da\u30fc\u30b8\u306b\u79fb\u52d5: -DataResultPanel.pageLabel.text=\u30da\u30fc\u30b8: -DataResultPanel.pagesLabel.text=\u30da\u30fc\u30b8: -DataResultPanel.pageNumLabel.text= -DataResultPanel.pageNextButton.text= -DataResultPanel.pagePrevButton.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form index c468a485b657ad27df9e80a7c696c506fb21f5fd..3c24e28182ac20e79536bbf35fae7de775180dcd 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form @@ -19,31 +19,47 @@ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> - <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/> </AuxValues> - <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <Component id="descriptionLabel" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + <Component id="numberOfChildNodesLabel" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="matchLabel" min="-2" max="-2" attributes="0"/> + </Group> + <Component id="resultViewerTabs" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="103" alignment="0" groupAlignment="3" attributes="0"> + <Component id="numberOfChildNodesLabel" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="matchLabel" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="resultViewerTabs" max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> <SubComponents> <Component class="javax.swing.JLabel" name="descriptionLabel"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.descriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[32767, 16]"/> - </Property> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[50, 14]"/> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[32767, 16]"/> + <Dimension value="[5, 14]"/> </Property> </Properties> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="0" gridY="0" gridWidth="8" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> </Component> <Component class="javax.swing.JLabel" name="numberOfChildNodesLabel"> <Properties> @@ -51,11 +67,6 @@ <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.numberOfChildNodesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="9" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="13" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> </Component> <Component class="javax.swing.JLabel" name="matchLabel"> <Properties> @@ -63,186 +74,6 @@ <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.matchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> - <AuxValues> - <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> - <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> - </AuxValues> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="10" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> - </Component> - <Component class="javax.swing.JLabel" name="pageLabel"> - <Properties> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.pageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - </Properties> - <AuxValues> - <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> - <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> - </AuxValues> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> - </Component> - <Component class="javax.swing.JLabel" name="pageNumLabel"> - <Properties> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.pageNumLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="null"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="null"/> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="null"/> - </Property> - </Properties> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> - </Component> - <Component class="javax.swing.JLabel" name="pagesLabel"> - <Properties> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.pagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - </Properties> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="2" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> - </Component> - <Component class="javax.swing.JButton" name="pagePrevButton"> - <Properties> - <Property name="background" type="java.awt.Color" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> - <Connection code="null" type="code"/> - </Property> - <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> - <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"/> - </Property> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.pagePrevButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - <Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> - <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"/> - </Property> - <Property name="focusable" type="boolean" value="false"/> - <Property name="horizontalTextPosition" type="int" value="0"/> - <Property name="iconTextGap" type="int" value="0"/> - <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor"> - <Insets value="[0, 0, 0, 0]"/> - </Property> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[22, 23]"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[22, 23]"/> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[22, 23]"/> - </Property> - <Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> - <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"/> - </Property> - <Property name="verticalTextPosition" type="int" value="3"/> - </Properties> - <Events> - <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pagePrevButtonActionPerformed"/> - </Events> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="3" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> - </Component> - <Component class="javax.swing.JButton" name="pageNextButton"> - <Properties> - <Property name="background" type="java.awt.Color" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> - <Connection code="null" type="code"/> - </Property> - <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> - <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"/> - </Property> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.pageNextButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - <Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> - <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"/> - </Property> - <Property name="focusable" type="boolean" value="false"/> - <Property name="horizontalTextPosition" type="int" value="0"/> - <Property name="iconTextGap" type="int" value="0"/> - <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor"> - <Insets value="[0, 0, 0, 0]"/> - </Property> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[22, 23]"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[22, 23]"/> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[22, 23]"/> - </Property> - <Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> - <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"/> - </Property> - <Property name="verticalTextPosition" type="int" value="3"/> - </Properties> - <Events> - <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pageNextButtonActionPerformed"/> - </Events> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="4" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> - </Component> - <Component class="javax.swing.JLabel" name="gotoPageLabel"> - <Properties> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.gotoPageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - </Properties> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="5" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> - </Component> - <Component class="javax.swing.JTextField" name="gotoPageTextField"> - <Properties> - <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.gotoPageTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - </Property> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[32767, 22]"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[50, 22]"/> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[50, 22]"/> - </Property> - </Properties> - <Events> - <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gotoPageTextFieldActionPerformed"/> - </Events> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="6" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="17" weightX="0.0" weightY="0.0"/> - </Constraint> - </Constraints> </Component> <Container class="javax.swing.JTabbedPane" name="resultViewerTabs"> <Properties> @@ -250,48 +81,8 @@ <Dimension value="[0, 5]"/> </Property> </Properties> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="0" gridY="2" gridWidth="11" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/> - </Constraint> - </Constraints> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/> </Container> - <Container class="javax.swing.JPanel" name="horizontalSpacer"> - <Properties> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[0, 0]"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[20, 0]"/> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[20, 0]"/> - </Property> - </Properties> - <AuxValues> - <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> - <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> - </AuxValues> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="7" gridY="0" gridWidth="1" gridHeight="2" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/> - </Constraint> - </Constraints> - - <Layout> - <DimensionLayout dim="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <EmptySpace min="0" pref="0" max="32767" attributes="0"/> - </Group> - </DimensionLayout> - <DimensionLayout dim="1"> - <Group type="103" groupAlignment="0" attributes="0"> - <EmptySpace min="0" pref="0" max="32767" attributes="0"/> - </Group> - </DimensionLayout> - </Layout> - </Container> </SubComponents> </Form> diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 244ac0cd7490bf1b56b174ce534ad7ee297ce4bf..55965946cc530f55a1124cc0313cce2f9d590239 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -18,98 +18,31 @@ */ package org.sleuthkit.autopsy.corecomponents; -import com.google.common.eventbus.Subscribe; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.awt.Cursor; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.prefs.PreferenceChangeEvent; -import java.util.prefs.PreferenceChangeListener; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import org.apache.commons.lang3.StringUtils; import org.openide.explorer.ExplorerManager; -import org.openide.nodes.Children; -import org.openide.nodes.FilterNode; import org.openide.nodes.Node; -import org.openide.nodes.NodeAdapter; +import org.openide.nodes.NodeEvent; +import org.openide.nodes.NodeListener; import org.openide.nodes.NodeMemberEvent; +import org.openide.nodes.NodeReorderEvent; import org.openide.util.Lookup; import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; -import org.openide.util.WeakListeners; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeTableFilterNode; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.BaseChildFactory; -import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageChangeEvent; -import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageCountChangeEvent; -import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageSizeChangeEvent; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.mainui.datamodel.CommAccountsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO.AnalysisResultFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO.AnalysisResultConfigFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO.KeywordHitResultFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.CommAccountsDAO.CommAccountFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardBinSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardDAO.CreditCardByBinFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardDAO.CreditCardByFileFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardFileSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactDAO.DataArtifactFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.EmailSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.EmailsDAO.EmailFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemContentSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemHostFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemHostSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeExtensionsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.KeywordHitSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.OsAccountsDAO.AccountFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.OsAccountsSearchParams; -import org.sleuthkit.autopsy.mainui.nodes.SearchResultRootNode; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TagsDAO.TagFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.ViewsDAO.FileTypeExtFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.ViewsDAO.FileTypeMimeFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.ViewsDAO.FileTypeSizeFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.ViewsDAO.DeletedFileFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.ReportsDAO.ReportsFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.ReportsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.ScoreDAO.ScoreContentFetcher; -import org.sleuthkit.autopsy.mainui.datamodel.events.CacheClearEvent; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo; -import org.sleuthkit.autopsy.mainui.nodes.SearchManager; /** * A result view panel is a JPanel with a JTabbedPane child component that @@ -142,77 +75,17 @@ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { - private static final Logger logger = Logger.getLogger(DataResultPanel.class.getName()); - - private final Map<String, BaseChildFactoryPager> nodeNameToPageCountListenerMap = new ConcurrentHashMap<>(); private static final long serialVersionUID = 1L; private static final int NO_TAB_SELECTED = -1; private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName"); private final boolean isMain; private final List<DataResultViewer> resultViewers; private final ExplorerManagerListener explorerManagerListener; - private RootNodeListener rootNodeListener = null; + private final RootNodeListener rootNodeListener; private DataContent contentView; private ExplorerManager explorerManager; private Node currentRootNode; private boolean listeningToTabbedPane; - private BaseChildFactoryPager pagingSupport = null; - private SearchManager searchResultManager = null; - private final PagingControls pagingControls = new PagingControls(); - private boolean pagingControlsEnabled = true; - - private final PreferenceChangeListener pageSizeListener = (PreferenceChangeEvent evt) -> { - if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) { - nodeNameToPageCountListenerMap.values().forEach((ps) -> { - ps.postPageSizeChangeEvent(); - }); - } - }; - - private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); - - private final PropertyChangeListener caseEventListener = evt -> { - String evtName = evt.getPropertyName(); - if (Case.Events.CURRENT_CASE.toString().equals(evtName)) { - searchResultManager = null; - if (evt.getNewValue() == null) { - nodeNameToPageCountListenerMap.clear(); - } - } - }; - - private final PropertyChangeListener weakCaseEventListener = WeakListeners.propertyChange(caseEventListener, null); - - private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS = EnumSet.of( - IngestManager.IngestModuleEvent.FILE_DONE, - IngestManager.IngestModuleEvent.CONTENT_CHANGED, - IngestManager.IngestModuleEvent.DATA_ADDED); - - private final MainDAO mainDAO = MainDAO.getInstance(); - - private final PropertyChangeListener DAOListener = evt -> { - SearchManager manager = this.searchResultManager; - if (manager != null && evt != null) { - if (evt.getNewValue() instanceof DAOAggregateEvent) { - DAOAggregateEvent daoAggrEvt = (DAOAggregateEvent) evt.getNewValue(); - if (daoAggrEvt.getEvents().stream().anyMatch((daoEvt) -> manager.isRefreshRequired(daoEvt))) { - refreshSearchResultChildren(); - } - } else if (evt.getNewValue() instanceof CacheClearEvent) { - try { - this.searchResultManager = new SearchManager(this.searchResultManager.getDaoFetcher(), getPageSize()); - displaySearchResults(this.searchResultManager.getResults(), true); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, "An exception occurred while handling cache clear event.", ex); - } - } - } - }; - - private final PropertyChangeListener weakDAOListener = WeakListeners.propertyChange(DAOListener, mainDAO); - - private ExecutorService nodeBackgroundTasksPool; - private static final Integer MAX_POOL_SIZE = 10; /** * Creates and opens a Swing JPanel with a JTabbedPane child component that @@ -361,6 +234,7 @@ private static void createInstanceCommon(String title, String description, Node this.contentView = contentView; this.resultViewers = new ArrayList<>(viewers); this.explorerManagerListener = new ExplorerManagerListener(); + this.rootNodeListener = new RootNodeListener(); initComponents(); } @@ -402,7 +276,6 @@ public void setPath(String description) { * @param resultViewer The results viewer. */ public void addResultViewer(DataResultViewer resultViewer) { - resultViewer.setPagingControls(pagingControls); resultViewers.add(resultViewer); resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()); } @@ -455,8 +328,7 @@ public void open() { */ if (this.resultViewerTabs.getTabCount() == 0) { if (this.resultViewers.isEmpty()) { - for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class - )) { + for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) { if (this.isMain) { this.resultViewers.add(resultViewer); } else { @@ -465,33 +337,11 @@ public void open() { } } this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent())); - this.resultViewers.forEach((resultViewer) -> resultViewer.setPagingControls(pagingControls)); } - initListeners(); - this.setVisible(true); } - /** - * Initializes autopsy event listeners. - */ - private void initListeners() { - UserPreferences.addChangeListener(this.pageSizeListener); - Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this.weakCaseEventListener); - this.mainDAO.getResultEventsManager().addPropertyChangeListener(this.weakDAOListener); - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS, this.weakDAOListener); - } - - /** - * Unregisters this panel from autopsy event listeners. - */ - private void closeListeners() { - UserPreferences.removeChangeListener(this.pageSizeListener); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), this.weakCaseEventListener); - this.mainDAO.getResultEventsManager().removePropertyChangeListener(this.weakDAOListener); - } - /** * Sets the current root node for this result view panel. The child nodes of * the current root node will be displayed in the child result viewers. For @@ -504,16 +354,7 @@ private void closeListeners() { */ @Override public void setNode(Node rootNode) { - setNode(rootNode, true); - } - - private void setNode(Node rootNode, boolean fullRefresh) { - if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(() -> setNode(rootNode, fullRefresh)); - return; - } - - if (this.currentRootNode != null && this.rootNodeListener != null) { + if (this.currentRootNode != null) { this.currentRootNode.removeNodeListener(rootNodeListener); } @@ -527,38 +368,7 @@ private void setNode(Node rootNode, boolean fullRefresh) { listeningToTabbedPane = true; } - // If search result root node it's fine. - // CommonAttributeTableFilterNodes are intended to have grandchildren. - // Otherwise, wrap in result - // viewer filter node to make sure there are no grandchildren - this.currentRootNode = ((rootNode instanceof SearchResultRootNode) || (rootNode instanceof CommonAttributeTableFilterNode)) - ? rootNode - : new ResultViewerFilterParentNode(rootNode); - - // if search result node clear out base child factory paging - if (this.currentRootNode instanceof SearchResultRootNode) { - this.pagingSupport = null; - } else { - // otherwise clear out search result support parameters - this.searchResultManager = null; - - // if there is a node, set up paging - if (this.currentRootNode != null) { - this.pagingSupport - = this.nodeNameToPageCountListenerMap.computeIfAbsent(this.currentRootNode.getName(), (name) -> { - BaseChildFactoryPager listener = new BaseChildFactoryPager(name); - BaseChildFactory.register(name, listener); - return listener; - }); - - if (fullRefresh && this.pagingSupport.getCurrentPageIdx() != 0) { - this.pagingSupport.setCurrentPageIdx(0); - } - } else { - this.pagingSupport = null; - } - } - + this.currentRootNode = rootNode; if (this.currentRootNode != null) { /* * The only place we reset the rootNodeListener allowing the @@ -567,29 +377,20 @@ private void setNode(Node rootNode, boolean fullRefresh) { * Necessary when transitioning from "Please wait..." node to having * contents. */ - rootNodeListener = new RootNodeListener(fullRefresh); + rootNodeListener.reset(); this.currentRootNode.addNodeListener(rootNodeListener); } - if (fullRefresh) { - this.resultViewers.forEach((viewer) -> { - viewer.resetComponent(); - }); - } - - setupTabs(this.currentRootNode, fullRefresh); - - if (fullRefresh && this.currentRootNode != null) { - long childrenCount = (this.searchResultManager != null) - ? this.searchResultManager.getTotalResults() - : this.currentRootNode.getChildren().getNodesCount(); + this.resultViewers.forEach((viewer) -> { + viewer.resetComponent(); + }); + setupTabs(this.currentRootNode); - this.numberOfChildNodesLabel.setText(Long.toString(childrenCount)); + if (this.currentRootNode != null) { + int childrenCount = this.currentRootNode.getChildren().getNodesCount(); + this.numberOfChildNodesLabel.setText(Integer.toString(childrenCount)); } - this.numberOfChildNodesLabel.setVisible(true); - - updatePagingComponents(); } /** @@ -624,14 +425,24 @@ public void setSelectedNodes(Node[] selectedNodes) { } /** - * Returns the data result viewer tab index to select based on selection - * info or first available viewer. + * Sets the state of the child result viewers, based on a selected root + * node. * * @param selectedNode The selected node. - * - * @return The tab index. */ - private int getPriorityTabIdx(Node selectedNode) { + private void setupTabs(Node selectedNode) { + /* + * Enable or disable the result viewer tabs based on whether or not the + * corresponding results viewer supports display of the selected node. + */ + for (int i = 0; i < resultViewerTabs.getTabCount(); i++) { + if (resultViewers.get(i).isSupported(selectedNode)) { + resultViewerTabs.setEnabledAt(i, true); + } else { + resultViewerTabs.setEnabledAt(i, false); + } + } + /* * If the selected node has a child to be selected, default the selected * tab to the table result viewer. Otherwise, use the last selected tab, @@ -660,46 +471,13 @@ private int getPriorityTabIdx(Node selectedNode) { } } - return tabToSelect; - } - - /** - * Sets the state of the child result viewers, based on a selected root - * node. - * - * @param selectedNode The selected node. - * @param fullReset Whether or not to perform a full reset (including the - * tab index). - */ - private void setupTabs(Node selectedNode, boolean fullReset) { - if (fullReset) { - /* - * Enable or disable the result viewer tabs based on whether or not the - * corresponding results viewer supports display of the selected node. - */ - for (int i = 0; i < resultViewerTabs.getTabCount(); i++) { - if (resultViewers.get(i).isSupported(selectedNode)) { - resultViewerTabs.setEnabledAt(i, true); - } else { - resultViewerTabs.setEnabledAt(i, false); - } - } - } - - int tabToSelect = fullReset - ? getPriorityTabIdx(selectedNode) - : resultViewerTabs.getSelectedIndex(); - /* * If there is a tab to select, do so, and push the selected node to the * corresponding result viewer. */ if (tabToSelect != NO_TAB_SELECTED) { resultViewerTabs.setSelectedIndex(tabToSelect); - resultViewers.get(tabToSelect).setNode(selectedNode, - this.searchResultManager == null - ? null - : this.searchResultManager.getCurrentSearchResults()); + resultViewers.get(tabToSelect).setNode(selectedNode); } } @@ -716,12 +494,11 @@ public void stateChanged(ChangeEvent event) { if (currentTab != DataResultPanel.NO_TAB_SELECTED) { DataResultViewer currentViewer = this.resultViewers.get(currentTab); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - if (this.searchResultManager != null) { - currentViewer.setNode(currentRootNode, this.searchResultManager.getCurrentSearchResults()); - } else { + try { currentViewer.setNode(currentRootNode); + } finally { + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } - this.setCursor(null); } } @@ -759,12 +536,13 @@ void close() { if (!this.isMain) { this.resultViewers.forEach(DataResultViewer::clearComponent); + this.descriptionLabel.removeAll(); + this.numberOfChildNodesLabel.removeAll(); + this.matchLabel.removeAll(); this.setLayout(null); this.removeAll(); this.setVisible(false); } - - closeListeners(); } @Override @@ -812,14 +590,13 @@ public void propertyChange(PropertyChangeEvent evt) { * set up again after the "Please wait..." node has ended and actual content * should be displayed in the table. */ - private class RootNodeListener extends NodeAdapter { + private class RootNodeListener implements NodeListener { //it is assumed we are still waiting for data when the node is initially constructed private volatile boolean waitingForData = true; - private final boolean fullReset; - public RootNodeListener(boolean fullReset) { - this.fullReset = fullReset; + public void reset() { + waitingForData = true; } @Override @@ -838,10 +615,10 @@ public void childrenAdded(final NodeMemberEvent nme) { if (waitingForData && containsReal(delta)) { waitingForData = false; if (SwingUtilities.isEventDispatchThread()) { - setupTabs(nme.getNode(), this.fullReset); + setupTabs(nme.getNode()); } else { SwingUtilities.invokeLater(() -> { - setupTabs(nme.getNode(), this.fullReset); + setupTabs(nme.getNode()); }); } } @@ -861,14 +638,7 @@ private boolean containsReal(Node[] delta) { * */ private void updateMatches() { - if (DataResultPanel.this.searchResultManager != null) { - long resultCount = DataResultPanel.this.searchResultManager.getTotalResults(); - if (resultCount > Integer.MAX_VALUE) { - resultCount = Integer.MAX_VALUE; - } - - setNumMatches((int) resultCount); - } else if (currentRootNode != null && currentRootNode.getChildren() != null) { + if (currentRootNode != null && currentRootNode.getChildren() != null) { setNumMatches(currentRootNode.getChildren().getNodesCount()); } } @@ -877,43 +647,17 @@ private void updateMatches() { public void childrenRemoved(NodeMemberEvent nme) { updateMatches(); } - } - - /** - * Controls top level paging in DataResultPanel. This class is typically - * passed to result viewers and allows then to access and configure top - * level result paging. - */ - public class PagingControls { - - public int getTotalPages() { - if (searchResultManager != null) { - return searchResultManager.getTotalPages(); - } else if (pagingSupport != null) { - return pagingSupport.getLastKnownPageCount(); - } - return 0; - } - public int getCurrentPage() { - if (searchResultManager != null) { - // NOTE: SearchManager returns page indexes that start at 0, not 1. - return searchResultManager.getPageIdx() + 1; - } else if (pagingSupport != null) { - return pagingSupport.getCurrentPageIdx(); - } - return 0; + @Override + public void childrenReordered(NodeReorderEvent nre) { } - public void gotoPage(int idx) { - goToPage(idx); + @Override + public void nodeDestroyed(NodeEvent ne) { } - public void setPageControlsEnabled(boolean enabled) { - if (pagingControlsEnabled != enabled){ - pagingControlsEnabled = enabled; - updatePagingComponents(); - } + @Override + public void propertyChange(PropertyChangeEvent evt) { } } @@ -925,263 +669,52 @@ public void setPageControlsEnabled(boolean enabled) { @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { - java.awt.GridBagConstraints gridBagConstraints; descriptionLabel = new javax.swing.JLabel(); numberOfChildNodesLabel = new javax.swing.JLabel(); - javax.swing.JLabel matchLabel = new javax.swing.JLabel(); - javax.swing.JLabel pageLabel = new javax.swing.JLabel(); - pageNumLabel = new javax.swing.JLabel(); - pagesLabel = new javax.swing.JLabel(); - pagePrevButton = new javax.swing.JButton(); - pageNextButton = new javax.swing.JButton(); - gotoPageLabel = new javax.swing.JLabel(); - gotoPageTextField = new javax.swing.JTextField(); + matchLabel = new javax.swing.JLabel(); resultViewerTabs = new javax.swing.JTabbedPane(); - javax.swing.JPanel horizontalSpacer = new javax.swing.JPanel(); setMinimumSize(new java.awt.Dimension(0, 5)); setPreferredSize(new java.awt.Dimension(5, 5)); - setLayout(new java.awt.GridBagLayout()); org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N - descriptionLabel.setMaximumSize(new java.awt.Dimension(32767, 16)); - descriptionLabel.setMinimumSize(new java.awt.Dimension(50, 14)); - descriptionLabel.setPreferredSize(new java.awt.Dimension(32767, 16)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.gridwidth = 8; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); - add(descriptionLabel, gridBagConstraints); + descriptionLabel.setMinimumSize(new java.awt.Dimension(5, 14)); org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 9; - gridBagConstraints.gridy = 0; - gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); - add(numberOfChildNodesLabel, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 10; - gridBagConstraints.gridy = 0; - gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0); - add(matchLabel, gridBagConstraints); - - org.openide.awt.Mnemonics.setLocalizedText(pageLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pageLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); - add(pageLabel, gridBagConstraints); - - org.openide.awt.Mnemonics.setLocalizedText(pageNumLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pageNumLabel.text")); // NOI18N - pageNumLabel.setMaximumSize(null); - pageNumLabel.setMinimumSize(null); - pageNumLabel.setPreferredSize(null); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 1; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); - add(pageNumLabel, gridBagConstraints); - - org.openide.awt.Mnemonics.setLocalizedText(pagesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pagesLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 2; - gridBagConstraints.gridy = 1; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); - add(pagesLabel, gridBagConstraints); - - pagePrevButton.setBackground(null); - pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(pagePrevButton, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pagePrevButton.text")); // NOI18N - pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N - pagePrevButton.setFocusable(false); - pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - pagePrevButton.setIconTextGap(0); - pagePrevButton.setMargin(new java.awt.Insets(0, 0, 0, 0)); - pagePrevButton.setMaximumSize(new java.awt.Dimension(22, 23)); - pagePrevButton.setMinimumSize(new java.awt.Dimension(22, 23)); - pagePrevButton.setPreferredSize(new java.awt.Dimension(22, 23)); - pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N - pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - pagePrevButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - pagePrevButtonActionPerformed(evt); - } - }); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 3; - gridBagConstraints.gridy = 1; - gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0); - add(pagePrevButton, gridBagConstraints); - - pageNextButton.setBackground(null); - pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(pageNextButton, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pageNextButton.text")); // NOI18N - pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N - pageNextButton.setFocusable(false); - pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - pageNextButton.setIconTextGap(0); - pageNextButton.setMargin(new java.awt.Insets(0, 0, 0, 0)); - pageNextButton.setMaximumSize(new java.awt.Dimension(22, 23)); - pageNextButton.setMinimumSize(new java.awt.Dimension(22, 23)); - pageNextButton.setPreferredSize(new java.awt.Dimension(22, 23)); - pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N - pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - pageNextButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - pageNextButtonActionPerformed(evt); - } - }); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 4; - gridBagConstraints.gridy = 1; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); - add(pageNextButton, gridBagConstraints); - - org.openide.awt.Mnemonics.setLocalizedText(gotoPageLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.gotoPageLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 5; - gridBagConstraints.gridy = 1; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); - add(gotoPageLabel, gridBagConstraints); - - gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.gotoPageTextField.text")); // NOI18N - gotoPageTextField.setMaximumSize(new java.awt.Dimension(32767, 22)); - gotoPageTextField.setMinimumSize(new java.awt.Dimension(50, 22)); - gotoPageTextField.setPreferredSize(new java.awt.Dimension(50, 22)); - gotoPageTextField.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - gotoPageTextFieldActionPerformed(evt); - } - }); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 6; - gridBagConstraints.gridy = 1; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); - add(gotoPageTextField, gridBagConstraints); resultViewerTabs.setMinimumSize(new java.awt.Dimension(0, 5)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 2; - gridBagConstraints.gridwidth = 11; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - add(resultViewerTabs, gridBagConstraints); - horizontalSpacer.setMaximumSize(new java.awt.Dimension(0, 0)); - horizontalSpacer.setMinimumSize(new java.awt.Dimension(20, 0)); - horizontalSpacer.setPreferredSize(new java.awt.Dimension(20, 0)); - - javax.swing.GroupLayout horizontalSpacerLayout = new javax.swing.GroupLayout(horizontalSpacer); - horizontalSpacer.setLayout(horizontalSpacerLayout); - horizontalSpacerLayout.setHorizontalGroup( - horizontalSpacerLayout.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) + .addGroup(layout.createSequentialGroup() + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(numberOfChildNodesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(matchLabel)) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); - horizontalSpacerLayout.setVerticalGroup( - horizontalSpacerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(numberOfChildNodesLabel) + .addComponent(matchLabel)) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 7; - gridBagConstraints.gridy = 0; - gridBagConstraints.gridheight = 2; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.weightx = 1.0; - add(horizontalSpacer, gridBagConstraints); }// </editor-fold>//GEN-END:initComponents - - private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed - if (this.searchResultManager != null) { - // NOTE: SearchManager uses page indexes that start at 0, not 1. - // So SearchManager page 1 is UI page 2. - goToPage(this.searchResultManager.getPageIdx()); - } else if (this.pagingSupport != null) { - goToPage(this.pagingSupport.getCurrentPageIdx() - 1); - } - }//GEN-LAST:event_pagePrevButtonActionPerformed - - private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed - if (this.searchResultManager != null) { - // NOTE: SearchManager uses page indexes that start at 0, not 1. - // So SearchManager page 1 is UI page 2. - goToPage(this.searchResultManager.getPageIdx() + 2); - } else if (this.pagingSupport != null) { - goToPage(this.pagingSupport.getCurrentPageIdx() + 1); - } - }//GEN-LAST:event_pageNextButtonActionPerformed - - private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed - int parsedIdx = Integer.parseInt(this.gotoPageTextField.getText()); - goToPage(parsedIdx); - }//GEN-LAST:event_gotoPageTextFieldActionPerformed - - /** - * Triggers an update to get and display a page of results. - * - * @param uiPageToSet UI page index (between 1 and "total number of pages") - */ - void goToPage(int uiPageToSet) { - try { - // Switching a top level page. Reset the DataResultViewer paging so that - // we start at page 1. - resultViewers.forEach((resultViewer) -> resultViewer.resetComponent()); - if (this.searchResultManager != null) { - // UI pages go from 1 to "number of pages" - int uiPageIdx = Math.max(1, Math.min(this.searchResultManager.getTotalPages(), uiPageToSet)); - // NOTE: SearchManager uses page indexes that start at 0, not 1. - // ensure index is [0, number of pages - 1) - displaySearchResults(this.searchResultManager.updatePageIdx(uiPageIdx - 1), false); - } else { - setBaseChildFactoryPageIdx(uiPageToSet); - } - } catch (IllegalArgumentException | ExecutionException ex) { - logger.log(Level.WARNING, "Go to page index failed", ex); - updatePagingComponents(); - } - } - - private void setBaseChildFactoryPageIdx(int pageIdx) { - if (this.pagingSupport != null) { - int boundedPageIdx = Math.max(0, Math.min(this.pagingSupport.getLastKnownPageCount() - 1, pageIdx)); - int currentTab = this.resultViewerTabs.getSelectedIndex(); - if (currentTab != NO_TAB_SELECTED) { - setNode(this.currentRootNode, false); - this.pagingSupport.setCurrentPageIdx(boundedPageIdx); - updatePagingComponents(); - } - } - } - - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel descriptionLabel; - private javax.swing.JLabel gotoPageLabel; - private javax.swing.JTextField gotoPageTextField; + private javax.swing.JLabel matchLabel; private javax.swing.JLabel numberOfChildNodesLabel; - private javax.swing.JButton pageNextButton; - private javax.swing.JLabel pageNumLabel; - private javax.swing.JButton pagePrevButton; - private javax.swing.JLabel pagesLabel; private javax.swing.JTabbedPane resultViewerTabs; // End of variables declaration//GEN-END:variables @@ -1226,604 +759,4 @@ public void resetTabs(Node unusedSelectedNode) { this.setNode(null); } - /** - * @return The current user preference page size. - */ - private int getPageSize() { - return UserPreferences.getResultsTablePageSize(); - } - - /** - * Displays results of querying the DAO for data artifacts matching the - * search parameters query. - * - * @param dataArtifactParams The search parameter query. - */ - void displayDataArtifact(DataArtifactSearchParam dataArtifactParams) { - try { - this.searchResultManager = new SearchManager(new DataArtifactFetcher(dataArtifactParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, dataArtifactParams.getNodeSelectionInfo()); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [artifact type: {0}, data source id: {1}]", - dataArtifactParams.getArtifactType(), - dataArtifactParams.getDataSourceId() == null ? "<null>" : dataArtifactParams.getDataSourceId()), - ex); - } - } - - /** - * Displays results for querying the DAO for accounts matching the search - * parameters query. - * - * @param accountParams The search parameter query. - */ - void displayAccounts(CommAccountsSearchParams accountParams) { - try { - this.searchResultManager = new SearchManager(new CommAccountFetcher(accountParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, accountParams.getNodeSelectionInfo()); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [artifact type: {0}, data source id: {1}, account type: {2}]", - accountParams.getType(), - accountParams.getDataSourceId() == null ? "<null>" : accountParams.getDataSourceId(), - accountParams.getType()), - ex); - } - } - - /** - * Displays credit cards by bin number prefix. - * - * @param searchParams The search parameters. - */ - void displayCreditCardsByBin(CreditCardBinSearchParams searchParams) { - try { - this.searchResultManager = new SearchManager(new CreditCardByBinFetcher(searchParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, searchParams.getNodeSelectionInfo()); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [credit cards, data source id: {0}, bin prefix: {1}, show rejected: {2}]", - searchParams.getDataSourceId() == null ? "<null>" : searchParams.getDataSourceId(), - StringUtils.defaultString(searchParams.getBinPrefix(), "<null>"), - searchParams.isRejectedIncluded()), - ex); - } - } - - /** - * Displays credit cards by file name. - * - * @param searchParams The search parameters. - */ - void displayCreditCardsByFile(CreditCardFileSearchParams searchParams) { - try { - this.searchResultManager = new SearchManager(new CreditCardByFileFetcher(searchParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [credit cards, data source id: {0}, show rejected: {1}]", - searchParams.getDataSourceId() == null ? "<null>" : searchParams.getDataSourceId(), - searchParams.isRejectedIncluded()), - ex); - } - } - - /** - * Display results for querying the DAO for email messages matching the - * search parameters query. - * - * @param searchParams The search parameter query. - */ - void displayEmailMessages(EmailSearchParams searchParams) { - try { - this.searchResultManager = new SearchManager(new EmailFetcher(searchParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, searchParams.getNodeSelectionInfo()); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [data source id: {0}, folder: {1}]", - searchParams.getDataSourceId() == null ? "<null>" : searchParams.getDataSourceId(), - searchParams.getFolder() == null ? "<null>" : searchParams.getFolder()), - ex); - } - } - - void displayAnalysisResult(AnalysisResultSearchParam analysisResultParams) { - try { - this.searchResultManager = new SearchManager(new AnalysisResultFetcher(analysisResultParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, analysisResultParams.getNodeSelectionInfo()); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [artifact type: {0}, data source id: {1}]", - analysisResultParams.getArtifactType(), - analysisResultParams.getDataSourceId() == null ? "<null>" : analysisResultParams.getDataSourceId()), - ex); - } - } - - /** - * Displays deleted content in the file views section. - * - * @param deletedSearchParams The deleted content search params. - */ - void displayDeletedContent(DeletedContentSearchParams deletedSearchParams) { - try { - this.searchResultManager = new SearchManager(new DeletedFileFetcher(deletedSearchParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [filter: {0}, data source id: {1}]", - deletedSearchParams.getFilter() == null ? "<null>" : deletedSearchParams.getFilter(), - deletedSearchParams.getDataSourceId() == null ? "<null>" : deletedSearchParams.getDataSourceId()), - ex); - } - } - - /** - * Displays content with bad or suspicious scores in the file views section. - * - * @param scoreSearchParams The scored content search params. - */ - void displayScoreContent(ScoreViewSearchParams scoreSearchParams) { - try { - this.searchResultManager = new SearchManager(new ScoreContentFetcher(scoreSearchParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [filter: {0}, data source id: {1}]", - scoreSearchParams.getFilter() == null ? "<null>" : scoreSearchParams.getFilter(), - scoreSearchParams.getDataSourceId() == null ? "<null>" : scoreSearchParams.getDataSourceId()), - ex); - } - } - - /** - * Displays results of querying the DAO for files matching the file - * extension search parameters query. - * - * @param fileExtensionsParams The search parameter query. - */ - void displayFileExtensions(FileTypeExtensionsSearchParams fileExtensionsParams) { - try { - - this.searchResultManager = new SearchManager(new FileTypeExtFetcher(fileExtensionsParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, - MessageFormat.format("There was an error displaying search results for [search filter: {0}, data source id: {1}]", - fileExtensionsParams.getFilter(), - fileExtensionsParams.getDataSourceId() == null ? "<null>" : fileExtensionsParams.getDataSourceId()), - ex); - } - } - - /** - * Display results of querying the DAO for files matching the file mime - * search parameters query. - * - * @param fileMimeKey The search parameter query. - */ - void displayFileMimes(FileTypeMimeSearchParams fileMimeKey) { - try { - - this.searchResultManager = new SearchManager(new FileTypeMimeFetcher(fileMimeKey), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for files of mime filter: {0} and data source id: {1}.", - fileMimeKey.getMimeType(), - fileMimeKey.getDataSourceId() == null ? "<null>" : fileMimeKey.getDataSourceId()), - ex); - } - } - - /** - * Displays results of querying the DAO for given search parameters query. - * - * @param keywordHitKey The search parameter query. - */ - void displayKeywordHits(KeywordHitSearchParam keywordHitKey) { - try { - this.searchResultManager = new SearchManager(new KeywordHitResultFetcher(keywordHitKey), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, keywordHitKey.getNodeSelectionInfo()); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for keyword filter: {0} and data source id: {1}.", - keywordHitKey.getConfiguration(), - keywordHitKey.getDataSourceId() == null ? "<null>" : keywordHitKey.getDataSourceId()), - ex); - } - } - - /** - * Displays results of querying the DAO for files matching the file size - * search parameters query. - * - * @param fileSizeKey - */ - void displayFileSizes(FileTypeSizeSearchParams fileSizeKey) { - try { - this.searchResultManager = new SearchManager(MainDAO.getInstance().getViewsDAO().new FileTypeSizeFetcher(fileSizeKey), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for files of size filter: {0} and data source id: {1}.", - fileSizeKey.getSizeFilter().getDisplayName(), - fileSizeKey.getDataSourceId() == null ? "<null>" : fileSizeKey.getDataSourceId()), - ex); - } - } - - /** - * Displays results of querying the DAO for given search parameters (set and - * artifact type) query. - * - * @param setKey The search parameter query. - */ - void displayAnalysisResultSet(AnalysisResultSearchParam setKey) { - try { - this.searchResultManager = new SearchManager(new AnalysisResultConfigFetcher(setKey), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, setKey.getNodeSelectionInfo()); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for hash set filter: {0} and data source id: {1}.", - setKey.getConfiguration(), - setKey.getDataSourceId() == null ? "<null>" : setKey.getDataSourceId()), - ex); - } - } - - /** - * Displays results of querying the DAO for given search parameters (set and - * artifact type) query. - * - * @param searchParams The search parameter query. - */ - void displayReports(ReportsSearchParams searchParams) { - try { - this.searchResultManager = new SearchManager(new ReportsFetcher(searchParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, "There was an error fetching data for reports", ex); - } - } - - /** - * Displays results of querying the DAO for the given search parameters - * query. - * - * @param tagParams The search parameters. - */ - void displayTags(TagsSearchParams tagParams) { - try { - this.searchResultManager = new SearchManager(new TagFetcher(tagParams), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for Tags filter: {0} and data source id: {1}.", - tagParams.getTagName(), - tagParams.getDataSourceId() == null ? "<null>" : tagParams.getDataSourceId()), - ex); - } - } - - /** - * Displays results of querying the DAO for the given search parameters - * query. - * - * @param fileSystemKey The search parameters. - * - */ - void displayFileSystemContent(FileSystemContentSearchParam fileSystemKey) { - try { - this.searchResultManager = new SearchManager(new FileSystemFetcher(fileSystemKey), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, fileSystemKey.getNodeSelectionInfo()); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for file system filter: {0}.", - fileSystemKey.getContentObjectId()), - ex); - } - } - - /** - * Displays results of querying the DAO for the given search parameters - * query. - * - * @param hostSystemKey The search parameters. - * - */ - void displayFileSystemForHost(FileSystemHostSearchParam hostSystemKey) { - try { - this.searchResultManager = new SearchManager(new FileSystemHostFetcher(hostSystemKey), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for host filter: {0}.", - hostSystemKey.getHostObjectId()), - ex); - } - } - - /** - * Displays results of querying the DAO for the given search parameters - * query. - * - * @param osAccountKey The search parameters. - * @param nodeSelectionInfo The selected node or null for no selection. - */ - void displayOsAccount(OsAccountsSearchParams osAccountKey, ChildNodeSelectionInfo nodeSelectionInfo) { - try { - this.searchResultManager = new SearchManager(new AccountFetcher(osAccountKey), getPageSize()); - SearchResultsDTO results = searchResultManager.getResults(); - displaySearchResults(results, true, nodeSelectionInfo); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, MessageFormat.format( - "There was an error fetching data for Os Account filter: {0}.", - osAccountKey.getDataSourceId()), - ex); - } - } - - /** - * Displays current search result in the result view. This assumes that - * search result support has already been updated. - * - * @param searchResults The new search results to display. - * @param resetPaging Whether or not to reset paging to index 0 and tabs - * selection. - */ - @Messages({ - "# {0} - pageNumber", - "# {1} - pageCount", - "DataResultPanel_pageIdxOfCount={0} of {1}" - }) - - private void displaySearchResults(SearchResultsDTO searchResults, boolean resetPaging) { - displaySearchResults(searchResults, resetPaging, null); - } - - private void displaySearchResults(SearchResultsDTO searchResults, boolean resetPaging, ChildNodeSelectionInfo childSelectionInfo) { - if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(() -> displaySearchResults(searchResults, resetPaging, childSelectionInfo)); - return; - } - - // Stop any running background threads. - if(nodeBackgroundTasksPool != null) { - nodeBackgroundTasksPool.shutdown(); - } - - nodeBackgroundTasksPool = Executors.newFixedThreadPool(MAX_POOL_SIZE, - new ThreadFactoryBuilder().setNameFormat("DataResultPanel-background-task-%d").build()); - - if (searchResults == null) { - setNode(null, resetPaging); - } else { - SearchResultRootNode node = new SearchResultRootNode(searchResults, nodeBackgroundTasksPool); - node.setNodeSelectionInfo(childSelectionInfo); - setNode(node, resetPaging); - setNumberOfChildNodes( - searchResults.getTotalResultsCount() > Integer.MAX_VALUE - ? Integer.MAX_VALUE - : (int) searchResults.getTotalResultsCount() - ); - } - } - - /** - * Refreshes the currently displayed search result node by updating the - * children with the search results as backing data handling errors with a - * log entry. - * - */ - private void refreshSearchResultChildren() { - try { - refreshSearchResultChildren(this.searchResultManager.getResults()); - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, "There was an error refreshing data: ", ex); - } - } - - /** - * Refreshes the currently displayed search result node by updating the - * children with the search results as backing data. - * - * @param searchResults The search results to serve as the updated children. - */ - private void refreshSearchResultChildren(SearchResultsDTO searchResults) { - if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(() -> refreshSearchResultChildren(searchResults)); - return; - } - - if (searchResults == null) { - setNode(null, false); - return; - } - - SearchResultRootNode searchResultNode = this.currentRootNode instanceof SearchResultRootNode - ? (SearchResultRootNode) this.currentRootNode - : null; - - if (searchResultNode == null) { - displaySearchResults(searchResults, true); - } else { - searchResultNode.updateChildren(searchResults); - setNumberOfChildNodes( - searchResults.getTotalResultsCount() > Integer.MAX_VALUE - ? Integer.MAX_VALUE - : (int) searchResults.getTotalResultsCount() - ); - updatePagingComponents(); - } - } - - void enablePagingControls(boolean enable) { - this.pagePrevButton.setEnabled(enable); - this.pageNextButton.setEnabled(enable); - this.gotoPageTextField.setEnabled(enable); - this.pageNumLabel.setText(""); - this.gotoPageTextField.setText(""); - } - - private void updatePagingComponents() { - - if (!pagingControlsEnabled) { - enablePagingControls(pagingControlsEnabled); - return; - } - - if (this.searchResultManager != null) { - this.pagePrevButton.setEnabled(this.searchResultManager.hasPrevPage()); - this.pageNextButton.setEnabled(this.searchResultManager.hasNextPage()); - this.pageNumLabel.setText(Bundle.DataResultPanel_pageIdxOfCount( - this.searchResultManager.getPageIdx() + 1, - Math.max(this.searchResultManager.getTotalPages(), 1))); - this.gotoPageTextField.setEnabled(true); - this.gotoPageTextField.setText(Integer.toString(this.searchResultManager.getPageIdx() + 1)); - } else if (this.pagingSupport != null) { - this.pagePrevButton.setEnabled(this.pagingSupport.getCurrentPageIdx() > 0); - this.pageNextButton.setEnabled(this.pagingSupport.getCurrentPageIdx() < this.pagingSupport.getLastKnownPageCount() - 1); - this.pageNumLabel.setText(Bundle.DataResultPanel_pageIdxOfCount( - this.pagingSupport.getCurrentPageIdx() + 1, - Math.max(this.pagingSupport.getLastKnownPageCount(), 1))); - this.gotoPageTextField.setEnabled(true); - this.gotoPageTextField.setText(Integer.toString(this.pagingSupport.getCurrentPageIdx() + 1)); - } else { - enablePagingControls(false); - } - } - - /** - * Children for a parent node in the result viewer that creates filter nodes - * with no children. - */ - private class ResultViewerFilterChildren extends FilterNode.Children { - - /** - * Main constructor. - * - * @param baseNode The parent node to wrap. - */ - ResultViewerFilterChildren(Node baseNode) { - super(baseNode == null ? Node.EMPTY : baseNode); - } - - @Override - protected Node[] createNodes(Node key) { - return new Node[]{new FilterNode(key, Children.LEAF)}; - } - } - - /** - * A parent node of items to display in the result viewer that shows no - * grandchildren. - */ - private class ResultViewerFilterParentNode extends FilterNode { - - /** - * Main constructor. - * - * @param original The original node to wrap. - */ - ResultViewerFilterParentNode(Node original) { - super(original == null ? Node.EMPTY : original, new ResultViewerFilterChildren(original)); - } - } - - /** - * Listens for updates in page count for a BaseChildFactory. - */ - private class BaseChildFactoryPager { - - private final String nodeName; - private int lastKnownPageCount = 0; - private int currentPageIdx = 0; - - BaseChildFactoryPager(String nodeName) { - this.nodeName = nodeName; - } - - int getLastKnownPageCount() { - return lastKnownPageCount; - } - - int getCurrentPageIdx() { - return currentPageIdx; - } - - void setCurrentPageIdx(int currentPageIdx) { - this.currentPageIdx = Math.min(getLastKnownPageCount(), Math.max(0, currentPageIdx)); - postPageChangeEvent(); - } - - /** - * Notify subscribers (i.e. child factories) that a page change has - * occurred. - */ - void postPageChangeEvent() { - try { - BaseChildFactory.post(nodeName, new PageChangeEvent(currentPageIdx + 1)); - } catch (BaseChildFactory.NoSuchEventBusException ex) { - logger.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS - } - - if (pagingSupport == this) { - updatePagingComponents(); - } - } - - /** - * Notify subscribers (i.e. child factories) that a page size change has - * occurred. - */ - void postPageSizeChangeEvent() { - try { - BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize())); - this.currentPageIdx = 0; - } catch (BaseChildFactory.NoSuchEventBusException ex) { - logger.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS - } - - if (pagingSupport == this) { - updatePagingComponents(); - } - } - - /** - * Subscribe to notification that the number of pages has changed. - * - * @param event - */ - @Subscribe - public void subscribeToPageCountChange(PageCountChangeEvent event) { - this.lastKnownPageCount = event.getPageCount(); - if (DataResultPanel.this.searchResultManager == null - && event != null - && this.nodeName != null - && DataResultPanel.this.currentRootNode != null - && this.nodeName.equals(DataResultPanel.this.currentRootNode.getName())) { - this.lastKnownPageCount = event.getPageCount(); - updatePagingComponents(); - } - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index c11e625806f676dc5ecdf8ab5396c746c85913a1..35a3007ece20a2e6cf04c7a8bcf754d3a12167d5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2021 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,26 +38,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeExtensionsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemContentSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemHostSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.CommAccountsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardBinSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardFileSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.EmailSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.KeywordHitSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.OsAccountsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.ReportsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo; /** * A DataResultTopComponent object is a NetBeans top component that provides @@ -379,184 +360,6 @@ public void setNode(Node selectedNode) { dataResultPanel.setNode(selectedNode); } - private final MainDAO threePanelDAO = MainDAO.getInstance(); - - /** - * Displays results of querying the DAO for analysis results matching the - * search parameters query. - * - * @param analysisResultParams The search parameter query. - */ - public void displayAnalysisResult(AnalysisResultSearchParam analysisResultParams) { - dataResultPanel.displayAnalysisResult(analysisResultParams); - } - - /** - * Displays credit cards by bin number prefix. - * - * @param searchParams The search parameters. - */ - public void displayCreditCardsByBin(CreditCardBinSearchParams searchParams) { - dataResultPanel.displayCreditCardsByBin(searchParams); - } - - /** - * Displays credit cards by file name. - * - * @param searchParams The search parameters. - */ - public void displayCreditCardsByFile(CreditCardFileSearchParams searchParams) { - dataResultPanel.displayCreditCardsByFile(searchParams); - } - - /** - * Displays results of querying the DAO for data artifacts matching the - * search parameters query. - * - * @param dataArtifactParams The search parameter query. - */ - public void displayDataArtifact(DataArtifactSearchParam dataArtifactParams) { - dataResultPanel.displayDataArtifact(dataArtifactParams); - } - - /** - * Displays deleted content in the file views section. - * - * @param deletedSearchParams The deleted content search params. - */ - public void displayDeletedContent(DeletedContentSearchParams deletedSearchParams) { - dataResultPanel.displayDeletedContent(deletedSearchParams); - } - - /** - * Displays content with bad or suspicious scores in the file views section. - * - * @param scoreSearchParams The scored content search params. - */ - public void displayScoreContent(ScoreViewSearchParams scoreSearchParams) { - dataResultPanel.displayScoreContent(scoreSearchParams); - } - - /** - * Displays results of querying the DAO for demails matching the search - * parameters query. - * - * @param dataArtifactParams The search parameter query. - */ - public void displayEmailMessages(EmailSearchParams searchParams) { - dataResultPanel.displayEmailMessages(searchParams); - } - - /** - * Displays results of querying the DAO for files matching the mime search - * parameters query. - * - * @param fileMimeKey The search parameter query. - */ - public void displayFileMimes(FileTypeMimeSearchParams fileMimeKey) { - dataResultPanel.displayFileMimes(fileMimeKey); - } - - /** - * Displays results of querying the DAO for files matching the file - * extension search parameters query. - * - * @param fileExtensionsParams The search parameter query. - */ - public void displayFileExtensions(FileTypeExtensionsSearchParams fileExtensionsParams) { - dataResultPanel.displayFileExtensions(fileExtensionsParams); - } - - /** - * Displays results of querying the DAO for files matching the file size - * search parameters query. - * - * @param fileSizeParams The search parameter query. - */ - public void displayFileSizes(FileTypeSizeSearchParams fileSizeParams) { - dataResultPanel.displayFileSizes(fileSizeParams); - } - - /** - * Displays results of querying the DAO for an artifact type and set name. - * - * @param params The search parameters. - */ - public void displayAnalysisResultConfig(AnalysisResultSearchParam params) { - dataResultPanel.displayAnalysisResultSet(params); - } - - /** - * Displays results of querying the DAO for keyword hits matching the search - * parameters query. - * - * @param keywordParams The search parameter query. - */ - public void displayKeywordHits(KeywordHitSearchParam keywordParams) { - dataResultPanel.displayKeywordHits(keywordParams); - } - - /** - * Displays results of querying the DAO for reports matching the search - * parameters query. - * - * @param searchParams The search parameter query. - */ - public void displayReports(ReportsSearchParams searchParams) { - dataResultPanel.displayReports(searchParams); - } - - /** - * Displays results for querying the DAO for tags matching the search - * parameters query. - * - * @param tagParams The search parameter query. - */ - public void displayTags(TagsSearchParams tagParams) { - dataResultPanel.displayTags(tagParams); - } - - /** - * Displays results for querying the DAO for tags matching the search - * parameters query. - * - * @param fileSystemParams The search parameter query. - */ - public void displayFileSystemContent(FileSystemContentSearchParam fileSystemParams) { - dataResultPanel.displayFileSystemContent(fileSystemParams); - } - - /** - * Displays results for querying the DAO for tags matching the search - * parameters query. - * - * @param hostSystempParams The search parameter query. - */ - public void displayFileSystemForHost(FileSystemHostSearchParam hostSystempParams) { - dataResultPanel.displayFileSystemForHost(hostSystempParams); - } - - /** - * Displays results for querying the DAO for tags matching the search - * parameters query. - * - * @param osAccountParams The search parameter query. - * @param nodeSelectionInfo The os account selected or null if no selection. - */ - public void displayOsAccounts(OsAccountsSearchParams osAccountParams, ChildNodeSelectionInfo nodeSelectionInfo) { - dataResultPanel.displayOsAccount(osAccountParams, nodeSelectionInfo); - } - - /** - * Displays results for querying the DAO for accounts matching the search - * parameters query. - * - * @param accountParams The search parameter query. - */ - public void displayAccounts(CommAccountsSearchParams accountParams) { - dataResultPanel.displayAccounts(accountParams); - } - @Override public void setTitle(String title) { setName(title); @@ -660,4 +463,5 @@ public DataResultTopComponent(boolean isMain, String title) { public void resetTabs(Node node) { dataResultPanel.setNode(node); } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index b60b30a286e21f9f49943d8eada3cdd75d6a4638..6aeda07298d5ccd8ccf7d1198e690f78ff7d6c37 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -17,7 +17,21 @@ <DimensionLayout dim="0"> <Group type="103" groupAlignment="0" attributes="0"> <Component id="outlineView" pref="904" max="32767" attributes="0"/> - <Group type="102" alignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Component id="pageLabel" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="pageNumLabel" min="-2" pref="53" max="-2" attributes="0"/> + <EmptySpace min="-2" pref="14" max="-2" attributes="0"/> + <Component id="pagesLabel" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="pagePrevButton" linkSize="1" min="-2" pref="16" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="pageNextButton" linkSize="1" min="-2" pref="16" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="gotoPageLabel" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="gotoPageTextField" min="-2" pref="33" max="-2" attributes="0"/> <EmptySpace max="32767" attributes="0"/> <Component id="exportCSVButton" min="-2" max="-2" attributes="0"/> </Group> @@ -27,7 +41,16 @@ <Group type="103" groupAlignment="0" attributes="0"> <Group type="102" alignment="1" attributes="0"> <EmptySpace min="-2" pref="3" max="-2" attributes="0"/> - <Component id="exportCSVButton" min="-2" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="pageLabel" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="pageNumLabel" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="pagesLabel" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="pagePrevButton" linkSize="2" alignment="2" min="-2" pref="14" max="-2" attributes="0"/> + <Component id="pageNextButton" linkSize="2" alignment="2" min="-2" pref="15" max="-2" attributes="0"/> + <Component id="gotoPageLabel" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="gotoPageTextField" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="exportCSVButton" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> <EmptySpace min="-2" pref="3" max="-2" attributes="0"/> <Component id="outlineView" pref="321" max="32767" attributes="0"/> <EmptySpace max="-2" attributes="0"/> @@ -36,6 +59,86 @@ </DimensionLayout> </Layout> <SubComponents> + <Component class="javax.swing.JLabel" name="pageLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="pageNumLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageNumLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="pagesLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JButton" name="pagePrevButton"> + <Properties> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"/> + </Property> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pagePrevButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"/> + </Property> + <Property name="focusable" type="boolean" value="false"/> + <Property name="horizontalTextPosition" type="int" value="0"/> + <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor"> + <Insets value="[2, 0, 2, 0]"/> + </Property> + <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[55, 23]"/> + </Property> + <Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"/> + </Property> + <Property name="verticalTextPosition" type="int" value="3"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pagePrevButtonActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="pageNextButton"> + <Properties> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"/> + </Property> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageNextButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"/> + </Property> + <Property name="focusable" type="boolean" value="false"/> + <Property name="horizontalTextPosition" type="int" value="0"/> + <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor"> + <Insets value="[2, 0, 2, 0]"/> + </Property> + <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[27, 23]"/> + </Property> + <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[27, 23]"/> + </Property> + <Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"/> + </Property> + <Property name="verticalTextPosition" type="int" value="3"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pageNextButtonActionPerformed"/> + </Events> + </Component> <Container class="org.openide.explorer.view.OutlineView" name="outlineView"> <AuxValues> <AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);"/> @@ -43,6 +146,26 @@ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> </Container> + <Component class="javax.swing.JLabel" name="gotoPageLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.gotoPageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <AccessibilityProperties> + <Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/> + </AccessibilityProperties> + </Component> + <Component class="javax.swing.JTextField" name="gotoPageTextField"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.gotoPageTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gotoPageTextFieldActionPerformed"/> + </Events> + </Component> <Component class="javax.swing.JButton" name="exportCSVButton"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 3513202c49a1247c4319141a4aec26367324f960..c6ecfb52740a47f098c1109ba1531e434ad615ee 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -18,11 +18,14 @@ */ package org.sleuthkit.autopsy.corecomponents; +import com.google.common.eventbus.Subscribe; import java.awt.Component; +import java.awt.Cursor; import java.awt.dnd.DnDConstants; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.FeatureDescriptor; +import java.beans.PropertyChangeEvent; import java.beans.PropertyVetoException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -34,10 +37,12 @@ import java.util.Queue; import java.util.TreeMap; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; +import java.util.prefs.PreferenceChangeEvent; import java.util.prefs.Preferences; -import java.util.stream.Collectors; import javax.swing.ImageIcon; +import javax.swing.JOptionPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import static javax.swing.SwingConstants.CENTER; @@ -62,18 +67,25 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Node.Property; +import org.openide.nodes.NodeEvent; +import org.openide.nodes.NodeListener; +import org.openide.nodes.NodeMemberEvent; +import org.openide.nodes.NodeReorderEvent; import org.openide.util.ImageUtilities; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo; -import org.sleuthkit.autopsy.mainui.nodes.SearchResultRootNode; +import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageChangeEvent; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageCountChangeEvent; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageSizeChangeEvent; import org.sleuthkit.datamodel.Score.Significance; /** @@ -129,8 +141,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private final TableListener outlineViewListener; private final IconRendererTableListener iconRendererListener; private Node rootNode; - private SearchResultsDTO searchResults; - private DataResultPanel.PagingControls pagingControls = null; + + /** + * Multiple nodes may have been visited in the context of this + * DataResultViewerTable. We keep track of the page state for these nodes in + * the following map. + */ + private final Map<String, PagingSupport> nodeNameToPagingSupportMap = new ConcurrentHashMap<>(); + + /** + * The paging support instance for the current node. + */ + private PagingSupport pagingSupport = null; /** * Constructs a tabular result viewer that displays the children of the @@ -177,6 +199,8 @@ public DataResultViewerTable(ExplorerManager explorerManager, String title) { */ initComponents(); + initializePagingSupport(); + /* * Disable the CSV export button for the common properties results */ @@ -212,9 +236,32 @@ public DataResultViewerTable(ExplorerManager explorerManager, String title) { outline.getTableHeader().addMouseListener(outlineViewListener); } - @Override - public void setPagingControls(DataResultPanel.PagingControls pagingControls) { - this.pagingControls = pagingControls; + private void initializePagingSupport() { + if (pagingSupport == null) { + pagingSupport = new PagingSupport(""); + } + + // Start out with paging controls invisible + pagingSupport.togglePageControls(false); + + /** + * Set up a change listener so we know when the user changes the page + * size + */ + UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> { + if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + /** + * If multiple nodes have been viewed we have to notify all of + * them about the change in page size. + */ + nodeNameToPagingSupportMap.values().forEach((ps) -> { + ps.postPageSizeChangeEvent(); + }); + + setCursor(null); + } + }); } /** @@ -255,11 +302,6 @@ public boolean isSupported(Node candidateRootNode) { return true; } - @Override - public void setNode(Node rootNode) { - setNode(rootNode, null); - } - /** * Sets the current root node of this tabular result viewer. * @@ -267,14 +309,7 @@ public void setNode(Node rootNode) { */ @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void setNode(Node rootNode, SearchResultsDTO searchResults) { - this.searchResults = searchResults; - - // enable paging controls (they could have been disabled by different viewer) - if (pagingControls != null) { - pagingControls.setPageControlsEnabled(true); - } - + public void setNode(Node rootNode) { if (!SwingUtilities.isEventDispatchThread()) { LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread"); return; @@ -289,16 +324,69 @@ public void setNode(Node rootNode, SearchResultsDTO searchResults) { */ outline.unsetQuickFilter(); + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - this.rootNode = rootNode; + if (rootNode != null) { + this.rootNode = rootNode; + + /** + * Check to see if we have previously created a paging support + * class for this node. + */ + if (!Node.EMPTY.equals(rootNode)) { + String nodeName = rootNode.getName(); + pagingSupport = nodeNameToPagingSupportMap.get(nodeName); + if (pagingSupport == null) { + pagingSupport = new PagingSupport(nodeName); + nodeNameToPagingSupportMap.put(nodeName, pagingSupport); + } + pagingSupport.updateControls(); + + rootNode.addNodeListener(new NodeListener() { + @Override + public void childrenAdded(NodeMemberEvent nme) { + /** + * This is the only somewhat reliable way I could + * find to reset the cursor after a page change. + * When you change page the old children nodes will + * be removed and new ones added. + */ + SwingUtilities.invokeLater(() -> { + setCursor(null); + }); + } + + @Override + public void childrenRemoved(NodeMemberEvent nme) { + SwingUtilities.invokeLater(() -> { + setCursor(null); + }); + } + + @Override + public void childrenReordered(NodeReorderEvent nre) { + // No-op + } + + @Override + public void nodeDestroyed(NodeEvent ne) { + // No-op + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + // No-op + } + }); + } + } /* * If the given node is not null and has children, set it as the * root context of the child OutlineView, otherwise make an * "empty"node the root context. */ - if ((searchResults != null && searchResults.getTotalResultsCount() > 0) - || (rootNode != null && rootNode.getChildren().getNodesCount() > 0)) { + if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { this.getExplorerManager().setRootContext(this.rootNode); outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); setupTable(); @@ -358,7 +446,6 @@ private void setupTable() { ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName()); } - /* * Load column sorting information from preferences file and apply it to * columns. @@ -397,8 +484,8 @@ private void setupTable() { * If one of the child nodes of the root node is to be selected, select * it. */ - if (rootNode instanceof SearchResultRootNode) { - ChildNodeSelectionInfo selectedChildInfo = ((SearchResultRootNode)rootNode).getNodeSelectionInfo(); + if (rootNode instanceof TableFilterNode) { + NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { Node[] childNodes = rootNode.getChildren().getNodes(true); for (int i = 0; i < childNodes.length; ++i) { @@ -415,8 +502,7 @@ private void setupTable() { break; } } - // Once it is selected clear the id. - ((SearchResultRootNode) rootNode).setNodeSelectionInfo(null); + ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null); } } @@ -538,15 +624,13 @@ private synchronized void storeColumnVisibility() { if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (rootNode instanceof TableFilterNode || searchResults != null) { - TableFilterNode tfn = searchResults == null ? (TableFilterNode) rootNode : null; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { String columnName = entry.getKey(); - final String columnHiddenKey = - tfn != null ? ResultViewerPersistence.getColumnHiddenKey(tfn, columnName) : - ResultViewerPersistence.getColumnHiddenKey(searchResults, columnName); + final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName); final TableColumn column = entry.getValue(); boolean columnHidden = columnModel.isColumnHidden(column); if (columnHidden) { @@ -566,14 +650,12 @@ private synchronized void storeColumnOrder() { if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (rootNode instanceof TableFilterNode || searchResults != null) { - TableFilterNode tfn = searchResults == null ? (TableFilterNode) rootNode : null; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); // Store the current order of the columns into settings for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { - preferences.putInt(tfn != null ? - ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()) : - ResultViewerPersistence.getColumnPositionKey(searchResults, entry.getValue().getName()), entry.getKey()); + preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey()); } } } @@ -585,20 +667,16 @@ private synchronized void storeColumnSorting() { if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (rootNode instanceof TableFilterNode || searchResults != null) { - final TableFilterNode tfn = searchResults == null ? ((TableFilterNode) rootNode) : null; + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = ((TableFilterNode) rootNode); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { ETableColumn etc = entry.getValue(); String columnName = entry.getKey(); //store sort rank and order - final String columnSortOrderKey = - searchResults == null ? ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName) : - ResultViewerPersistence.getColumnSortOrderKey(searchResults, columnName); - final String columnSortRankKey = - searchResults == null ? ResultViewerPersistence.getColumnSortRankKey(tfn, columnName): - ResultViewerPersistence.getColumnSortRankKey(searchResults, columnName); + final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName); + final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName); if (etc.isSorted() && (columnModel.isColumnHidden(etc) == false)) { preferences.putBoolean(columnSortOrderKey, etc.isAscending()); preferences.putInt(columnSortRankKey, etc.getSortRank()); @@ -608,7 +686,7 @@ private synchronized void storeColumnSorting() { preferences.remove(columnSortRankKey); } } - } + } } /** @@ -621,23 +699,17 @@ private synchronized void loadColumnSorting() { if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (rootNode instanceof TableFilterNode || searchResults != null) { - final TableFilterNode tfn = (searchResults == null ? (TableFilterNode) rootNode : null); + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); //organize property sorting information, sorted by rank TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); propertiesMap.entrySet().stream().forEach(entry -> { final String propName = entry.getValue().getName(); //if the sort rank is undefined, it will be defaulted to 0 => unsorted. - Integer sortRank = preferences.getInt( - tfn != null ? - ResultViewerPersistence.getColumnSortRankKey(tfn, propName) : - ResultViewerPersistence.getColumnSortRankKey(searchResults, propName), 0); + Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0); //default to true => ascending - Boolean sortOrder = preferences.getBoolean( - tfn != null ? - ResultViewerPersistence.getColumnSortOrderKey(tfn, propName) : - ResultViewerPersistence.getColumnSortOrderKey(searchResults, propName), true); + Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true); sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); }); //apply sort information in rank order. @@ -653,16 +725,13 @@ private synchronized void loadColumnVisibility() { if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (rootNode instanceof TableFilterNode || searchResults != null) { + if (rootNode instanceof TableFilterNode) { final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - final TableFilterNode tfn = (searchResults == null ? ((TableFilterNode) rootNode) : null); + final TableFilterNode tfn = ((TableFilterNode) rootNode); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { final String propName = entry.getValue().getName(); - boolean hidden = preferences.getBoolean( - tfn != null ? - ResultViewerPersistence.getColumnHiddenKey(tfn, propName) : - ResultViewerPersistence.getColumnHiddenKey(searchResults, propName), false); + boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false); final TableColumn column = columnMap.get(propName); columnModel.setColumnHidden(column, hidden); } @@ -679,10 +748,6 @@ private synchronized void loadColumnVisibility() { */ private synchronized List<Node.Property<?>> loadColumnOrder() { - if (searchResults != null) { - return loadColumnOrderForSearchResults(); - } - List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100); // If node is not table filter node, use default order for columns @@ -723,51 +788,6 @@ private synchronized List<Node.Property<?>> loadColumnOrder() { return new ArrayList<>(propertiesMap.values()); } - - private synchronized List<Node.Property<?>> loadColumnOrderForSearchResults() { - List<Node.Property<?>> props = searchResults.getColumns().stream() - .map(columnKey -> { - return new NodeProperty<>( - columnKey.getFieldName(), - columnKey.getDisplayName(), - columnKey.getDescription(), - "" - ); - }) - .collect(Collectors.toList()); - - propertiesMap.clear(); - - /* - * We load column index values into the properties map. If a property's - * index is outside the range of the number of properties or the index - * has already appeared as the position of another property, we put that - * property at the end. - */ - int offset = props.size(); - - final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - - for (Property<?> prop : props) { - Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(searchResults, prop.getName()), -1); - if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) { - propertiesMap.put(value, prop); - } else { - propertiesMap.put(offset, prop); - offset++; - } - } - - /* - * NOTE: it is possible to have "discontinuities" in the keys (i.e. - * column numbers) of the map. This happens when some of the columns had - * a previous setting, and other columns did not. We need to make the - * keys 0-indexed and continuous. - */ - compactPropertiesMap(); - - return new ArrayList<>(propertiesMap.values()); - } /** * Makes properties map 0-indexed and re-arranges elements to make sure the @@ -831,6 +851,155 @@ private int getRank() { } } + /** + * Maintains the current page state for a node and provides support for + * paging through results. Uses an EventBus to communicate with child + * factory implementations. + */ + private class PagingSupport { + + private int currentPage; + private int totalPages; + private final String nodeName; + + PagingSupport(String nodeName) { + currentPage = 1; + totalPages = 0; + this.nodeName = nodeName; + initialize(); + } + + private void initialize() { + if (!nodeName.isEmpty()) { + BaseChildFactory.register(nodeName, this); + } + updateControls(); + } + + void nextPage() { + currentPage++; + postPageChangeEvent(); + } + + void previousPage() { + currentPage--; + postPageChangeEvent(); + } + + @NbBundle.Messages({"# {0} - totalPages", + "DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}", + "DataResultViewerTable.goToPageTextField.err=Invalid page number"}) + void gotoPage() { + int originalPage = currentPage; + + try { + currentPage = Integer.decode(gotoPageTextField.getText()); + } catch (NumberFormatException e) { + //ignore input + return; + } + + if (currentPage > totalPages || currentPage < 1) { + currentPage = originalPage; + JOptionPane.showMessageDialog(DataResultViewerTable.this, + Bundle.DataResultViewerTable_goToPageTextField_msgDlg(totalPages), + Bundle.DataResultViewerTable_goToPageTextField_err(), + JOptionPane.WARNING_MESSAGE); + return; + } + postPageChangeEvent(); + } + + /** + * Notify subscribers (i.e. child factories) that a page change has + * occurred. + */ + void postPageChangeEvent() { + try { + BaseChildFactory.post(nodeName, new PageChangeEvent(currentPage)); + } catch (BaseChildFactory.NoSuchEventBusException ex) { + LOGGER.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS + } + DataResultViewerTable.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + updateControls(); + } + + /** + * Notify subscribers (i.e. child factories) that a page size change has + * occurred. + */ + void postPageSizeChangeEvent() { + // Reset page variables when page size changes + currentPage = 1; + + if (this == pagingSupport) { + updateControls(); + } + try { + BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize())); + } catch (BaseChildFactory.NoSuchEventBusException ex) { + LOGGER.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS + } + } + + /** + * Subscribe to notification that the number of pages has changed. + * + * @param event + */ + @Subscribe + public void subscribeToPageCountChange(PageCountChangeEvent event) { + if (event != null) { + totalPages = event.getPageCount(); + if (totalPages > 1) { + // Make paging controls visible if there is more than one page. + togglePageControls(true); + } + + // Only update UI controls if this event is for the node currently being viewed. + if (nodeName.equals(rootNode.getName())) { + updateControls(); + } + } + } + + /** + * Make paging controls visible or invisible based on flag. + * + * @param onOff + */ + private void togglePageControls(boolean onOff) { + pageLabel.setVisible(onOff); + pagesLabel.setVisible(onOff); + pagePrevButton.setVisible(onOff); + pageNextButton.setVisible(onOff); + pageNumLabel.setVisible(onOff); + gotoPageLabel.setVisible(onOff); + gotoPageTextField.setVisible(onOff); + gotoPageTextField.setVisible(onOff); + validate(); + repaint(); + } + + @NbBundle.Messages({"# {0} - currentPage", "# {1} - totalPages", + "DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}"}) + private void updateControls() { + if (totalPages == 0) { + pagePrevButton.setEnabled(false); + pageNextButton.setEnabled(false); + pageNumLabel.setText(""); + gotoPageTextField.setText(""); + gotoPageTextField.setEnabled(false); + } else { + pageNumLabel.setText(Bundle.DataResultViewerTable_pageNumbers_curOfTotal(Integer.toString(currentPage), Integer.toString(totalPages))); + + pageNextButton.setEnabled(currentPage != totalPages); + pagePrevButton.setEnabled(currentPage != 1); + gotoPageTextField.setEnabled(totalPages > 1); + gotoPageTextField.setText(""); + } + } + } /** * Listener which sets the custom icon renderer on columns which contain @@ -1214,9 +1383,62 @@ public enum Score { // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { + pageLabel = new javax.swing.JLabel(); + pageNumLabel = new javax.swing.JLabel(); + pagesLabel = new javax.swing.JLabel(); + pagePrevButton = new javax.swing.JButton(); + pageNextButton = new javax.swing.JButton(); outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); + gotoPageLabel = new javax.swing.JLabel(); + gotoPageTextField = new javax.swing.JTextField(); exportCSVButton = new javax.swing.JButton(); + pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N + + pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNumLabel.text")); // NOI18N + + pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagesLabel.text")); // NOI18N + + pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N + pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagePrevButton.text")); // NOI18N + pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N + pagePrevButton.setFocusable(false); + pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23)); + pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N + pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + pagePrevButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pagePrevButtonActionPerformed(evt); + } + }); + + pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N + pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNextButton.text")); // NOI18N + pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N + pageNextButton.setFocusable(false); + pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23)); + pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23)); + pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N + pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + pageNextButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pageNextButtonActionPerformed(evt); + } + }); + + gotoPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageLabel.text")); // NOI18N + + gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageTextField.text")); // NOI18N + gotoPageTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + gotoPageTextFieldActionPerformed(evt); + } + }); + exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N exportCSVButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -1230,20 +1452,61 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap() + .addComponent(pageLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(14, 14, 14) + .addComponent(pagesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(gotoPageLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(exportCSVButton)) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(3, 3, 3) - .addComponent(exportCSVButton) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(pageLabel) + .addComponent(pageNumLabel) + .addComponent(pagesLabel) + .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(gotoPageLabel) + .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(exportCSVButton)) .addGap(3, 3, 3) .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {pageNextButton, pagePrevButton}); + + gotoPageLabel.getAccessibleContext().setAccessibleName(""); }// </editor-fold>//GEN-END:initComponents + private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed + pagingSupport.previousPage(); + }//GEN-LAST:event_pagePrevButtonActionPerformed + + private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed + pagingSupport.nextPage(); + }//GEN-LAST:event_pageNextButtonActionPerformed + + private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed + pagingSupport.gotoPage(); + }//GEN-LAST:event_gotoPageTextFieldActionPerformed + @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export" }) private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed @@ -1257,7 +1520,14 @@ private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//G // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton exportCSVButton; + private javax.swing.JLabel gotoPageLabel; + private javax.swing.JTextField gotoPageTextField; private org.openide.explorer.view.OutlineView outlineView; + private javax.swing.JLabel pageLabel; + private javax.swing.JButton pageNextButton; + private javax.swing.JLabel pageNumLabel; + private javax.swing.JButton pagePrevButton; + private javax.swing.JLabel pagesLabel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index b56a61ac1a3d0b4750e78856f6c10d574b49f345..c0aa235223520c93534b303043a840ac40722de0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -21,10 +21,12 @@ import java.awt.Color; import java.awt.Cursor; import java.awt.Dialog; +import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.logging.Level; import java.util.prefs.Preferences; @@ -32,7 +34,7 @@ import javax.swing.JOptionPane; import javax.swing.ListSelectionModel; import javax.swing.SortOrder; -import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import org.apache.commons.lang3.StringUtils; import org.netbeans.api.progress.ProgressHandle; import org.openide.DialogDescriptor; @@ -54,7 +56,6 @@ import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; import org.sleuthkit.autopsy.guiutils.WrapLayout; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -84,7 +85,6 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private int totalPages; private int currentPageImages; private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; - private DataResultPanel.PagingControls pagingControls = null; /** * Constructs a thumbnail result viewer, with paging support, that displays @@ -129,11 +129,6 @@ public DataResultViewerThumbnail(ExplorerManager explorerManager) { // how the components are laid out as size of the window changes. buttonBarPanel.setLayout(new WrapLayout()); } - - @Override - public void setPagingControls(DataResultPanel.PagingControls pagingControls) { - this.pagingControls = pagingControls; - } /** * This method is called from within the constructor to initialize the form. @@ -455,19 +450,8 @@ public boolean isSupported(Node selectedNode) { return (selectedNode != null); } - @Override - public void setNode(Node givenNode) { - setNode(givenNode, null); - } - @Override - public void setNode(Node givenNode, SearchResultsDTO searchResults) { - - // enable paging controls (they could have been disabled by different viewer) - if (pagingControls != null) { - pagingControls.setPageControlsEnabled(true); - } - + public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (selectionListener == null) { this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); @@ -483,11 +467,7 @@ public void setNode(Node givenNode, SearchResultsDTO searchResults) { // case where the DataResultViewerThumbnail stands along from the // DataResultViewer. See DataResultViewer setNode for more information. if (givenNode != null && givenNode.getChildren().getNodesCount() > 0) { - - rootNode = (givenNode instanceof TableFilterNode) - ? (TableFilterNode) givenNode - : new TableFilterNode(givenNode, true); - + rootNode = (TableFilterNode) givenNode; /* * Wrap the given node in a ThumbnailViewChildren that will * produce ThumbnailPageNodes with ThumbnailViewNode children @@ -541,16 +521,6 @@ private void nextPage() { if (currentPage < totalPages) { currentPage++; switchPage(); - } else { - // current page was the last thumbnail subpage. advance to the next top level - // page and go to first thumbnail subpage. For example, if we were on - // top level page 3 and thumbnail subpage 5 of 5, then go to top level - // page 4 and thumbnail sub-page 1 of 5. - if (pagingControls.getCurrentPage() < pagingControls.getTotalPages()) { - pagingControls.gotoPage( pagingControls.getCurrentPage() + 1 ); - currentPage = 1; - switchPage(); - } } } @@ -582,39 +552,56 @@ private void goToPage(String pageNumText) { switchPage(); } - private void switchPage() { - SwingUtilities.invokeLater(() -> { + private void switchPage() { + + EventQueue.invokeLater(() -> { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { + }); + + //Note the nodes factories are likely creating nodes in EDT anyway, but worker still helps + new SwingWorker<Object, Void>() { + private ProgressHandle progress; + + @Override + protected Object doInBackground() throws Exception { pagePrevButton.setEnabled(false); pageNextButton.setEnabled(false); goToPageField.setEnabled(false); - ProgressHandle progress = ProgressHandle.createHandle( + progress = ProgressHandle.createHandle( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs")); progress.start(); progress.switchToIndeterminate(); - ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager(); Node root = explorerManager.getRootContext(); Node pageNode = root.getChildren().getNodeAt(currentPage - 1); - if (pageNode != null) { - explorerManager.setExploredContext(pageNode); - currentPageImages = pageNode.getChildren().getNodesCount(); - } + explorerManager.setExploredContext(pageNode); + currentPageImages = pageNode.getChildren().getNodesCount(); + return null; + } + + @Override + protected void done() { progress.finish(); - } catch (Exception ex) { - NotifyDescriptor d - = new NotifyDescriptor.Message( - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", - ex.getMessage()), - NotifyDescriptor.ERROR_MESSAGE); - DialogDisplayer.getDefault().notify(d); - logger.log(Level.SEVERE, "Error making thumbnails: {0}", ex); //NON-NLS - } finally { setCursor(null); updateControls(); + // see if any exceptions were thrown + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + NotifyDescriptor d + = new NotifyDescriptor.Message( + NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", + ex.getMessage()), + NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(d); + logger.log(Level.SEVERE, "Error making thumbnails: {0}", ex.getMessage()); //NON-NLS + } + catch (java.util.concurrent.CancellationException ex) { + // catch and ignore if we were cancelled + } } - }); + }.execute(); + } @NbBundle.Messages({ @@ -637,10 +624,8 @@ private void updateControls() { final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; imagesRangeLabel.setText(imagesFrom + "-" + imagesTo); - - // enable "next page" button if there is either next thumbnail subpage or next top level page - pageNextButton.setEnabled(!(currentPage == totalPages && pagingControls.getCurrentPage() == pagingControls.getTotalPages())); - + + pageNextButton.setEnabled(!(currentPage == totalPages)); pagePrevButton.setEnabled(!(currentPage == 1)); goToPageField.setEnabled(totalPages > 1); sortButton.setEnabled(true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java index 645bfaadaaace9423062f563bb7a68ec4bb40f96..a981b04112497235f61cfc424090165765653811 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java @@ -28,7 +28,6 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbPreferences; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; final class ResultViewerPersistence { @@ -47,10 +46,6 @@ private ResultViewerPersistence() { static String getColumnPositionKey(TableFilterNode node, String propName) { return getColumnKeyBase(node, propName) + ".column"; } - - static String getColumnPositionKey(SearchResultsDTO searchResult, String propName) { - return getColumnKeyBase(searchResult, propName) + ".column"; - } /** * Gets a key for the given node and a property of its child nodes to store @@ -64,10 +59,6 @@ static String getColumnPositionKey(SearchResultsDTO searchResult, String propNam static String getColumnSortOrderKey(TableFilterNode node, String propName) { return getColumnKeyBase(node, propName) + ".sortOrder"; } - - static String getColumnSortOrderKey(SearchResultsDTO searchResult, String propName) { - return getColumnKeyBase(searchResult, propName) + ".sortOrder"; - } /** * Gets a key for the given node and a property of its child nodes to store @@ -81,10 +72,6 @@ static String getColumnSortOrderKey(SearchResultsDTO searchResult, String propNa static String getColumnSortRankKey(TableFilterNode node, String propName) { return getColumnKeyBase(node, propName) + ".sortRank"; } - - static String getColumnSortRankKey(SearchResultsDTO searchResult, String propName) { - return getColumnKeyBase(searchResult, propName) + ".sortRank"; - } /** * Gets a key for the given node and a property of its child nodes to store @@ -98,18 +85,10 @@ static String getColumnSortRankKey(SearchResultsDTO searchResult, String propNam static String getColumnHiddenKey(TableFilterNode node, String propName) { return getColumnKeyBase(node, propName) + ".hidden"; } - - static String getColumnHiddenKey(SearchResultsDTO searchResult, String propName) { - return getColumnKeyBase(searchResult, propName) + ".hidden"; - } private static String getColumnKeyBase(TableFilterNode node, String propName) { return stripNonAlphanumeric(node.getColumnOrderKey()) + "." + stripNonAlphanumeric(propName); } - - private static String getColumnKeyBase(SearchResultsDTO searchResult, String propName) { - return stripNonAlphanumeric(searchResult.getSignature()) + "." + stripNonAlphanumeric(propName); - } private static String stripNonAlphanumeric(String str) { return str.replaceAll("[^a-zA-Z0-9_]", ""); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form index 32e8f58d5fa9939f3b16f3a3b0b2e2528a6ad570..b2101993eae42e4efbfb048ac31854290d148c72 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.form @@ -66,6 +66,7 @@ <Group type="102" alignment="1" attributes="0"> <EmptySpace max="-2" attributes="0"/> <Group type="103" groupAlignment="1" attributes="0"> + <Component id="currentSessionSettingsPanel" max="32767" attributes="0"/> <Component id="currentCaseSettingsPanel" max="32767" attributes="0"/> <Component id="globalSettingsPanel" max="32767" attributes="0"/> </Group> @@ -79,7 +80,9 @@ <Component id="globalSettingsPanel" min="-2" max="-2" attributes="0"/> <EmptySpace type="unrelated" max="-2" attributes="0"/> <Component id="currentCaseSettingsPanel" min="-2" max="-2" attributes="0"/> - <EmptySpace max="32767" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="currentSessionSettingsPanel" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> </Group> </Group> </DimensionLayout> @@ -145,7 +148,7 @@ </Group> </Group> </Group> - <EmptySpace pref="107" max="32767" attributes="0"/> + <EmptySpace pref="94" max="32767" attributes="0"/> </Group> </Group> </DimensionLayout> @@ -422,8 +425,8 @@ </Component> <Component class="javax.swing.JSpinner" name="maxResultsSpinner"> <Properties> - <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> - <Connection code="new javax.swing.SpinnerNumberModel(0, 0, PAGE_SIZE_MAX, PAGE_SIZE_INTERVAL)" type="code"/> + <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor"> + <SpinnerModel initial="0" maximum="50000" minimum="0" numberType="java.lang.Integer" stepSize="10000" type="number"/> </Property> </Properties> <Events> @@ -496,6 +499,49 @@ </Component> </SubComponents> </Container> + <Container class="javax.swing.JPanel" name="currentSessionSettingsPanel"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Current Session Settings"> + <ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.currentSessionSettingsPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </TitledBorder> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Component id="hideRejectedResultsCheckbox" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="32767" attributes="0"/> + <Component id="hideRejectedResultsCheckbox" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JCheckBox" name="hideRejectedResultsCheckbox"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.hideRejectedResultsCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="hideRejectedResultsCheckboxActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> </SubComponents> </Container> </SubComponents> diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java index fb8f459ab030a113dcdb052b23a618568eb3ab2d..b39013a3ef8ad4cbc60297fc30cb05b8d1f7a283 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java @@ -38,9 +38,6 @@ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ViewPreferencesPanel extends JPanel implements OptionsPanel { - private static final int PAGE_SIZE_INTERVAL = 100; - private static final int PAGE_SIZE_MAX = 50000; - private final boolean immediateUpdates; /** @@ -107,6 +104,9 @@ public void load() { radioGroupByDataType.setEnabled(false); radioGroupByPersonHost.setEnabled(false); } + + // Current Session Settings + hideRejectedResultsCheckbox.setSelected(DirectoryTreeTopComponent.getDefault().getShowRejectedResults() == false); } @Override @@ -126,6 +126,8 @@ public void store() { UserPreferences.setResultsTablePageSize((int) maxResultsSpinner.getValue()); storeGroupItemsInTreeByDataSource(); + + DirectoryTreeTopComponent.getDefault().setShowRejectedResults(hideRejectedResultsCheckbox.isSelected() == false); } /** @@ -182,6 +184,8 @@ private void initComponents() { currentCaseSettingsPanel = new javax.swing.JPanel(); radioGroupByPersonHost = new javax.swing.JRadioButton(); radioGroupByDataType = new javax.swing.JRadioButton(); + currentSessionSettingsPanel = new javax.swing.JPanel(); + hideRejectedResultsCheckbox = new javax.swing.JCheckBox(); setMinimumSize(new java.awt.Dimension(727, 520)); setPreferredSize(new java.awt.Dimension(727, 520)); @@ -302,7 +306,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { org.openide.awt.Mnemonics.setLocalizedText(maxResultsLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.maxResultsLabel.text")); // NOI18N maxResultsLabel.setToolTipText(org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.maxResultsLabel.toolTipText")); // NOI18N - maxResultsSpinner.setModel(new javax.swing.SpinnerNumberModel(0, 0, PAGE_SIZE_MAX, PAGE_SIZE_INTERVAL)); + maxResultsSpinner.setModel(new javax.swing.SpinnerNumberModel(0, 0, 50000, 10000)); maxResultsSpinner.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent evt) { maxResultsSpinnerStateChanged(evt); @@ -350,7 +354,7 @@ public void stateChanged(javax.swing.event.ChangeEvent evt) { .addComponent(displayTimeLabel) .addComponent(selectFileLabel) .addComponent(translateTextLabel)))) - .addContainerGap(107, Short.MAX_VALUE)) + .addContainerGap(94, Short.MAX_VALUE)) ); globalSettingsPanelLayout.setVerticalGroup( globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -442,6 +446,31 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGap(0, 6, Short.MAX_VALUE)) ); + currentSessionSettingsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.currentSessionSettingsPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(hideRejectedResultsCheckbox, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.hideRejectedResultsCheckbox.text")); // NOI18N + hideRejectedResultsCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + hideRejectedResultsCheckboxActionPerformed(evt); + } + }); + + javax.swing.GroupLayout currentSessionSettingsPanelLayout = new javax.swing.GroupLayout(currentSessionSettingsPanel); + currentSessionSettingsPanel.setLayout(currentSessionSettingsPanelLayout); + currentSessionSettingsPanelLayout.setHorizontalGroup( + currentSessionSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(currentSessionSettingsPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(hideRejectedResultsCheckbox) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + currentSessionSettingsPanelLayout.setVerticalGroup( + currentSessionSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(currentSessionSettingsPanelLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(hideRejectedResultsCheckbox)) + ); + javax.swing.GroupLayout viewPreferencesPanelLayout = new javax.swing.GroupLayout(viewPreferencesPanel); viewPreferencesPanel.setLayout(viewPreferencesPanelLayout); viewPreferencesPanelLayout.setHorizontalGroup( @@ -449,6 +478,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, viewPreferencesPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(viewPreferencesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(currentSessionSettingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(currentCaseSettingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(globalSettingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) @@ -459,7 +489,9 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(globalSettingsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(currentCaseSettingsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(currentSessionSettingsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) ); viewPreferencesScrollPane.setViewportView(viewPreferencesPanel); @@ -476,6 +508,14 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { ); }// </editor-fold>//GEN-END:initComponents + private void hideRejectedResultsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hideRejectedResultsCheckboxActionPerformed + if (immediateUpdates) { + DirectoryTreeTopComponent.getDefault().setShowRejectedResults(hideRejectedResultsCheckbox.isSelected() == false); + } else { + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + }//GEN-LAST:event_hideRejectedResultsCheckboxActionPerformed + private void maxResultsSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_maxResultsSpinnerStateChanged if (immediateUpdates) { UserPreferences.setResultsTablePageSize((int) maxResultsSpinner.getValue()); @@ -610,6 +650,7 @@ private void radioGroupByPersonHostActionPerformed(java.awt.event.ActionEvent ev // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup curCaseRadioGroup; private javax.swing.JPanel currentCaseSettingsPanel; + private javax.swing.JPanel currentSessionSettingsPanel; private javax.swing.JCheckBox dataSourcesHideKnownCheckbox; private javax.swing.JCheckBox dataSourcesHideSlackCheckbox; private javax.swing.JLabel displayTimeLabel; @@ -618,6 +659,7 @@ private void radioGroupByPersonHostActionPerformed(java.awt.event.ActionEvent ev private javax.swing.JLabel hideKnownFilesLabel; private javax.swing.JCheckBox hideOtherUsersTagsCheckbox; private javax.swing.JLabel hideOtherUsersTagsLabel; + private javax.swing.JCheckBox hideRejectedResultsCheckbox; private javax.swing.JLabel hideSlackFilesLabel; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JRadioButton keepCurrentViewerRadioButton; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 34323583b1db901550522b320a9be01b1f323b0c..3058ce485d9824e1fbe1ee869008c00dab561d06 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -126,9 +126,9 @@ public class ImageUtils { if (OpenCvLoader.openCvIsLoaded()) { try { if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS - System.loadLibrary("opencv_ffmpeg3416_64"); //NON-NLS + System.loadLibrary("opencv_ffmpeg2413_64"); //NON-NLS } else { - System.loadLibrary("opencv_ffmpeg3416"); //NON-NLS + System.loadLibrary("opencv_ffmpeg2413"); //NON-NLS } tempFfmpegLoaded = true; } catch (UnsatisfiedLinkError e) { diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java index b519c50d39cc180fd4e620ab41eb39eba18f86b7..1cb0a40d35eeed82531491bb288ec6efa18757a9 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/VideoUtils.java @@ -20,14 +20,8 @@ import com.google.common.io.Files; import java.awt.image.BufferedImage; -import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; @@ -37,13 +31,7 @@ import java.util.logging.Level; import org.netbeans.api.progress.ProgressHandle; import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; -import org.opencv.videoio.VideoCapture; -import org.opencv.videoio.VideoWriter; -import org.opencv.videoio.Videoio; -import org.openide.modules.InstalledFileLocator; -import org.openide.util.Exceptions; +import org.opencv.highgui.VideoCapture; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -57,9 +45,6 @@ */ public class VideoUtils { - private static final String FFMPEG = "ffmpeg"; - private static final String FFMPEG_EXE = "ffmpeg.exe"; - private static final List<String> SUPPORTED_VIDEO_EXTENSIONS = Arrays.asList("mov", "m4v", "flv", "mp4", "3gp", "avi", "mpg", //NON-NLS "mpeg", "asf", "divx", "rm", "moov", "wmv", "vob", "dat", //NON-NLS @@ -98,8 +83,8 @@ public static SortedSet<String> getSupportedVideoMimeTypes() { private static final int CV_CAP_PROP_POS_MSEC = 0; private static final int CV_CAP_PROP_FRAME_COUNT = 7; private static final int CV_CAP_PROP_FPS = 5; - - private static final double[] FRAME_GRAB_POS_RATIO = {0.50, 0.25, 0.75, 0.01}; + + private static final double[] FRAME_GRAB_POS_RATIO = { 0.50, 0.25, 0.75, 0.01 }; static final Logger LOGGER = Logger.getLogger(VideoUtils.class.getName()); @@ -126,10 +111,10 @@ public static boolean isVideoThumbnailSupported(AbstractFile file) { /** * Generate a thumbnail for the supplied video. - * - * @param file The video file. + * + * @param file The video file. * @param iconSize The target icon size in pixels. - * + * * @return The generated thumbnail. Can return null if an error occurred * trying to generate the thumbnail, or if the current thread was * interrupted. @@ -176,7 +161,7 @@ static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { if (Thread.interrupted()) { return null; } - + double duration = 1000 * (totalFrames / fps); //total milliseconds /* @@ -187,18 +172,19 @@ static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { * be tried in a last-ditch effort, the idea being the video may be * corrupt and that our best chance at retrieving a frame is early * on in the video. - * + * * If no frame can be retrieved, no thumbnail will be created. */ - int[] framePositions = new int[]{ + int[] framePositions = new int[] { (int) (duration * FRAME_GRAB_POS_RATIO[0]), (int) (duration * FRAME_GRAB_POS_RATIO[1]), (int) (duration * FRAME_GRAB_POS_RATIO[2]), - (int) (duration * FRAME_GRAB_POS_RATIO[3]),}; + (int) (duration * FRAME_GRAB_POS_RATIO[3]), + }; Mat imageMatrix = new Mat(); - - for (int i = 0; i < framePositions.length; i++) { + + for (int i=0; i < framePositions.length; i++) { if (!videoFile.set(CV_CAP_PROP_POS_MSEC, framePositions[i])) { LOGGER.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS // If we can't set the time, continue to the next frame position and try again. @@ -210,15 +196,15 @@ static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { // If the image is bad for some reason, continue to the next frame position and try again. continue; } - + break; } - + // If the image is empty, return since no buffered image can be created. if (imageMatrix.empty()) { return null; } - + int matrixColumns = imageMatrix.cols(); int matrixRows = imageMatrix.rows(); @@ -249,280 +235,6 @@ static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { return bufferedImage == null ? null : ScalrWrapper.resizeFast(bufferedImage, iconSize); } - public static boolean canCompressAndScale(AbstractFile file) { - - if (PlatformUtil.getOSName().toLowerCase().startsWith("windows")) { - return isVideoThumbnailSupported(file); - } - - return false; - } - - /** - * Compress the given the files. This method takes advantage of the exiting - * getVideoFile method to create a temp copy of the inputFile. It does not - * delete the temp file, it leaves it in the video temp file for future use. - * - * When using this method there is no way to cancel the process between the - * creation of the temp file and the launching of the process. For better - * control use the other compressVideo method. - * - * @param inputFile The AbstractFile representing the video. - * @param outputFile Output file. - * @param terminator A processTerminator for the ffmpeg executable. - * @param logFileDirectory A file to send the output of ffmpeg stdout. - * - * @return The ffmpeg process exit value. - * - * @throws IOException - */ - static public int compressVideo(AbstractFile inputFile, File outputFile, ExecUtil.ProcessTerminator terminator, File logFileDirectory) throws IOException { - return compressVideo(getVideoFile(inputFile), outputFile, terminator, logFileDirectory); - } - - /** - * Compress the given the files. - * - * The output from ffmpeg seem to go to the error file not the out file. - * Text in the err file does not mean an issue occurred. - * - * @param inputFile Absolute path to input video. - * @param outputFile Path for scaled file. - * @param terminator A processTerminator for the ffmpeg executable. - * @param logFileDirectory Location to put the output of ffmpeg - * - * @return The ffmpeg process exit value. - * - * @throws IOException - */ - static public int compressVideo(File inputFile, File outputFile, ExecUtil.ProcessTerminator terminator, File logFileDirectory) throws IOException { - Path executablePath = Paths.get(FFMPEG, FFMPEG_EXE); - File exeFile = InstalledFileLocator.getDefault().locate(executablePath.toString(), VideoUtils.class.getPackage().getName(), true); - if (exeFile == null) { - throw new IOException("Unable to compress ffmpeg.exe was not found."); - } - - if (!exeFile.canExecute()) { - throw new IOException("Unable to compress ffmpeg.exe could not be execute"); - } - - if (outputFile.exists()) { - throw new IOException(String.format("Failed to compress %s, output file already exists %s", inputFile.toString(), outputFile.toString())); - } - - File ffmpegStderr = null; - File ffmpegStdout = null; - - // In case the folder doesn't exist. - if(logFileDirectory != null) { - logFileDirectory.mkdirs(); - ffmpegStderr = File.createTempFile("ffmpegstderr", ".txt", logFileDirectory); - ffmpegStdout = File.createTempFile("ffmpegstdout", ".txt", logFileDirectory); - } else { - throw new IOException("Invalid output file location null"); - } - - ProcessBuilder processBuilder = buildProcessWithRunAsInvoker( - "\"" + exeFile.getAbsolutePath() + "\"", - "-i", "\"" + inputFile.toString() + "\"", - "-vcodec", "libx264", - "-crf", "28", - "\"" + outputFile.toString() + "\""); - - processBuilder.redirectError(ffmpegStderr); - processBuilder.redirectOutput(ffmpegStdout); - - if (terminator == null) { - return ExecUtil.execute(processBuilder); - } - - return ExecUtil.execute(processBuilder, terminator); - } - - /** - * Returns a File object representing a temporary copy of the video file - * representing by the AbstractFile object. - * - * @param file - * - * @return - */ - static File getVideoFile(AbstractFile file) { - java.io.File tempFile; - - try { - tempFile = getVideoFileInTempDir(file); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS - return null; - } - if (tempFile.exists() == false || tempFile.length() < file.getSize()) { - ProgressHandle progress = ProgressHandle.createHandle(Bundle.VideoUtils_genVideoThumb_progress_text(file.getName())); - progress.start(100); - try { - Files.createParentDirs(tempFile); - if (Thread.interrupted()) { - return null; - } - ContentUtils.writeToFile(file, tempFile, progress, null, true); - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS - } finally { - progress.finish(); - } - } - - return tempFile; - } - - static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) { - ProcessBuilder processBuilder = new ProcessBuilder(commandLine); - /* - * Add an environment variable to force aLeapp to run with the same - * permissions Autopsy uses. - */ - processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS - return processBuilder; - } - - // Defaults for the screen capture methods. - private static final long MIN_FRAME_INTERVAL_MILLIS = 500; - private static final int DEFAULT_FRAMES_PER_SECOND = 1; - private static final int DEFAULT_TOTAL_FRAMES = 100; - private static final int DEFAULT_SCALE = 100; - - /** - * Creates an output video containing a series of screen captures from the - * input video. The output file will be an avi with a unique name based on - * the input file. - * - * @param inputFile File representing the input video. - * @param outDirectory The directory for the output file. - * - * @return The newly created file. - * - * @throws IOException - */ - static public File createScreenCaptureVideo(File inputFile, File outDirectory) throws IOException { - return createScreenCaptureVideo(inputFile, outDirectory, DEFAULT_TOTAL_FRAMES, DEFAULT_SCALE, DEFAULT_FRAMES_PER_SECOND); - } - - /** - * Creates a video containing a series of screen captures from the input - * video. - * - * @param inputFile File representing the input video. - * @param outDirectory The directory for the output file. - * @param numFrames Total number of screen captures to included in the - * video. - * @param scale Percentage to scale the screen captures. The value - * of 0 or 100 will cause no change. - * @param framesPerSecond The number of frames to show in the video each - * second The lower this value the longer the image - * will appear on the screen. - * - * @return The newly created file. - * - * @throws IOException - */ - static public File createScreenCaptureVideo(File inputFile, File outDirectory, long numFrames, double scale, int framesPerSecond) throws IOException { - - if (!outDirectory.exists()) { - outDirectory.mkdirs(); - } else if (!outDirectory.isDirectory()) { - throw new IOException(String.format("The passed in outDir is not a directory", outDirectory.getName())); - } - - if (!inputFile.exists() || !inputFile.canRead()) { - throw new IOException(String.format("Failed to compress %s, input file cannot be read.", inputFile.getName())); - } - - String file_name = inputFile.toString();//OpenCV API requires string for file name - VideoCapture videoCapture = new VideoCapture(file_name); //VV will contain the videos - - if (!videoCapture.isOpened()) //checks if file is not open - { - // add this file to the set of known bad ones - throw new IOException("Problem with video file; problem when attempting to open file."); - } - VideoWriter writer = null; - try { - // get the duration of the video - double fps = framesPerSecond == 0 ? videoCapture.get(Videoio.CAP_PROP_FPS) : framesPerSecond; // gets frame per second - double total_frames = videoCapture.get(7); // gets total frames - double milliseconds = 1000 * (total_frames / fps); //convert to ms duration - long myDurationMillis = (long) milliseconds; - - if (myDurationMillis <= 0) { - throw new IOException(String.format("Failed to make snapshot video, original video has no duration. %s", inputFile.toString())); - } - - // calculate the number of frames to capture - int numFramesToGet = (int) numFrames; - long frameInterval = myDurationMillis / numFrames; - - if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { - numFramesToGet = 1; - } - - // for each timeStamp, grab a frame - Mat mat = new Mat(); // holds image received from video in mat format (opencv format) - - Size s = new Size((int) videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH), (int) videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT)); - Size newSize = (scale == 0 || scale == 100) ? s : new Size((int) (videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH) * scale), (int) (videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT) * scale)); - - File outputFile = createEmptyOutputFile(inputFile, outDirectory, ".avi"); - writer = new VideoWriter(outputFile.toString(), VideoWriter.fourcc('M', 'J', 'P', 'G'), 1, newSize, true); - - if (!writer.isOpened()) { - outputFile.delete(); - throw new IOException(String.format("Problem with video file; problem when attempting to open output file. %s", outputFile.toString())); - } - - for (int frame = 0; frame < numFramesToGet; ++frame) { - long timeStamp = frame * frameInterval; - videoCapture.set(0, timeStamp); //set video in timeStamp ms - - if (!videoCapture.read(mat)) { // on Wav files, usually the last frame we try to read does not succeed. - continue; - } - - Mat resized = new Mat(); - Imgproc.resize(mat, resized, newSize, 0, 0, Imgproc.INTER_CUBIC); - writer.write(resized); - } - - return outputFile; - - } finally { - videoCapture.release(); - if (writer != null) { - writer.release(); - } - } - } - - /** - * Generates an empty at the given outputDirectory location with a unique - * name based on the inputFile name with the given extension. - * - * @param inputFile InputFile to base new file name on. - * @param outputDirectory The directory the new file should be created in. - * @param extension The extension the new file should have. - * - * @return The file. - * - * @throws IOException - */ - private static File createEmptyOutputFile(File inputFile, File outputDirectory, String extension) throws IOException { - Path path = Paths.get(inputFile.getAbsolutePath()); - String fileName = path.getFileName().toString(); - if (fileName.contains(".")) { - fileName = fileName.substring(0, fileName.lastIndexOf('.')); - } - return File.createTempFile(fileName, extension, outputDirectory); - } - /** * Gets a File object in the temp directory of the current case for the * given AbstractFile object. @@ -542,5 +254,5 @@ public static File getTempVideoFile(AbstractFile file) { throw new IllegalStateException(ex); } } - + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 9df546d113fe7bac62de8608a1e50f67af6d27a8..aed26a0b3795abfbda23bbf7f94ec9cf75c51d1c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -50,6 +50,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory.RefreshKeysEvent; import org.sleuthkit.autopsy.ingest.IngestManager; import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.CONTENT_CHANGED; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; @@ -63,8 +65,6 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; -import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; -import org.sleuthkit.autopsy.datamodel.BaseChildFactory.RefreshKeysEvent; import org.sleuthkit.autopsy.texttranslation.utils.FileNameTranslationUtil; import org.sleuthkit.datamodel.Score; import org.sleuthkit.datamodel.VirtualDirectory; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index bc2c7dc86a9fca39d88eb1d4bec5071ddd95b4e5..82c7418a7f4f7ee3ec46765a970faf7ca50b5500 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -20,19 +20,26 @@ import org.openide.nodes.Node; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; /** - * Abstract subclass for ContentChildren implementation that handles creating - * Nodes from Content objects. + * Abstract subclass for ContentChildren implementation + * that handles creating Nodes from Content objects. */ abstract class AbstractContentChildren<T extends Content> extends BaseChildFactory<T> { + private final CreateSleuthkitNodeVisitor createSleuthkitNodeVisitor = new CreateSleuthkitNodeVisitor(); + AbstractContentChildren(String nodeName) { super(nodeName, new DataSourcesKnownAndSlackFilter<>()); } @Override protected Node createNodeForKey(T key) { - return RootContentChildren.createNode(key); + if (key instanceof SleuthkitVisitableItem) { + return ((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor); + } else { + return null; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 1f581b8d6b62e33e53d54bae9fa0dd7cdc375329..b81a495e429a36dbd6e0c2575afe16ab9bbfd78e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -36,11 +36,8 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemContentSearchParam; -import org.sleuthkit.autopsy.corecomponents.SelectionResponder; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Score; @@ -56,7 +53,7 @@ * * @param <T> type of wrapped Content */ -public abstract class AbstractContentNode<T extends Content> extends ContentNode implements SelectionResponder { +public abstract class AbstractContentNode<T extends Content> extends ContentNode { /** * Underlying Sleuth Kit Content object @@ -118,11 +115,6 @@ enum NodeSpecificEvents { //super.setName(ContentUtils.getSystemName(content)); super.setName("content_" + Long.toString(content.getId())); //NON-NLS } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayFileSystemContent(new FileSystemContentSearchParam(content.getId())); - } /** * Return the content data associated with this node diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResultItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResultItem.java index 8970c036efbe79eb792d3056873f740d4f5c0533..a704bb8693576522d69108d3af000220e0788740 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResultItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResultItem.java @@ -36,7 +36,7 @@ public class AnalysisResultItem extends BlackboardArtifactItem<AnalysisResult> { * @param sourceContent The source content of the AnalysisResult. */ @Beta - public AnalysisResultItem(AnalysisResult analysisResult, Content sourceContent) { + AnalysisResultItem(AnalysisResult analysisResult, Content sourceContent) { super(analysisResult, sourceContent); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResults.java b/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResults.java new file mode 100644 index 0000000000000000000000000000000000000000..fc51d97cc9f7d278206f9e247c5f096e0a7964f4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AnalysisResults.java @@ -0,0 +1,95 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.openide.nodes.Children; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Analysis Results node support. + */ +@NbBundle.Messages({ + "AnalysisResults_name=Analysis Results",}) +public class AnalysisResults implements AutopsyVisitableItem { + + /** + * Returns the name of this node that is the key in the children object. + * + * @return The name of this node that is the key in the children object. + */ + public static String getName() { + return Bundle.AnalysisResults_name(); + } + + /** + * Parent node of all analysis results. + */ + static class RootNode extends Artifacts.BaseArtifactNode { + + /** + * Main constructor. + * + * @param filteringDSObjId The data source object id for which results + * should be filtered. If no filtering should + * occur, this number should be less than or + * equal to 0. + */ + RootNode(long filteringDSObjId) { + super(Children.create(new Artifacts.TypeFactory(BlackboardArtifact.Category.ANALYSIS_RESULT, filteringDSObjId), true), + "org/sleuthkit/autopsy/images/analysis_result.png", + AnalysisResults.getName(), + AnalysisResults.getName()); + } + } + + private final long datasourceObjId; + + /** + * Main constructor. + */ + public AnalysisResults() { + this(0); + } + + /** + * Main constructor. + * + * @param dsObjId The data source object id. + */ + public AnalysisResults(long dsObjId) { + this.datasourceObjId = dsObjId; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * Returns whether or not there is a data source object for which results + * should be filtered. + * + * @return Whether or not there is a data source object for which results + * should be filtered. + */ + Long getFilteringDataSourceObjId() { + return datasourceObjId; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactNodeSelectionInfo.java b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactNodeSelectionInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..0bb113d484aec397729765b566777a9620daed51 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactNodeSelectionInfo.java @@ -0,0 +1,56 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.openide.nodes.Node; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Stores sufficient information to identify a blackboard artifact node that is + * intended to be selected in a view. + */ +public class ArtifactNodeSelectionInfo implements NodeSelectionInfo { + + private final long artifactId; + + /** + * Constructs an object that stores sufficient information to identify a + * blackboard artifact node that is intended to be selected in a view. + * + * @param artifact The artifact represented by the node to be selected. + */ + public ArtifactNodeSelectionInfo(BlackboardArtifact artifact) { + this.artifactId = artifact.getArtifactID(); + } + + /** + * Determines whether or not a given node satisfies the stored node + * selection criteria. + * + * @param candidateNode A node to evaluate. + * + * @return True or false. + */ + @Override + public boolean matches(Node candidateNode) { + BlackboardArtifact artifact = candidateNode.getLookup().lookup(BlackboardArtifact.class); + return artifact.getArtifactID() == artifactId; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java new file mode 100644 index 0000000000000000000000000000000000000000..ea3fa670601d268ccdaf6e65fb4d72f2ce55f65b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java @@ -0,0 +1,204 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.logging.Level; +import org.apache.commons.lang.StringUtils; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * An HTML representation of an artifact. The representation is plain vanilla + * HTML, so any styling needs to be supplied by the display mechansim. For + * example, GUI components such as content viewers might use HTMLEditorKit to + * add styling. + * + * @deprecated - No longer used by DataContentViewerArtifact because the table is no longer HTML + */ +@Deprecated +public class ArtifactStringContent implements StringContent { + + private final static Logger logger = Logger.getLogger(ArtifactStringContent.class.getName()); + private final BlackboardArtifact artifact; + private String stringContent = ""; + + /** + * Constructs an HTML representation of an artifact. The representation is + * plain vanilla HTML, so any styling needs to be supplied by the display + * mechansim. For example, GUI components such as content viewers might use + * HTMLEditorKit to add styling. + * + * @param artifact The artifact to be represented as HTML. + */ + public ArtifactStringContent(BlackboardArtifact artifact) { + this.artifact = artifact; + } + + /** + * Gets the HTML representation of the artifact. + * + * @return The HTML representation of the artifact as a string. + */ + @Messages({ + "ArtifactStringContent.attrsTableHeader.type=Type", + "ArtifactStringContent.attrsTableHeader.value=Value", + "ArtifactStringContent.attrsTableHeader.sources=Source(s)", + "ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database", + "ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database" + }) + @Override + public String getString() { + if (stringContent.isEmpty()) { + /* + * Start the document. + */ + StringBuilder buffer = new StringBuilder(1024); + buffer.append("<html>\n"); //NON-NLS + buffer.append("<body>\n"); //NON-NLS + + /* + * Use the artifact display name as a header. + */ + buffer.append("<h3>"); //NON-NLS + buffer.append(artifact.getDisplayName()); + buffer.append("</h3>\n"); //NON-NLS + + /* + * Put the attributes, source content path and artifact id in a + * table. + */ + buffer.append("<table border='1'>"); //NON-NLS + + // header row + buffer.append("<tr>"); //NON-NLS + buffer.append("<th><b>"); //NON-NLS + buffer.append(Bundle.ArtifactStringContent_attrsTableHeader_type()); + buffer.append("</b></th>"); //NON-NLS + buffer.append("<th><b>"); //NON-NLS + buffer.append(Bundle.ArtifactStringContent_attrsTableHeader_value()); + buffer.append("</b></th>"); //NON-NLS + buffer.append("<th><b>"); //NON-NLS + buffer.append(Bundle.ArtifactStringContent_attrsTableHeader_sources()); + buffer.append("</b></th>"); //NON-NLS + buffer.append("</tr>\n"); //NON-NLS + try { + Content content = artifact.getSleuthkitCase().getContentById(artifact.getObjectID()); + + /* + * Add rows for each attribute. + */ + for (BlackboardAttribute attr : artifact.getAttributes()) { + + /* + * Attribute value column. + */ + String value = ""; + switch (attr.getAttributeType().getValueType()) { + case STRING: + case INTEGER: + case LONG: + case DOUBLE: + case BYTE: + case JSON: + default: + value = attr.getDisplayString(); + break; + + // Use Autopsy date formatting settings, not TSK defaults + case DATETIME: + long epoch = attr.getValueLong(); + value = TimeZoneUtils.getFormattedTime(epoch * 1000); + break; + } + + /* + * Attribute sources column. + */ + String sources = StringUtils.join(attr.getSources(), ", "); + buffer.append(makeTableRow(attr.getAttributeType().getDisplayName(), value, sources)); + } + + /* + * Add a row for the source content path. + */ + + String path = ""; + try { + if (null != content) { + path = content.getUniquePath(); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting source content path for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + path = Bundle.ArtifactStringContent_failedToGetSourcePath_message(); + } + + buffer.append(makeTableRow(NbBundle.getMessage(this.getClass(), "ArtifactStringContent.getStr.srcFilePath.text"), + path, "")); + + + /* + * Add a row for the artifact id. + */ + buffer.append(makeTableRow(NbBundle.getMessage(this.getClass(), "ArtifactStringContent.getStr.artifactId.text"), + Long.toString(artifact.getArtifactID()), "")); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting data for artifact (artifact_id=%d)", artifact.getArtifactID()), ex); + buffer.append(makeTableRow(Bundle.ArtifactStringContent_failedToGetAttributes_message(), "", "")); + } finally { + /* + * Finish the document + */ + buffer.append("</table>"); //NON-NLS + buffer.append("</html>\n"); //NON-NLS + stringContent = buffer.toString(); + } + } + + return stringContent; + } + + // escape special HTML characters + private String escapeHtmlString(String str) { + str = str.replaceAll(" ", " "); //NON-NLS + str = str.replaceAll("<", "<"); //NON-NLS + str = str.replaceAll(">", ">"); //NON-NLS + str = str.replaceAll("(\r\n|\n)", "<br />"); //NON-NLS + return str; + } + + /** + * Make a row in the result table + * @param type String for column1 (Type of attribute)) + * @param value String for column2 (value of attribute) + * @param source Column 3 (attribute source) + * @return HTML formatted string of these values + */ + private String makeTableRow(String type, String value, String source) { + String row = "<tr><td>" + escapeHtmlString(type) + "</td><td>" + escapeHtmlString(value) + "</td><td>" + escapeHtmlString(source) + "</td></tr>"; + return row; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java new file mode 100644 index 0000000000000000000000000000000000000000..3dee2839996272ad689bd02beef13d9af647b64d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java @@ -0,0 +1,738 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.accounts.Accounts; +import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; +import org.sleuthkit.datamodel.BlackboardArtifact.Category; +import org.python.google.common.collect.Sets; +import org.sleuthkit.datamodel.Blackboard; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ACCOUNT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_DATA_SOURCE_USAGE; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_EMAIL_MSG; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_HASHSET_HIT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_GEN_INFO; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_INTERESTING_ITEM; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_KEYWORD_HIT; + +/** + * Classes for creating nodes for BlackboardArtifacts. + */ +public class Artifacts { + + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST + = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + + /** + * Base class for a parent node of artifacts. + */ + static class BaseArtifactNode extends DisplayableItemNode { + + /** + * Main constructor. + * + * @param children The children of the node. + * @param icon The icon for the node. + * @param name The name identifier of the node. + * @param displayName The display name for the node. + */ + BaseArtifactNode(Children children, String icon, String name, String displayName) { + super(children, Lookups.singleton(name)); + super.setName(name); + super.setDisplayName(displayName); + this.setIconBaseWithExtension(icon); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.desc"), + super.getDisplayName())); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * A key to be used with the type factory. + */ + private static class TypeNodeKey { + + private final UpdatableCountTypeNode node; + private final Set<BlackboardArtifact.Type> applicableTypes; + + /** + * Constructor generating a generic TypeNode for a given artifact type. + * + * @param type The type for the key. + * @param dsObjId The data source object id if filtering should occur. + * If no filtering should occur, this number should be + * less than or equal to 0. + */ + TypeNodeKey(BlackboardArtifact.Type type, long dsObjId) { + this(new TypeNode(type, dsObjId), type); + } + + /** + * Constructor for any UpdatableCountTypeNode. + * + * @param typeNode The UpdatableCountTypeNode. + * @param types The blackboard artifact types corresponding to this + * node. + */ + TypeNodeKey(UpdatableCountTypeNode typeNode, BlackboardArtifact.Type... types) { + this.node = typeNode; + this.applicableTypes = Stream.of(types) + .filter(t -> t != null) + .collect(Collectors.toSet()); + } + + /** + * Returns the node associated with this key. + * + * @return The node associated with this key. + */ + UpdatableCountTypeNode getNode() { + return node; + } + + /** + * Returns the blackboard artifact types associated with this key. + * + * @return The blackboard artifact types associated with this key. + */ + Set<BlackboardArtifact.Type> getApplicableTypes() { + return applicableTypes; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 61 * hash + Objects.hashCode(this.applicableTypes); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TypeNodeKey other = (TypeNodeKey) obj; + if (!Objects.equals(this.applicableTypes, other.applicableTypes)) { + return false; + } + return true; + } + + } + + /** + * Factory for showing a list of artifact types (i.e. all the data artifact + * types). + */ + static class TypeFactory extends ChildFactory.Detachable<TypeNodeKey> implements RefreshThrottler.Refresher { + + private static final Logger logger = Logger.getLogger(TypeNode.class.getName()); + + /** + * Types that should not be shown in the tree. + */ + @SuppressWarnings("deprecation") + private static final Set<BlackboardArtifact.Type> IGNORED_TYPES = Sets.newHashSet( + // these are shown in other parts of the UI (and different node types) + TSK_DATA_SOURCE_USAGE, + TSK_GEN_INFO, + new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE), + TSK_TL_EVENT, + //This is not meant to be shown in the UI at all. It is more of a meta artifact. + TSK_ASSOCIATED_OBJECT + ); + + /** + * Returns a Children key to be use for a particular artifact type. + * + * @param type The artifact type. + * @param skCase The relevant Sleuthkit case in order to create the + * node. + * @param dsObjId The data source object id to use for filtering. If id + * is less than or equal to 0, no filtering will occur. + * + * @return The generated key. + * + * @SuppressWarnings("deprecation") - we need to support already + * existing interesting file and artifact hits. + */ + @SuppressWarnings("deprecation") + private static TypeNodeKey getTypeKey(BlackboardArtifact.Type type, SleuthkitCase skCase, long dsObjId) { + int typeId = type.getTypeID(); + if (TSK_EMAIL_MSG.getTypeID() == typeId) { + EmailExtracted.RootNode emailNode = new EmailExtracted(skCase, dsObjId).new RootNode(); + return new TypeNodeKey(emailNode, TSK_EMAIL_MSG); + + } else if (TSK_ACCOUNT.getTypeID() == typeId) { + Accounts.AccountsRootNode accountsNode = new Accounts(dsObjId).new AccountsRootNode(); + return new TypeNodeKey(accountsNode, TSK_ACCOUNT); + + } else if (TSK_KEYWORD_HIT.getTypeID() == typeId) { + KeywordHits.RootNode keywordsNode = new KeywordHits(skCase, dsObjId).new RootNode(); + return new TypeNodeKey(keywordsNode, TSK_KEYWORD_HIT); + + } else if (TSK_INTERESTING_ITEM.getTypeID() == typeId) { + InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, TSK_INTERESTING_ITEM, dsObjId).new RootNode(); + return new TypeNodeKey(interestingHitsNode, TSK_INTERESTING_ITEM); + } else if (TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == typeId) { + InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, TSK_INTERESTING_ARTIFACT_HIT, dsObjId).new RootNode(); + return new TypeNodeKey(interestingHitsNode, TSK_INTERESTING_ARTIFACT_HIT); + } else if (TSK_INTERESTING_FILE_HIT.getTypeID() == typeId) { + InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, TSK_INTERESTING_FILE_HIT, dsObjId).new RootNode(); + return new TypeNodeKey(interestingHitsNode, TSK_INTERESTING_FILE_HIT); + } else if (TSK_HASHSET_HIT.getTypeID() == typeId) { + HashsetHits.RootNode hashsetHits = new HashsetHits(skCase, dsObjId).new RootNode(); + return new TypeNodeKey(hashsetHits, TSK_HASHSET_HIT); + + } else { + return new TypeNodeKey(type, dsObjId); + } + } + + // maps the artifact type to its child node + private final Map<BlackboardArtifact.Type, TypeNodeKey> typeNodeMap = new HashMap<>(); + private final long filteringDSObjId; + + /** + * RefreshThrottler is used to limit the number of refreshes performed + * when CONTENT_CHANGED and DATA_ADDED ingest module events are + * received. + */ + private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); + private final Category category; + + private final PropertyChangeListener weakPcl; + + /** + * Main constructor. + * + * @param category The category of types to be displayed. + * @param filteringDSObjId The data source object id to use for + * filtering. If id is less than or equal to 0, + * no filtering will occur. + */ + TypeFactory(Category category, long filteringDSObjId) { + super(); + this.filteringDSObjId = filteringDSObjId; + this.category = category; + + PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { + /** + * This 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(); + refresh(false); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + }; + + weakPcl = WeakListeners.propertyChange(pcl, null); + } + + @Override + protected void addNotify() { + super.addNotify(); + refreshThrottler.registerForIngestModuleEvents(); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + refreshThrottler.unregisterEventListener(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + typeNodeMap.clear(); + } + + @Override + protected boolean createKeys(List<TypeNodeKey> list) { + try { + // Get all types in use + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + List<BlackboardArtifact.Type> types = (this.filteringDSObjId > 0) + ? skCase.getBlackboard().getArtifactTypesInUse(this.filteringDSObjId) + : skCase.getArtifactTypesInUse(); + + List<TypeNodeKey> allKeysSorted = types.stream() + // filter types by category and ensure they are not in the list of ignored types + .filter(tp -> category.equals(tp.getCategory()) && !IGNORED_TYPES.contains(tp)) + .map(tp -> { + // if typeNodeMap already contains key, update the relevant node and return the node + if (typeNodeMap.containsKey(tp)) { + TypeNodeKey typeKey = typeNodeMap.get(tp); + typeKey.getNode().updateDisplayName(); + return typeKey; + } else { + // if key is not in map, create the type key and add to map + TypeNodeKey newTypeKey = getTypeKey(tp, skCase, filteringDSObjId); + for (BlackboardArtifact.Type recordType : newTypeKey.getApplicableTypes()) { + typeNodeMap.put(recordType, newTypeKey); + } + return newTypeKey; + } + }) + // ensure record is returned + .filter(record -> record != null) + // there are potentially multiple types that apply to the same node (i.e. Interesting Files / Artifacts) + // ensure the keys are distinct + .distinct() + // sort by display name + .sorted((a, b) -> { + String aSafe = (a.getNode() == null || a.getNode().getDisplayName() == null) ? "" : a.getNode().getDisplayName(); + String bSafe = (b.getNode() == null || b.getNode().getDisplayName() == null) ? "" : b.getNode().getDisplayName(); + return aSafe.compareToIgnoreCase(bSafe); + }) + .collect(Collectors.toList()); + + list.addAll(allKeysSorted); + + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Trying to access case when no case is open.", ex); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting list of artifacts in use: " + ex.getLocalizedMessage()); //NON-NLS + } + return true; + } + + @Override + protected Node createNodeForKey(TypeNodeKey key) { + return key.getNode(); + } + + @Override + public void refresh() { + refresh(false); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { + /** + * This 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(); + /** + * Due to some unresolved issues with how cases are closed, + * it is possible for the event to have a null oldValue if + * the event is a remote event. + */ + final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); + if (null != event && category.equals(event.getBlackboardArtifactType().getCategory()) + && !(IGNORED_TYPES.contains(event.getBlackboardArtifactType()))) { + return true; + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + return false; + } + } + + /** + * Abstract class for type(s) nodes. This class allows for displaying a + * count artifacts with the type(s) associated with this node. + */ + public static abstract class UpdatableCountTypeNode extends DisplayableItemNode { + + private static final Logger logger = Logger.getLogger(UpdatableCountTypeNode.class.getName()); + + private final Set<BlackboardArtifact.Type> types; + private final long filteringDSObjId; + private long childCount = 0; + private final String baseName; + + /** + * Main constructor. + * + * @param children The Children to associated with this node. + * @param lookup The Lookup to use with this name. + * @param baseName The display name. The Node.displayName will + * be of format "[baseName] ([count])". + * @param filteringDSObjId The data source object id to use for + * filtering. If id is less than or equal to 0, + * no filtering will occur. + * @param types The types associated with this type node. + */ + public UpdatableCountTypeNode(Children children, Lookup lookup, String baseName, + long filteringDSObjId, BlackboardArtifact.Type... types) { + + super(children, lookup); + this.types = Stream.of(types).collect(Collectors.toSet()); + this.filteringDSObjId = filteringDSObjId; + this.baseName = baseName; + updateDisplayName(); + } + + /** + * Returns the count of artifacts associated with these type(s). + * + * @return The count of artifacts associated with these type(s). + */ + protected long getChildCount() { + return this.childCount; + } + + /** + * Fetches the count to be displayed from the case. + * + * @param skCase The relevant SleuthkitCase. + * + * @return The count to be displayed. + * + * @throws TskCoreException + */ + protected long fetchChildCount(SleuthkitCase skCase) throws TskCoreException { + int count = 0; + for (BlackboardArtifact.Type type : this.types) { + if (filteringDSObjId > 0) { + count += skCase.getBlackboard().getArtifactsCount(type.getTypeID(), filteringDSObjId); + } else { + count += skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); + } + } + return count; + } + + /** + * When this method is called, the count to be displayed will be + * updated. + */ + void updateDisplayName() { + try { + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + this.childCount = fetchChildCount(skCase); + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Error fetching data when case closed.", ex); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting child count", ex); //NON-NLS + } + super.setDisplayName(this.baseName + " (" + this.childCount + ")"); + } + } + + /** + * Default node encapsulating a blackboard artifact type. This is used on + * the left-hand navigation side of the Autopsy UI as the parent node for + * all of the artifacts of a given type. Its children will be + * BlackboardArtifactNode objects. + */ + static class TypeNode extends UpdatableCountTypeNode { + + private final BlackboardArtifact.Type type; + + /** + * Main constructor. + * + * @param type The blackboard artifact type for this node. + * @param filteringDSObjId The data source object id to use for + * filtering. If id is less than or equal to 0, + * no filtering will occur. + */ + TypeNode(BlackboardArtifact.Type type, long filteringDSObjId) { + super(Children.create(new ArtifactFactory(type, filteringDSObjId), true), + Lookups.singleton(type.getDisplayName()), + type.getDisplayName(), + filteringDSObjId, + type); + + super.setName(type.getTypeName()); + this.type = type; + String iconPath = IconsUtil.getIconFilePath(type.getTypeID()); + setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.name"), + NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.displayName"), + NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.desc"), + type.getDisplayName())); + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.name"), + NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.displayName"), + NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.desc"), + getChildCount())); + + return sheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + return getClass().getName() + type.getDisplayName(); + } + } + + /** + * Creates children for a given artifact type + */ + private static class ArtifactFactory extends BaseChildFactory<BlackboardArtifact> implements RefreshThrottler.Refresher { + + private static final Logger logger = Logger.getLogger(ArtifactFactory.class.getName()); + private final BlackboardArtifact.Type type; + + /** + * RefreshThrottler is used to limit the number of refreshes performed + * when CONTENT_CHANGED and DATA_ADDED ingest module events are + * received. + */ + private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); + private final long filteringDSObjId; + + /** + * Main constructor. + * + * @param type The blackboard artifact type for this node. + * @param filteringDSObjId The data source object id to use for + * filtering. If id is less than or equal to 0, + * no filtering will occur. + */ + ArtifactFactory(BlackboardArtifact.Type type, long filteringDSObjId) { + super(type.getTypeName()); + this.type = type; + this.filteringDSObjId = filteringDSObjId; + } + + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + refresh(false); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void onAdd() { + refreshThrottler.registerForIngestModuleEvents(); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + } + + @Override + protected void onRemove() { + if (refreshThrottler != null) { + refreshThrottler.unregisterEventListener(); + } + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + } + + @Override + protected Node createNodeForKey(BlackboardArtifact key) { + return new BlackboardArtifactNode(key); + } + + @Override + protected List<BlackboardArtifact> makeKeys() { + try { + List<? extends BlackboardArtifact> arts; + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); + switch (this.type.getCategory()) { + + case ANALYSIS_RESULT: + arts = (filteringDSObjId > 0) + ? blackboard.getAnalysisResultsByType(type.getTypeID(), filteringDSObjId) + : blackboard.getAnalysisResultsByType(type.getTypeID()); + break; + case DATA_ARTIFACT: + default: + arts = (filteringDSObjId > 0) + ? blackboard.getDataArtifacts(type.getTypeID(), filteringDSObjId) + : blackboard.getDataArtifacts(type.getTypeID()); + break; + } + + for (BlackboardArtifact art : arts) { + //Cache attributes while we are off the EDT. + //See JIRA-5969 + art.getAttributes(); + } + + @SuppressWarnings("unchecked") + List<BlackboardArtifact> toRet = (List<BlackboardArtifact>) (List<?>) arts; + return toRet; + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Trying to access case when no case is open.", ex); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS + } + return Collections.emptyList(); + } + + @Override + public void refresh() { + refresh(false); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that the + * case will be closed in a different thread before this + * code executes. If that happens, it is possible for the + * event to have a null oldValue. + */ + final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); + if (null != event && event.getBlackboardArtifactType().equals(type)) { + return true; + } + + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + return false; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..d433d0d6df40cd81369cdec5e01914fc3e4b8f71 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -0,0 +1,252 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.sleuthkit.autopsy.datamodel.accounts.Accounts; + +/** + * This visitor goes over the AutopsyVisitableItems, which are currently the + * nodes in the tree that are structural and not nodes that are from + * Sleuthkit-based data model objects. + */ +public interface AutopsyItemVisitor<T> { + + T visit(DataSources i); + + T visit(DataSourceGrouping datasourceGrouping); + + T visit(Views v); + + T visit(FileTypesByExtension sf); + + T visit(FileTypesByExtension.RootFilter fsf); + + T visit(FileTypesByExtension.DocumentFilter df); + + T visit(FileTypesByExtension.ExecutableFilter ef); + + T visit(RecentFiles rf); + + T visit(RecentFiles.RecentFilesFilter rff); + + T visit(DeletedContent dc); + + T visit(DeletedContent.DeletedContentFilter dcf); + + T visit(ScoreContent sc); + + T visit(ScoreContent.ScoreContentFilter scf); + + T visit(FileSize fs); + + T visit(FileSize.FileSizeFilter fsf); + + T visit(KeywordHits kh); + + T visit(HashsetHits hh); + + T visit(EmailExtracted ee); + + T visit(InterestingHits ih); + + T visit(Tags tagsNodeKey); + + T visit(Reports reportsItem); + + T visit(Accounts accountsItem); + + T visit(FileTypes fileTypesItem); + + T visit(FileTypesByMimeType aThis); + + T visit(OsAccounts osAccoutItem); + + T visit(HostGrouping aThis); + + T visit(PersonGrouping aThis); + + T visit(HostDataSources aThis); + + T visit(DataSourcesByType aThis); + + T visit(AnalysisResults aThis); + + T visit(DataArtifacts aThis); + + + static abstract public class Default<T> implements AutopsyItemVisitor<T> { + + protected abstract T defaultVisit(AutopsyVisitableItem ec); + + @Override + public T visit(FileTypesByExtension sf) { + return defaultVisit(sf); + } + + @Override + public T visit(FileTypesByExtension.RootFilter fsf) { + return defaultVisit(fsf); + } + + @Override + public T visit(FileTypesByExtension.DocumentFilter df) { + return defaultVisit(df); + } + + @Override + public T visit(FileTypesByExtension.ExecutableFilter ef) { + return defaultVisit(ef); + } + + @Override + public T visit(FileTypesByMimeType ftByMimeType) { + return defaultVisit(ftByMimeType); + } + + @Override + public T visit(DeletedContent dc) { + return defaultVisit(dc); + } + + @Override + public T visit(DeletedContent.DeletedContentFilter dcf) { + return defaultVisit(dcf); + } + + @Override + public T visit(ScoreContent dc) { + return defaultVisit(dc); + } + + @Override + public T visit(ScoreContent.ScoreContentFilter dcf) { + return defaultVisit(dcf); + } + + @Override + public T visit(FileSize fs) { + return defaultVisit(fs); + } + + @Override + public T visit(FileSize.FileSizeFilter fsf) { + return defaultVisit(fsf); + } + + @Override + public T visit(RecentFiles rf) { + return defaultVisit(rf); + } + + @Override + public T visit(RecentFiles.RecentFilesFilter rff) { + return defaultVisit(rff); + } + + @Override + public T visit(KeywordHits kh) { + return defaultVisit(kh); + } + + @Override + public T visit(HashsetHits hh) { + return defaultVisit(hh); + } + + @Override + public T visit(InterestingHits ih) { + return defaultVisit(ih); + } + + @Override + public T visit(EmailExtracted ee) { + return defaultVisit(ee); + } + + @Override + public T visit(Tags tagsNodeKey) { + return defaultVisit(tagsNodeKey); + } + + @Override + public T visit(DataSources i) { + return defaultVisit(i); + } + + @Override + public T visit(Views v) { + return defaultVisit(v); + } + + @Override + public T visit(DataSourceGrouping datasourceGrouping) { + return defaultVisit(datasourceGrouping); + } + + @Override + public T visit(HostGrouping hostGrouping) { + return defaultVisit(hostGrouping); + } + + @Override + public T visit(PersonGrouping personGrouping) { + return defaultVisit(personGrouping); + } + + @Override + public T visit(FileTypes ft) { + return defaultVisit(ft); + } + + @Override + public T visit(Reports reportsItem) { + return defaultVisit(reportsItem); + } + + @Override + public T visit(Accounts accountsItem) { + return defaultVisit(accountsItem); + } + + @Override + public T visit(OsAccounts osAccountItem) { + return defaultVisit(osAccountItem); + } + + @Override + public T visit(HostDataSources hostDataSources) { + return defaultVisit(hostDataSources); + } + + @Override + public T visit(DataSourcesByType dataSourceHosts) { + return defaultVisit(dataSourceHosts); + } + + @Override + public T visit(DataArtifacts aThis) { + return defaultVisit(aThis); + } + + @Override + public T visit(AnalysisResults aThis) { + return defaultVisit(aThis); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..f76bfe5469af3cfc576cea310615ee70185efdc2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java @@ -0,0 +1,200 @@ +/* + * Autopsy Forensic Browser + * Copyright 2018-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.apache.commons.collections.CollectionUtils; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.openide.util.WeakListeners; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CasePreferences; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.PersonManager; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A child factory to create the top level nodes in the main tree view. These + * nodes are the child nodes of the invisible root node of the tree. The child + * nodes that are created vary with the view option selected by the user: group + * by data type or group by person/host. + */ +public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Object> { + + private static final Set<Case.Events> EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, + Case.Events.HOSTS_ADDED, + Case.Events.HOSTS_DELETED, + Case.Events.PERSONS_ADDED, + Case.Events.PERSONS_DELETED, + Case.Events.HOSTS_ADDED_TO_PERSON, + Case.Events.HOSTS_REMOVED_FROM_PERSON + ); + + private static final Set<String> EVENTS_OF_INTEREST_NAMES = EVENTS_OF_INTEREST.stream() + .map(evt -> evt.name()) + .collect(Collectors.toSet()); + + private static final Logger logger = Logger.getLogger(AutopsyTreeChildFactory.class.getName()); + + /** + * Listener for application events published when persons and/or hosts are + * added to or deleted from the data model for the current case. If the user + * has selected the group by person/host option for the tree, these events + * mean that the top-level person/host nodes in the tree need to be + * refreshed to reflect the changes. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (EVENTS_OF_INTEREST_NAMES.contains(eventType) + && Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { + refreshChildren(); + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + super.addNotify(); + Case.addEventTypeSubscriber(EVENTS_OF_INTEREST, weakPcl); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Case.removeEventTypeSubscriber(EVENTS_OF_INTEREST, weakPcl); + } + + /** + * Creates the keys for the top level nodes in the main tree view. These + * nodes are the child nodes of the invisible root node of the tree. The + * child nodes that are created vary with the view option selected by the + * user: group by data type or group by person/host. + * + * IMPORTANT: Every time a key is added to the keys list, the NetBeans + * framework reacts. To avoid significant performance hits, all of the keys + * need to be added at once. + * + * @param list A list to contain the keys. + * + * @return True, indicating that the list of keys is complete. + */ + @Override + protected boolean createKeys(List<Object> list) { + List<Object> nodes = Collections.emptyList(); + try { + SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { + /* + * The user has selected the group by person/host tree view + * option. + */ + PersonManager personManager = tskCase.getPersonManager(); + List<Person> persons = personManager.getPersons(); + // show persons level if there are persons to be shown + if (!CollectionUtils.isEmpty(persons)) { + nodes = persons.stream() + .map(PersonGrouping::new) + .sorted() + .collect(Collectors.toList()); + + if (CollectionUtils.isNotEmpty(personManager.getHostsWithoutPersons())) { + nodes.add(new PersonGrouping(null)); + } + } else { + // otherwise, just show host level + nodes = tskCase.getHostManager().getAllHosts().stream() + .map(HostGrouping::new) + .sorted() + .collect(Collectors.toList()); + } + + // either way, add in reports node + nodes.add(new Reports()); + } else { + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + // data source by type view + nodes = Arrays.asList( + new DataSourcesByType(), + new Views(skCase), + new DataArtifacts(), + new AnalysisResults(), + new OsAccounts(skCase), + new Tags(), + new ScoreContent(skCase), + new Reports() + ); + } + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Failed to create tree because there is no current case", ex); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to create tree because of an error querying the case database", ex); //NON-NLS + } + + // add all nodes to the netbeans node list + list.addAll(nodes); + return true; + } + + /** + * Creates a node for a given key for the top level nodes in the main tree + * view. + * + * @param key The key. + * + * @return A node for the key. + */ + @Override + protected Node createNodeForKey(Object key) { + Node node = null; + if (key != null) { + if (key instanceof SleuthkitVisitableItem) { + node = ((SleuthkitVisitableItem) key).accept(new CreateSleuthkitNodeVisitor()); + } else if (key instanceof AutopsyVisitableItem) { + node = ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor()); + } else { + logger.log(Level.SEVERE, "Unknown key type: ", key.getClass().getName()); + } + } + return node; + } + + /** + * Refreshes the top level nodes in the main tree view. + */ + public void refreshChildren() { + refresh(true); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/SelectionResponder.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java old mode 100755 new mode 100644 similarity index 59% rename from Core/src/org/sleuthkit/autopsy/corecomponents/SelectionResponder.java rename to Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java index 91fbb1248da9572196571ccc5e3447a3efad445e..af868a68c4a60364b1b1bbd404949de65718cb54 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/SelectionResponder.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java @@ -1,33 +1,37 @@ /* * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. + * + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> 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; - -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +package org.sleuthkit.autopsy.datamodel; +; /** - * Interface for Nodes that can respond to a tree selection event. + * AutopsyVisitableItems are the nodes in the directory tree that are for + * structure only. They are not associated with content objects. */ -public interface SelectionResponder { +public interface AutopsyVisitableItem { + /** - * Method to be called on tree nodes that can handle selection. - * - * @param dataResultPanel + * visitor pattern support + * + * @param visitor visitor + * + * @return visitor return value */ - void respondSelection(DataResultTopComponent dataResultPanel); + public <T> T accept(AutopsyItemVisitor<T> visitor); + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java index 1501f6c8201a081c5459a4c75db0b64dd79eff32..c142e3c290fc9eec9bcf218c5e6ac6b9cbdc6039 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java @@ -23,7 +23,6 @@ import com.google.common.eventbus.Subscribe; import java.util.ArrayList; import java.util.Collections; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -34,7 +33,6 @@ import java.util.stream.Collectors; import org.openide.nodes.ChildFactory; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.datamodel.Content; @@ -59,14 +57,6 @@ public abstract class BaseChildFactory<T extends Content> extends ChildFactory.D * the child factory. */ private static Map<String, EventBus> nodeNameToEventBusMap = new ConcurrentHashMap<>(); - - static { - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> { - if (evt.getNewValue() == null) { - nodeNameToEventBusMap.clear(); - } - }); - } @Messages({ "# {0} - node name", "BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0}" diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 54164bbcbe308b31a80299bdf3f5420e83e72346..eb0a2e8838adb4e1115830bf9e7c08d3dc47b590 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -1391,6 +1391,11 @@ public String getItemType() { return getClass().getName(); } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Messages({ "BlackboardArtifactNode_analysisSheet_sourceType_name=Source Type", "BlackboardArtifactNode_analysisSheet_soureName_name=Source Name", diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java new file mode 100644 index 0000000000000000000000000000000000000000..76787cb5f64f2ac6712123a502f54b5f1d25d9e7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -0,0 +1,189 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import javax.swing.Action; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; +import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TskCoreException; +import static org.sleuthkit.autopsy.datamodel.Bundle.*; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataArtifact; + +/** + * Instances of this class wrap BlackboardArtifactTag objects. In the Autopsy + * presentation of the SleuthKit data model, they are leaf nodes of a sub-tree + * organized as follows: there is a tags root node with tag name child nodes; + * tag name nodes have tag type child nodes; tag type nodes are the parents of + * either content or blackboard artifact tag nodes. + */ +public class BlackboardArtifactTagNode extends TagNode { + + private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactTagNode.class.getName()); + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/green-tag-icon-16.png"; //NON-NLS + private final BlackboardArtifactTag tag; + + public BlackboardArtifactTagNode(BlackboardArtifactTag tag) { + super(createLookup(tag), tag.getContent()); + String name = tag.getContent().getName(); // As a backup. + try { + name = tag.getArtifact().getShortDescription(); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Failed to get short description for artifact id=" + tag.getArtifact().getId(), ex); + } + setName(name); + setDisplayName(name); + this.setIconBaseWithExtension(ICON_PATH); + this.tag = tag; + } + + /** + * Create the Lookup for this node. + * + * @param tag The artifact tag that this node represents. + * + * @return The Lookup object. + */ + private static Lookup createLookup(BlackboardArtifactTag tag) { + /* + * Make an Autopsy Data Model wrapper for the artifact. + * + * NOTE: The creation of an Autopsy Data Model independent of the + * NetBeans nodes is a work in progress. At the time this comment is + * being written, this object is only being used to indicate the item + * represented by this BlackboardArtifactTagNode. + */ + Content sourceContent = tag.getContent(); + BlackboardArtifact artifact = tag.getArtifact(); + BlackboardArtifactItem<?> artifactItem; + if (artifact instanceof AnalysisResult) { + artifactItem = new AnalysisResultItem((AnalysisResult) artifact, sourceContent); + } else { + artifactItem = new DataArtifactItem((DataArtifact) artifact, sourceContent); + } + return Lookups.fixed(tag, artifactItem, artifact, sourceContent); + } + + @Messages({"BlackboardArtifactTagNode.createSheet.userName.text=User Name"}) + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFile.text"), + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFile.text"), + "", + getDisplayName())); + addOriginalNameProp(properties); + String contentPath; + try { + contentPath = tag.getContent().getUniquePath(); + } catch (TskCoreException ex) { + Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + tag.getContent().getId() + ")", ex); //NON-NLS + contentPath = NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.unavail.text"); + } + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), + "", + contentPath)); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.resultType.text"), + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.resultType.text"), + "", + tag.getArtifact().getDisplayName())); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.comment.text"), + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.comment.text"), + "", + tag.getComment())); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.userName.text"), + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.userName.text"), + "", + tag.getUserName())); + return propertySheet; + } + + @NbBundle.Messages("BlackboardArtifactTagNode.viewSourceArtifact.text=View Source Result") + @Override + public Action[] getActions(boolean context) { + List<Action> actions = new ArrayList<>(); + BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class); + //if this artifact has a time stamp add the action to view it in the timeline + try { + if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact)) { + actions.add(new ViewArtifactInTimelineAction(artifact)); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting arttribute(s) from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS + } + + actions.add(new ViewTaggedArtifactAction(Bundle.BlackboardArtifactTagNode_viewSourceArtifact_text(), artifact)); + actions.add(null); + // if the artifact links to another file, add an action to go to that file + try { + AbstractFile c = findLinked(artifact); + if (c != null) { + actions.add(ViewFileInTimelineAction.createViewFileAction(c)); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS + } + //if this artifact has associated content, add the action to view the content in the timeline + AbstractFile file = getLookup().lookup(AbstractFile.class); + if (null != file) { + actions.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); + } + actions.addAll(DataModelActionsFactory.getActions(tag, true)); + actions.add(null); + actions.addAll(Arrays.asList(super.getActions(context))); + return actions.toArray(new Action[0]); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 255a36eb747a73b461ae1414991792917a581456..5dbdff8b898b6320bd3fdd0442342033a79894e1 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -35,6 +35,12 @@ AbstractContentNode.valueLoading=value loading # {0} - significanceDisplayName AbstractContentNode_getScorePropertyAndDescription_description=Has an {0} analysis result score AbstractFsContentNode.noDesc.text=no description +AnalysisResults_name=Analysis Results +ArtifactStringContent.attrsTableHeader.sources=Source(s) +ArtifactStringContent.attrsTableHeader.type=Type +ArtifactStringContent.attrsTableHeader.value=Value +ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database +ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database AttachmentNode.getActions.openInExtViewer.text=Open in External Viewer Ctrl+E AttachmentNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash AttachmentNode.getActions.viewFileInDir.text=View File in Directory @@ -96,12 +102,55 @@ BlackboardArtifactNode_getViewSrcContentAction_type_DataArtifact=Data Artifact BlackboardArtifactNode_getViewSrcContentAction_type_File=File BlackboardArtifactNode_getViewSrcContentAction_type_OSAccount=OS Account BlackboardArtifactNode_getViewSrcContentAction_type_unknown=Item +BlackboardArtifactTagNode.createSheet.userName.text=User Name +BlackboardArtifactTagNode.viewSourceArtifact.text=View Source Result +Category.five=CAT-5: Non-pertinent +Category.four=CAT-4: Exemplar/Comparison (Internal Use Only) +Category.one=CAT-1: Child Exploitation (Illegal) +Category.three=CAT-3: CGI/Animation (Child Exploitive) +Category.two=CAT-2: Child Exploitation (Non-Illegal/Age Difficult) +Category.zero=CAT-0: Uncategorized +ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash +ContentTagNode.createSheet.artifactMD5.name=MD5 Hash +ContentTagNode.createSheet.origFileName=Original Name +ContentTagNode.createSheet.userName.text=User Name +DataArtifacts_name=Data Artifacts +DataSourcesHostsNode_name=Data Sources +DeletedContent.allDelFilter.text=All +DeletedContent.createSheet.filterType.desc=no description +DeletedContent.createSheet.filterType.displayName=Type +DeletedContent.createSheet.name.desc=no description +DeletedContent.createSheet.name.displayName=Name +DeletedContent.deletedContentsNode.name=Deleted Files +DeletedContent.fsDelFilter.text=File System +DeleteReportAction.showConfirmDialog.errorMsg=An error occurred while deleting the reports. +DeleteReportAction.showConfirmDialog.multiple.explanation=The reports will remain on disk. +DeleteReportAction.showConfirmDialog.single.explanation=The report will remain on disk. FileNode.getActions.openInExtViewer.text=Open in External Viewer Ctrl+E FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash FileNode.getActions.viewFileInDir.text=View File in Directory FileNode.getActions.viewInNewWin.text=View Item in New Window +FileTypeExtensionFilters.tskDatabaseFilter.text=Databases +FileTypes.bgCounting.placeholder=\ (counting...) +FileTypes.createSheet.name.desc=no description +FileTypes.createSheet.name.displayName=Name +FileTypes.createSheet.name.name=Name +FileTypes.name.text=File Types +FileTypesByMimeType.name.text=By MIME Type +FileTypesByMimeTypeNode.createSheet.mediaSubtype.desc=no description +FileTypesByMimeTypeNode.createSheet.mediaSubtype.displayName=Subtype +FileTypesByMimeTypeNode.createSheet.mediaSubtype.name=Subtype +FileTypesByMimeTypeNode.createSheet.mediaType.desc=no description +FileTypesByMimeTypeNode.createSheet.mediaType.displayName=Type +FileTypesByMimeTypeNode.createSheet.mediaType.name=Type GetSCOTask.occurrences.defaultDescription=No correlation properties found GetSCOTask.occurrences.multipleProperties=Multiple different correlation properties exist for this result +HostGroupingNode_unknownHostNode_title=Unknown Host +HostNode_actions_associateWithExisting=Associate with existing person... +HostNode_actions_associateWithNew=Associate with new person... +# {0} - hostName +HostNode_actions_removeFromPerson=Remove from person ({0}) +HostNode_createSheet_nameProperty=Name ImageNode.action.runIngestMods.text=Run Ingest Modules ImageNode.createSheet.deviceId.desc=Device ID of the image ImageNode.createSheet.deviceId.displayName=Device ID @@ -121,6 +170,30 @@ ImageNode.createSheet.type.name=Type ImageNode.createSheet.type.text=Image ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes KeyValueNode.menuItemText.viewFileInDir=View Source File in Directory +KeywordHits.createNodeForKey.accessTime.desc=Access Time +KeywordHits.createNodeForKey.accessTime.displayName=Access Time +KeywordHits.createNodeForKey.accessTime.name=AccessTime +KeywordHits.createNodeForKey.chgTime.desc=Change Time +KeywordHits.createNodeForKey.chgTime.displayName=Change Time +KeywordHits.createNodeForKey.chgTime.name=ChangeTime +KeywordHits.createNodeForKey.modTime.desc=Modified Time +KeywordHits.createNodeForKey.modTime.displayName=Modified Time +KeywordHits.createNodeForKey.modTime.name=ModifiedTime +KeywordHits.createSheet.filesWithHits.desc=no description +KeywordHits.createSheet.filesWithHits.displayName=Files with Hits +KeywordHits.createSheet.filesWithHits.name=Files with Hits +KeywordHits.createSheet.listName.desc=no description +KeywordHits.createSheet.listName.displayName=List Name +KeywordHits.createSheet.listName.name=List Name +KeywordHits.createSheet.name.desc=no description +KeywordHits.createSheet.name.displayName=Name +KeywordHits.createSheet.name.name=Name +KeywordHits.createSheet.numChildren.desc=no description +KeywordHits.createSheet.numChildren.displayName=Number of Children +KeywordHits.createSheet.numChildren.name=Number of Children +KeywordHits.kwHits.text=Keyword Hits +KeywordHits.simpleLiteralSearch.text=Single Literal Keyword Search +KeywordHits.singleRegexSearch.text=Single Regular Expression Search LayoutFileNode.getActions.viewFileInDir.text=View File in Directory LocalFilesDataSourceNode.createSheet.deviceId.desc=Device ID of the image LocalFilesDataSourceNode.createSheet.deviceId.displayName=Device ID @@ -273,6 +346,38 @@ OpenReportAction.actionPerformed.NoAssociatedEditorMessage=There is no associate OpenReportAction.actionPerformed.NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way. OpenReportAction.actionPerformed.MissingReportFileMessage=The report file no longer exists. OpenReportAction.actionPerformed.ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied. +OsAccount_listNode_name=OS Accounts +OsAccounts.createSheet.comment.displayName=C +OsAccounts.createSheet.comment.name=C +# {0} - occurrenceCount +OsAccounts.createSheet.count.description=There were {0} datasource(s) found with occurrences of the OS Account correlation value +OsAccounts.createSheet.count.displayName=O +OsAccounts.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated +OsAccounts.createSheet.count.name=O +OsAccounts.createSheet.score.displayName=S +OsAccounts.createSheet.score.name=S +OsAccounts_accountHostNameProperty_desc=OS Account Host Name +OsAccounts_accountHostNameProperty_displayName=Host +OsAccounts_accountHostNameProperty_name=HostName +OsAccounts_accountNameProperty_desc=Os Account name +OsAccounts_accountNameProperty_displayName=Name +OsAccounts_accountNameProperty_name=Name +OsAccounts_accountRealmNameProperty_desc=OS Account Realm Name +OsAccounts_accountRealmNameProperty_displayName=Realm Name +OsAccounts_accountRealmNameProperty_name=RealmName +OsAccounts_accountScopeNameProperty_desc=OS Account Scope Name +OsAccounts_accountScopeNameProperty_displayName=Scope +OsAccounts_accountScopeNameProperty_name=ScopeName +OsAccounts_createdTimeProperty_desc=OS Account Creation Time +OsAccounts_createdTimeProperty_displayName=Creation Time +OsAccounts_createdTimeProperty_name=creationTime +OsAccounts_loginNameProperty_desc=OS Account login name +OsAccounts_loginNameProperty_displayName=Login Name +OsAccounts_loginNameProperty_name=loginName +PersonGroupingNode_actions_delete=Delete Person +PersonGroupingNode_actions_rename=Rename Person... +PersonGroupingNode_createSheet_nameProperty=Name +PersonNode_unknownPersonNode_title=Unknown Persons PoolNode.createSheet.name.desc=no description PoolNode.createSheet.name.displayName=Name PoolNode.createSheet.name.name=Name @@ -307,6 +412,13 @@ ReportNode.reportNameProperty.name=Report Name ReportNode.reportNameProperty.displayName=Report Name ReportNode.reportNameProperty.desc=Name of the report ReportsListNode.displayName=Reports +ScoreContent_badFilter_text=Bad Items +ScoreContent_createSheet_filterType_desc=no description +ScoreContent_createSheet_filterType_displayName=Type +ScoreContent_createSheet_name_desc=no description +ScoreContent_createSheet_name_displayName=Name +ScoreContent_ScoreContentNode_name=Score +ScoreContent_susFilter_text=Suspicious Items SlackFileNode.getActions.viewInNewWin.text=View in New Window SlackFileNode.getActions.viewFileInDir.text=View File in Directory SpecialDirectoryNode.getActions.viewInNewWin.text=View in New Window @@ -316,6 +428,8 @@ TagNameNode.bbArtTagTypeNodeKey.text=Result Tags TagNameNode.bookmark.text=Bookmark TagNameNode.createSheet.name.name=Name TagNameNode.createSheet.name.displayName=Name +TagNode.propertySheet.origName=Original Name +TagNode.propertySheet.origNameDisplayName=Original Name TagsNode.displayName.text=Tags TagsNode.createSheet.name.name=Name TagsNode.createSheet.name.displayName=Name diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java index 49617ffff777b5b3c31af5d57ec0f7a7eb66b927..1bf43f5e7902cb69a77bc18e7487e8513b1aed94 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java @@ -36,4 +36,13 @@ public ContentNode(Children children) { public ContentNode(Children children, Lookup lookup) { super(children, lookup); } + + /** + * Visitor pattern support. + * + * @param visitor visitor + * + * @return visitor's visit return value + */ + public abstract <T> T accept(ContentNodeVisitor<T> visitor); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeSelectionInfo.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeSelectionInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..fe922354544d72f9a7314674a9857f08caa23cd6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeSelectionInfo.java @@ -0,0 +1,56 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.openide.nodes.Node; +import org.sleuthkit.datamodel.Content; + +/** + * Stores sufficient information to identify a content node that is intended to + * be selected in a view. + */ +public class ContentNodeSelectionInfo implements NodeSelectionInfo { + + private final long contentId; + + /** + * Constructs an object that stores sufficient information to identify a + * content node that is intended to be selected in a view. + * + * @param content The content represented by the node to be selected. + */ + public ContentNodeSelectionInfo(Content content) { + this.contentId = content.getId(); + } + + /** + * Determines whether or not a given node satisfies the stored node + * selection criteria. + * + * @param candidateNode A node to evaluate. + * + * @return True or false. + */ + @Override + public boolean matches(Node candidateNode) { + Content content = candidateNode.getLookup().lookup(Content.class); + return (content != null && content.getId() == contentId); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..e7ce03dd7d65a7cda1bffb81d8addaa4b86fa031 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java @@ -0,0 +1,148 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.sleuthkit.autopsy.datamodel.OsAccounts.OsAccountNode; + +/** + * Visitor Pattern interface that goes over Content nodes in the data source + * area of the tree. + * + * The DisplayableItemNodeVisitor goes over all nodes in the tree. + * + * @param <T> visit method return type + */ +interface ContentNodeVisitor<T> { + + T visit(ImageNode in); + + T visit(VirtualDirectoryNode lcn); + + T visit(LocalDirectoryNode ldn); + + T visit(VolumeNode vn); + + T visit(PoolNode pn); + + T visit(DirectoryNode dn); + + T visit(FileNode fn); + + T visit(LayoutFileNode lcn); + + T visit(LocalFileNode dfn); + + T visit(SlackFileNode sfn); + + T visit(BlackboardArtifactNode bban); + + T visit(UnsupportedContentNode ucn); + + T visit(OsAccountNode bban); + + T visit(LocalFilesDataSourceNode lfdsn); + + /** + * Visitor with an implementable default behavior for all types. Override + * specific visit types to not use the default behavior. + * + * @param <T> + */ + static abstract class Default<T> implements ContentNodeVisitor<T> { + + /** + * Default visit for all types + * + * @param c + * + * @return + */ + protected abstract T defaultVisit(ContentNode c); + + @Override + public T visit(DirectoryNode dn) { + return defaultVisit(dn); + } + + @Override + public T visit(FileNode fn) { + return defaultVisit(fn); + } + + @Override + public T visit(ImageNode in) { + return defaultVisit(in); + } + + @Override + public T visit(VolumeNode vn) { + return defaultVisit(vn); + } + + @Override + public T visit(PoolNode pn) { + return defaultVisit(pn); + } + + @Override + public T visit(LayoutFileNode lcn) { + return defaultVisit(lcn); + } + + @Override + public T visit(LocalFileNode dfn) { + return defaultVisit(dfn); + } + + @Override + public T visit(VirtualDirectoryNode ldn) { + return defaultVisit(ldn); + } + + @Override + public T visit(LocalDirectoryNode ldn) { + return defaultVisit(ldn); + } + + @Override + public T visit(SlackFileNode sfn) { + return defaultVisit(sfn); + } + + @Override + public T visit(BlackboardArtifactNode bban) { + return defaultVisit(bban); + } + + @Override + public T visit(UnsupportedContentNode ucn) { + return defaultVisit(ucn); + } + + @Override + public T visit(OsAccountNode bban) { + return defaultVisit(bban); + } + + @Override + public T visit(LocalFilesDataSourceNode lfdsn) { + return defaultVisit(lfdsn); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java new file mode 100644 index 0000000000000000000000000000000000000000..a61ad2576eeab95be672506155fb77e8ab2a5e52 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -0,0 +1,160 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import javax.swing.Action; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this class wrap ContentTag objects. In the Autopsy presentation + * of the SleuthKit data model, they are leaf nodes of a tree consisting of + * content and artifact tags, grouped first by tag type, then by tag name. + */ +class ContentTagNode extends TagNode { + + private static final Logger LOGGER = Logger.getLogger(ContentTagNode.class.getName()); + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/blue-tag-icon-16.png"; //NON-NLS + private final ContentTag tag; + + ContentTagNode(ContentTag tag) { + super(Lookups.fixed(tag, tag.getContent()), tag.getContent()); + super.setName(tag.getContent().getName()); + super.setDisplayName(tag.getContent().getName()); + this.setIconBaseWithExtension(ICON_PATH); + this.tag = tag; + } + + @Messages({ + "ContentTagNode.createSheet.origFileName=Original Name", + "ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash", + "ContentTagNode.createSheet.artifactMD5.name=MD5 Hash", + "ContentTagNode.createSheet.userName.text=User Name"}) + @Override + protected Sheet createSheet() { + Content content = tag.getContent(); + String contentPath; + try { + contentPath = content.getUniquePath(); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to get path for content (id = " + content.getId() + ")", ex); //NON-NLS + contentPath = NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.unavail.path"); + } + AbstractFile file = content instanceof AbstractFile ? (AbstractFile) content : null; + + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.displayName"), + "", + content.getName())); + addOriginalNameProp(properties); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.displayName"), + "", + contentPath)); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.displayName"), + "", + tag.getComment())); + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"), + "", + file != null ? TimeZoneUtils.getFormattedTime(file.getMtime()) : "")); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"), + "", + file != null ? TimeZoneUtils.getFormattedTime(file.getCtime()) : "")); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"), + "", + file != null ? TimeZoneUtils.getFormattedTime(file.getAtime()) : "")); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), + "", + file != null ? TimeZoneUtils.getFormattedTime(file.getCrtime()) : "")); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), + "", + content.getSize())); + properties.put(new NodeProperty<>( + Bundle.ContentTagNode_createSheet_artifactMD5_name(), + Bundle.ContentTagNode_createSheet_artifactMD5_displayName(), + "", + file != null ? StringUtils.defaultString(file.getMd5Hash()) : "")); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.userName.text"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.userName.text"), + "", + tag.getUserName())); + return propertySheet; + } + + @Override + public Action[] getActions(boolean context) { + List<Action> actions = new ArrayList<>(); + + + AbstractFile file = getLookup().lookup(AbstractFile.class); + if (file != null) { + actions.add(ViewFileInTimelineAction.createViewFileAction(file)); + } + + actions.addAll(DataModelActionsFactory.getActions(tag, false)); + actions.add(null); + actions.addAll(Arrays.asList(super.getActions(context))); + return actions.toArray(new Action[actions.size()]); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..00712fc99adde8fbf045bd1bdda1866e05a00793 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java @@ -0,0 +1,120 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.LocalDirectory; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.Pool; +import org.sleuthkit.datamodel.SlackFile; +import org.sleuthkit.datamodel.SleuthkitItemVisitor; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.UnsupportedContent; +import org.sleuthkit.datamodel.VirtualDirectory; +import org.sleuthkit.datamodel.Volume; + +/** + * Creates appropriate Node for each sub-class of Content + */ +public class CreateSleuthkitNodeVisitor extends SleuthkitItemVisitor.Default<AbstractContentNode<? extends Content>> { + + @Override + public AbstractContentNode<? extends Content> visit(Directory drctr) { + return new DirectoryNode(drctr); + } + + @Override + public AbstractContentNode<? extends Content> visit(File file) { + return new FileNode(file); + } + + @Override + public AbstractContentNode<? extends Content> visit(Image image) { + return new ImageNode(image); + } + + @Override + public AbstractContentNode<? extends Content> visit(Volume volume) { + return new VolumeNode(volume); + } + + @Override + public AbstractContentNode<? extends Content> visit(Pool pool) { + return new PoolNode(pool); + } + + @Override + public AbstractContentNode<? extends Content> visit(LayoutFile lf) { + return new LayoutFileNode(lf); + } + + @Override + public AbstractContentNode<? extends Content> visit(DerivedFile df) { + return new LocalFileNode(df); + } + + @Override + public AbstractContentNode<? extends Content> visit(LocalFile lf) { + return new LocalFileNode(lf); + } + + @Override + public AbstractContentNode<? extends Content> visit(VirtualDirectory ld) { + return new VirtualDirectoryNode(ld); + } + + @Override + public AbstractContentNode<? extends Content> visit(LocalDirectory ld) { + return new LocalDirectoryNode(ld); + } + + @Override + public AbstractContentNode<? extends Content> visit(SlackFile sf) { + return new SlackFileNode(sf); + } + + @Override + public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) { + return new BlackboardArtifactNode(art); + } + + @Override + public AbstractContentNode<? extends Content> visit(UnsupportedContent uc) { + return new UnsupportedContentNode(uc); + } + + @Override + protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) { + throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(), + "AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg")); + } + + @Override + public AbstractContentNode<? extends Content> visit(LocalFilesDataSource ld) { + return new LocalFilesDataSourceNode(ld); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifactItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifactItem.java index ce1702dcff9b4c995a0edf3a9c2393e8ba2978d2..8de75de2876e6f7660b4ff39af825a7717d3b2d8 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifactItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifactItem.java @@ -36,7 +36,7 @@ public class DataArtifactItem extends BlackboardArtifactItem<DataArtifact> { * @param sourceContent The source content of the DataArtifact. */ @Beta - public DataArtifactItem(DataArtifact dataArtifact, Content sourceContent) { + DataArtifactItem(DataArtifact dataArtifact, Content sourceContent) { super(dataArtifact, sourceContent); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java new file mode 100644 index 0000000000000000000000000000000000000000..54e9e5da0afb35987c6f72c36c32fbe22809c8b6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java @@ -0,0 +1,95 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.openide.nodes.Children; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Analysis Results node support. + */ +@NbBundle.Messages({ + "DataArtifacts_name=Data Artifacts",}) +public class DataArtifacts implements AutopsyVisitableItem { + + /** + * Returns the name of this node that is the key in the children object. + * + * @return The name of this node that is the key in the children object. + */ + public static String getName() { + return Bundle.DataArtifacts_name(); + } + + /** + * Parent node of all data artifacts. + */ + static class RootNode extends Artifacts.BaseArtifactNode { + + /** + * Main constructor. + * + * @param filteringDSObjId The data source object id for which results + * should be filtered. If no filtering should + * occur, this number should be less than or + * equal to 0. + */ + RootNode(long filteringDSObjId) { + super(Children.create(new Artifacts.TypeFactory(BlackboardArtifact.Category.DATA_ARTIFACT, filteringDSObjId), true), + "org/sleuthkit/autopsy/images/extracted_content.png", + DataArtifacts.getName(), + DataArtifacts.getName()); + } + } + + private final long datasourceObjId; + + /** + * Main constructor. + */ + public DataArtifacts() { + this(0); + } + + /** + * Main constructor. + * + * @param dsObjId The data source object id. + */ + public DataArtifacts(long dsObjId) { + this.datasourceObjId = dsObjId; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * Returns whether or not there is a data source object for which results + * should be filtered. + * + * @return Whether or not there is a data source object for which results + * should be filtered. + */ + Long getFilteringDataSourceObjId() { + return datasourceObjId; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index 465d8b811fc4f93642bd910bd78214fbb8e639cf..9c39992432ab74090597f9497459edc83d3c163d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -25,12 +25,19 @@ import javax.swing.Action; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.DeleteContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; +import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.datamodel.OsAccounts.OsAccountNode; +import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; @@ -39,7 +46,9 @@ import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.File; @@ -347,6 +356,112 @@ public static List<Action> getActions(DerivedFile file, boolean isArtifactSource return actionsList; } + public static List<Action> getActions(Report report, boolean isArtifactSource) { + List<Action> actionsList = new ArrayList<>(); + final ReportNode reportNode = new ReportNode(report); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, reportNode)); + actionsList.add(null); // creates a menu separator + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + if (isArtifactSource) { + final Collection<BlackboardArtifact> selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + + public static List<Action> getActions(ContentTag contentTag, boolean isArtifactSource) { + List<Action> actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), contentTag.getContent())); + final ContentTagNode tagNode = new ContentTagNode(contentTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + final Collection<AbstractFile> selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + if (selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if (isArtifactSource) { + final Collection<BlackboardArtifact> selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + actionsList.add(DeleteContentTagAction.getInstance()); + actionsList.add(ReplaceContentTagAction.getInstance()); + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + + public static List<Action> getActions(BlackboardArtifactTag artifactTag, boolean isArtifactSource) { + List<Action> actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), artifactTag.getContent())); + final BlackboardArtifactTagNode tagNode = new BlackboardArtifactTagNode(artifactTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + final Collection<AbstractFile> selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + if (selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if (isArtifactSource) { + final Collection<BlackboardArtifact> selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + actionsList.add(DeleteBlackboardArtifactTagAction.getInstance()); + actionsList.add(ReplaceBlackboardArtifactTagAction.getInstance()); + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + + public static List<Action> getActions(OsAccount osAccount) { + List<Action> actionsList = new ArrayList<>(); + + OsAccountNode node = new OsAccountNode(osAccount); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, node)); + actionsList.add(null); + actionsList.add(ExportCSVAction.getInstance()); + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + public static List<Action> getActions(Content content, boolean isArtifactSource) { if (content instanceof File) { return getActions((File) content, isArtifactSource); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceFilesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceFilesNode.java new file mode 100644 index 0000000000000000000000000000000000000000..b7e33860b9792348b453ae68e89a6039e230f001 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceFilesNode.java @@ -0,0 +1,182 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2012-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; + +/** + * A structural node in the main tree view when the user has selected the group + * by persons/hosts option. Instances of this node appear as children of a node + * representing a data source association with a host, and as a parent of a data + * source node. For example: "Host X" -> "Data Source Y" -> "Data Source Files" + * -> "Data Source Y", where "Data Source Files" is an instance of this node. + */ +public class DataSourceFilesNode extends DisplayableItemNode { + + private static final String NAME = NbBundle.getMessage(DataSourceFilesNode.class, "DataSourcesNode.name"); + + /** + * @return The name used to identify the node of this type with a lookup. + */ + public static String getNameIdentifier() { + return NAME; + } + + private final String displayName; + + // NOTE: The images passed in via argument will be ignored. + @Deprecated + public DataSourceFilesNode(List<Content> images) { + this(0); + } + + public DataSourceFilesNode() { + this(0); + } + + public DataSourceFilesNode(long dsObjId) { + super(Children.create(new DataSourcesNodeChildren(dsObjId), true), Lookups.singleton(NAME)); + displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourceFilesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME; + init(); + } + + private void init() { + setName(NAME); + setDisplayName(displayName); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); //NON-NLS + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + /* + * Custom Keys implementation that listens for new data sources being added. + */ + public static class DataSourcesNodeChildren extends AbstractContentChildren<Content> { + + private static final Logger logger = Logger.getLogger(DataSourcesNodeChildren.class.getName()); + private final long datasourceObjId; + + List<Content> currentKeys; + + public DataSourcesNodeChildren() { + this(0); + } + + public DataSourcesNodeChildren(long dsObjId) { + super("ds_" + Long.toString(dsObjId)); + this.currentKeys = new ArrayList<>(); + this.datasourceObjId = dsObjId; + } + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + refresh(true); + } + } + }; + + @Override + protected void onAdd() { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + @Override + protected void onRemove() { + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + currentKeys.clear(); + } + + @Override + protected List<Content> makeKeys() { + try { + if (datasourceObjId == 0) { + currentKeys = Case.getCurrentCaseThrows().getDataSources(); + } else { + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(datasourceObjId); + currentKeys = new ArrayList<>(Arrays.asList(content)); + } + + Collections.sort(currentKeys, new Comparator<Content>() { + @Override + public int compare(Content content1, Content content2) { + String content1Name = content1.getName().toLowerCase(); + String content2Name = content2.getName().toLowerCase(); + return content1Name.compareTo(content2Name); + } + + }); + + } catch (TskCoreException | NoCurrentCaseException | TskDataException ex) { + logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS + } + + return currentKeys; + } + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.desc"), + NAME)); + return sheet; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/PersonSearchParams.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java similarity index 55% rename from Core/src/org/sleuthkit/autopsy/mainui/datamodel/PersonSearchParams.java rename to Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java index 8c882a80c8501d1a4efda2a7909f8d7d358956de..db291f3b505d1b9b7a645d51a50b3df3efa5049d 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/PersonSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2022 Basis Technology Corp. + * Copyright 2018 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,55 +16,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.mainui.datamodel; +package org.sleuthkit.autopsy.datamodel; import java.util.Objects; -import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.DataSource; /** - * Search parameters for a given person. + * A top level UI grouping of Files, Views, Results, Tags + * for 'Group by Data Source' view of the tree. + * */ -public class PersonSearchParams { - private static final String TYPE_ID = "Person"; +public class DataSourceGrouping implements AutopsyVisitableItem { - public static String getTypeId() { - return TYPE_ID; - } - - private final Person person; + private final DataSource dataSource; - public PersonSearchParams(Person person) { - this.person = person; + public DataSourceGrouping(DataSource dataSource) { + this.dataSource = dataSource; } - - public Person getPerson() { - return person; + + DataSource getDataSource() { + return this.dataSource; } - + @Override - public int hashCode() { - int hash = 7; - hash = 59 * hash + Objects.hashCode(this.person); - return hash; + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); } - + + @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } - final PersonSearchParams other = (PersonSearchParams) obj; - if (!Objects.equals(this.person, other.person)) { - return false; - } - return true; + final DataSourceGrouping other = (DataSourceGrouping) obj; + return this.dataSource.getId() == other.getDataSource().getId(); } - - + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + Objects.hashCode(this.dataSource); + return hash; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java new file mode 100644 index 0000000000000000000000000000000000000000..04757d49873473654f328fa5efd8ad889bc83dab --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -0,0 +1,104 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.logging.Level; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * Data source grouping node - an optional grouping node in the data tree view + * + */ +class DataSourceGroupingNode extends DisplayableItemNode { + + private static final Logger logger = Logger.getLogger(DataSourceGroupingNode.class.getName()); + + /** + * Creates a data source grouping node for the given data source. + * + * @param dataSource specifies the data source + */ + DataSourceGroupingNode(DataSource dataSource) { + + super(Optional.ofNullable(createDSGroupingNodeChildren(dataSource)) + .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST))), + Lookups.singleton(dataSource)); + + if (dataSource instanceof Image) { + Image image = (Image) dataSource; + + super.setName(image.getName()); + super.setDisplayName(image.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); + } else if (dataSource instanceof LocalFilesDataSource) { + LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource; + + super.setName(localFilesDataSource.getName()); + super.setDisplayName(localFilesDataSource.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); + } + + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + private static RootContentChildren createDSGroupingNodeChildren(DataSource dataSource) { + + long dsObjId = dataSource.getId(); + try { + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + return new RootContentChildren(Arrays.asList( + new DataSources(dsObjId), + new Views(skCase, dsObjId), + new DataArtifacts(dsObjId), + new AnalysisResults(dsObjId), + new OsAccounts(skCase, dsObjId), + new Tags(dsObjId), + new ScoreContent(skCase, dsObjId) + )); + + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS + return null; + } + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DeleteAnalysisResultEvent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java old mode 100755 new mode 100644 similarity index 54% rename from Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DeleteAnalysisResultEvent.java rename to Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index dc43ee3a569d7fbfed84cc38fe6e5f28985e97a9..4f233274011184c45631012025d72e88811aa867 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DeleteAnalysisResultEvent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -1,41 +1,44 @@ /* * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. + * + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.List; +package org.sleuthkit.autopsy.datamodel; /** - * An event for the deletion of an analysis result. + * An "Autopsy visitable item" that supplies a */ -public class DeleteAnalysisResultEvent implements DAOEvent { +public class DataSources implements AutopsyVisitableItem { - private final List<Long> deletedAnalysisResultIds; - - private final DAOEvent.Type type; + private final long datasourceObjId; + + public DataSources() { + this(0); + } + + public DataSources(long datasourceObjId) { + this.datasourceObjId = datasourceObjId; + } - public DeleteAnalysisResultEvent(DAOEvent.Type type, List<Long> deletedIds) { - deletedAnalysisResultIds = deletedIds; - this.type = type; + long filteringDataSourceObjId() { + return this.datasourceObjId; } @Override - public DAOEvent.Type getType() { - return type; + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/OsAccountEvent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesByType.java similarity index 67% rename from Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/OsAccountEvent.java rename to Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesByType.java index 50805b16b8316855ebce2e4b9c8d166c3e466fad..a70514828404c0b610b3e5237ffd6ed26be92e93 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/OsAccountEvent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesByType.java @@ -1,30 +1,30 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2021 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; +package org.sleuthkit.autopsy.datamodel; /** - * An event that OS Accounts were changed. + * Signifies a "Data Sources" node with hosts underneath it, and data sources + * (and only data sources) underneath that. */ -public class OsAccountEvent implements DAOEvent { - +public class DataSourcesByType implements AutopsyVisitableItem { @Override - public Type getType() { - return Type.RESULT; + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java new file mode 100644 index 0000000000000000000000000000000000000000..e735eca3d933915d479d6514c9bb11812536b5d5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -0,0 +1,165 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A top-level structural node (child of the invisible root node) in the main + * tree view when the user has selected the group by data type option. It + * appears as the parent node of the "directory tree" nodes that are the roots + * of the file trees for the individual data sources in a case. For example: + * "Data Sources" -> "Data Source X", "Data Source Y", where "Data Sources" is + * an instance of this node. The siblings of this node are the "Views, "Analysis + * Results," "Os Accounts," "Tags," and "Reports" nodes. + */ +@Messages({ + "DataSourcesHostsNode_name=Data Sources" +}) +public class DataSourcesNode extends DisplayableItemNode { + + /* + * Custom Keys implementation that listens for new data sources being added. + */ + public static class DataSourcesByTypeChildren extends ChildFactory.Detachable<HostDataSources> { + + private static final Set<Case.Events> UPDATE_EVTS = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, + Case.Events.HOSTS_ADDED, + Case.Events.HOSTS_DELETED, + Case.Events.HOSTS_UPDATED); + + private static final Set<String> UPDATE_EVT_STRS = UPDATE_EVTS.stream() + .map(evt -> evt.name()) + .collect(Collectors.toSet()); + + private static final Logger logger = Logger.getLogger(DataSourcesByTypeChildren.class.getName()); + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (UPDATE_EVT_STRS.contains(eventType)) { + refresh(true); + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + Case.addEventTypeSubscriber(UPDATE_EVTS, weakPcl); + } + + @Override + protected void finalize() throws Throwable{ + Case.removeEventTypeSubscriber(UPDATE_EVTS, weakPcl); + super.finalize(); + } + + @Override + protected boolean createKeys(List<HostDataSources> toPopulate) { + try { + Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getAllHosts().stream() + .map(HostDataSources::new) + .sorted() + .forEach(toPopulate::add); + + } catch (TskCoreException | NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS + } + + return true; + } + + @Override + protected Node createNodeForKey(HostDataSources key) { + return new HostNode(key); + } + + } + + private static final String NAME = Bundle.DataSourcesHostsNode_name(); + + /** + * @return The name used to identify the node of this type with a lookup. + */ + public static String getNameIdentifier() { + return NAME; + } + + /** + * Main constructor. + */ + DataSourcesNode() { + super(Children.create(new DataSourcesByTypeChildren(), true), Lookups.singleton(NAME)); + setName(NAME); + setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "DataSourcesNode.createSheet.name.desc"), + NAME)); + return sheet; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java new file mode 100644 index 0000000000000000000000000000000000000000..5ee741a496bd0af62cc58c66d19ebfa42e57ab37 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -0,0 +1,519 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.CONTENT_CHANGED; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.FsContent; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.VirtualDirectory; + +/** + * deleted content view nodes + */ +public class DeletedContent implements AutopsyVisitableItem { + + private SleuthkitCase skCase; + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + @NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System", + "DeletedContent.allDelFilter.text=All"}) + public enum DeletedContentFilter implements AutopsyVisitableItem { + + FS_DELETED_FILTER(0, "FS_DELETED_FILTER", //NON-NLS + Bundle.DeletedContent_fsDelFilter_text()), + ALL_DELETED_FILTER(1, "ALL_DELETED_FILTER", //NON-NLS + Bundle.DeletedContent_allDelFilter_text()); + + private int id; + private String name; + private String displayName; + + private DeletedContentFilter(int id, String name, String displayName) { + this.id = id; + this.name = name; + this.displayName = displayName; + + } + + public String getName() { + return this.name; + } + + public int getId() { + return this.id; + } + + public String getDisplayName() { + return this.displayName; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + } + + public DeletedContent(SleuthkitCase skCase) { + this(skCase, 0); + } + + public DeletedContent(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.filteringDSObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.filteringDSObjId; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + public SleuthkitCase getSleuthkitCase() { + return this.skCase; + } + + public static class DeletedContentsNode extends DisplayableItemNode { + + @NbBundle.Messages("DeletedContent.deletedContentsNode.name=Deleted Files") + private static final String NAME = Bundle.DeletedContent_deletedContentsNode_name(); + + DeletedContentsNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new DeletedContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); + super.setName(NAME); + super.setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + @NbBundle.Messages({ + "DeletedContent.createSheet.name.displayName=Name", + "DeletedContent.createSheet.name.desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("Name", //NON-NLS + Bundle.DeletedContent_createSheet_name_displayName(), + Bundle.DeletedContent_createSheet_name_desc(), + NAME)); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + public static class DeletedContentsChildren extends ChildFactory<DeletedContent.DeletedContentFilter> { + + private SleuthkitCase skCase; + private Observable notifier; + private final long datasourceObjId; + // true if we have already told user that not all files will be shown + private static volatile boolean maxFilesDialogShown = false; + + public DeletedContentsChildren(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + this.notifier = new DeletedContentsChildrenObservable(); + } + + /** + * Listens for case and ingest invest. Updates observers when events are + * fired. Other nodes are listening to this for changes. + */ + private static final class DeletedContentsChildrenObservable extends Observable { + + private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of( + Case.Events.DATA_SOURCE_ADDED, + Case.Events.CURRENT_CASE + ); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(CONTENT_CHANGED); + + DeletedContentsChildrenObservable() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + } + + private void removeListeners() { + deleteObservers(); + IngestManager.getInstance().removeIngestJobEventListener(pcl); + IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + } + + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + 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. + */ + try { + Case.getCurrentCaseThrows(); + // new file was added + // @@@ COULD CHECK If the new file is deleted before notifying... + update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || 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. + */ + try { + Case.getCurrentCaseThrows(); + update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeListeners(); + } + maxFilesDialogShown = false; + } + }; + + private void update() { + setChanged(); + notifyObservers(); + } + } + + @Override + + protected boolean createKeys(List<DeletedContent.DeletedContentFilter> list) { + list.addAll(Arrays.asList(DeletedContent.DeletedContentFilter.values())); + return true; + } + + @Override + protected Node createNodeForKey(DeletedContent.DeletedContentFilter key) { + return new DeletedContentNode(skCase, key, notifier, datasourceObjId); + } + + public class DeletedContentNode extends DisplayableItemNode { + + private final DeletedContent.DeletedContentFilter filter; + private final long datasourceObjId; + + // 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())); + this.filter = filter; + this.datasourceObjId = dsObjId; + init(); + } + + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, o, dsObjId), true), Lookups.singleton(filter.getDisplayName())); + this.filter = filter; + this.datasourceObjId = dsObjId; + init(); + o.addObserver(new DeletedContentNodeObserver()); + } + + private void init() { + super.setName(filter.getName()); + + String tooltip = filter.getDisplayName(); + this.setShortDescription(tooltip); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS + updateDisplayName(); + } + + // update the display name when new events are fired + private class DeletedContentNodeObserver implements Observer { + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + } + + private void updateDisplayName() { + //get count of children without preloading all children nodes + final long count = DeletedContentChildren.calculateItems(skCase, filter, datasourceObjId); + //final long count = getChildren().getNodesCount(true); + super.setDisplayName(filter.getDisplayName() + " (" + count + ")"); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + @NbBundle.Messages({ + "DeletedContent.createSheet.filterType.displayName=Type", + "DeletedContent.createSheet.filterType.desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("Type", //NON_NLS + Bundle.DeletedContent_createSheet_filterType_displayName(), + Bundle.DeletedContent_createSheet_filterType_desc(), + filter.getDisplayName())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + /** + * Return getClass().getName() + filter.getName() if custom + * settings are desired for different filters. + */ + return DisplayableItemNode.FILE_PARENT_NODE_KEY; + } + } + + static class DeletedContentChildren extends BaseChildFactory<AbstractFile> { + + private final SleuthkitCase skCase; + private final DeletedContent.DeletedContentFilter filter; + private static final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName()); + + private final Observable notifier; + private final long datasourceObjId; + + DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) { + super(filter.getName(), new ViewsKnownAndSlackFilter<>()); + this.skCase = skCase; + this.filter = filter; + this.notifier = o; + this.datasourceObjId = datasourceObjId; + } + + private final Observer observer = new DeletedContentChildrenObserver(); + + @Override + protected List<AbstractFile> makeKeys() { + return runFsQuery(); + } + + // Cause refresh of children if there are changes + private class DeletedContentChildrenObserver implements Observer { + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + @Override + protected void onAdd() { + if (notifier != null) { + notifier.addObserver(observer); + } + } + + @Override + protected void onRemove() { + if (notifier != null) { + notifier.deleteObserver(observer); + } + } + + static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) { + String query = ""; + switch (filter) { + case FS_DELETED_FILTER: + query = "dir_flags = " + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() //NON-NLS + + " AND meta_flags != " + TskData.TSK_FS_META_FLAG_ENUM.ORPHAN.getValue() //NON-NLS + + " AND type = " + TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType(); //NON-NLS + + break; + case ALL_DELETED_FILTER: + query = " ( " + + "( " + + "(dir_flags = " + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() //NON-NLS + + " OR " //NON-NLS + + "meta_flags = " + TskData.TSK_FS_META_FLAG_ENUM.ORPHAN.getValue() //NON-NLS + + ")" + + " AND type = " + TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType() //NON-NLS + + " )" + + " OR type = " + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.getFileType() //NON-NLS + + " OR (dir_flags = " + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() + + " AND type = " + TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.getFileType() + " )" + + " )"; + //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS.getFileType() + //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS.getFileType() + //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.getFileType() + //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.getFileType() + //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType(); + break; + + default: + logger.log(Level.SEVERE, "Unsupported filter type to get deleted content: {0}", filter); //NON-NLS + + } + + if (filteringDSObjId > 0) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + return query; + } + + private List<AbstractFile> runFsQuery() { + List<AbstractFile> ret = new ArrayList<>(); + + String query = makeQuery(filter, datasourceObjId); + try { + ret = skCase.findAllFilesWhere(query); + } catch (TskCoreException e) { + logger.log(Level.SEVERE, "Error getting files for the deleted content view using: " + query, e); //NON-NLS + } + + return ret; + + } + + /** + * Get children count without actually loading all nodes + * + * @param sleuthkitCase + * @param filter + * + * @return + */ + static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter, long datasourceObjId) { + try { + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting deleted files search view count", ex); //NON-NLS + return 0; + } + } + + @Override + protected Node createNodeForKey(AbstractFile key) { + return key.accept(new ContentVisitor.Default<AbstractNode>() { + public FileNode visit(AbstractFile f) { + return new FileNode(f, false); + } + + public FileNode visit(FsContent f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(LayoutFile f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(File f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(Directory f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(VirtualDirectory f) { + return new FileNode(f, false); + } + + @Override + protected AbstractNode defaultVisit(Content di) { + throw new UnsupportedOperationException("Not supported for this type of Displayable Item: " + di.toString()); + } + }); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DhsImageCategory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DhsImageCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..4d7f87389b2568c8257a0a273d167d3c6700af42 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DhsImageCategory.java @@ -0,0 +1,108 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-18 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import java.util.Arrays; +import java.util.Map; +import javafx.scene.Node; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.paint.Color; +import org.openide.util.NbBundle; + +/** + * Enum to represent the six categories in the DHS image categorization scheme. + * NOTE: This appears to not be used anywhere anymore after the ImageGallery refactoring + */ +@NbBundle.Messages({ + "Category.one=CAT-1: Child Exploitation (Illegal)", + "Category.two=CAT-2: Child Exploitation (Non-Illegal/Age Difficult)", + "Category.three=CAT-3: CGI/Animation (Child Exploitive)", + "Category.four=CAT-4: Exemplar/Comparison (Internal Use Only)", + "Category.five=CAT-5: Non-pertinent", + "Category.zero=CAT-0: Uncategorized"}) +public enum DhsImageCategory { + + /* + * This order of declaration is required so that Enum's compareTo method + * preserves the fact that lower category numbers are first/most sever, + * except 0 which is last + */ + ONE(Color.RED, 1, Bundle.Category_one(), "cat1.png"), + TWO(Color.ORANGE, 2, Bundle.Category_two(), "cat2.png"), + THREE(Color.YELLOW, 3, Bundle.Category_three(), "cat3.png"), + FOUR(Color.BISQUE, 4, Bundle.Category_four(), "cat4.png"), + FIVE(Color.GREEN, 5, Bundle.Category_five(), "cat5.png"), + ZERO(Color.LIGHTGREY, 0, Bundle.Category_zero(), "cat0.png"); + + /** Map from displayName to enum value */ + private static final Map<String, DhsImageCategory> nameMap + = Maps.uniqueIndex(Arrays.asList(values()), DhsImageCategory::getDisplayName); + + private final Color color; + private final String displayName; + private final int id; + private final Image icon; + + private DhsImageCategory(Color color, int id, String name, String filename) { + this.color = color; + this.displayName = name; + this.id = id; + this.icon = new Image(getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/" + filename)); + } + + public static ImmutableList<DhsImageCategory> getNonZeroCategories() { + return ImmutableList.of(FIVE, FOUR, THREE, TWO, ONE); + } + + public static DhsImageCategory fromDisplayName(String displayName) { + return nameMap.get(displayName); + } + + public static boolean isCategoryName(String tName) { + return nameMap.containsKey(tName); + } + + public static boolean isNotCategoryName(String tName) { + return nameMap.containsKey(tName) == false; + } + + public int getCategoryNumber() { + return id; + } + + public Color getColor() { + return color; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public String toString() { + return displayName; + } + + public Node getGraphic() { + return new ImageView(icon); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index c29c574c6f7c3b06c4ca58b09d352a5f19fa8412..4f5fd7dfb417aa4f7d2f9c40517b925c54d3a58a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -105,6 +105,11 @@ public Action[] getActions(boolean popup) { return actionsList.toArray(new Action[actionsList.size()]); } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 91ee4127e7ee1786db5660cd22037aadf391a391..85e715382eac73ae9d081fa03e5153ef55c25d77 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -25,7 +25,15 @@ import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeValueNode; import org.sleuthkit.autopsy.commonpropertiessearch.CaseDBCommonAttributeInstanceNode; import org.sleuthkit.autopsy.commonpropertiessearch.InstanceDataSourceNode; +import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; +import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; +import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; +import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; +import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.allcasessearch.CorrelationAttributeInstanceNode; +import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsNode; +import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsChildren.ScoreContentNode; /** * Visitor pattern that goes over all nodes in the directory tree. This includes @@ -36,6 +44,8 @@ public interface DisplayableItemNodeVisitor<T> { /* * Data Sources Area */ + T visit(DataSourceFilesNode in); + T visit(LayoutFileNode lfn); T visit(LocalFileNode dfn); @@ -60,8 +70,58 @@ public interface DisplayableItemNodeVisitor<T> { /* * Views Area */ + T visit(ViewsNode vn); + + T visit(DataSourceGroupingNode dataSourceGroupingNode); + + T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileExtensionNode fsfn); + + T visit(DeletedContentNode dcn); + + T visit(DeletedContentsNode dcn); + + T visit(ScoreContentNode scn); + + T visit(ScoreContentsNode scn); + + T visit(FileSizeRootNode fsrn); + + T visit(FileSizeNode fsn); + + T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileTypesByExtNode sfn); + + T visit(RecentFilesNode rfn); + + T visit(RecentFilesFilterNode rffn); + T visit(BlackboardArtifactNode ban); + T visit(Artifacts.TypeNode atn); + + T visit(Artifacts.BaseArtifactNode ecn); + + T visit(KeywordHits.RootNode khrn); + + T visit(KeywordHits.ListNode khsn); + + T visit(KeywordHits.TermNode khmln); + + T visit(KeywordHits.RegExpInstanceNode khmln); + + T visit(HashsetHits.RootNode hhrn); + + T visit(HashsetHits.HashsetNameNode hhsn); + + T visit(EmailExtracted.RootNode eern); + + T visit(EmailExtracted.AccountNode eean); + + T visit(EmailExtracted.FolderNode eefn); + + T visit(InterestingHits.RootNode ihrn); + + T visit(InterestingHits.SetNameNode ihsn); + T visit(CommonAttributeValueNode cavn); T visit(CommonAttributeSearchResultRootNode cfn); @@ -83,10 +143,48 @@ public interface DisplayableItemNodeVisitor<T> { */ T visit(Tags.RootNode node); + T visit(Tags.TagNameNode node); + + T visit(Tags.ContentTagTypeNode node); + + T visit(ContentTagNode node); + + T visit(Tags.BlackboardArtifactTagTypeNode node); + + T visit(BlackboardArtifactTagNode node); /* * Reports */ + T visit(Reports.ReportsListNode reportsNode); + + T visit(Reports.ReportNode reportNode); + + /* + * Accounts + */ + T visit(Accounts.AccountsRootNode accountRootNode); + + T visit(Accounts.CreditCardNumberAccountTypeNode accountTypeNode); + + T visit(Accounts.ByBINNode byArtifactNode); + + T visit(Accounts.ByFileNode byFileNode); + + T visit(Accounts.FileWithCCNNode byFileNode); + + T visit(Accounts.BINNode binNode); + + T visit(Accounts.DefaultAccountTypeNode node); + + T visit(FileTypes.FileTypesNode fileTypes); + + T visit(FileTypesByMimeType.ByMimeTypeNode ftByMimeTypeNode); + + T visit(FileTypesByMimeType.MediaTypeNode ftByMimeTypeMediaType); + + T visit(FileTypesByMimeType.MediaSubTypeNode ftByMimeTypeMediaSubType); + T visit(EmptyNode.MessageNode emptyNode); /* @@ -94,13 +192,22 @@ public interface DisplayableItemNodeVisitor<T> { */ T visit(AttachmentNode node); + T visit(OsAccounts.OsAccountNode node); + + T visit(OsAccounts.OsAccountListNode node); + + T visit(PersonNode node); + + T visit(HostNode node); + + T visit(DataSourcesNode node); + /* * Unsupported node */ T visit(UnsupportedContentNode ucn); T visit(LocalFilesDataSourceNode lfdsn); - /** * Visitor with an implementable default behavior for all types. Override @@ -194,11 +301,161 @@ public T visit(BlackboardArtifactNode ban) { return defaultVisit(ban); } + @Override + public T visit(Artifacts.TypeNode atn) { + return defaultVisit(atn); + } + + @Override + public T visit(Artifacts.BaseArtifactNode ecn) { + return defaultVisit(ecn); + } + + @Override + public T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileExtensionNode fsfn) { + return defaultVisit(fsfn); + } + + @Override + public T visit(FileTypesByMimeType.ByMimeTypeNode ftByMimeTypeNode) { + return defaultVisit(ftByMimeTypeNode); + } + + @Override + public T visit(FileTypesByMimeType.MediaTypeNode ftByMimeTypeMediaTypeNode) { + return defaultVisit(ftByMimeTypeMediaTypeNode); + } + + @Override + public T visit(FileTypesByMimeType.MediaSubTypeNode ftByMimeTypeMediaTypeNode) { + return defaultVisit(ftByMimeTypeMediaTypeNode); + } + @Override public T visit(EmptyNode.MessageNode ftByMimeTypeEmptyNode) { return defaultVisit(ftByMimeTypeEmptyNode); } + @Override + public T visit(DeletedContentNode dcn) { + return defaultVisit(dcn); + } + + @Override + public T visit(ScoreContentNode scn) { + return defaultVisit(scn); + } + + @Override + public T visit(ScoreContentsNode scn) { + return defaultVisit(scn); + } + + @Override + public T visit(DeletedContentsNode dcn) { + return defaultVisit(dcn); + } + + @Override + public T visit(FileSizeRootNode fsrn) { + return defaultVisit(fsrn); + } + + @Override + public T visit(FileSizeNode fsn) { + return defaultVisit(fsn); + } + + @Override + public T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileTypesByExtNode sfn) { + return defaultVisit(sfn); + } + + @Override + public T visit(RecentFilesNode rfn) { + return defaultVisit(rfn); + } + + @Override + public T visit(RecentFilesFilterNode rffn) { + return defaultVisit(rffn); + } + + @Override + public T visit(KeywordHits.RootNode khrn) { + return defaultVisit(khrn); + } + + @Override + public T visit(KeywordHits.ListNode khsn) { + return defaultVisit(khsn); + } + + @Override + public T visit(KeywordHits.RegExpInstanceNode khsn) { + return defaultVisit(khsn); + } + + @Override + public T visit(KeywordHits.TermNode khmln) { + return defaultVisit(khmln); + } + + @Override + public T visit(ViewsNode vn) { + return defaultVisit(vn); + } + + @Override + public T visit(DataSourceGroupingNode dataSourceGroupingNode) { + return defaultVisit(dataSourceGroupingNode); + } + + @Override + public T visit(FileTypesNode ft) { + return defaultVisit(ft); + } + + @Override + public T visit(DataSourceFilesNode in) { + return defaultVisit(in); + } + + @Override + public T visit(HashsetHits.RootNode hhrn) { + return defaultVisit(hhrn); + } + + @Override + public T visit(HashsetHits.HashsetNameNode hhsn) { + return defaultVisit(hhsn); + } + + @Override + public T visit(InterestingHits.RootNode ihrn) { + return defaultVisit(ihrn); + } + + @Override + public T visit(InterestingHits.SetNameNode ihsn) { + return defaultVisit(ihsn); + } + + @Override + public T visit(EmailExtracted.RootNode eern) { + return defaultVisit(eern); + } + + @Override + public T visit(EmailExtracted.AccountNode eean) { + return defaultVisit(eean); + } + + @Override + public T visit(EmailExtracted.FolderNode eefn) { + return defaultVisit(eefn); + } + @Override public T visit(LayoutFileNode lfn) { return defaultVisit(lfn); @@ -224,11 +481,106 @@ public T visit(Tags.RootNode node) { return defaultVisit(node); } + @Override + public T visit(Tags.TagNameNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Tags.ContentTagTypeNode node) { + return defaultVisit(node); + } + + @Override + public T visit(ContentTagNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Tags.BlackboardArtifactTagTypeNode node) { + return defaultVisit(node); + } + + @Override + public T visit(BlackboardArtifactTagNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Reports.ReportsListNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Reports.ReportNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Accounts.CreditCardNumberAccountTypeNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Accounts.AccountsRootNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Accounts.ByBINNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Accounts.ByFileNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Accounts.FileWithCCNNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Accounts.BINNode node) { + return defaultVisit(node); + } + + @Override + public T visit(Accounts.DefaultAccountTypeNode node) { + return defaultVisit(node); + } + @Override public T visit(AttachmentNode node) { return defaultVisit(node); } + @Override + public T visit(OsAccounts.OsAccountNode node) { + return defaultVisit(node); + } + + @Override + public T visit(OsAccounts.OsAccountListNode node) { + return defaultVisit(node); + } + + @Override + public T visit(HostNode node) { + return defaultVisit(node); + } + + @Override + public T visit(DataSourcesNode node) { + return defaultVisit(node); + } + + @Override + public T visit(PersonNode node) { + return defaultVisit(node); + } + @Override public T visit(UnsupportedContentNode node) { return defaultVisit(node); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java new file mode 100644 index 0000000000000000000000000000000000000000..00d941b7145a2895e4921f4dd6e2a669a0618b86 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -0,0 +1,576 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2012-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_EMAIL_MSG; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode; +import org.sleuthkit.datamodel.DataArtifact; + +/** + * Support for TSK_EMAIL_MSG nodes and displaying emails in the directory tree. + * Email messages are grouped into parent folders, and the folders are grouped + * into parent accounts if TSK_PATH is available to define the relationship + * structure for every message. + */ +public class EmailExtracted implements AutopsyVisitableItem { + + private static final String LABEL_NAME = BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeName(); + private static final Logger logger = Logger.getLogger(EmailExtracted.class.getName()); + private static final String MAIL_ACCOUNT = NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.mailAccount.text"); + private static final String MAIL_FOLDER = NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.mailFolder.text"); + private static final String MAIL_PATH_SEPARATOR = "/"; + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED); + + /** + * Parse the path of the email msg to get the account name and folder in + * which the email is contained. + * + * @param path - the TSK_PATH to the email msg + * + * @return a map containg the account and folder which the email is stored + * in + */ + public static final Map<String, String> parsePath(String path) { + Map<String, String> parsed = new HashMap<>(); + String[] split = path == null ? new String[0] : path.split(MAIL_PATH_SEPARATOR); + if (split.length < 4) { + parsed.put(MAIL_ACCOUNT, NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultAcct.text")); + parsed.put(MAIL_FOLDER, NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultFolder.text")); + return parsed; + } + parsed.put(MAIL_ACCOUNT, split[2]); + parsed.put(MAIL_FOLDER, split[3]); + return parsed; + } + private SleuthkitCase skCase; + private final EmailResults emailResults; + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + /** + * Constructor + * + * @param skCase Case DB + */ + public EmailExtracted(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public EmailExtracted(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.filteringDSObjId = objId; + emailResults = new EmailResults(); + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + private final class EmailResults extends Observable { + + // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized + private final Map<String, Map<String, List<Long>>> accounts = new LinkedHashMap<>(); + + EmailResults() { + update(); + } + + public Set<String> getAccounts() { + synchronized (accounts) { + return accounts.keySet(); + } + } + + public Set<String> getFolders(String account) { + synchronized (accounts) { + return accounts.get(account).keySet(); + } + } + + public List<Long> getArtifactIds(String account, String folder) { + synchronized (accounts) { + return accounts.get(account).get(folder); + } + } + + @SuppressWarnings("deprecation") + public void update() { + // clear cache if no case + if (skCase == null) { + synchronized (accounts) { + accounts.clear(); + } + return; + } + + // get artifact id and path (if present) of all email artifacts + int emailArtifactId = BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID(); + int pathAttrId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID(); + + String query = "SELECT \n" + + " art.artifact_obj_id AS artifact_obj_id,\n" + + " (SELECT value_text FROM blackboard_attributes attr\n" + + " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + pathAttrId + "\n" + + " LIMIT 1) AS value_text\n" + + "FROM \n" + + " blackboard_artifacts art\n" + + " WHERE art.artifact_type_id = " + emailArtifactId + "\n" + + ((filteringDSObjId > 0) ? " AND art.data_source_obj_id = " + filteringDSObjId : ""); + + // form hierarchy of account -> folder -> account id + Map<String, Map<String, List<Long>>> newMapping = new HashMap<>(); + + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + while (resultSet.next()) { + Long artifactObjId = resultSet.getLong("artifact_obj_id"); + Map<String, String> accountFolderMap = parsePath(resultSet.getString("value_text")); + String account = accountFolderMap.get(MAIL_ACCOUNT); + String folder = accountFolderMap.get(MAIL_FOLDER); + + Map<String, List<Long>> folders = newMapping.computeIfAbsent(account, (str) -> new LinkedHashMap<>()); + List<Long> messages = folders.computeIfAbsent(folder, (str) -> new ArrayList<>()); + messages.add(artifactObjId); + } + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "Cannot initialize email extraction: ", ex); //NON-NLS + } + + synchronized (accounts) { + accounts.clear(); + accounts.putAll(newMapping); + } + + setChanged(); + notifyObservers(); + } + } + + /** + * Mail root node grouping all mail accounts, supports account-> folder + * structure + */ + public class RootNode extends UpdatableCountTypeNode { + + public RootNode() { + super(Children.create(new AccountFactory(), true), + Lookups.singleton(TSK_EMAIL_MSG.getDisplayName()), + TSK_EMAIL_MSG.getDisplayName(), + filteringDSObjId, + TSK_EMAIL_MSG); + //super(Children.create(new AccountFactory(), true), Lookups.singleton(DISPLAY_NAME)); + super.setName(LABEL_NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS + emailResults.update(); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * Mail root child node creating each account node + */ + private class AccountFactory extends ChildFactory.Detachable<String> implements Observer { + + /* + * The pcl is in the class because it has the easiest mechanisms to add + * and remove itself during its life cycles. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) { + emailResults.update(); + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + emailResults.update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + skCase = null; + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + emailResults.update(); + emailResults.addObserver(this); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + emailResults.deleteObserver(this); + } + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(emailResults.getAccounts()); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new AccountNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + /** + * Account node representation + */ + public class AccountNode extends DisplayableItemNode implements Observer { + + private final String accountName; + + public AccountNode(String accountName) { + super(Children.create(new FolderFactory(accountName), true), Lookups.singleton(accountName)); + super.setName(accountName); + this.accountName = accountName; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/account-icon-16.png"); //NON-NLS + updateDisplayName(); + emailResults.addObserver(this); + } + + private void updateDisplayName() { + super.setDisplayName(accountName + " (" + emailResults.getFolders(accountName) + ")"); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * Account node child creating sub nodes for every folder + */ + private class FolderFactory extends ChildFactory<String> implements Observer { + + private final String accountName; + + private FolderFactory(String accountName) { + super(); + this.accountName = accountName; + emailResults.addObserver(this); + } + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(emailResults.getFolders(accountName)); + return true; + } + + @Override + protected Node createNodeForKey(String folderName) { + return new FolderNode(accountName, folderName); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + /** + * Ensures that the key for the parent node and child factory is the same to + * ensure that the BaseChildFactory registered listener node name + * (BaseChildFactory.register and DataResultViewerTable.setNode with event + * registration) is the same as the factory name that will post events from + * BaseChildFactory.post called in BaseChildFactory.makeKeys. See JIRA-7752 + * for more details. + * + * @param accountName The account name. + * @param folderName The folder name. + * + * @return The generated key. + */ + private static String getFolderKey(String accountName, String folderName) { + return accountName + "_" + folderName; + } + + /** + * Node representing mail folder + */ + public class FolderNode extends DisplayableItemNode implements Observer { + + private final String accountName; + private final String folderName; + + public FolderNode(String accountName, String folderName) { + super(Children.create(new MessageFactory(accountName, folderName), true), Lookups.singleton(accountName)); + super.setName(getFolderKey(accountName, folderName)); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-16.png"); //NON-NLS + this.accountName = accountName; + this.folderName = folderName; + updateDisplayName(); + emailResults.addObserver(this); + } + + private void updateDisplayName() { + super.setDisplayName(folderName + " (" + emailResults.getArtifactIds(accountName, folderName).size() + ")"); + + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * Node representing mail folder content (mail messages) + */ + private class MessageFactory extends BaseChildFactory<DataArtifact> implements Observer { + + private final String accountName; + private final String folderName; + + private MessageFactory(String accountName, String folderName) { + super(getFolderKey(accountName, folderName)); + this.accountName = accountName; + this.folderName = folderName; + emailResults.addObserver(this); + } + + @Override + protected Node createNodeForKey(DataArtifact art) { + return new BlackboardArtifactNode(art); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + @Override + protected List<DataArtifact> makeKeys() { + List<DataArtifact> keys = new ArrayList<>(); + + if (skCase != null) { + emailResults.getArtifactIds(accountName, folderName).forEach((id) -> { + try { + DataArtifact art = skCase.getBlackboard().getDataArtifactById(id); + //Cache attributes while we are off the EDT. + //See JIRA-5969 + art.getAttributes(); + keys.add(art); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting mail messages keys", ex); //NON-NLS + } + }); + } + return keys; + } + + @Override + protected void onAdd() { + // No-op + } + + @Override + protected void onRemove() { + // No-op + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index fcff696bf04d4a8181556180c0f245f699fd3535..d64ab40d1ceca9818715ace522b2dc05100cecc7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -198,6 +198,18 @@ public Action[] getActions(boolean context) { return actionsList.toArray(new Action[actionsList.size()]); } + /** + * Accepts a ContentNodeVisitor. + * + * @param <T> The type parameter of the Visitor. + * @param visitor The Visitor. + * + * @return An object determied by the type parameter of the Visitor. + */ + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } /** * Accepts a DisplayableItemNodeVisitor. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java new file mode 100644 index 0000000000000000000000000000000000000000..a29a85332054c6f397fc73f491caff49564a9530 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -0,0 +1,533 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.FsContent; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.SlackFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.VirtualDirectory; + +/** + * Files by Size View node and related child nodes + */ +public class FileSize implements AutopsyVisitableItem { + + private SleuthkitCase skCase; + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + public enum FileSizeFilter implements AutopsyVisitableItem { + + SIZE_50_200(0, "SIZE_50_200", "50 - 200MB"), //NON-NLS + SIZE_200_1000(1, "SIZE_200_1GB", "200MB - 1GB"), //NON-NLS + SIZE_1000_(2, "SIZE_1000+", "1GB+"); //NON-NLS + private int id; + private String name; + private String displayName; + + private FileSizeFilter(int id, String name, String displayName) { + this.id = id; + this.name = name; + this.displayName = displayName; + + } + + public String getName() { + return this.name; + } + + public int getId() { + return this.id; + } + + public String getDisplayName() { + return this.displayName; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + } + + public FileSize(SleuthkitCase skCase) { + this(skCase, 0); + } + + public FileSize(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.filteringDSObjId = dsObjId; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + public SleuthkitCase getSleuthkitCase() { + return this.skCase; + } + + long filteringDataSourceObjId() { + return this.filteringDSObjId; + } + + /* + * Root node. Children are nodes for specific sizes. + */ + public static class FileSizeRootNode extends DisplayableItemNode { + + private static final String NAME = NbBundle.getMessage(FileSize.class, "FileSize.fileSizeRootNode.name"); + + FileSizeRootNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new FileSizeRootChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); + super.setName(NAME); + super.setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-size-16.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileSize.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "FileSize.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "FileSize.createSheet.name.desc"), + NAME)); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /* + * Makes the children for specific sizes + */ + public static class FileSizeRootChildren extends ChildFactory<org.sleuthkit.autopsy.datamodel.FileSize.FileSizeFilter> { + + private SleuthkitCase skCase; + private final long datasourceObjId; + private Observable notifier; + + public FileSizeRootChildren(SleuthkitCase skCase, long datasourceObjId) { + this.skCase = skCase; + this.datasourceObjId = datasourceObjId; + notifier = new FileSizeRootChildrenObservable(); + } + + /** + * Listens for case and ingest invest. Updates observers when events are + * fired. Size-based nodes are listening to this for changes. + */ + private static final class FileSizeRootChildrenObservable extends Observable { + + private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.CONTENT_CHANGED); + + FileSizeRootChildrenObservable() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + } + + private void removeListeners() { + deleteObservers(); + IngestManager.getInstance().removeIngestJobEventListener(pcl); + IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + } + + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.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. + */ + try { + // new file was added + // @@@ could check the size here and only fire off updates if we know the file meets the min size criteria + Case.getCurrentCaseThrows(); + update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || 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. + */ + try { + Case.getCurrentCaseThrows(); + update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeListeners(); + } + } + }; + + private void update() { + setChanged(); + notifyObservers(); + } + } + + @Override + protected boolean createKeys(List<FileSizeFilter> list) { + list.addAll(Arrays.asList(FileSizeFilter.values())); + return true; + } + + @Override + protected Node createNodeForKey(FileSizeFilter key) { + return new FileSizeNode(skCase, key, notifier, datasourceObjId); + } + + /* + * Node for a specific size range. Children are files. + */ + public class FileSizeNode extends DisplayableItemNode { + + private final FileSizeFilter filter; + private final long datasourceObjId; + + // use version with observer instead so that it updates + @Deprecated + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, null, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); + this.filter = filter; + this.datasourceObjId = datasourceObjId; + init(); + } + + /** + * + * @param skCase + * @param filter + * @param o Observable that provides updates when + * events are fired + * @param datasourceObjId filter by data source, if configured in + * user preferences + */ + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, o, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); + this.filter = filter; + this.datasourceObjId = datasourceObjId; + init(); + o.addObserver(new FileSizeNodeObserver()); + } + + private void init() { + super.setName(filter.getName()); + + String tooltip = filter.getDisplayName(); + this.setShortDescription(tooltip); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-size-16.png"); //NON-NLS + + updateDisplayName(); + } + + @Override + public String getItemType() { + /** + * Return getClass().getName() + filter.getName() if custom + * settings are desired for different filters. + */ + return DisplayableItemNode.FILE_PARENT_NODE_KEY; + } + + // update the display name when new events are fired + private class FileSizeNodeObserver implements Observer { + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + } + + private void updateDisplayName() { + final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter, datasourceObjId); + super.setDisplayName(filter.getDisplayName() + " (" + numVisibleChildren + ")"); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileSize.createSheet.filterType.name"), + NbBundle.getMessage(this.getClass(), "FileSize.createSheet.filterType.displayName"), + NbBundle.getMessage(this.getClass(), "FileSize.createSheet.filterType.desc"), + filter.getDisplayName())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + } + + /* + * Makes children, which are nodes for files of a given range + */ + static class FileSizeChildren extends BaseChildFactory<AbstractFile> { + + private static final Logger logger = Logger.getLogger(FileSizeChildren.class.getName()); + private final SleuthkitCase skCase; + private final FileSizeFilter filter; + private final Observable notifier; + private final long datasourceObjId; + + /** + * + * @param filter + * @param skCase + * @param o Observable that provides updates when new files are + * added to case + */ + FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { + super(filter.getName(), new ViewsKnownAndSlackFilter<>()); + this.skCase = skCase; + this.filter = filter; + this.notifier = o; + this.datasourceObjId = dsObjId; + + } + + @Override + protected void onAdd() { + if (notifier != null) { + notifier.addObserver(observer); + } + } + + @Override + protected void onRemove() { + if (notifier != null) { + notifier.deleteObserver(observer); + } + } + + private final Observer observer = new FileSizeChildrenObserver(); + + @Override + protected List<AbstractFile> makeKeys() { + return runFsQuery(); + } + + // Cause refresh of children if there are changes + private class FileSizeChildrenObserver implements Observer { + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) { + String query; + switch (filter) { + case SIZE_50_200: + query = "(size >= 50000000 AND size < 200000000)"; //NON-NLS + break; + case SIZE_200_1000: + query = "(size >= 200000000 AND size < 1000000000)"; //NON-NLS + break; + + case SIZE_1000_: + query = "(size >= 1000000000)"; //NON-NLS + break; + + default: + throw new IllegalArgumentException("Unsupported filter type to get files by size: " + filter); //NON-NLS + } + + // Ignore unallocated block files. + query = query + " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"; //NON-NLS + + // filter by datasource if indicated in case preferences + if (filteringDSObjId > 0) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + + return query; + } + + private List<AbstractFile> runFsQuery() { + List<AbstractFile> ret = new ArrayList<>(); + + try { + String query = makeQuery(filter, datasourceObjId); + + ret = skCase.findAllFilesWhere(query); + } catch (Exception e) { + logger.log(Level.SEVERE, "Error getting files for the file size view: " + e.getMessage()); //NON-NLS + } + + return ret; + } + + /** + * Get children count without actually loading all nodes + * + * @return + */ + static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter, long datasourceObjId) { + try { + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting files by size search view count", ex); //NON-NLS + return 0; + } + } + + @Override + protected Node createNodeForKey(AbstractFile key) { + return key.accept(new ContentVisitor.Default<AbstractNode>() { + public FileNode visit(AbstractFile f) { + return new FileNode(f, false); + } + + public FileNode visit(FsContent f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(LayoutFile f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(File f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(Directory f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(LocalFile f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(DerivedFile f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(VirtualDirectory f) { + return new FileNode(f, false); + } + + @Override + public FileNode visit(SlackFile f) { + return new FileNode(f, false); + } + + @Override + protected AbstractNode defaultVisit(Content di) { + throw new UnsupportedOperationException( + NbBundle.getMessage(this.getClass(), + "FileSize.exception.notSupported.msg", + di.toString())); + } + }); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..9404362218a375f2047f9dbbebb683bc22f81335 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -0,0 +1,500 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.SwingWorker; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.AnalysisResultAdded; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.Score; +import org.sleuthkit.datamodel.SlackFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitItemVisitor; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * File Types node support + */ +public final class FileTypes implements AutopsyVisitableItem { + + private static final Logger logger = Logger.getLogger(FileTypes.class.getName()); + @NbBundle.Messages("FileTypes.name.text=File Types") + private static final String NAME = Bundle.FileTypes_name_text(); + /** + * Threshold used to limit db queries for child node counts. When the + * tsk_files table has more than this number of rows, we don't query for the + * child node counts, and since we don't have an accurate number we don't + * show the counts. + */ + private static final int NODE_COUNT_FILE_TABLE_THRESHOLD = 1_000_000; + /** + * Used to keep track of whether we have hit + * NODE_COUNT_FILE_TABLE_THRESHOLD. If we have, we stop querying for the + * number of rows in tsk_files, since it is already too large. + */ + private boolean showCounts = true; + + private final long datasourceObjId; + + + FileTypes(long dsObjId) { + this.datasourceObjId = dsObjId; + updateShowCounts(); + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + /** + * Check the db to determine if the nodes should show child counts. + */ + void updateShowCounts() { + /* + * once we have passed the threshold, we don't need to keep checking the + * number of rows in tsk_files + */ + if (showCounts) { + try { + if (Case.getCurrentCaseThrows().getSleuthkitCase().countFilesWhere("1=1") > NODE_COUNT_FILE_TABLE_THRESHOLD) { //NON-NLS + showCounts = false; + } + } catch (NoCurrentCaseException | TskCoreException tskCoreException) { + showCounts = false; + logger.log(Level.SEVERE, "Error counting files.", tskCoreException); //NON-NLS + } + } + } + + /** + * Node which will contain By Mime Type and By Extension nodes. + */ + public final class FileTypesNode extends DisplayableItemNode { + + FileTypesNode() { + super(new RootContentChildren(Arrays.asList( + new FileTypesByExtension(FileTypes.this), + new FileTypesByMimeType(FileTypes.this))), + Lookups.singleton(NAME)); + this.setName(NAME); + this.setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + @NbBundle.Messages({ + "FileTypes.createSheet.name.name=Name", + "FileTypes.createSheet.name.displayName=Name", + "FileTypes.createSheet.name.desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(Bundle.FileTypes_createSheet_name_name(), + Bundle.FileTypes_createSheet_name_displayName(), + Bundle.FileTypes_createSheet_name_desc(), + NAME + )); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + } + + static class FileNodeCreationVisitor extends ContentVisitor.Default<AbstractNode> { + + FileNodeCreationVisitor() { + } + + @Override + public FileNode visit(File f) { + return new FileNode(f, false); + } + + @Override + public DirectoryNode visit(Directory d) { + return new DirectoryNode(d); + } + + @Override + public LayoutFileNode visit(LayoutFile lf) { + return new LayoutFileNode(lf); + } + + @Override + public LocalFileNode visit(DerivedFile df) { + return new LocalFileNode(df); + } + + @Override + public LocalFileNode visit(LocalFile lf) { + return new LocalFileNode(lf); + } + + @Override + public SlackFileNode visit(SlackFile sf) { + return new SlackFileNode(sf, false); + } + + @Override + protected AbstractNode defaultVisit(Content di) { + throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(), "FileTypeChildren.exception.notSupported.msg", di.toString())); + } + } + + static abstract class BGCountUpdatingNode extends DisplayableItemNode implements Observer { + + private long childCount = -1; + private FileTypes typesRoot; + + BGCountUpdatingNode(FileTypes typesRoot, Children children) { + this(typesRoot, children, null); + } + + BGCountUpdatingNode(FileTypes typesRoot, Children children, Lookup lookup) { + super(children, lookup); + this.typesRoot = typesRoot; + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + abstract String getDisplayNameBase(); + + /** + * Calculate the number of children of this node, possibly by querying + * the DB. + * + * @return @throws TskCoreException if there was an error querying the + * DB to calculate the number of children. + */ + abstract long calculateChildCount() throws TskCoreException; + + /** + * Updates the display name of the mediaSubTypeNode to include the count + * of files which it represents. + */ + @NbBundle.Messages("FileTypes.bgCounting.placeholder= (counting...)") + void updateDisplayName() { + if (typesRoot.showCounts) { + //only show "(counting...)" the first time, otherwise it is distracting. + setDisplayName(getDisplayNameBase() + ((childCount < 0) ? Bundle.FileTypes_bgCounting_placeholder() + : (" (" + childCount + ")"))); //NON-NLS + new SwingWorker<Long, Void>() { + @Override + protected Long doInBackground() throws Exception { + return calculateChildCount(); + } + + @Override + protected void done() { + try { + childCount = get(); + setDisplayName(getDisplayNameBase() + " (" + childCount + ")"); //NON-NLS + } catch (InterruptedException | ExecutionException ex) { + setDisplayName(getDisplayNameBase()); + logger.log(Level.WARNING, "Failed to get count of files for " + getDisplayNameBase(), ex); //NON-NLS + } + } + }.execute(); + } else { + setDisplayName(getDisplayNameBase() + ((childCount < 0) ? "" : (" (" + childCount + "+)"))); //NON-NLS + } + } + } + + /** + * Class that is used as a key by NetBeans for creating result nodes. This + * is a wrapper around a Content object and is being put in place as an + * optimization to avoid the Content.hashCode() implementation which issues + * a database query to get the number of children when determining whether 2 + * Content objects represent the same thing. TODO: This is a temporary + * solution that can hopefully be removed once we address the issue of + * determining how many children a Content has (JIRA-2823). + */ + static class FileTypesKey implements Content { + + private final Content content; + + public FileTypesKey(Content content) { + this.content = content; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FileTypesKey other = (FileTypesKey) obj; + + return this.content.getId() == other.content.getId(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 101 * hash + (int)(this.content.getId() ^ (this.content.getId() >>> 32)); + return hash; + } + + @Override + public <T> T accept(SleuthkitItemVisitor<T> v) { + return content.accept(v); + } + + @Override + public int read(byte[] buf, long offset, long len) throws TskCoreException { + return content.read(buf, offset, len); + } + + @Override + public void close() { + content.close(); + } + + @Override + public long getSize() { + return content.getSize(); + } + + @Override + public <T> T accept(ContentVisitor<T> v) { + return content.accept(v); + } + + @Override + public String getName() { + return content.getName(); + } + + @Override + public String getUniquePath() throws TskCoreException { + return content.getUniquePath(); + } + + @Override + public long getId() { + return content.getId(); + } + + @Override + public Content getDataSource() throws TskCoreException { + return content.getDataSource(); + } + + @Override + public List<Content> getChildren() throws TskCoreException { + return content.getChildren(); + } + + @Override + public boolean hasChildren() throws TskCoreException { + return content.hasChildren(); + } + + @Override + public int getChildrenCount() throws TskCoreException { + return content.getChildrenCount(); + } + + @Override + public Content getParent() throws TskCoreException { + return content.getParent(); + } + + @Override + public List<Long> getChildrenIds() throws TskCoreException { + return content.getChildrenIds(); + } + + @Deprecated + @SuppressWarnings("deprecation") + @Override + public BlackboardArtifact newArtifact(int artifactTypeID) throws TskCoreException { + return content.newArtifact(artifactTypeID); + } + + @Deprecated + @SuppressWarnings("deprecation") + @Override + public BlackboardArtifact newArtifact(BlackboardArtifact.ARTIFACT_TYPE type) throws TskCoreException { + return content.newArtifact(type); + } + + @Override + public DataArtifact newDataArtifact(BlackboardArtifact.Type artifactType, Collection<BlackboardAttribute> attributesList, Long osAccountId) throws TskCoreException { + return content.newDataArtifact(artifactType, attributesList, osAccountId); + } + + @Override + public DataArtifact newDataArtifact(BlackboardArtifact.Type artifactType, Collection<BlackboardAttribute> attributesList, Long osAccountId, long dataSourceId) throws TskCoreException { + return content.newDataArtifact(artifactType, attributesList, osAccountId, dataSourceId); + } + + @Override + public DataArtifact newDataArtifact(BlackboardArtifact.Type artifactType, Collection<BlackboardAttribute> attributesList) throws TskCoreException { + return content.newDataArtifact(artifactType, attributesList); + } + + @Override + public ArrayList<BlackboardArtifact> getArtifacts(String artifactTypeName) throws TskCoreException { + return content.getArtifacts(artifactTypeName); + } + + @Override + public BlackboardArtifact getGenInfoArtifact() throws TskCoreException { + return content.getGenInfoArtifact(); + } + + @Override + public BlackboardArtifact getGenInfoArtifact(boolean create) throws TskCoreException { + return content.getGenInfoArtifact(create); + } + + @Override + public ArrayList<BlackboardAttribute> getGenInfoAttributes(BlackboardAttribute.ATTRIBUTE_TYPE attr_type) throws TskCoreException { + return content.getGenInfoAttributes(attr_type); + } + + @Override + public ArrayList<BlackboardArtifact> getArtifacts(int artifactTypeID) throws TskCoreException { + return content.getArtifacts(artifactTypeID); + } + + @Override + public ArrayList<BlackboardArtifact> getArtifacts(BlackboardArtifact.ARTIFACT_TYPE type) throws TskCoreException { + return content.getArtifacts(type); + } + + @Override + public ArrayList<BlackboardArtifact> getAllArtifacts() throws TskCoreException { + return content.getAllArtifacts(); + } + + @Override + public Set<String> getHashSetNames() throws TskCoreException { + return content.getHashSetNames(); + } + + @Override + public long getArtifactsCount(String artifactTypeName) throws TskCoreException { + return content.getArtifactsCount(artifactTypeName); + } + + @Override + public long getArtifactsCount(int artifactTypeID) throws TskCoreException { + return content.getArtifactsCount(artifactTypeID); + } + + @Override + public long getArtifactsCount(BlackboardArtifact.ARTIFACT_TYPE type) throws TskCoreException { + return content.getArtifactsCount(type); + } + + @Override + public long getAllArtifactsCount() throws TskCoreException { + return content.getAllArtifactsCount(); + } + + @Override + public AnalysisResultAdded newAnalysisResult(BlackboardArtifact.Type type, Score score, String string, String string1, String string2, Collection<BlackboardAttribute> clctn) throws TskCoreException { + return content.newAnalysisResult(type, score, string, string1, string2, clctn); + } + + @Override + public AnalysisResultAdded newAnalysisResult(BlackboardArtifact.Type type, Score score, String string, String string1, String string2, Collection<BlackboardAttribute> clctn, long dataSourceId) throws TskCoreException { + return content.newAnalysisResult(type, score, string, string1, string2, clctn, dataSourceId); + } + + @Override + public Score getAggregateScore() throws TskCoreException { + return content.getAggregateScore(); + } + + @Override + public List<AnalysisResult> getAnalysisResults(BlackboardArtifact.Type type) throws TskCoreException { + return content.getAnalysisResults(type); + } + + @Override + public List<AnalysisResult> getAllAnalysisResults() throws TskCoreException { + return content.getAllAnalysisResults(); + } + + @Override + public List<DataArtifact> getAllDataArtifacts() throws TskCoreException { + return content.getAllDataArtifacts(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..9d2191cb098165874dc9f3e632240c33fb4802a5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -0,0 +1,678 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesKey; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; + +/** + * Filters database results by file extension. + */ +public final class FileTypesByExtension implements AutopsyVisitableItem { + + private final static Logger logger = Logger.getLogger(FileTypesByExtension.class.getName()); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.CONTENT_CHANGED); + private final FileTypes typesRoot; + + public FileTypesByExtension(FileTypes typesRoot) { + this.typesRoot = typesRoot; + } + + public SleuthkitCase getSleuthkitCase() { + try { + return Case.getCurrentCaseThrows().getSleuthkitCase(); + } catch (NoCurrentCaseException ex) { + return null; + } + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + + /** + * Listens for case and ingest invest. Updates observers when events are + * fired. FileType and FileTypes nodes are all listening to this. + */ + private class FileTypesByExtObservable extends Observable implements RefreshThrottler.Refresher { + + private final PropertyChangeListener pcl; + private final Set<Case.Events> CASE_EVENTS_OF_INTEREST; + /** + * RefreshThrottler is used to limit the number of refreshes performed + * when CONTENT_CHANGED and DATA_ADDED ingest module events are + * received. + */ + private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); + + private FileTypesByExtObservable() { + super(); + this.CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); + this.pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || 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. + */ + try { + Case.getCurrentCaseThrows(); + typesRoot.updateShowCounts(); + update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeListeners(); + } + } + }; + + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); + refreshThrottler.registerForIngestModuleEvents(); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + } + + private void removeListeners() { + deleteObservers(); + IngestManager.getInstance().removeIngestJobEventListener(pcl); + refreshThrottler.unregisterEventListener(); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + } + + private void update() { + setChanged(); + notifyObservers(); + } + + @Override + public void refresh() { + typesRoot.updateShowCounts(); + update(); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * If a new file has been added but does not have an + * extension there is nothing to do. + */ + if ((evt.getOldValue() instanceof ModuleContentEvent) == false) { + return false; + } + ModuleContentEvent moduleContentEvent = (ModuleContentEvent) evt.getOldValue(); + if ((moduleContentEvent.getSource() instanceof AbstractFile) == false) { + return false; + } + AbstractFile abstractFile = (AbstractFile) moduleContentEvent.getSource(); + if (!abstractFile.getNameExtension().isEmpty()) { + return true; + } + } catch (NoCurrentCaseException ex) { + /** + * Case is closed, no refresh needed. + */ + return false; + } + } + return false; + } + } + + private static final String FNAME = NbBundle.getMessage(FileTypesByExtNode.class, "FileTypesByExtNode.fname.text"); + + /** + * Node for root of file types view. Children are nodes for specific types. + */ + class FileTypesByExtNode extends DisplayableItemNode { + + private final FileTypesByExtension.RootFilter filter; + + /** + * + * @param skCase + * @param filter null to display root node of file type tree, pass in + * something to provide a sub-node. + */ + FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter) { + this(skCase, filter, null); + } + + /** + * + * @param skCase + * @param filter + * @param o Observable that was created by a higher-level node that + * provides updates on events + */ + private FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o) { + + super(Children.create(new FileTypesByExtNodeChildren(skCase, filter, o), true), + Lookups.singleton(filter == null ? FNAME : filter.getDisplayName())); + this.filter = filter; + + // root node of tree + if (filter == null) { + super.setName(FNAME); + super.setDisplayName(FNAME); + } // sub-node in file tree (i.e. documents, exec, etc.) + else { + super.setName(filter.getDisplayName()); + super.setDisplayName(filter.getDisplayName()); + } + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + if (filter != null && (filter.equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER) || filter.equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER))) { + String extensions = ""; + for (String ext : filter.getFilter()) { + extensions += "'" + ext + "', "; + } + extensions = extensions.substring(0, extensions.lastIndexOf(',')); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.desc"), extensions)); + } else { + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.name.desc"), getDisplayName())); + } + return sheet; + } + + @Override + public String getItemType() { + /** + * Because Documents and Executable are further expandable, their + * column order settings should be stored separately. + */ + if (filter == null) { + return getClass().getName(); + } + if (filter.equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER) || filter.equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER)) { + return getClass().getName() + filter.getName(); + } + return getClass().getName(); + } + + } + + private class FileTypesByExtNodeChildren extends ChildFactory<FileTypesByExtension.SearchFilterInterface> { + + private final SleuthkitCase skCase; + private final FileTypesByExtension.RootFilter filter; + private final FileTypesByExtObservable notifier; + + /** + * + * @param skCase + * @param filter Is null for root node + * @param o Observable that provides updates based on events being + * fired (or null if one needs to be created) + */ + private FileTypesByExtNodeChildren(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o) { + super(); + this.skCase = skCase; + this.filter = filter; + if (o == null) { + this.notifier = new FileTypesByExtObservable(); + } else { + this.notifier = o; + } + } + + @Override + protected boolean createKeys(List<FileTypesByExtension.SearchFilterInterface> list) { + // root node + if (filter == null) { + list.addAll(Arrays.asList(FileTypesByExtension.RootFilter.values())); + } // document and executable has another level of nodes + else if (filter.equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER)) { + list.addAll(Arrays.asList(FileTypesByExtension.DocumentFilter.values())); + } else if (filter.equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER)) { + list.addAll(Arrays.asList(FileTypesByExtension.ExecutableFilter.values())); + } + return true; + } + + @Override + protected Node createNodeForKey(FileTypesByExtension.SearchFilterInterface key) { + // make new nodes for the sub-nodes + if (key.getName().equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER.getName())) { + return new FileTypesByExtNode(skCase, FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER, notifier); + } else if (key.getName().equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER.getName())) { + return new FileTypesByExtNode(skCase, FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER, notifier); + } else { + return new FileExtensionNode(key, skCase, notifier); + } + } + } + + /** + * Node for a specific file type / extension. Children of it will be the + * files of that type. + */ + final class FileExtensionNode extends FileTypes.BGCountUpdatingNode { + + private final FileTypesByExtension.SearchFilterInterface filter; + + /** + * + * @param filter Extensions that will be shown for this node + * @param skCase + * @param o Observable that sends updates when the child factories + * should refresh + */ + FileExtensionNode(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, FileTypesByExtObservable o) { + super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o, filter.getDisplayName()), true), + Lookups.singleton(filter.getDisplayName())); + this.filter = filter; + super.setName(filter.getDisplayName()); + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-filter-icon.png"); //NON-NLS + + o.addObserver(this); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.name"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.displayName"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.desc"), + filter.getDisplayName())); + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.displayName"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.desc"), + String.join(", ", filter.getFilter()))); + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + /** + * Consider allowing different configurations for Images, Videos, etc + * (in which case we'd return getClass().getName() + filter.getName() + * for all filters). + */ + @Override + public String getItemType() { + return DisplayableItemNode.FILE_PARENT_NODE_KEY; + } + + @Override + String getDisplayNameBase() { + return filter.getDisplayName(); + } + + @Override + long calculateChildCount() throws TskCoreException { + try { + return Case.getCurrentCaseThrows().getSleuthkitCase().countFilesWhere(createQuery(filter)); + } catch (NoCurrentCaseException ex) { + throw new TskCoreException("No open case.", ex); + } + } + } + + private String createQuery(FileTypesByExtension.SearchFilterInterface filter) { + if (filter.getFilter().isEmpty()) { + // We should never be given a search filter without extensions + // but if we are it is clearly a programming error so we throw + // an IllegalArgumentException. + throw new IllegalArgumentException("Empty filter list passed to createQuery()"); // NON-NLS + } + + return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" + + (UserPreferences.hideKnownFilesInViewsTree() + ? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")" + : " ") + + (filteringDataSourceObjId() > 0 + ? " AND data_source_obj_id = " + filteringDataSourceObjId() + : " ") + + " AND (extension IN (" + filter.getFilter().stream() + .map(String::toLowerCase) + .map(s -> "'" + StringUtils.substringAfter(s, ".") + "'") + .collect(Collectors.joining(", ")) + "))"; + } + + /** + * Child node factory for a specific file type - does the database query. + */ + private class FileExtensionNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer { + + private final SleuthkitCase skCase; + private final FileTypesByExtension.SearchFilterInterface filter; + private final Observable notifier; + + /** + * + * @param filter Extensions to display + * @param skCase + * @param o Observable that will notify when there could be new + * data to display + * @param nodeName + */ + private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) { + super(nodeName, new ViewsKnownAndSlackFilter<>()); + this.filter = filter; + this.skCase = skCase; + notifier = o; + } + + @Override + protected void onAdd() { + if (notifier != null) { + notifier.addObserver(this); + } + } + + @Override + protected void onRemove() { + if (notifier != null) { + notifier.deleteObserver(this); + } + } + + @Override + public void update(Observable o, Object arg) { + refresh(false); + } + + @Override + protected Node createNodeForKey(FileTypesKey key) { + return key.accept(new FileTypes.FileNodeCreationVisitor()); + } + + @Override + protected List<FileTypesKey> makeKeys() { + try { + return skCase.findAllFilesWhere(createQuery(filter)) + .stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS + } + return Collections.emptyList(); + } + } + + // root node filters + @Messages({"FileTypeExtensionFilters.tskDatabaseFilter.text=Databases"}) + public static enum RootFilter implements AutopsyVisitableItem, SearchFilterInterface { + + TSK_IMAGE_FILTER(0, "TSK_IMAGE_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskImgFilter.text"), + FileTypeExtensions.getImageExtensions()), + TSK_VIDEO_FILTER(1, "TSK_VIDEO_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskVideoFilter.text"), + FileTypeExtensions.getVideoExtensions()), + TSK_AUDIO_FILTER(2, "TSK_AUDIO_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskAudioFilter.text"), + FileTypeExtensions.getAudioExtensions()), + TSK_ARCHIVE_FILTER(3, "TSK_ARCHIVE_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskArchiveFilter.text"), + FileTypeExtensions.getArchiveExtensions()), + TSK_DATABASE_FILTER(4, "TSK_DATABASE_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDatabaseFilter.text"), + FileTypeExtensions.getDatabaseExtensions()), + TSK_DOCUMENT_FILTER(5, "TSK_DOCUMENT_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDocumentFilter.text"), + Arrays.asList(".htm", ".html", ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".rtf")), //NON-NLS + TSK_EXECUTABLE_FILTER(6, "TSK_EXECUTABLE_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskExecFilter.text"), + FileTypeExtensions.getExecutableExtensions()); //NON-NLS + + private final int id; + private final String name; + private final String displayName; + private final List<String> filter; + + private RootFilter(int id, String name, String displayName, List<String> filter) { + this.id = id; + this.name = name; + this.displayName = displayName; + this.filter = filter; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public int getId() { + return this.id; + } + + @Override + public String getDisplayName() { + return this.displayName; + } + + @Override + public List<String> getFilter() { + return Collections.unmodifiableList(this.filter); + } + } + + // document sub-node filters + public static enum DocumentFilter implements AutopsyVisitableItem, SearchFilterInterface { + + AUT_DOC_HTML(0, "AUT_DOC_HTML", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.autDocHtmlFilter.text"), + Arrays.asList(".htm", ".html")), //NON-NLS + AUT_DOC_OFFICE(1, "AUT_DOC_OFFICE", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.autDocOfficeFilter.text"), + Arrays.asList(".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx")), //NON-NLS + AUT_DOC_PDF(2, "AUT_DOC_PDF", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.autoDocPdfFilter.text"), + Arrays.asList(".pdf")), //NON-NLS + AUT_DOC_TXT(3, "AUT_DOC_TXT", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.autDocTxtFilter.text"), + Arrays.asList(".txt")), //NON-NLS + AUT_DOC_RTF(4, "AUT_DOC_RTF", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.autDocRtfFilter.text"), + Arrays.asList(".rtf")); //NON-NLS + + private final int id; + private final String name; + private final String displayName; + private final List<String> filter; + + private DocumentFilter(int id, String name, String displayName, List<String> filter) { + this.id = id; + this.name = name; + this.displayName = displayName; + this.filter = filter; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public int getId() { + return this.id; + } + + @Override + public String getDisplayName() { + return this.displayName; + } + + @Override + public List<String> getFilter() { + return Collections.unmodifiableList(this.filter); + } + } + + // executable sub-node filters + public static enum ExecutableFilter implements AutopsyVisitableItem, SearchFilterInterface { + + ExecutableFilter_EXE(0, "ExecutableFilter_EXE", ".exe", Arrays.asList(".exe")), //NON-NLS + ExecutableFilter_DLL(1, "ExecutableFilter_DLL", ".dll", Arrays.asList(".dll")), //NON-NLS + ExecutableFilter_BAT(2, "ExecutableFilter_BAT", ".bat", Arrays.asList(".bat")), //NON-NLS + ExecutableFilter_CMD(3, "ExecutableFilter_CMD", ".cmd", Arrays.asList(".cmd")), //NON-NLS + ExecutableFilter_COM(4, "ExecutableFilter_COM", ".com", Arrays.asList(".com")); //NON-NLS + + private final int id; + private final String name; + private final String displayName; + private final List<String> filter; + + private ExecutableFilter(int id, String name, String displayName, List<String> filter) { + this.id = id; + this.name = name; + this.displayName = displayName; + this.filter = filter; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public int getId() { + return this.id; + } + + @Override + public String getDisplayName() { + return this.displayName; + } + + @Override + public List<String> getFilter() { + return Collections.unmodifiableList(this.filter); + } + } + + interface SearchFilterInterface { + + public String getName(); + + public int getId(); + + public String getDisplayName(); + + public List<String> getFilter(); + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java new file mode 100644 index 0000000000000000000000000000000000000000..e75739290c2865ea683b539f0808d27dc50084fb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -0,0 +1,515 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; +import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesKey; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; + +/** + * Class which contains the Nodes for the 'By Mime Type' view located in the + * File Types view, shows all files with a mime type. Will initially be empty + * until file type identification has been performed. Contains a Property Change + * Listener which is checking for changes in IngestJobEvent Completed or + * Canceled and IngestModuleEvent Content Changed. + */ +public final class FileTypesByMimeType extends Observable implements AutopsyVisitableItem { + + private final static Logger logger = Logger.getLogger(FileTypesByMimeType.class.getName()); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + + /** + * The nodes of this tree will be determined dynamically by the mimetypes + * which exist in the database. This hashmap will store them with the media + * type as the key and a Map, from media subtype to count, as the value. + */ + private final HashMap<String, Map<String, Long>> existingMimeTypeCounts = new HashMap<>(); + /** + * Root of the File Types tree. Used to provide single answer to question: + * Should the child counts be shown next to the nodes? + */ + private final FileTypes typesRoot; + + /** + * The pcl is in the class because it has the easiest mechanisms to add and + * remove itself during its life cycles. + */ + private final PropertyChangeListener pcl; + + private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); + + /** + * RefreshThrottler is used to limit the number of refreshes performed when + * CONTENT_CHANGED and DATA_ADDED ingest module events are received. + */ + private final RefreshThrottler refreshThrottler; + + /** + * Create the base expression used as the where clause in the queries for + * files by mime type. Filters out certain kinds of files and directories, + * and known/slack files based on user preferences. + * + * @return The base expression to be used in the where clause of queries for + * files by mime type. + */ + private String createBaseWhereExpr() { + return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" + + " AND (type IN (" + + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() + + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + + "))" + + ((filteringDataSourceObjId() > 0) ? " AND data_source_obj_id = " + this.filteringDataSourceObjId() : " ") + + (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : ""); + } + + private void removeListeners() { + deleteObservers(); + IngestManager.getInstance().removeIngestJobEventListener(pcl); + refreshThrottler.unregisterEventListener(); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + } + + /** + * Performs the query on the database to get all distinct MIME types of + * files in it, and populate the hashmap with those results. + */ + private void populateHashMap() { + String query = "SELECT mime_type, count(*) AS count FROM tsk_files " + + " WHERE mime_type IS NOT null " + + " AND " + createBaseWhereExpr() + + " GROUP BY mime_type"; + synchronized (existingMimeTypeCounts) { + existingMimeTypeCounts.clear(); + try + (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + while (resultSet.next()) { + final String mime_type = resultSet.getString("mime_type"); //NON-NLS + if (!mime_type.isEmpty()) { + //if the mime_type contained multiple slashes then everything after the first slash will become the subtype + final String mediaType = StringUtils.substringBefore(mime_type, "/"); + final String subType = StringUtils.removeStart(mime_type, mediaType + "/"); + if (!mediaType.isEmpty() && !subType.isEmpty()) { + final long count = resultSet.getLong("count"); + existingMimeTypeCounts.computeIfAbsent(mediaType, t -> new HashMap<>()) + .put(subType, count); + } + } + } + } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { + logger.log(Level.SEVERE, "Unable to populate File Types by MIME Type tree view from DB: ", ex); //NON-NLS + } + } + + setChanged(); + notifyObservers(); + } + + FileTypesByMimeType(FileTypes typesRoot) { + this.typesRoot = typesRoot; + this.pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) + || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + + refreshMimeTypes(); + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + if (evt.getNewValue() == null) { + removeListeners(); + } + } + }; + refreshThrottler = new RefreshThrottler(new FileTypesByMimeTypeRefresher()); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); + refreshThrottler.registerForIngestModuleEvents(); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); + populateHashMap(); + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + + /** + * Method to check if the node in question is a ByMimeTypeNode which is + * empty. + * + * @param node the Node which you wish to check. + * + * @return True if originNode is an instance of ByMimeTypeNode and is empty, + * false otherwise. + */ + public static boolean isEmptyMimeTypeNode(Node node) { + boolean isEmptyMimeNode = false; + if (node instanceof FileTypesByMimeType.ByMimeTypeNode && ((FileTypesByMimeType.ByMimeTypeNode) node).isEmpty()) { + isEmptyMimeNode = true; + } + return isEmptyMimeNode; + + } + + private void refreshMimeTypes() { + /** + * 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(); + typesRoot.updateShowCounts(); + populateHashMap(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + + /** + * Responsible for updating the 'By Mime Type' view in the UI. See + * RefreshThrottler for more details. + */ + private class FileTypesByMimeTypeRefresher implements RefreshThrottler.Refresher { + + @Override + public void refresh() { + refreshMimeTypes(); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + return true; + } + + } + + /** + * Class which represents the root node of the "By MIME Type" tree, will + * have children of each media type present in the database or no children + * when the file detection module has not been run and MIME type is + * currently unknown. + */ + class ByMimeTypeNode extends DisplayableItemNode { + + @NbBundle.Messages({"FileTypesByMimeType.name.text=By MIME Type"}) + + final String NAME = Bundle.FileTypesByMimeType_name_text(); + + ByMimeTypeNode() { + super(Children.create(new ByMimeTypeNodeChildren(), true), Lookups.singleton(Bundle.FileTypesByMimeType_name_text())); + super.setName(NAME); + super.setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + boolean isEmpty() { + synchronized (existingMimeTypeCounts) { + return existingMimeTypeCounts.isEmpty(); + } + } + } + + /** + * Creates the children for the "By MIME Type" node these children will each + * represent a distinct media type present in the DB + */ + private class ByMimeTypeNodeChildren extends ChildFactory<String> implements Observer { + + private ByMimeTypeNodeChildren() { + super(); + addObserver(this); + } + + @Override + protected boolean createKeys(List<String> mediaTypeNodes) { + final List<String> keylist; + synchronized (existingMimeTypeCounts) { + keylist = new ArrayList<>(existingMimeTypeCounts.keySet()); + } + Collections.sort(keylist); + mediaTypeNodes.addAll(keylist); + + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new MediaTypeNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + /** + * The Media type node created by the ByMimeTypeNodeChildren and contains + * one of the unique media types present in the database for this case. + */ + class MediaTypeNode extends DisplayableItemNode { + + @NbBundle.Messages({"FileTypesByMimeTypeNode.createSheet.mediaType.name=Type", + "FileTypesByMimeTypeNode.createSheet.mediaType.displayName=Type", + "FileTypesByMimeTypeNode.createSheet.mediaType.desc=no description"}) + + MediaTypeNode(String name) { + super(Children.create(new MediaTypeNodeChildren(name), true), Lookups.singleton(name)); + setName(name); + setDisplayName(name); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.name"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaType.desc"), getDisplayName())); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + } + + /** + * Creates children for media type nodes, children will be MediaSubTypeNodes + * and represent one of the subtypes which are present in the database of + * their media type. + */ + private class MediaTypeNodeChildren extends ChildFactory<String> implements Observer { + + String mediaType; + + MediaTypeNodeChildren(String name) { + addObserver(this); + this.mediaType = name; + } + + @Override + protected boolean createKeys(List<String> mediaTypeNodes) { + mediaTypeNodes.addAll(existingMimeTypeCounts.get(mediaType).keySet()); + return true; + } + + @Override + protected Node createNodeForKey(String subtype) { + String mimeType = mediaType + "/" + subtype; + return new MediaSubTypeNode(mimeType); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + } + + /** + * Node which represents the media sub type in the By MIME type tree, the + * media subtype is the portion of the MIME type following the /. + */ + final class MediaSubTypeNode extends FileTypes.BGCountUpdatingNode { + + @NbBundle.Messages({"FileTypesByMimeTypeNode.createSheet.mediaSubtype.name=Subtype", + "FileTypesByMimeTypeNode.createSheet.mediaSubtype.displayName=Subtype", + "FileTypesByMimeTypeNode.createSheet.mediaSubtype.desc=no description"}) + private final String mimeType; + private final String subType; + + private MediaSubTypeNode(String mimeType) { + super(typesRoot, Children.create(new MediaSubTypeNodeChildren(mimeType), true), Lookups.singleton(mimeType)); + this.mimeType = mimeType; + this.subType = StringUtils.substringAfter(mimeType, "/"); + super.setName(mimeType); + super.setDisplayName(subType); + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-filter-icon.png"); //NON-NLS + addObserver(this); + } + + /** + * This returns true because any MediaSubTypeNode that exists is going + * to be a bottom level node in the Tree view on the left of Autopsy. + * + * @return true + */ + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor< T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.name"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByMimeTypeNode.createSheet.mediaSubtype.desc"), getDisplayName())); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + String getDisplayNameBase() { + return subType; + } + + @Override + long calculateChildCount() { + return existingMimeTypeCounts.get(StringUtils.substringBefore(mimeType, "/")).get(subType); + } + } + + /** + * Factory for populating the contents of the Media Sub Type Node with the + * files that match MimeType which is represented by this position in the + * tree. + */ + private class MediaSubTypeNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer { + + private final String mimeType; + + private MediaSubTypeNodeChildren(String mimeType) { + super(mimeType, new ViewsKnownAndSlackFilter<>()); + addObserver(this); + this.mimeType = mimeType; + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + @Override + protected Node createNodeForKey(FileTypesKey key) { + return key.accept(new FileTypes.FileNodeCreationVisitor()); + } + + @Override + protected List<FileTypesKey> makeKeys() { + try { + return Case.getCurrentCaseThrows().getSleuthkitCase() + .findAllFilesWhere(createBaseWhereExpr() + " AND mime_type = '" + mimeType + "'") + .stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()); //NON-NLS + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS + } + return Collections.emptyList(); + } + + @Override + protected void onAdd() { + // No-op + } + + @Override + protected void onRemove() { + // No-op + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java new file mode 100644 index 0000000000000000000000000000000000000000..970e546f9899897db89babcbced2ca71d6e910da --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -0,0 +1,440 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_HASHSET_HIT; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode; +import org.sleuthkit.datamodel.AnalysisResult; + +/** + * Hash set hits node support. Inner classes have all of the nodes in the tree. + */ +public class HashsetHits implements AutopsyVisitableItem { + + private static final String HASHSET_HITS = BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeName(); + private static final String DISPLAY_NAME = BlackboardArtifact.Type.TSK_HASHSET_HIT.getDisplayName(); + private static final Logger logger = Logger.getLogger(HashsetHits.class.getName()); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED); + private SleuthkitCase skCase; + private final HashsetResults hashsetResults; + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + /** + * Constructor + * + * @param skCase Case DB + * + */ + public HashsetHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public HashsetHits(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.filteringDSObjId = objId; + hashsetResults = new HashsetResults(); + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * Stores all of the hashset results in a single class that is observable + * for the child nodes + */ + private class HashsetResults extends Observable { + + // maps hashset name to list of artifacts for that set + // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized + private final Map<String, Set<Long>> hashSetHitsMap = new LinkedHashMap<>(); + + HashsetResults() { + update(); + } + + List<String> getSetNames() { + List<String> names; + synchronized (hashSetHitsMap) { + names = new ArrayList<>(hashSetHitsMap.keySet()); + } + Collections.sort(names); + return names; + } + + Set<Long> getArtifactIds(String hashSetName) { + synchronized (hashSetHitsMap) { + return hashSetHitsMap.get(hashSetName); + } + } + + @SuppressWarnings("deprecation") + final void update() { + synchronized (hashSetHitsMap) { + hashSetHitsMap.clear(); + } + + if (skCase == null) { + return; + } + + int setNameId = ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(); + int artId = TSK_HASHSET_HIT.getTypeID(); + String query = "SELECT value_text,blackboard_artifacts.artifact_obj_id,attribute_type_id " //NON-NLS + + "FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS + + "attribute_type_id=" + setNameId //NON-NLS + + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (filteringDSObjId > 0) { + query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + synchronized (hashSetHitsMap) { + while (resultSet.next()) { + String setName = resultSet.getString("value_text"); //NON-NLS + long artifactObjId = resultSet.getLong("artifact_obj_id"); //NON-NLS + if (!hashSetHitsMap.containsKey(setName)) { + hashSetHitsMap.put(setName, new HashSet<>()); + } + hashSetHitsMap.get(setName).add(artifactObjId); + } + } + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS + } + + setChanged(); + notifyObservers(); + } + } + + /** + * Top-level node for all hash sets + */ + public class RootNode extends UpdatableCountTypeNode { + + public RootNode() { + super(Children.create(new HashsetNameFactory(), true), + Lookups.singleton(DISPLAY_NAME), + DISPLAY_NAME, + filteringDSObjId, + TSK_HASHSET_HIT); + + super.setName(HASHSET_HITS); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * Creates child nodes for each hashset name + */ + private class HashsetNameFactory extends ChildFactory.Detachable<String> implements Observer { + + /* + * This should probably be in the HashsetHits class, but the factory has + * nice methods for its startup and shutdown, so it seemed like a + * cleaner place to register the property change listener. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Due to some unresolved issues with how cases are + * closed, it is possible for the event to have a null + * oldValue if the event is a remote event. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_HASHSET_HIT.getTypeID()) { + hashsetResults.update(); + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + hashsetResults.update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + skCase = null; + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + hashsetResults.update(); + hashsetResults.addObserver(this); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + hashsetResults.deleteObserver(this); + } + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(hashsetResults.getSetNames()); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new HashsetNameNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + /** + * Node for a hash set name + */ + public class HashsetNameNode extends DisplayableItemNode implements Observer { + + private final String hashSetName; + + public HashsetNameNode(String hashSetName) { + super(Children.create(new HitFactory(hashSetName), true), Lookups.singleton(hashSetName)); + super.setName(hashSetName); + this.hashSetName = hashSetName; + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS + hashsetResults.addObserver(this); + } + + /** + * Update the count in the display name + */ + private void updateDisplayName() { + super.setDisplayName(hashSetName + " (" + hashsetResults.getArtifactIds(hashSetName).size() + ")"); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + /** + * For custom settings for each hash set, return + * getClass().getName() + hashSetName instead. + */ + return getClass().getName(); + } + } + + /** + * Creates the nodes for the hits in a given set. + */ + private class HitFactory extends BaseChildFactory<AnalysisResult> implements Observer { + + private final String hashsetName; + private final Map<Long, AnalysisResult> artifactHits = new HashMap<>(); + + private HitFactory(String hashsetName) { + super(hashsetName); + this.hashsetName = hashsetName; + } + + @Override + protected void onAdd() { + hashsetResults.addObserver(this); + } + + @Override + protected void onRemove() { + hashsetResults.deleteObserver(this); + } + + @Override + protected Node createNodeForKey(AnalysisResult key) { + return new BlackboardArtifactNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + @Override + protected List<AnalysisResult> makeKeys() { + if (skCase != null) { + + hashsetResults.getArtifactIds(hashsetName).forEach((id) -> { + try { + if (!artifactHits.containsKey(id)) { + AnalysisResult art = skCase.getBlackboard().getAnalysisResultById(id); + //Cache attributes while we are off the EDT. + //See JIRA-5969 + art.getAttributes(); + artifactHits.put(id, art); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS + } + }); + return new ArrayList<>(artifactHits.values()); + } + return Collections.emptyList(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HostDataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/HostDataSources.java new file mode 100644 index 0000000000000000000000000000000000000000..9451e401da9dcb6579099b3f496ab910e0894b4e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HostDataSources.java @@ -0,0 +1,95 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.Objects; +import org.sleuthkit.datamodel.Host; + +/** + * A Host node only showing data sources (no results view, reports, etc.). + */ +public class HostDataSources implements AutopsyVisitableItem, Comparable<HostDataSources> { + + private final Host host; + + /** + * Main constructor. + * + * @param host The host record. + */ + HostDataSources(Host host) { + this.host = host; + } + + /** + * @return The pertinent host. + */ + Host getHost() { + return host; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.host == null ? 0 : this.host.getHostId()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HostDataSources other = (HostDataSources) obj; + long thisId = (this.getHost() == null) ? 0 : this.getHost().getHostId(); + long otherId = (other.getHost() == null) ? 0 : other.getHost().getHostId(); + return thisId == otherId; + } + + /* + * Compares two host groupings to be displayed in a list of children under + * the person. + */ + @Override + public int compareTo(HostDataSources o) { + String thisHost = this.getHost() == null ? null : this.getHost().getName(); + String otherHost = o == null || o.getHost() == null ? null : o.getHost().getName(); + + // push unknown host to bottom + if (thisHost == null && otherHost == null) { + return 0; + } else if (thisHost == null) { + return 1; + } else if (otherHost == null) { + return -1; + } + + return thisHost.compareToIgnoreCase(otherHost); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HostGrouping.java b/Core/src/org/sleuthkit/autopsy/datamodel/HostGrouping.java new file mode 100644 index 0000000000000000000000000000000000000000..4bc797364568d15762e9e7b2274023715e5f2e87 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HostGrouping.java @@ -0,0 +1,95 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.Objects; +import org.sleuthkit.datamodel.Host; + +/** + * A top level UI grouping of data sources under a host. + */ +public class HostGrouping implements AutopsyVisitableItem, Comparable<HostGrouping> { + + private final Host host; + + /** + * Main constructor. + * + * @param host The host record. + */ + HostGrouping(Host host) { + this.host = host; + } + + /** + * @return The pertinent host. + */ + Host getHost() { + return host; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.host == null ? 0 : this.host.getHostId()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HostGrouping other = (HostGrouping) obj; + long thisId = (this.getHost() == null) ? 0 : this.getHost().getHostId(); + long otherId = (other.getHost() == null) ? 0 : other.getHost().getHostId(); + return thisId == otherId; + } + + /* + * Compares two host groupings to be displayed in a list of children under + * the person. + */ + @Override + public int compareTo(HostGrouping o) { + String thisHost = this.getHost() == null ? null : this.getHost().getName(); + String otherHost = o == null || o.getHost() == null ? null : o.getHost().getName(); + + // push unknown host to bottom + if (thisHost == null && otherHost == null) { + return 0; + } else if (thisHost == null) { + return 1; + } else if (otherHost == null) { + return -1; + } + + return thisHost.compareToIgnoreCase(otherHost); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java new file mode 100644 index 0000000000000000000000000000000000000000..95e6254510a0c105bbeb93caa5e0f9c0001b38d1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java @@ -0,0 +1,327 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.swing.Action; +import org.openide.nodes.ChildFactory; + +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.events.HostsUpdatedEvent; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.hosts.AssociatePersonsMenuAction; +import org.sleuthkit.autopsy.datamodel.hosts.MergeHostMenuAction; +import org.sleuthkit.autopsy.datamodel.hosts.RemoveParentPersonAction; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A node to be displayed in the UI tree for a host and data sources grouped in + * this host. + */ +@NbBundle.Messages(value = {"HostGroupingNode_unknownHostNode_title=Unknown Host"}) +public class HostNode extends DisplayableItemNode { + + /** + * Provides the data source children for this host. + */ + private static class HostGroupingChildren extends ChildFactory.Detachable<DataSourceGrouping> { + + private static final Logger logger = Logger.getLogger(HostGroupingChildren.class.getName()); + + private final Host host; + private final Function<DataSourceGrouping, Node> dataSourceToNode; + + /** + * Main constructor. + * + * @param dataSourceToItem Converts a data source to a node. + * @param host The host. + */ + HostGroupingChildren(Function<DataSourceGrouping, Node> dataSourceToNode, Host host) { + this.host = host; + this.dataSourceToNode = dataSourceToNode; + } + + /** + * Listener for handling DATA_SOURCE_ADDED / HOST_DELETED events. + * A host may have been deleted as part of a merge, which means its data sources could + * have moved to a different host requiring a refresh. + */ + private final PropertyChangeListener dataSourceAddedPcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString()) + || eventType.equals(Case.Events.HOSTS_DELETED.toString())) { + refresh(true); + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(dataSourceAddedPcl, null); + + @Override + protected void addNotify() { + super.addNotify(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), weakPcl); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), weakPcl); + } + + @Override + protected Node createNodeForKey(DataSourceGrouping key) { + return this.dataSourceToNode.apply(key); + } + + @Override + protected boolean createKeys(List<DataSourceGrouping> toPopulate) { + List<DataSource> dataSources = null; + try { + dataSources = Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getDataSourcesForHost(host); + } catch (NoCurrentCaseException | TskCoreException ex) { + String hostName = host == null || host.getName() == null ? "<unknown>" : host.getName(); + logger.log(Level.WARNING, String.format("Unable to get data sources for host: %s", hostName), ex); + } + + if (dataSources != null) { + toPopulate.addAll(dataSources.stream() + .filter(ds -> ds != null) + .map(DataSourceGrouping::new) + .sorted((a, b) -> getNameOrEmpty(a).compareToIgnoreCase(getNameOrEmpty(b))) + .collect(Collectors.toList())); + } + + return true; + } + + /** + * Get name for data source in data source grouping node or empty + * string. + * + * @param dsGroup The data source grouping. + * @return The name or empty if none exists. + */ + private String getNameOrEmpty(DataSourceGrouping dsGroup) { + return (dsGroup == null || dsGroup.getDataSource() == null || dsGroup.getDataSource().getName() == null) + ? "" + : dsGroup.getDataSource().getName(); + } + } + + private static final Logger logger = Logger.getLogger(HostNode.class.getName()); + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/host.png"; + private static final CreateSleuthkitNodeVisitor CREATE_TSK_NODE_VISITOR = new CreateSleuthkitNodeVisitor(); + + /** + * Means of creating just data source nodes underneath the host (i.e. no + * results, reports, etc.) + */ + private static final Function<DataSourceGrouping, Node> HOST_DATA_SOURCES = key -> { + if (key.getDataSource() instanceof SleuthkitVisitableItem) { + return ((SleuthkitVisitableItem) key.getDataSource()).accept(CREATE_TSK_NODE_VISITOR); + } else { + return null; + } + }; + + /** + * Shows data sources with results, reports, etc. + */ + private static final Function<DataSourceGrouping, Node> HOST_GROUPING_CONVERTER = key -> { + if (key == null || key.getDataSource() == null) { + return null; + } + + return new DataSourceGroupingNode(key.getDataSource()); + }; + + /** + * Listener for handling host change events. + */ + private final PropertyChangeListener hostChangePcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (hostId != null && eventType.equals(Case.Events.HOSTS_UPDATED.toString()) && evt instanceof HostsUpdatedEvent) { + ((HostsUpdatedEvent) evt).getHosts().stream() + .filter(h -> h != null && h.getHostId() == hostId) + .findFirst() + .ifPresent((newHost) -> { + setName(newHost.getName()); + setDisplayName(newHost.getName()); + }); + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(hostChangePcl, null); + + /* + * Get the host name or 'unknown host' if null. + * + * @param host The host. + * @return The display name. + */ + private static String getHostName(Host host) { + return (host == null || host.getName() == null) + ? Bundle.HostGroupingNode_unknownHostNode_title() + : host.getName(); + } + + private final Host host; + private final Long hostId; + + /** + * Main constructor for HostDataSources key where data source children + * should be displayed without additional results, reports, etc. + * + * @param hosts The HostDataSources key. + */ + HostNode(HostDataSources hosts) { + this(Children.create(new HostGroupingChildren(HOST_DATA_SOURCES, hosts.getHost()), true), hosts.getHost()); + } + + /** + * Main constructor for HostGrouping key where data sources should be + * displayed with results, reports, etc. + * + * @param hostGrouping The HostGrouping key. + */ + HostNode(HostGrouping hostGrouping) { + this(Children.create(new HostGroupingChildren(HOST_GROUPING_CONVERTER, hostGrouping.getHost()), true), hostGrouping.getHost()); + } + + /** + * Constructor. + * + * @param children The children for this host node. + * @param host The host. + */ + private HostNode(Children children, Host host) { + this(children, host, getHostName(host)); + } + + /** + * Constructor. + * + * @param children The children for this host node. + * @param host The host. + * @param displayName The displayName. + */ + private HostNode(Children children, Host host, String displayName) { + super(children, + host == null ? Lookups.fixed(displayName) : Lookups.fixed(host, displayName)); + + hostId = host == null ? null : host.getHostId(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.HOSTS_UPDATED), weakPcl); + super.setName(displayName); + super.setDisplayName(displayName); + this.setIconBaseWithExtension(ICON_PATH); + this.host = host; + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Messages({ + "HostNode_createSheet_nameProperty=Name",}) + @Override + protected Sheet createSheet() { + Sheet sheet = Sheet.createDefault(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("Name", Bundle.HostNode_createSheet_nameProperty(), "", getDisplayName())); //NON-NLS + + return sheet; + } + + @Override + @Messages({"HostNode_actions_associateWithExisting=Associate with existing person...", + "HostNode_actions_associateWithNew=Associate with new person...", + "# {0} - hostName", + "HostNode_actions_removeFromPerson=Remove from person ({0})"}) + public Action[] getActions(boolean context) { + + List<Action> actionsList = new ArrayList<>(); + + // if there is a host, then provide actions + if (this.host != null) { + + // Add the appropriate Person action + Optional<Person> parent; + try { + parent = Case.getCurrentCaseThrows().getSleuthkitCase().getPersonManager().getPerson(this.host); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, String.format("Error fetching parent person of host: %s", this.host.getName() == null ? "<null>" : this.host.getName()), ex); + return new Action[0]; + } + + // if there is a parent, only give option to remove parent person. + if (parent.isPresent()) { + actionsList.add(new RemoveParentPersonAction(this.host, parent.get())); + } else { + actionsList.add(new AssociatePersonsMenuAction(this.host)); + } + + // Add option to merge hosts + actionsList.add(new MergeHostMenuAction(this.host)); + } + return actionsList.toArray(new Action[actionsList.size()]); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index 9761bb00e95637a0eed0b85d7fa98410f473d53f..982e2137697003caa032b51c1d1bed2a385d6476 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -172,6 +172,11 @@ protected Sheet createSheet() { return sheet; } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java new file mode 100644 index 0000000000000000000000000000000000000000..c22d09572699308e58b29d9e45ebc4458a385f72 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -0,0 +1,477 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode; + +public class InterestingHits implements AutopsyVisitableItem { + + private static final Logger logger = Logger.getLogger(InterestingHits.class.getName()); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED); + + private SleuthkitCase skCase; + private final InterestingResults interestingResults = new InterestingResults(); + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + private final BlackboardArtifact.Type artifactType; + + /** + * Constructor + * + * @param skCase Case DB + * @param artifactType The artifact type (either interesting file or + * artifact). + * + */ + public InterestingHits(SleuthkitCase skCase, BlackboardArtifact.Type artifactType) { + this(skCase, artifactType, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param artifactType The artifact type (either interesting file or + * artifact). + * @param objId Object id of the data source + * + */ + public InterestingHits(SleuthkitCase skCase, BlackboardArtifact.Type artifactType, long objId) { + this.skCase = skCase; + this.artifactType = artifactType; + this.filteringDSObjId = objId; + interestingResults.update(); + } + + /** + * Cache of result ids mapped by artifact type -> set name -> artifact id. + */ + private class InterestingResults extends Observable { + + // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized + private final Map<String, Set<Long>> interestingItemsMap = new LinkedHashMap<>(); + + /** + * Returns all the set names for a given interesting item type. + * + * @param type The interesting item type. + * + * @return The set names. + */ + List<String> getSetNames() { + List<String> setNames; + synchronized (interestingItemsMap) { + setNames = new ArrayList<>(interestingItemsMap.keySet()); + } + Collections.sort(setNames, (a, b) -> a.compareToIgnoreCase(b)); + return setNames; + } + + /** + * Returns all artifact ids belonging to the specified interesting item + * type and set name. + * + * @param type The interesting item type. + * @param setName The set name. + * + * @return The artifact ids in that set name and type. + */ + Set<Long> getArtifactIds(String setName) { + synchronized (interestingItemsMap) { + return new HashSet<>(interestingItemsMap.getOrDefault(setName, Collections.emptySet())); + } + } + + /** + * Triggers a fetch from the database to update this cache. + */ + void update() { + synchronized (interestingItemsMap) { + interestingItemsMap.clear(); + } + loadArtifacts(); + setChanged(); + notifyObservers(); + } + + /* + * Reads the artifacts of specified type, grouped by Set, and loads into + * the interestingItemsMap + */ + @SuppressWarnings("deprecation") + private void loadArtifacts() { + if (skCase == null) { + return; + } + + int setNameId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(); + + String query = "SELECT value_text, blackboard_artifacts.artifact_obj_id " //NON-NLS + + "FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS + + "attribute_type_id=" + setNameId //NON-NLS + + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + + " AND blackboard_artifacts.artifact_type_id = " + artifactType.getTypeID(); //NON-NLS + if (filteringDSObjId > 0) { + query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { + synchronized (interestingItemsMap) { + ResultSet resultSet = dbQuery.getResultSet(); + while (resultSet.next()) { + String value = resultSet.getString("value_text"); //NON-NLS + long artifactObjId = resultSet.getLong("artifact_obj_id"); //NON-NLS + interestingItemsMap + .computeIfAbsent(value, (k) -> new HashSet<>()) + .add(artifactObjId); + } + } + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS + } + } + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * Creates nodes for all sets for a specified interesting item type. + */ + private class SetNameFactory extends ChildFactory.Detachable<String> implements Observer { + + /* + * This should probably be in the top-level class, but the factory has + * nice methods for its startup and shutdown, so it seemed like a + * cleaner place to register the property change listener. + */ + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that the + * case will be closed in a different thread before this + * code executes. If that happens, it is possible for the + * event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == artifactType.getTypeID())) { + interestingResults.update(); + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + interestingResults.update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + skCase = null; + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(interestingResults.getSetNames()); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new SetNameNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + interestingResults.addObserver(this); + interestingResults.update(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + interestingResults.deleteObserver(this); + } + } + + /** + * A node for a set to be displayed in the tree. + */ + public class SetNameNode extends DisplayableItemNode implements Observer { + + private final String setName; + + public SetNameNode(String setName) {//, Set<Long> children) { + super(Children.create(new HitFactory(setName), true), Lookups.singleton(setName)); + this.setName = setName; + super.setName(setName); + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS + interestingResults.addObserver(this); + } + + private void updateDisplayName() { + int sizeOfSet = interestingResults.getArtifactIds(setName).size(); + super.setDisplayName(setName + " (" + sizeOfSet + ")"); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + /** + * For custom settings for each rule set, return + * getClass().getName() + setName instead. + */ + return getClass().getName(); + } + } + + /** + * Parent node for interesting item type that shows child set nodes. + */ + public class RootNode extends UpdatableCountTypeNode { + + /** + * Main constructor. + */ + public RootNode() { + super(Children.create(new SetNameFactory(), true), + Lookups.singleton(artifactType), + artifactType.getDisplayName(), + filteringDSObjId, + artifactType); + + /** + * We use the combination of setName and typeName as the name of the + * node to ensure that nodes have a unique name. This comes into + * play when associating paging state with the node. + */ + setName(artifactType.getDisplayName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"), + getName())); + return sheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + /** + * For custom settings for each rule set, return + * getClass().getName() + setName instead. + */ + return getClass().getName(); + } + } + + /** + * Factory for creating individual interesting item BlackboardArtifactNodes. + */ + private class HitFactory extends BaseChildFactory<AnalysisResult> implements Observer { + + private final String setName; + private final Map<Long, AnalysisResult> artifactHits = new HashMap<>(); + + /** + * Main constructor. + * + * @param setName The set name of artifacts to be displayed. + */ + private HitFactory(String setName) { + /** + * The node name passed to the parent constructor must be the same + * as the name set in the InterestingItemTypeNode constructor, i.e. + * setName underscore typeName + */ + super(setName); + this.setName = setName; + interestingResults.addObserver(this); + } + + @Override + protected List<AnalysisResult> makeKeys() { + + if (skCase != null) { + interestingResults.getArtifactIds(setName).forEach((id) -> { + try { + if (!artifactHits.containsKey(id)) { + AnalysisResult art = skCase.getBlackboard().getAnalysisResultById(id); + //Cache attributes while we are off the EDT. + //See JIRA-5969 + art.getAttributes(); + artifactHits.put(id, art); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS + } + }); + + return new ArrayList<>(artifactHits.values()); + } + return Collections.emptyList(); + } + + @Override + protected Node createNodeForKey(AnalysisResult art) { + return new BlackboardArtifactNode(art); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + @Override + protected void onAdd() { + // No-op + } + + @Override + protected void onRemove() { + // No-op + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java new file mode 100644 index 0000000000000000000000000000000000000000..4406944fa347f82772cd4bcdf1354c76f3fd38cf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -0,0 +1,976 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import static org.sleuthkit.autopsy.datamodel.Bundle.*; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_KEYWORD_HIT; +import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode; +import org.sleuthkit.datamodel.AnalysisResult; + +/** + * Keyword hits node support + */ +public class KeywordHits implements AutopsyVisitableItem { + + private static final Logger logger = Logger.getLogger(KeywordHits.class.getName()); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED); + @NbBundle.Messages("KeywordHits.kwHits.text=Keyword Hits") + private static final String KEYWORD_HITS = KeywordHits_kwHits_text(); + @NbBundle.Messages("KeywordHits.simpleLiteralSearch.text=Single Literal Keyword Search") + private static final String SIMPLE_LITERAL_SEARCH = KeywordHits_simpleLiteralSearch_text(); + @NbBundle.Messages("KeywordHits.singleRegexSearch.text=Single Regular Expression Search") + private static final String SIMPLE_REGEX_SEARCH = KeywordHits_singleRegexSearch_text(); + + public static final String NAME = BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName(); + + private SleuthkitCase skCase; + private final KeywordResults keywordResults; + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + /** + * String used in the instance MAP so that exact matches and substring can + * fit into the same data structure as regexps, even though they don't use + * instances. + */ + private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME"; + + /** + * query attributes table for the ones that we need for the tree + */ + private static final String KEYWORD_HIT_ATTRIBUTES_QUERY = "SELECT blackboard_attributes.value_text, "//NON-NLS + + "blackboard_attributes.value_int32, "//NON-NLS + + "blackboard_artifacts.artifact_obj_id, " //NON-NLS + + "blackboard_attributes.attribute_type_id "//NON-NLS + + "FROM blackboard_attributes, blackboard_artifacts "//NON-NLS + + "WHERE blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id "//NON-NLS + + " AND blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() //NON-NLS + + " AND (attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()//NON-NLS + + " OR attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()//NON-NLS + + " OR attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID()//NON-NLS + + " OR attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()//NON-NLS + + ")"; //NON-NLS + + static private boolean isOnlyDefaultInstance(List<String> instances) { + return (instances.size() == 1) && (instances.get(0).equals(DEFAULT_INSTANCE_NAME)); + } + + /** + * Constructor + * + * @param skCase Case DB + */ + KeywordHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public KeywordHits(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.filteringDSObjId = objId; + keywordResults = new KeywordResults(); + } + + /* + * All of these maps and code assume the following: Regexps will have an + * 'instance' layer that shows the specific words that matched the regexp + * Exact match and substring will not have the instance layer and instead + * will have the specific hits below their term. + */ + private final class KeywordResults extends Observable { + + // Map from listName/Type to Map of keywords/regexp to Map of instance terms to Set of artifact Ids + // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized + private final Map<String, Map<String, Map<String, Set<Long>>>> topLevelMap = new LinkedHashMap<>(); + + KeywordResults() { + update(); + } + + /** + * Get the list names used in searches. + * + * @return The list of list names. + */ + List<String> getListNames() { + synchronized (topLevelMap) { + List<String> names = new ArrayList<>(topLevelMap.keySet()); + + // sort the list names, but ensure that the special lists + // stay at the top. + Collections.sort(names, new Comparator<String>() { + + @Override + public int compare(String o1, String o2) { + // ideally, they would not be hard coded, but this module + // doesn't know about Keyword Search NBM + if (o1.startsWith("Single Literal Keyword Search")) { + return -1; + } else if (o2.startsWith("Single Literal Keyword Search")) { + return 1; + } else if (o1.startsWith("Single Regular Expression Search")) { + return -1; + } else if (o2.startsWith("Single Regular Expression Search")) { + return 1; + } + return o1.compareTo(o2); + } + }); + + return names; + } + } + + /** + * Get keywords used in a given list. Will be regexp patterns for + * regexps and search term for non-regexps. + * + * @param listName Keyword list name + * + * @return + */ + List<String> getKeywords(String listName) { + List<String> keywords; + synchronized (topLevelMap) { + keywords = new ArrayList<>(topLevelMap.get(listName).keySet()); + } + Collections.sort(keywords); + return keywords; + } + + /** + * Get specific keyword terms that were found for a given list and + * keyword combination. For example, a specific phone number for a phone + * number regexp. Will be the default instance for non-regexp searches. + * + * @param listName Keyword list name + * @param keyword search term (regexp pattern or exact match term) + * + * @return + */ + List<String> getKeywordInstances(String listName, String keyword) { + List<String> instances; + synchronized (topLevelMap) { + instances = new ArrayList<>(topLevelMap.get(listName).get(keyword).keySet()); + } + Collections.sort(instances); + return instances; + } + + /** + * Get artifact ids for a given list, keyword, and instance triple + * + * @param listName Keyword list name + * @param keyword search term (regexp pattern or exact match + * term) + * @param keywordInstance specific term that matched (or default + * instance name) + * + * @return + */ + Set<Long> getArtifactIds(String listName, String keyword, String keywordInstance) { + synchronized (topLevelMap) { + return topLevelMap.get(listName).get(keyword).get(keywordInstance); + } + } + + /** + * Add a hit for a regexp to the internal data structure. + * + * @param listMap Maps keywords/regexp to instances to artifact + * IDs + * @param regExp Regular expression that was used in search + * @param keywordInstance Specific term that matched regexp + * @param artifactId Artifact id of file that had hit + */ + void addRegExpToList(Map<String, Map<String, Set<Long>>> listMap, String regExp, String keywordInstance, Long artifactId) { + Map<String, Set<Long>> instanceMap = listMap.computeIfAbsent(regExp, r -> new LinkedHashMap<>()); + // add this ID to the instances entry, creating one if needed + instanceMap.computeIfAbsent(keywordInstance, ki -> new HashSet<>()).add(artifactId); + } + + /** + * Add a hit for a exactmatch (or substring) to the internal data + * structure. + * + * @param listMap Maps keywords/regexp to instances to artifact IDs + * @param keyWord Term that was hit + * @param artifactId Artifact id of file that had hit + */ + void addNonRegExpMatchToList(Map<String, Map<String, Set<Long>>> listMap, String keyWord, Long artifactId) { + Map<String, Set<Long>> instanceMap = listMap.computeIfAbsent(keyWord, k -> new LinkedHashMap<>()); + + // Use the default instance name, since we don't need that level in the tree + instanceMap.computeIfAbsent(DEFAULT_INSTANCE_NAME, DIN -> new HashSet<>()).add(artifactId); + } + + /** + * Populate data structure for the tree based on the keyword hit + * artifacts + * + * @param artifactIds Maps Artifact ID to map of attribute types to + * attribute values + */ + void populateTreeMaps(Map<Long, Map<Long, String>> artifactIds) { + synchronized (topLevelMap) { + topLevelMap.clear(); + + // map of list name to keword to artifact IDs + Map<String, Map<String, Map<String, Set<Long>>>> listsMap = new LinkedHashMap<>(); + + // Map from from literal keyword to instances (which will be empty) to artifact IDs + Map<String, Map<String, Set<Long>>> literalMap = new LinkedHashMap<>(); + + // Map from regex keyword artifact to instances to artifact IDs + Map<String, Map<String, Set<Long>>> regexMap = new LinkedHashMap<>(); + + // top-level nodes + topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap); + topLevelMap.put(SIMPLE_REGEX_SEARCH, regexMap); + + for (Map.Entry<Long, Map<Long, String>> art : artifactIds.entrySet()) { + long id = art.getKey(); + Map<Long, String> attributes = art.getValue(); + + // I think we can use attributes.remove(...) here? - why should bwe use remove? + String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID())); + String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID())); + String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID())); + String kwType = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID())); + + if (listName != null) { // part of a list + // get or create list entry + Map<String, Map<String, Set<Long>>> listMap = listsMap.computeIfAbsent(listName, ln -> new LinkedHashMap<>()); + + if ("1".equals(kwType) || reg == null) { //literal, substring or exact + /* + * Substring, treated same as exact match. "1" is + * the ordinal value for substring as defined in + * KeywordSearch.java. The original term should be + * stored in reg + */ + word = (reg != null) ? reg : word; //use original term if it there. + addNonRegExpMatchToList(listMap, word, id); + } else { + addRegExpToList(listMap, reg, word, id); + } + } else {//single term + if ("1".equals(kwType) || reg == null) { //literal, substring or exact + /* + * Substring, treated same as exact match. "1" is + * the ordinal value for substring as defined in + * KeywordSearch.java. The original term should be + * stored in reg + */ + word = (reg != null) ? reg : word; //use original term if it there. + addNonRegExpMatchToList(literalMap, word, id); + } else { + addRegExpToList(regexMap, reg, word, id); + } + } + } + topLevelMap.putAll(listsMap); + } + + setChanged(); + notifyObservers(); + } + + public void update() { + // maps Artifact ID to map of attribute types to attribute values + Map<Long, Map<Long, String>> artifactIds = new LinkedHashMap<>(); + + if (skCase == null) { + return; + } + + String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY; + if (filteringDSObjId > 0) { + queryStr += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) { + ResultSet resultSet = dbQuery.getResultSet(); + while (resultSet.next()) { + long artifactObjId = resultSet.getLong("artifact_obj_id"); //NON-NLS + long typeId = resultSet.getLong("attribute_type_id"); //NON-NLS + String valueStr = resultSet.getString("value_text"); //NON-NLS + + //get the map of attributes for this artifact + Map<Long, String> attributesByTypeMap = artifactIds.computeIfAbsent(artifactObjId, ai -> new LinkedHashMap<>()); + if (StringUtils.isNotEmpty(valueStr)) { + attributesByTypeMap.put(typeId, valueStr); + } else { + // Keyword Search Type is an int + Long valueLong = resultSet.getLong("value_int32"); + attributesByTypeMap.put(typeId, valueLong.toString()); + } + } + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS + } + + populateTreeMaps(artifactIds); + } + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + // Created by CreateAutopsyNodeVisitor + public class RootNode extends UpdatableCountTypeNode { + + public RootNode() { + super(Children.create(new ListFactory(), true), + Lookups.singleton(KEYWORD_HITS), + KEYWORD_HITS, + filteringDSObjId, + TSK_KEYWORD_HIT); + + super.setName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + @NbBundle.Messages({"KeywordHits.createSheet.name.name=Name", + "KeywordHits.createSheet.name.displayName=Name", + "KeywordHits.createSheet.name.desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>( + KeywordHits_createSheet_name_name(), + KeywordHits_createSheet_name_displayName(), + KeywordHits_createSheet_name_desc(), + getName())); + + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + private abstract class DetachableObserverChildFactory<X> extends ChildFactory.Detachable<X> implements Observer { + + @Override + protected void addNotify() { + keywordResults.addObserver(this); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + keywordResults.deleteObserver(this); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + /** + * Creates the list nodes + */ + private class ListFactory extends DetachableObserverChildFactory<String> { + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) { + keywordResults.update(); + } + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + keywordResults.update(); + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString()) + && evt.getNewValue() == null) { + /* + * Case was closed. Remove listeners so that we don't get + * called with a stale case handle + */ + removeNotify(); + skCase = null; + } + + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + keywordResults.update(); + super.addNotify(); + } + + @Override + protected void finalize() throws Throwable{ + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.finalize(); + } + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(keywordResults.getListNames()); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new ListNode(key); + } + } + + private abstract class KWHitsNodeBase extends DisplayableItemNode implements Observer { + + private String displayName; + + private KWHitsNodeBase(Children children, Lookup lookup, String displayName) { + super(children, lookup); + this.displayName = displayName; + } + + private KWHitsNodeBase(Children children) { + super(children); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + final void updateDisplayName() { + super.setDisplayName(displayName + " (" + countTotalDescendants() + ")"); + } + + abstract int countTotalDescendants(); + } + + /** + * Represents the keyword search lists (or default groupings if list was not + * given) + */ + class ListNode extends KWHitsNodeBase { + + private final String listName; + + private ListNode(String listName) { + super(Children.create(new TermFactory(listName), true), Lookups.singleton(listName), listName); + super.setName(listName); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS + this.listName = listName; + updateDisplayName(); + keywordResults.addObserver(this); + } + + @Override + public int countTotalDescendants() { + int totalDescendants = 0; + + for (String word : keywordResults.getKeywords(listName)) { + for (String instance : keywordResults.getKeywordInstances(listName, word)) { + Set<Long> ids = keywordResults.getArtifactIds(listName, word, instance); + totalDescendants += ids.size(); + } + } + return totalDescendants; + } + + @Override + @NbBundle.Messages({"KeywordHits.createSheet.listName.name=List Name", + "KeywordHits.createSheet.listName.displayName=List Name", + "KeywordHits.createSheet.listName.desc=no description", + "KeywordHits.createSheet.numChildren.name=Number of Children", + "KeywordHits.createSheet.numChildren.displayName=Number of Children", + "KeywordHits.createSheet.numChildren.desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>( + KeywordHits_createSheet_listName_name(), + KeywordHits_createSheet_listName_displayName(), + KeywordHits_createSheet_listName_desc(), + listName)); + + sheetSet.put(new NodeProperty<>( + KeywordHits_createSheet_numChildren_name(), + KeywordHits_createSheet_numChildren_displayName(), + KeywordHits_createSheet_numChildren_desc(), + keywordResults.getKeywords(listName).size())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + } + + /** + * Creates the nodes that represent search terms + */ + private class TermFactory extends DetachableObserverChildFactory<String> { + + private final String setName; + + private TermFactory(String setName) { + super(); + this.setName = setName; + } + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(keywordResults.getKeywords(setName)); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new TermNode(setName, key); + } + } + + /** + * Create a ChildFactory object for the given set name and keyword. + * + * The type of ChildFactory we create is based on whether the node + * represents a regular expression keyword search or not. For regular + * expression keyword searches there will be an extra layer in the tree that + * represents each of the individual terms found by the regular expression. + * E.g., for an email regular expression search there will be a node in the + * tree for every email address hit. + */ + ChildFactory<?> createChildFactory(String setName, String keyword) { + if (isOnlyDefaultInstance(keywordResults.getKeywordInstances(setName, keyword))) { + return new HitsFactory(setName, keyword, DEFAULT_INSTANCE_NAME); + } else { + return new RegExpInstancesFactory(setName, keyword); + } + } + + /** + * Represents the search term or regexp that user searched for + */ + class TermNode extends KWHitsNodeBase { + + private final String setName; + private final String keyword; + + private TermNode(String setName, String keyword) { + super(Children.create(createChildFactory(setName, keyword), true), Lookups.singleton(keyword), keyword); + + /** + * We differentiate between the programmatic name and the display + * name. The programmatic name is used to create an association with + * an event bus and must be the same as the node name passed by our + * ChildFactory to it's parent constructor. See the HitsFactory + * constructor for an example. + */ + super.setName(setName + "_" + keyword); + this.setName = setName; + this.keyword = keyword; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS + updateDisplayName(); + keywordResults.addObserver(this); + } + + @Override + int countTotalDescendants() { + return keywordResults.getKeywordInstances(setName, keyword).stream() + .mapToInt(instance -> keywordResults.getArtifactIds(setName, keyword, instance).size()) + .sum(); + } + + @Override + public boolean isLeafTypeNode() { + // is this an exact/substring match (i.e. did we use the DEFAULT name)? + return isOnlyDefaultInstance(keywordResults.getKeywordInstances(setName, keyword)); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + @NbBundle.Messages({"KeywordHits.createSheet.filesWithHits.name=Files with Hits", + "KeywordHits.createSheet.filesWithHits.displayName=Files with Hits", + "KeywordHits.createSheet.filesWithHits.desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + sheetSet.put(new NodeProperty<>( + KeywordHits_createSheet_listName_name(), + KeywordHits_createSheet_listName_displayName(), + KeywordHits_createSheet_listName_desc(), + getDisplayName())); + + sheetSet.put(new NodeProperty<>( + KeywordHits_createSheet_filesWithHits_name(), + KeywordHits_createSheet_filesWithHits_displayName(), + KeywordHits_createSheet_filesWithHits_desc(), + countTotalDescendants())); + + return sheet; + } + } + + /** + * Creates the nodes for a given regexp that represent the specific terms + * that were found + */ + private class RegExpInstancesFactory extends DetachableObserverChildFactory<String> { + + private final String keyword; + private final String setName; + + private RegExpInstancesFactory(String setName, String keyword) { + super(); + this.setName = setName; + this.keyword = keyword; + } + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(keywordResults.getKeywordInstances(setName, keyword)); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new RegExpInstanceNode(setName, keyword, key); + } + } + + /** + * Represents a specific term that was found from a regexp + */ + class RegExpInstanceNode extends KWHitsNodeBase { + + private final String setName; + private final String keyword; + private final String instance; + + private RegExpInstanceNode(String setName, String keyword, String instance) { + super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance), instance); + + /** + * We differentiate between the programmatic name and the display + * name. The programmatic name is used to create an association with + * an event bus and must be the same as the node name passed by our + * ChildFactory to it's parent constructor. See the HitsFactory + * constructor for an example. + */ + super.setName(setName + "_" + keyword + "_" + instance); + this.setName = setName; + this.keyword = keyword; + this.instance = instance; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS + updateDisplayName(); + keywordResults.addObserver(this); + } + + @Override + int countTotalDescendants() { + return keywordResults.getArtifactIds(setName, keyword, instance).size(); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>( + KeywordHits_createSheet_listName_name(), + KeywordHits_createSheet_listName_displayName(), + KeywordHits_createSheet_listName_desc(), + getDisplayName())); + + sheetSet.put(new NodeProperty<>( + KeywordHits_createSheet_filesWithHits_name(), + KeywordHits_createSheet_filesWithHits_displayName(), + KeywordHits_createSheet_filesWithHits_desc(), + keywordResults.getArtifactIds(setName, keyword, instance).size())); + + return sheet; + } + + } + + /** + * Create a blackboard node for the given Keyword Hit artifact + * + * @param art + * + * @return Node or null on error + */ + @NbBundle.Messages({"KeywordHits.createNodeForKey.modTime.name=ModifiedTime", + "KeywordHits.createNodeForKey.modTime.displayName=Modified Time", + "KeywordHits.createNodeForKey.modTime.desc=Modified Time", + "KeywordHits.createNodeForKey.accessTime.name=AccessTime", + "KeywordHits.createNodeForKey.accessTime.displayName=Access Time", + "KeywordHits.createNodeForKey.accessTime.desc=Access Time", + "KeywordHits.createNodeForKey.chgTime.name=ChangeTime", + "KeywordHits.createNodeForKey.chgTime.displayName=Change Time", + "KeywordHits.createNodeForKey.chgTime.desc=Change Time"}) + private BlackboardArtifactNode createBlackboardArtifactNode(AnalysisResult art) { + if (skCase == null) { + return null; + } + + BlackboardArtifactNode n = new BlackboardArtifactNode(art); //NON-NLS + + // The associated file should be available through the Lookup that + // gets created when the BlackboardArtifactNode is constructed. + AbstractFile file = n.getLookup().lookup(AbstractFile.class); + if (file == null) { + try { + file = skCase.getAbstractFileById(art.getObjectID()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren", ex); //NON-NLS + return n; + } + } + /* + * It is possible to get a keyword hit on artifacts generated for the + * underlying image in which case MAC times are not + * available/applicable/useful. + */ + if (file == null) { + return n; + } + n.addNodeProperty(new NodeProperty<>( + KeywordHits_createNodeForKey_modTime_name(), + KeywordHits_createNodeForKey_modTime_displayName(), + KeywordHits_createNodeForKey_modTime_desc(), + TimeZoneUtils.getFormattedTime(file.getMtime()))); + n.addNodeProperty(new NodeProperty<>( + KeywordHits_createNodeForKey_accessTime_name(), + KeywordHits_createNodeForKey_accessTime_displayName(), + KeywordHits_createNodeForKey_accessTime_desc(), + TimeZoneUtils.getFormattedTime(file.getAtime()))); + n.addNodeProperty(new NodeProperty<>( + KeywordHits_createNodeForKey_chgTime_name(), + KeywordHits_createNodeForKey_chgTime_displayName(), + KeywordHits_createNodeForKey_chgTime_desc(), + TimeZoneUtils.getFormattedTime(file.getCtime()))); + return n; + } + + /** + * Creates nodes for individual files that had hits + */ + private class HitsFactory extends BaseChildFactory<AnalysisResult> implements Observer { + + private final String keyword; + private final String setName; + private final String instance; + private final Map<Long, AnalysisResult> artifactHits = new HashMap<>(); + + private HitsFactory(String setName, String keyword, String instance) { + /** + * The node name passed to the parent constructor will consist of + * the set name, keyword and optionally the instance name (in the + * case of regular expression hits. This name must match the name + * set in the TermNode or RegExpInstanceNode constructors. + */ + super(setName + "_" + keyword + (DEFAULT_INSTANCE_NAME.equals(instance) ? "" : "_" + instance)); + this.setName = setName; + this.keyword = keyword; + this.instance = instance; + } + + @Override + protected List<AnalysisResult> makeKeys() { + if (skCase != null) { + keywordResults.getArtifactIds(setName, keyword, instance).forEach((id) -> { + try { + if (!artifactHits.containsKey(id)) { + AnalysisResult art = skCase.getBlackboard().getAnalysisResultById(id); + //Cache attributes while we are off the EDT. + //See JIRA-5969 + art.getAttributes(); + artifactHits.put(id, art); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS + } + }); + + return new ArrayList<>(artifactHits.values()); + } + return Collections.emptyList(); + } + + @Override + protected Node createNodeForKey(AnalysisResult art) { + return createBlackboardArtifactNode(art); + } + + @Override + protected void onAdd() { + keywordResults.addObserver(this); + } + + @Override + protected void onRemove() { + keywordResults.deleteObserver(this); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 8bb85a2c4c0cacaf5305788076ffc8fefdc9d4b0..5c5a2513eeaef1f3ece51431f66fff51828bf926 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -49,7 +49,7 @@ * Node for layout file */ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> { - + private static final Logger logger = Logger.getLogger(LayoutFileNode.class.getName()); @Deprecated @@ -85,6 +85,10 @@ public LayoutFileNode(LayoutFile lf) { } } + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public boolean isLeafTypeNode() { return false; @@ -102,7 +106,7 @@ public Action[] getActions(boolean context) { List<Action> actionsList = new ArrayList<>(); actionsList.add(new ViewContextAction(Bundle.LayoutFileNode_getActions_viewFileInDir_text(), this)); actionsList.add(null); // Creates an item separator - + actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "LayoutFileNode.getActions.viewInNewWin.text"), this)); final Collection<AbstractFile> selectedFilesList diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java index 4a9f6f96e57eb7b8b5c673c3f4c031263c79a9eb..3e72ae92aa74ef826c0d1f14f83955c4b0539056 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java @@ -37,6 +37,9 @@ public LocalDirectoryNode(LocalDirectory ld) { } + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } @Override public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index fae4304d73c1d7728d1ccc30a66e5882e8b75bde..a3273f448429a28cc3ab13259b0ee205f5453695 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -100,13 +100,18 @@ public Action[] getActions(boolean context) { logger.log(Level.WARNING, "Unable to add unzip with password action to context menus", ex); } } - + actionsList.add(null); actionsList.addAll(Arrays.asList(super.getActions(true))); - + return actionsList.toArray(new Action[actionsList.size()]); } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java index b043af914c5e27e6444090c9164b07ff08577a4f..cd011898e9719ae6baf592690a68157cf19df416 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java @@ -87,6 +87,11 @@ protected Sheet createSheet() { return sheet; } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/NodeProperty.java b/Core/src/org/sleuthkit/autopsy/datamodel/NodeProperty.java index 3c7e3e9afde7827af356670004ee9cd982ebfdfc..5aa0fb97d133e58692e7e4ddd2527b7e5ff11a88 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/NodeProperty.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/NodeProperty.java @@ -30,7 +30,7 @@ public class NodeProperty<T> extends PropertySupport.ReadOnly<T> { private T value; @SuppressWarnings("unchecked") - public NodeProperty(String name, String displayName, String desc, T value) { + public NodeProperty(String name, String displayName, String desc, T value) { super(name, (Class<T>) value.getClass(), displayName, desc); setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS this.value = value; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java new file mode 100755 index 0000000000000000000000000000000000000000..fbdb266e9bd2f4d3a5da86ab96105626197bfa0d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java @@ -0,0 +1,628 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.swing.Action; +import org.apache.commons.lang3.tuple.Pair; +import javax.swing.SwingUtilities; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.WeakListeners; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.events.OsAccountsUpdatedEvent; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.VALUE_LOADING; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool; +import org.sleuthkit.autopsy.events.AutopsyEvent; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.OsAccountRealm; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.Tag; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Implements the OS Accounts subnode of Results in the Autopsy tree. + */ +public final class OsAccounts implements AutopsyVisitableItem { + + private static final Logger logger = Logger.getLogger(OsAccounts.class.getName()); + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/os-account.png"; + private static final String OS_ACCOUNT_DATA_AVAILABLE_EVENT = "OS_ACCOUNT_DATA_AVAILABLE_EVENT"; + + private static final String LIST_NAME = Bundle.OsAccount_listNode_name(); + + private SleuthkitCase skCase; + private final long filteringDSObjId; + + /** + * Returns the name of the OsAccountListNode to be used for id purposes. + * + * @return The name of the OsAccountListNode to be used for id purposes. + */ + public static String getListName() { + return LIST_NAME; + } + + public OsAccounts(SleuthkitCase skCase) { + this(skCase, 0); + } + + public OsAccounts(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.filteringDSObjId = objId; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + @Messages({ + "OsAccount_listNode_name=OS Accounts" + }) + /** + * The root node of the OS Accounts subtree. + */ + public final class OsAccountListNode extends DisplayableItemNode { + + /** + * Construct a new OsAccountListNode. + */ + public OsAccountListNode() { + super(Children.create(new OsAccountNodeFactory(), true)); + setName(LIST_NAME); + setDisplayName(LIST_NAME); + setIconBaseWithExtension("org/sleuthkit/autopsy/images/os-account.png"); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * The child node factory that creates the OsAccountNode children for a + * OsAccountListNode. + */ + private final class OsAccountNodeFactory extends ChildFactory.Detachable<OsAccount> { + + private final PropertyChangeListener listener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.OS_ACCOUNTS_ADDED.toString()) + || eventType.equals(Case.Events.OS_ACCOUNTS_DELETED.toString())) { + refresh(true); + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + skCase = null; + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(listener, null); + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Case.removeEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNTS_ADDED), weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + } + + @Override + protected void addNotify() { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.OS_ACCOUNTS_ADDED, Case.Events.OS_ACCOUNTS_DELETED), listener); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), listener); + } + + @Override + protected boolean createKeys(List<OsAccount> list) { + if (skCase != null) { + try { + if (filteringDSObjId == 0) { + list.addAll(skCase.getOsAccountManager().getOsAccounts()); + } else { + list.addAll(skCase.getOsAccountManager().getOsAccountsByDataSourceObjId(filteringDSObjId)); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to retrieve list of OsAccounts for case", ex); + return false; + } + } + return true; + } + + @Override + protected Node createNodeForKey(OsAccount key) { + return new OsAccountNode(key); + } + } + + /** + * An OsAccount leaf Node. + */ + public static final class OsAccountNode extends AbstractContentNode<OsAccount> { + + private OsAccount account; + + @Messages({ + "OsAccounts_accountNameProperty_name=Name", + "OsAccounts_accountNameProperty_displayName=Name", + "OsAccounts_accountNameProperty_desc=Os Account name", + "OsAccounts_accountRealmNameProperty_name=RealmName", + "OsAccounts_accountRealmNameProperty_displayName=Realm Name", + "OsAccounts_accountRealmNameProperty_desc=OS Account Realm Name", + "OsAccounts_accountHostNameProperty_name=HostName", + "OsAccounts_accountHostNameProperty_displayName=Host", + "OsAccounts_accountHostNameProperty_desc=OS Account Host Name", + "OsAccounts_accountScopeNameProperty_name=ScopeName", + "OsAccounts_accountScopeNameProperty_displayName=Scope", + "OsAccounts_accountScopeNameProperty_desc=OS Account Scope Name", + "OsAccounts_createdTimeProperty_name=creationTime", + "OsAccounts_createdTimeProperty_displayName=Creation Time", + "OsAccounts_createdTimeProperty_desc=OS Account Creation Time", + "OsAccounts_loginNameProperty_name=loginName", + "OsAccounts_loginNameProperty_displayName=Login Name", + "OsAccounts_loginNameProperty_desc=OS Account login name", + "OsAccounts.createSheet.score.name=S", + "OsAccounts.createSheet.score.displayName=S", + "OsAccounts.createSheet.count.name=O", + "OsAccounts.createSheet.count.displayName=O", + "OsAccounts.createSheet.comment.name=C", + "OsAccounts.createSheet.comment.displayName=C" + }) + private final PropertyChangeListener listener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(Case.Events.OS_ACCOUNTS_UPDATED.name())) { + OsAccountsUpdatedEvent updateEvent = (OsAccountsUpdatedEvent) evt; + for (OsAccount acct : updateEvent.getOsAccounts()) { + if (acct.getId() == account.getId()) { + account = acct; + updateSheet(); + break; + } + } + } else if (evt.getPropertyName().equals(OS_ACCOUNT_DATA_AVAILABLE_EVENT) + && evt.getNewValue() instanceof AsynchOsAcctData + && ((AsynchOsAcctData) evt.getNewValue()).getOsAccountId() == account.getId()) { + + List<NodeProperty<?>> propertiesToUpdate = new ArrayList<>(); + + AsynchOsAcctData osAcctData = (AsynchOsAcctData) evt.getNewValue(); + + List<String> realmNames = osAcctData.getOsAcctRealm().getRealmNames(); + if (!realmNames.isEmpty()) { + String realmNamesStr = realmNames.stream() + .map(String::trim) + .distinct() + .sorted((a, b) -> a.compareToIgnoreCase(b)) + .collect(Collectors.joining(", ")); + + propertiesToUpdate.add(new NodeProperty<>( + Bundle.OsAccounts_accountRealmNameProperty_name(), + Bundle.OsAccounts_accountRealmNameProperty_displayName(), + Bundle.OsAccounts_accountRealmNameProperty_desc(), + realmNamesStr)); + } + + String scopeName = osAcctData.getOsAcctRealm().getScope().getName(); + if (StringUtils.isNotBlank(scopeName)) { + propertiesToUpdate.add(new NodeProperty<>( + Bundle.OsAccounts_accountScopeNameProperty_name(), + Bundle.OsAccounts_accountScopeNameProperty_displayName(), + Bundle.OsAccounts_accountScopeNameProperty_desc(), + scopeName)); + } + + List<Host> hosts = osAcctData.getHosts(); + if (!hosts.isEmpty()) { + String hostsString = hosts.stream() + .map(h -> h.getName().trim()) + .distinct() + .sorted((a, b) -> a.compareToIgnoreCase(b)) + .collect(Collectors.joining(", ")); + + propertiesToUpdate.add(new NodeProperty<>( + Bundle.OsAccounts_accountHostNameProperty_name(), + Bundle.OsAccounts_accountHostNameProperty_displayName(), + Bundle.OsAccounts_accountHostNameProperty_desc(), + hostsString)); + } + updateSheet(propertiesToUpdate.toArray(new NodeProperty<?>[propertiesToUpdate.size()])); + } else if (evt.getPropertyName().equals(NodeSpecificEvents.SCO_AVAILABLE.toString()) && !UserPreferences.getHideSCOColumns()) { + SCOData scoData = (SCOData) evt.getNewValue(); + if (scoData.getScoreAndDescription() != null) { + updateSheet(new NodeProperty<>( + Bundle.OsAccounts_createSheet_score_name(), + Bundle.OsAccounts_createSheet_score_displayName(), + scoData.getScoreAndDescription().getRight(), + scoData.getScoreAndDescription().getLeft())); + } + if (scoData.getComment() != null) { + updateSheet(new NodeProperty<>( + Bundle.OsAccounts_createSheet_comment_name(), + Bundle.OsAccounts_createSheet_comment_displayName(), + NO_DESCR, scoData.getComment())); + } + if (scoData.getCountAndDescription() != null) { + updateSheet(new NodeProperty<>( + Bundle.OsAccounts_createSheet_count_name(), + Bundle.OsAccounts_createSheet_count_displayName(), + scoData.getCountAndDescription().getRight(), + scoData.getCountAndDescription().getLeft())); + } + } + } + }; + + private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null); + + /** + * Constructs a new OsAccountNode. + * + * @param account Node object. + */ + OsAccountNode(OsAccount account) { + super(account); + this.account = account; + + setName(account.getName()); + setDisplayName(account.getName()); + setIconBaseWithExtension(ICON_PATH); + + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNTS_UPDATED), weakListener); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + /** + * Returns the OsAccount associated with this node. + * + * @return + */ + OsAccount getOsAccount() { + return account; + } + + /** + * Refreshes this node's property sheet. + */ + void updateSheet() { + SwingUtilities.invokeLater(() -> { + this.setSheet(createSheet()); + }); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set propertiesSet = sheet.get(Sheet.PROPERTIES); + if (propertiesSet == null) { + propertiesSet = Sheet.createPropertiesSet(); + sheet.put(propertiesSet); + } + propertiesSet.put(new NodeProperty<>( + Bundle.OsAccounts_accountNameProperty_name(), + Bundle.OsAccounts_accountNameProperty_displayName(), + Bundle.OsAccounts_accountNameProperty_desc(), + account.getName() != null ? account.getName() : "")); + addSCOColumns(propertiesSet); + Optional<String> optional = account.getLoginName(); + propertiesSet.put(new NodeProperty<>( + Bundle.OsAccounts_loginNameProperty_name(), + Bundle.OsAccounts_loginNameProperty_displayName(), + Bundle.OsAccounts_loginNameProperty_desc(), + optional.isPresent() ? optional.get() : "")); + + // Fill with empty string, fetch on background task. + propertiesSet.put(new NodeProperty<>( + Bundle.OsAccounts_accountHostNameProperty_name(), + Bundle.OsAccounts_accountHostNameProperty_displayName(), + Bundle.OsAccounts_accountHostNameProperty_desc(), + "")); + + propertiesSet.put(new NodeProperty<>( + Bundle.OsAccounts_accountScopeNameProperty_name(), + Bundle.OsAccounts_accountScopeNameProperty_displayName(), + Bundle.OsAccounts_accountScopeNameProperty_desc(), + "")); + + propertiesSet.put(new NodeProperty<>( + Bundle.OsAccounts_accountRealmNameProperty_name(), + Bundle.OsAccounts_accountRealmNameProperty_displayName(), + Bundle.OsAccounts_accountRealmNameProperty_desc(), + "")); + + Optional<Long> creationTimeValue = account.getCreationTime(); + String timeDisplayStr + = creationTimeValue.isPresent() ? TimeZoneUtils.getFormattedTime(creationTimeValue.get()) : ""; + + propertiesSet.put(new NodeProperty<>( + Bundle.OsAccounts_createdTimeProperty_name(), + Bundle.OsAccounts_createdTimeProperty_displayName(), + Bundle.OsAccounts_createdTimeProperty_desc(), + timeDisplayStr)); + + backgroundTasksPool.submit(new GetOsAccountRealmTask(new WeakReference<>(this), weakListener)); + return sheet; + } + + private void addSCOColumns(Sheet.Set sheetSet) { + if (!UserPreferences.getHideSCOColumns()) { + /* + * Add S(core), C(omments), and O(ther occurences) columns to + * the sheet and start a background task to compute the value of + * these properties for the artifact represented by this node. + * The task will fire a PropertyChangeEvent when the computation + * is completed and this node's PropertyChangeListener will + * update the sheet. + */ + sheetSet.put(new NodeProperty<>( + Bundle.OsAccounts_createSheet_score_name(), + Bundle.OsAccounts_createSheet_score_displayName(), + VALUE_LOADING, + "")); + sheetSet.put(new NodeProperty<>( + Bundle.OsAccounts_createSheet_comment_name(), + Bundle.OsAccounts_createSheet_comment_displayName(), + VALUE_LOADING, + "")); + if (CentralRepository.isEnabled()) { + sheetSet.put(new NodeProperty<>( + Bundle.OsAccounts_createSheet_count_name(), + Bundle.OsAccounts_createSheet_count_displayName(), + VALUE_LOADING, + "")); + } + backgroundTasksPool.submit(new GetSCOTask(new WeakReference<>(this), weakListener)); + } + } + + @Override + public Action[] getActions(boolean popup) { + List<Action> actionsList = new ArrayList<>(); + actionsList.addAll(DataModelActionsFactory.getActions(account)); + actionsList.add(null); + actionsList.addAll(Arrays.asList(super.getActions(popup))); + return actionsList.toArray(new Action[actionsList.size()]); + } + + @Override + protected List<Tag> getAllTagsFromDatabase() { + return new ArrayList<>(); + } + + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * Task for grabbing the osAccount realm. + */ + static class GetOsAccountRealmTask implements Runnable { + + private final WeakReference<OsAccountNode> weakNodeRef; + private final PropertyChangeListener listener; + + /** + * Construct a new task. + * + * @param weakContentRef + * @param listener + */ + GetOsAccountRealmTask(WeakReference<OsAccountNode> weakContentRef, PropertyChangeListener listener) { + this.weakNodeRef = weakContentRef; + this.listener = listener; + } + + @Override + public void run() { + OsAccountNode node = weakNodeRef.get(); + if (node == null) { + return; + } + + try { + SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); + OsAccount osAcct = node.getOsAccount(); + long realmId = osAcct.getRealmId(); + OsAccountRealm realm = skCase.getOsAccountRealmManager().getRealmByRealmId(realmId); + + List<Host> hosts = skCase.getOsAccountManager().getHosts(osAcct); + + AsynchOsAcctData evtData = new AsynchOsAcctData(osAcct.getId(), realm, hosts); + + if (listener != null && realm != null) { + listener.propertyChange(new PropertyChangeEvent( + AutopsyEvent.SourceType.LOCAL.toString(), + OS_ACCOUNT_DATA_AVAILABLE_EVENT, + null, evtData)); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error occurred getting realm information for Os Account Node from case db, for account: " + node.getOsAccount().getName(), ex); + } + } + } + + @NbBundle.Messages({ + "OsAccounts.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated", + "# {0} - occurrenceCount", + "OsAccounts.createSheet.count.description=There were {0} datasource(s) found with occurrences of the OS Account correlation value"}) + @Override + + protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attributeInstance, String defaultDescription) { + Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting + String description = defaultDescription; + try { + //don't perform the query if there is no correlation value + if (attributeInstance != null && StringUtils.isNotBlank(attributeInstance.getCorrelationValue())) { + count = CentralRepository.getInstance().getCountCasesWithOtherInstances(attributeInstance); + description = Bundle.OsAccounts_createSheet_count_description(count); + } else if (attributeInstance != null) { + description = Bundle.OsAccounts_createSheet_count_hashLookupNotRun_description(); + } + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, String.format("Error getting count of data sources with %s correlation attribute %s", attributeInstance.getCorrelationType().getDisplayName(), attributeInstance.getCorrelationValue()), ex); + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.WARNING, String.format("Unable to normalize %s correlation attribute %s", attributeInstance.getCorrelationType().getDisplayName(), attributeInstance.getCorrelationValue()), ex); + } + return Pair.of(count, description); + } + + /** + * Returns comment property for the node. + * + * @param tags The list of tags. + * @param attributes The list of correlation attribute instances. + * + * @return Comment property for the underlying content of the node. + */ + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) { + /* + * Has a tag with a comment been applied to the OsAccount or its + * source content? + */ + DataResultViewerTable.HasCommentStatus status = tags.size() > 0 ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT; + for (Tag tag : tags) { + if (!StringUtils.isBlank(tag.getComment())) { + status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT; + break; + } + } + /* + * Is there a comment in the CR for anything that matches the value + * and type of the specified attributes. + */ + try { + if (CentralRepoDbUtil.commentExistsOnAttributes(attributes)) { + if (status == DataResultViewerTable.HasCommentStatus.TAG_COMMENT) { + status = DataResultViewerTable.HasCommentStatus.CR_AND_TAG_COMMENTS; + } else { + status = DataResultViewerTable.HasCommentStatus.CR_COMMENT; + } + } + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Attempted to Query CR for presence of comments in an OS Account node and was unable to perform query, comment column will only reflect caseDB", ex); + } + return status; + } + + /** + * Data concerning an OS Account loaded asynchronously (and not at sheet + * creation). + */ + private static class AsynchOsAcctData { + + private final long osAccountId; + private final OsAccountRealm osAcctRealm; + private final List<Host> hosts; + + /** + * Main constructor. + * + * @param osAccountId The id of the os account. + * @param osAcctRealm The realm of the os account. + * @param hosts The hosts that the os account belongs to. + */ + AsynchOsAcctData(long osAccountId, OsAccountRealm osAcctRealm, List<Host> hosts) { + this.osAccountId = osAccountId; + this.osAcctRealm = osAcctRealm; + this.hosts = hosts; + } + + /** + * @return The id of the os account. + */ + long getOsAccountId() { + return osAccountId; + } + + /** + * @return The realm of the os account. + */ + OsAccountRealm getOsAcctRealm() { + return osAcctRealm; + } + + /** + * @return The hosts that the os account belongs to. + */ + List<Host> getHosts() { + return hosts; + } + + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/PersonGrouping.java b/Core/src/org/sleuthkit/autopsy/datamodel/PersonGrouping.java new file mode 100644 index 0000000000000000000000000000000000000000..1e65a0627181a6ed13a22122be3aa7a1d632cef1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/PersonGrouping.java @@ -0,0 +1,96 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.Objects; +import org.sleuthkit.datamodel.Person; + +/** + * A top level UI grouping of hosts under a person. + */ +public class PersonGrouping implements AutopsyVisitableItem, Comparable<PersonGrouping> { + + private final Person person; + + /** + * Main constructor. + * + * @param person The person to be represented. + */ + PersonGrouping(Person person) { + + this.person = person; + } + + /** + * @return The person to be represented. + */ + Person getPerson() { + return person; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.person == null ? 0 : this.person.getPersonId()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PersonGrouping other = (PersonGrouping) obj; + long thisId = (this.getPerson() == null) ? 0 : this.getPerson().getPersonId(); + long otherId = (other.getPerson() == null) ? 0 : other.getPerson().getPersonId(); + return thisId == otherId; + } + + /* + * Compares two person groupings to be displayed in a list of children under + * the root of the tree. + */ + @Override + public int compareTo(PersonGrouping o) { + String thisPerson = this.getPerson() == null ? null : this.getPerson().getName(); + String otherPerson = o == null || o.getPerson() == null ? null : o.getPerson().getName(); + + // push unknown host to bottom + if (thisPerson == null && otherPerson == null) { + return 0; + } else if (thisPerson == null) { + return 1; + } else if (otherPerson == null) { + return -1; + } + + return thisPerson.compareToIgnoreCase(otherPerson); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java new file mode 100644 index 0000000000000000000000000000000000000000..310fe7a350e8302c2663d4ecfe586156dabf7e10 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java @@ -0,0 +1,267 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.swing.Action; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.events.PersonsUpdatedEvent; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.persons.DeletePersonAction; +import org.sleuthkit.autopsy.datamodel.persons.EditPersonAction; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A main tree view node that represents a person in a case. Its child nodes, if + * any, represent hosts in the case. There must be at least one person in a case + * for the person nodes layer to appear. If the persons layer is present, any + * hosts that are not associated with a person are grouped under an "Unknown + * Persons" person node. + */ +@NbBundle.Messages(value = {"PersonNode_unknownPersonNode_title=Unknown Persons"}) +public class PersonNode extends DisplayableItemNode { + + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/person.png"; + + /** + * Returns the id of an unknown persons node. This can be used with a node + * lookup. + * + * @return The id of an unknown persons node. + */ + public static String getUnknownPersonId() { + return Bundle.PersonNode_unknownPersonNode_title(); + } + + /** + * Responsible for creating the host children of this person. + */ + private static class PersonChildren extends ChildFactory.Detachable<HostGrouping> { + + private static final Logger logger = Logger.getLogger(PersonChildren.class.getName()); + + private static final Set<Case.Events> HOST_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.HOSTS_ADDED, + Case.Events.HOSTS_ADDED, + Case.Events.HOSTS_DELETED, + Case.Events.HOSTS_ADDED_TO_PERSON, + Case.Events.HOSTS_REMOVED_FROM_PERSON); + + private static final Set<String> HOST_EVENTS_OF_INTEREST_NAMES = HOST_EVENTS_OF_INTEREST.stream() + .map(ev -> ev.name()) + .collect(Collectors.toSet()); + + private final Person person; + + /** + * Main constructor. + * + * @param person The person record. + */ + PersonChildren(Person person) { + this.person = person; + + } + + /** + * Listener for application events that are published when hosts are + * added to or deleted from a case, and for events published when the + * associations between persons and hosts change. If the user has + * selected the group by person/host option for the main tree view, + * these events mean that person nodes in the tree need to be refreshed + * to reflect the structural changes. + */ + private final PropertyChangeListener hostAddedDeletedPcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType != null && HOST_EVENTS_OF_INTEREST_NAMES.contains(eventType)) { + refresh(true); + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(hostAddedDeletedPcl, null); + + @Override + protected void addNotify() { + Case.addEventTypeSubscriber(HOST_EVENTS_OF_INTEREST, weakPcl); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Case.removeEventTypeSubscriber(HOST_EVENTS_OF_INTEREST, weakPcl); + } + + @Override + protected HostNode createNodeForKey(HostGrouping key) { + return key == null ? null : new HostNode(key); + } + + @Override + protected boolean createKeys(List<HostGrouping> toPopulate) { + List<Host> hosts = Collections.emptyList(); + try { + if (person != null) { + hosts = Case.getCurrentCaseThrows().getSleuthkitCase().getPersonManager().getHostsForPerson(person); + } else { + // This is the "Unknown Persons" node, get the hosts that are not associated with a person. + hosts = Case.getCurrentCaseThrows().getSleuthkitCase().getPersonManager().getHostsWithoutPersons(); + } + } catch (NoCurrentCaseException | TskCoreException ex) { + String personName = person == null || person.getName() == null ? "<unknown>" : person.getName(); + logger.log(Level.WARNING, String.format("Unable to get data sources for host: %s", personName), ex); + } + + toPopulate.addAll(hosts.stream() + .map(HostGrouping::new) + .sorted() + .collect(Collectors.toList())); + + return true; + } + } + + private final Person person; + private final Long personId; + + /** + * Listener for application events that are published when the properties of + * persons in the case change. + */ + private final PropertyChangeListener personChangePcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (personId != null && eventType.equals(Case.Events.PERSONS_UPDATED.toString()) && evt instanceof PersonsUpdatedEvent) { + ((PersonsUpdatedEvent) evt).getNewValue().stream() + .filter(p -> p != null && p.getPersonId() == personId) + .findFirst() + .ifPresent((newPerson) -> { + setName(newPerson.getName()); + setDisplayName(newPerson.getName()); + }); + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(personChangePcl, null); + + /** + * Gets the display name for this person or "Unknown Persons". + * + * @param person The person. + * + * @return The non-empty string for the display name. + */ + private static String getDisplayName(Person person) { + return (person == null || person.getName() == null) + ? getUnknownPersonId() + : person.getName(); + } + + /** + * Main constructor. + * + * @param person The person record to be represented. + */ + PersonNode(Person person) { + this(person, getDisplayName(person)); + } + + /** + * Constructor. + * + * @param person The person. + * @param displayName The display name for the person. + */ + private PersonNode(Person person, String displayName) { + super(Children.create(new PersonChildren(person), true), + person == null ? Lookups.fixed(displayName) : Lookups.fixed(person, displayName)); + super.setName(displayName); + super.setDisplayName(displayName); + this.setIconBaseWithExtension(ICON_PATH); + this.person = person; + this.personId = person == null ? null : person.getPersonId(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.PERSONS_UPDATED), weakPcl); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @NbBundle.Messages({ + "PersonGroupingNode_createSheet_nameProperty=Name",}) + @Override + protected Sheet createSheet() { + Sheet sheet = Sheet.createDefault(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("Name", Bundle.PersonGroupingNode_createSheet_nameProperty(), "", getDisplayName())); //NON-NLS + + return sheet; + } + + @Override + @Messages({"PersonGroupingNode_actions_rename=Rename Person...", + "PersonGroupingNode_actions_delete=Delete Person"}) + public Action[] getActions(boolean context) { + if (this.person == null) { + return new Action[0]; + } else { + return new Action[]{ + new EditPersonAction(this.person), + new DeletePersonAction(this.person), + null + }; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/PoolNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/PoolNode.java index 7a4c9f49eaf2c878c1f57650469b2dcf82444dcd..a2fe8c155c7a200c67c766ca5f34ef81ef54183a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/PoolNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/PoolNode.java @@ -106,6 +106,11 @@ protected Sheet createSheet() { return sheet; } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java new file mode 100644 index 0000000000000000000000000000000000000000..be0dc8dbcdae6eb1563b5dda3457908bae20e4df --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.sleuthkit.autopsy.datamodel.AutopsyVisitableItem; +import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * Recent files node support NOTE: As of june '15 we do not display this in the + * tree. It can be added back when we have filtering in the results area. + */ +public class RecentFiles implements AutopsyVisitableItem { + + SleuthkitCase skCase; + + public enum RecentFilesFilter implements AutopsyVisitableItem { + + AUT_0DAY_FILTER(0, "AUT_0DAY_FILTER", //NON-NLS + NbBundle.getMessage(RecentFiles.class, "RecentFiles.aut0DayFilter.displayName.text"), 0), + AUT_1DAY_FILTER(0, "AUT_1DAY_FILTER", //NON-NLS + NbBundle.getMessage(RecentFiles.class, "RecentFiles.aut1dayFilter.displayName.text"), 1), + AUT_2DAY_FILTER(0, "AUT_2DAY_FILTER", //NON-NLS + NbBundle.getMessage(RecentFiles.class, "RecentFiles.aut2dayFilter.displayName.text"), 2), + AUT_3DAY_FILTER(0, "AUT_3DAY_FILTER", //NON-NLS + NbBundle.getMessage(RecentFiles.class, "RecentFiles.aut3dayFilter.displayName.text"), 3), + AUT_4DAY_FILTER(0, "AUT_4DAY_FILTER", //NON-NLS + NbBundle.getMessage(RecentFiles.class, "RecentFiles.aut4dayFilter.displayName.text"), 4), + AUT_5DAY_FILTER(0, "AUT_5DAY_FILTER", //NON-NLS + NbBundle.getMessage(RecentFiles.class, "RecentFiles.aut5dayFilter.displayName.text"), 5), + AUT_6DAY_FILTER(0, "AUT_6DAY_FILTER", //NON-NLS + NbBundle.getMessage(RecentFiles.class, "RecentFiles.aut6dayFilter.displayName.text"), 6); + + private int id; + private String name; + private String displayName; + private int durationDays; + + private RecentFilesFilter(int id, String name, String displayName, int durationDays) { + this.id = id; + this.name = name; + this.displayName = displayName; + this.durationDays = durationDays; + } + + public String getName() { + return this.name; + } + + public int getId() { + return this.id; + } + + public String getDisplayName() { + return this.displayName; + } + + public int getDurationDays() { + return this.durationDays; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + } + + public RecentFiles(SleuthkitCase skCase) { + this.skCase = skCase; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + public SleuthkitCase getSleuthkitCase() { + return this.skCase; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java new file mode 100644 index 0000000000000000000000000000000000000000..391a49f757b861ba8ed777f8a5bc84b18554660b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java @@ -0,0 +1,97 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * @author dfickling + */ +class RecentFilesChildren extends ChildFactory<RecentFiles.RecentFilesFilter> { + + private SleuthkitCase skCase; + private Calendar lastDay; + private final static Logger logger = Logger.getLogger(RecentFilesChildren.class.getName()); + + public RecentFilesChildren(SleuthkitCase skCase) { + this.skCase = skCase; + } + + @Override + protected boolean createKeys(List<RecentFiles.RecentFilesFilter> list) { + list.addAll(Arrays.asList(RecentFiles.RecentFilesFilter.values())); + lastDay = Calendar.getInstance(); + lastDay.setTimeInMillis(getLastTime() * 1000); + lastDay.set(Calendar.HOUR_OF_DAY, 0); + lastDay.set(Calendar.MINUTE, 0); + lastDay.set(Calendar.SECOND, 0); + lastDay.set(Calendar.MILLISECOND, 0); + return true; + } + + @Override + protected Node createNodeForKey(RecentFiles.RecentFilesFilter key) { + return new RecentFilesFilterNode(skCase, key, lastDay); + } + + private long getLastTime() { + String query = createMaxQuery("crtime"); //NON-NLS + long maxcr = runTimeQuery(query); + query = createMaxQuery("ctime"); //NON-NLS + long maxc = runTimeQuery(query); + query = createMaxQuery("mtime"); //NON-NLS + long maxm = runTimeQuery(query); + //query = createMaxQuery("atime"); + //long maxa = runTimeQuery(query); + //return Math.max(maxcr, Math.max(maxc, Math.max(maxm, maxa))); + return Math.max(maxcr, Math.max(maxc, maxm)); + } + + //TODO add a generic query to SleuthkitCase + private String createMaxQuery(String attr) { + return "SELECT MAX(" + attr + ") FROM tsk_files WHERE " + attr + " < " + System.currentTimeMillis() / 1000; //NON-NLS + } + + @SuppressWarnings("deprecation") + private long runTimeQuery(String query) { + long result = 0; + + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + resultSet.next(); + result = resultSet.getLong(1); + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "Couldn't get recent files results: ", ex); //NON-NLS + } + + return result; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java new file mode 100644 index 0000000000000000000000000000000000000000..c6c9c72471488a54224d1119fb328bfce32abd82 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java @@ -0,0 +1,145 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.logging.Level; + +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * + * @author dfickling + */ +class RecentFilesFilterChildren extends ChildFactory<Content> { + + private SleuthkitCase skCase; + private RecentFilesFilter filter; + private Calendar prevDay; + private final static Logger logger = Logger.getLogger(RecentFilesFilterChildren.class.getName()); + //private final static int MAX_OBJECTS = 1000000; + + RecentFilesFilterChildren(RecentFilesFilter filter, SleuthkitCase skCase, Calendar lastDay) { + this.skCase = skCase; + this.filter = filter; + this.prevDay = (Calendar) lastDay.clone(); + prevDay.add(Calendar.DATE, -filter.getDurationDays()); + } + + @Override + protected boolean createKeys(List<Content> list) { + list.addAll(runQuery()); + return true; + } + + private String createQuery() { + Calendar prevDayQuery = (Calendar) prevDay.clone(); + String query = "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" //NON-NLS + + " AND (known IS NULL OR known != 1) AND ("; //NON-NLS + long lowerLimit = prevDayQuery.getTimeInMillis() / 1000; + prevDayQuery.add(Calendar.DATE, 1); + prevDayQuery.add(Calendar.MILLISECOND, -1); + long upperLimit = prevDayQuery.getTimeInMillis() / 1000; + query += "(crtime BETWEEN " + lowerLimit + " AND " + upperLimit + ") OR "; //NON-NLS + query += "(ctime BETWEEN " + lowerLimit + " AND " + upperLimit + ") OR "; //NON-NLS + //query += "(atime BETWEEN " + lowerLimit + " AND " + upperLimit + ") OR "; + query += "(mtime BETWEEN " + lowerLimit + " AND " + upperLimit + "))"; //NON-NLS + //query += " LIMIT " + MAX_OBJECTS; + return query; + } + + private List<AbstractFile> runQuery() { + List<AbstractFile> ret = new ArrayList<AbstractFile>(); + try { + List<AbstractFile> found = skCase.findAllFilesWhere(createQuery()); + for (AbstractFile c : found) { + ret.add(c); + } + + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Couldn't get search results", ex); //NON-NLS + } + return ret; + + } + + /** + * Get children count without actually loading all nodes + * + * @return + */ + long calculateItems() { + try { + return skCase.countFilesWhere(createQuery()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting recent files search view count", ex); //NON-NLS + return 0; + } + } + + @Override + protected Node createNodeForKey(Content key) { + return key.accept(new ContentVisitor.Default<AbstractNode>() { + @Override + public FileNode visit(File f) { + return new FileNode(f, false); + } + + @Override + public DirectoryNode visit(Directory d) { + return new DirectoryNode(d); + } + + @Override + public LocalFileNode visit(DerivedFile f) { + return new LocalFileNode(f); + } + + @Override + public LocalFileNode visit(LocalFile f) { + return new LocalFileNode(f); + } + + @Override + protected AbstractNode defaultVisit(Content di) { + throw new UnsupportedOperationException( + NbBundle.getMessage(this.getClass(), + "RecentFilesFilterChildren.exception.defaultVisit.msg", + di.toString())); + } + }); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java new file mode 100644 index 0000000000000000000000000000000000000000..86045f5e4be552d55b4c5c6d6ea82be8fd51e096 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java @@ -0,0 +1,94 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2014 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.Calendar; +import java.util.Locale; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * Node for recent files filter + */ +public class RecentFilesFilterNode extends DisplayableItemNode { + + SleuthkitCase skCase; + RecentFilesFilter filter; + private final static Logger logger = Logger.getLogger(RecentFilesFilterNode.class.getName()); + + RecentFilesFilterNode(SleuthkitCase skCase, RecentFilesFilter filter, Calendar lastDay) { + super(Children.create(new RecentFilesFilterChildren(filter, skCase, lastDay), true), Lookups.singleton(filter.getDisplayName())); + super.setName(filter.getName()); + this.skCase = skCase; + this.filter = filter; + Calendar prevDay = (Calendar) lastDay.clone(); + prevDay.add(Calendar.DATE, -filter.getDurationDays()); + String tooltip = prevDay.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.ENGLISH) + " " + + prevDay.get(Calendar.DATE) + ", " + + prevDay.get(Calendar.YEAR); + this.setShortDescription(tooltip); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/recent_files.png"); //NON-NLS + + //get count of children without preloading all children nodes + final long count = new RecentFilesFilterChildren(filter, skCase, lastDay).calculateItems(); + super.setDisplayName(filter.getDisplayName() + " (" + count + ")"); + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "RecentFilesFilterNode.createSheet.filterType.name"), + NbBundle.getMessage(this.getClass(), "RecentFilesFilterNode.createSheet.filterType.displayName"), + NbBundle.getMessage(this.getClass(), "RecentFilesFilterNode.createSheet.filterType.desc"), + filter.getDisplayName())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + if (filter == null) { + return getClass().getName(); + } else { + return getClass().getName() + filter.getName(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java new file mode 100644 index 0000000000000000000000000000000000000000..d4daf0784d1c8d5b23d05fde998dd3ab7b3b5380 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java @@ -0,0 +1,71 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2014 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * Node for recent files + */ +public class RecentFilesNode extends DisplayableItemNode { + + private static final String NAME = NbBundle.getMessage(RecentFilesNode.class, "RecentFilesNode.name.text"); + + RecentFilesNode(SleuthkitCase skCase) { + super(Children.create(new RecentFilesChildren(skCase), true), Lookups.singleton(NAME)); + super.setName(NAME); + super.setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/recent_files.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "RecentFilesNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "RecentFilesNode.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "RecentFilesNode.createSheet.name.desc"), + NAME)); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java new file mode 100644 index 0000000000000000000000000000000000000000..0698d9dc975b3f4eb65c4f4ec8cf3ef25532e759 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java @@ -0,0 +1,306 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2018 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JOptionPane; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.datamodel.Report; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Implements the Reports subtree of the Autopsy tree. + */ +public final class Reports implements AutopsyVisitableItem { + + private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + // CreateAutopsyNodeVisitor.visit() constructs a ReportsListNode. + return visitor.visit(this); + } + + /** + * The root node of the Reports subtree of the Autopsy tree. + */ + public static final class ReportsListNode extends DisplayableItemNode { + + private static final long serialVersionUID = 1L; + private static final String DISPLAY_NAME = NbBundle.getMessage(ReportsListNode.class, "ReportsListNode.displayName"); + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/report_16.png"; //NON-NLS + + public ReportsListNode() { + super(Children.create(new ReportNodeFactory(), true)); + setName(DISPLAY_NAME); + setDisplayName(DISPLAY_NAME); + this.setIconBaseWithExtension(ICON_PATH); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + // - GetPopupActionsDisplayableItemNodeVisitor.visit() returns null. + // - GetPreferredActionsDisplayableItemNodeVisitor.visit() returns null. + // - IsLeafItemVisitor.visit() returns false. + // - ShowItemVisitor.visit() returns true. + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * The child node factory that creates ReportNode children for a + * ReportsListNode. + */ + private static final class ReportNodeFactory extends ChildFactory<Report> { + + private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.REPORT_ADDED, Case.Events.REPORT_DELETED); + + ReportNodeFactory() { + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.REPORT_ADDED.toString()) || eventType.equals(Case.Events.REPORT_DELETED.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. + */ + try { + Case.getCurrentCaseThrows(); + ReportNodeFactory.this.refresh(true); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + }); + } + + @Override + protected boolean createKeys(List<Report> keys) { + try { + keys.addAll(Case.getCurrentCaseThrows().getAllReports()); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(Reports.ReportNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get reports", ex); //NON-NLS + } + return true; + } + + @Override + protected Node createNodeForKey(Report key) { + return new ReportNode(key); + } + } + + /** + * A leaf node in the Reports subtree of the Autopsy tree, wraps a Report + * object. + */ + public static final class ReportNode extends DisplayableItemNode { + + private static final long serialVersionUID = 1L; + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/report_16.png"; //NON-NLS + private final Report report; + + ReportNode(Report report) { + super(Children.LEAF, Lookups.fixed(report)); + this.report = report; + super.setName(this.report.getSourceModuleName()); + super.setDisplayName(this.report.getSourceModuleName()); + this.setIconBaseWithExtension(ICON_PATH); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + // - GetPopupActionsDisplayableItemNodeVisitor.visit() calls getActions(). + // - GetPreferredActionsDisplayableItemNodeVisitor.visit() calls getPreferredAction(). + // - IsLeafItemVisitor.visit() returns true. + // - ShowItemVisitor.visit() returns true. + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set propertiesSet = sheet.get(Sheet.PROPERTIES); + if (propertiesSet == null) { + propertiesSet = Sheet.createPropertiesSet(); + sheet.put(propertiesSet); + } + propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.sourceModuleNameProperty.name"), + NbBundle.getMessage(this.getClass(), "ReportNode.sourceModuleNameProperty.displayName"), + NbBundle.getMessage(this.getClass(), "ReportNode.sourceModuleNameProperty.desc"), + this.report.getSourceModuleName())); + propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.reportNameProperty.name"), + NbBundle.getMessage(this.getClass(), "ReportNode.reportNameProperty.displayName"), + NbBundle.getMessage(this.getClass(), "ReportNode.reportNameProperty.desc"), + this.report.getReportName())); + propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.createdTimeProperty.name"), + NbBundle.getMessage(this.getClass(), "ReportNode.createdTimeProperty.displayName"), + NbBundle.getMessage(this.getClass(), "ReportNode.createdTimeProperty.desc"), + dateFormatter.format(new java.util.Date(this.report.getCreatedTime() * 1000)))); + propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.pathProperty.name"), + NbBundle.getMessage(this.getClass(), "ReportNode.pathProperty.displayName"), + NbBundle.getMessage(this.getClass(), "ReportNode.pathProperty.desc"), + this.report.getPath())); + return sheet; + } + + @Override + public Action[] getActions(boolean popup) { + List<Action> actions = new ArrayList<>(); + actions.add(new OpenReportAction()); + actions.add(DeleteReportAction.getInstance()); + actions.add(null); + actions.addAll(Arrays.asList(super.getActions(true))); + return actions.toArray(new Action[actions.size()]); + } + + @Override + public AbstractAction getPreferredAction() { + return new OpenReportAction(); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + private static class DeleteReportAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private static DeleteReportAction instance; + + // This class is a singleton to support multi-selection of nodes, + // since org.openide.nodes.NodeOp.findActions(Node[] nodes) will + // only pick up an Action if every node in the array returns a + // reference to the same action object from Node.getActions(boolean). + private static DeleteReportAction getInstance() { + if (instance == null) { + instance = new DeleteReportAction(); + } + if (Utilities.actionsGlobalContext().lookupAll(Report.class).size() == 1) { + instance.putValue(Action.NAME, NbBundle.getMessage(Reports.class, "DeleteReportAction.actionDisplayName.singleReport")); + } else { + instance.putValue(Action.NAME, NbBundle.getMessage(Reports.class, "DeleteReportAction.actionDisplayName.multipleReports")); + } + return instance; + } + + /** + * Do not instantiate directly. Use + * DeleteReportAction.getInstance(), instead. + */ + private DeleteReportAction() { + } + + @NbBundle.Messages({ + "DeleteReportAction.showConfirmDialog.single.explanation=The report will remain on disk.", + "DeleteReportAction.showConfirmDialog.multiple.explanation=The reports will remain on disk.", + "DeleteReportAction.showConfirmDialog.errorMsg=An error occurred while deleting the reports."}) + @Override + public void actionPerformed(ActionEvent e) { + Collection<? extends Report> selectedReportsCollection = Utilities.actionsGlobalContext().lookupAll(Report.class); + String message = selectedReportsCollection.size() > 1 + ? NbBundle.getMessage(Reports.class, "DeleteReportAction.actionPerformed.showConfirmDialog.multiple.msg", selectedReportsCollection.size()) + : NbBundle.getMessage(Reports.class, "DeleteReportAction.actionPerformed.showConfirmDialog.single.msg"); + String explanation = selectedReportsCollection.size() > 1 + ? Bundle.DeleteReportAction_showConfirmDialog_multiple_explanation() + : Bundle.DeleteReportAction_showConfirmDialog_single_explanation(); + Object[] jOptionPaneContent = {message, explanation}; + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(null, jOptionPaneContent, + NbBundle.getMessage(Reports.class, "DeleteReportAction.actionPerformed.showConfirmDialog.title"), + JOptionPane.YES_NO_OPTION)) { + try { + Case.getCurrentCaseThrows().deleteReports(selectedReportsCollection); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(DeleteReportAction.class.getName()).log(Level.SEVERE, "Error deleting reports", ex); // NON-NLS + MessageNotifyUtil.Message.error(Bundle.DeleteReportAction_showConfirmDialog_errorMsg()); + } + } + } + } + + private final class OpenReportAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + private OpenReportAction() { + super(NbBundle.getMessage(OpenReportAction.class, "OpenReportAction.actionDisplayName")); + } + + @Override + public void actionPerformed(ActionEvent e) { + String reportPath = ReportNode.this.report.getPath(); + + if (reportPath.toLowerCase().startsWith("http")) { + ExternalViewerAction.openURL(reportPath); + } + else { + String extension = ""; + int extPosition = reportPath.lastIndexOf('.'); + if (extPosition != -1) { + extension = reportPath.substring(extPosition, reportPath.length()).toLowerCase(); + } + + ExternalViewerAction.openFile("", extension, new File(reportPath)); + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java index 6a8b592f07cee11ac16106976ea5c8d816ff41c7..43c1253eb7dbea42a6890e036634258343078b38 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java @@ -18,79 +18,200 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.util.Collection; +import java.util.Collections; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; import org.openide.nodes.Node; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.DerivedFile; -import org.sleuthkit.datamodel.Directory; -import org.sleuthkit.datamodel.File; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.LocalDirectory; -import org.sleuthkit.datamodel.LocalFile; -import org.sleuthkit.datamodel.LocalFilesDataSource; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.UnsupportedContent; -import org.sleuthkit.datamodel.VirtualDirectory; -import org.sleuthkit.datamodel.Volume; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsChildren; +import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsChildren.ScoreContentNode; +import org.sleuthkit.autopsy.datamodel.accounts.Accounts; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; /** - * Produces legacy version of content nodes. + * Children implementation for the root node of a ContentNode tree. Accepts a + * list of root Content objects for the tree. */ -public class RootContentChildren { +public class RootContentChildren extends Children.Keys<Object> { + + private final Collection<? extends Object> contentKeys; + private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor(); + private final CreateSleuthkitNodeVisitor createSleuthkitNodeVisitor = new CreateSleuthkitNodeVisitor(); + /** - * Creates a node for one of the known object keys that is not a sleuthkit - * item. - * - * @param key The node key. + * @param contentKeys root Content objects for the Node tree + */ + public RootContentChildren(Collection<? extends Object> contentKeys) { + super(); + this.contentKeys = contentKeys; + } + + @Override + protected void addNotify() { + setKeys(contentKeys); + } + + @Override + protected void removeNotify() { + setKeys(Collections.<Object>emptySet()); + } + + /** + * Refresh all content keys This creates new nodes of keys have changed. * - * @return The generated node or null if no match found. + * TODO ideally, nodes would respond to event from wrapped content object + * but we are not ready for this. */ - public static Node createNode(Object key) { - if (key instanceof Directory) { - Directory drctr = (Directory) key; - return new DirectoryNode(drctr); - } else if (key instanceof File) { - File file = (File) key; - return new FileNode(file); - } else if (key instanceof Image) { - Image image = (Image) key; - return new ImageNode(image); - } else if (key instanceof Volume) { - Volume volume = (Volume) key; - return new VolumeNode(volume); - } else if (key instanceof Pool) { - Pool pool = (Pool) key; - return new PoolNode(pool); - } else if (key instanceof LayoutFile) { - LayoutFile lf = (LayoutFile) key; - return new LayoutFileNode(lf); - } else if (key instanceof DerivedFile) { - DerivedFile df = (DerivedFile) key; - return new LocalFileNode(df); - } else if (key instanceof LocalFile) { - LocalFile lf = (LocalFile) key; - return new LocalFileNode(lf); - } else if (key instanceof VirtualDirectory) { - VirtualDirectory ld = (VirtualDirectory) key; - return new VirtualDirectoryNode(ld); - } else if (key instanceof LocalDirectory) { - LocalDirectory ld = (LocalDirectory) key; - return new LocalDirectoryNode(ld); - } else if (key instanceof SlackFile) { - SlackFile sf = (SlackFile) key; - return new SlackFileNode(sf); - } else if (key instanceof BlackboardArtifact) { - BlackboardArtifact art = (BlackboardArtifact) key; - return new BlackboardArtifactNode(art); - } else if (key instanceof UnsupportedContent) { - UnsupportedContent uc = (UnsupportedContent) key; - return new UnsupportedContentNode(uc); - } else if (key instanceof LocalFilesDataSource) { - LocalFilesDataSource ld = (LocalFilesDataSource) key; - return new LocalFilesDataSourceNode(ld); + public void refreshContentKeys() { + contentKeys.forEach(this::refreshKey); + } + + @Override + protected Node[] createNodes(Object key) { + if (key instanceof AutopsyVisitableItem) { + return new Node[]{((AutopsyVisitableItem) key).accept(createAutopsyNodeVisitor)}; } else { - return null; + return new Node[]{((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor)}; + } + } + + /** + * Gets a DisplayableItemNode for use as a subtree root node for the Autopsy + * tree view from each type of AutopsyVisitableItem visited. There are + * AutopsyVisitableItems for the Data Sources, Views, Results, and Reports + * subtrees, and for the subtrees of Results (e.g., Extracted Content, Hash + * Set Hits, etc.). + */ + static class CreateAutopsyNodeVisitor extends AutopsyItemVisitor.Default<AbstractNode> { + + @Override + public AbstractNode visit(FileTypesByExtension sf) { + return sf.new FileTypesByExtNode(sf.getSleuthkitCase(), null); + } + + @Override + public AbstractNode visit(RecentFiles rf) { + return new RecentFilesNode(rf.getSleuthkitCase()); + } + + @Override + public AbstractNode visit(DeletedContent dc) { + return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); + } + + @Override + public AbstractNode visit(ScoreContent sc) { + return new ScoreContent.ScoreContentsNode(sc.getSleuthkitCase(), sc.filteringDataSourceObjId()); + } + + @Override + public AbstractNode visit(FileSize dc) { + return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); + } + + @Override + public AbstractNode visit(KeywordHits kh) { + return kh.new RootNode(); + } + + @Override + public AbstractNode visit(HashsetHits hh) { + return hh.new RootNode(); + } + + @Override + public AbstractNode visit(InterestingHits ih) { + return ih.new RootNode(); + } + + @Override + public AbstractNode visit(EmailExtracted ee) { + return ee.new RootNode(); + } + + @Override + public AbstractNode visit(Tags tagsNodeKey) { + return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId()); + } + + @Override + public AbstractNode visit(DataSources i) { + return new DataSourceFilesNode(i.filteringDataSourceObjId()); + } + + @Override + public AbstractNode visit(DataSourceGrouping datasourceGrouping) { + return new DataSourceGroupingNode(datasourceGrouping.getDataSource()); + } + + @Override + public AbstractNode visit(Views v) { + return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId()); + } + + @Override + public AbstractNode visit(FileTypes ft) { + return ft.new FileTypesNode(); + } + + @Override + public AbstractNode visit(Reports reportsItem) { + return new Reports.ReportsListNode(); + } + + @Override + public AbstractNode visit(Accounts accountsItem) { + return accountsItem.new AccountsRootNode(); + } + + @Override + public AbstractNode visit(OsAccounts osAccountsItem) { + return osAccountsItem.new OsAccountListNode(); + } + + @Override + protected AbstractNode defaultVisit(AutopsyVisitableItem di) { + throw new UnsupportedOperationException( + NbBundle.getMessage(this.getClass(), + "AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg")); + } + + @Override + public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) { + return ftByMimeTypeItem.new ByMimeTypeNode(); + } + + @Override + public AbstractNode visit(PersonGrouping personGrouping) { + return new PersonNode(personGrouping.getPerson()); + } + + @Override + public AbstractNode visit(HostDataSources hosts) { + return new HostNode(hosts); + } + + @Override + public AbstractNode visit(HostGrouping hostGrouping) { + return new HostNode(hostGrouping); + } + + @Override + public AbstractNode visit(DataSourcesByType dataSourceHosts) { + return new DataSourcesNode(); + } + + @Override + public AbstractNode visit(AnalysisResults analysisResults) { + return new AnalysisResults.RootNode( + analysisResults.getFilteringDataSourceObjId()); + } + + @Override + public AbstractNode visit(DataArtifacts dataArtifacts) { + return new DataArtifacts.RootNode( + dataArtifacts.getFilteringDataSourceObjId()); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ScoreContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ScoreContent.java new file mode 100644 index 0000000000000000000000000000000000000000..a6cc791fd4ae2bb25f8c4496ef1021869c303f16 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ScoreContent.java @@ -0,0 +1,812 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.Category; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.FsContent; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.Score.Priority; +import org.sleuthkit.datamodel.Score.Significance; +import org.sleuthkit.datamodel.SlackFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.VirtualDirectory; + +/** + * Score content view nodes. + */ +public class ScoreContent implements AutopsyVisitableItem { + + private SleuthkitCase skCase; + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + @NbBundle.Messages({"ScoreContent_badFilter_text=Bad Items", + "ScoreContent_susFilter_text=Suspicious Items"}) + public enum ScoreContentFilter implements AutopsyVisitableItem { + + BAD_ITEM_FILTER(0, "BAD_ITEM_FILTER", + Bundle.ScoreContent_badFilter_text()), + SUS_ITEM_FILTER(1, "SUS_ITEM_FILTER", + Bundle.ScoreContent_susFilter_text()); + + private int id; + private String name; + private String displayName; + + private ScoreContentFilter(int id, String name, String displayName) { + this.id = id; + this.name = name; + this.displayName = displayName; + + } + + public String getName() { + return this.name; + } + + public int getId() { + return this.id; + } + + public String getDisplayName() { + return this.displayName; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + } + + /** + * Constructor assuming no data source filtering. + * + * @param skCase The sleuthkit case. + */ + public ScoreContent(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor. + * + * @param skCase The sleuthkit case. + * @param dsObjId The data source object id to filter on if > 0. + */ + public ScoreContent(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.filteringDSObjId = dsObjId; + } + + /** + * @return The data source object id to filter on if > 0. + */ + long filteringDataSourceObjId() { + return this.filteringDSObjId; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * @return The sleuthkit case used. + */ + public SleuthkitCase getSleuthkitCase() { + return this.skCase; + } + + private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of( + Case.Events.DATA_SOURCE_ADDED, + Case.Events.CURRENT_CASE, + Case.Events.CONTENT_TAG_ADDED, + Case.Events.CONTENT_TAG_DELETED, + Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, + Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED + ); + private static final Set<String> CASE_EVENTS_OF_INTEREST_STRS = CASE_EVENTS_OF_INTEREST.stream() + .map(evt -> evt.name()) + .collect(Collectors.toSet()); + + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestModuleEvent.CONTENT_CHANGED); + + /** + * Returns a property change listener listening for possible updates to + * aggregate score updates for files. + * + * @param onRefresh Action on refresh. + * @param onRemove Action to remove listener (i.e. case close). + * @return The property change listener. + */ + private static PropertyChangeListener getPcl(final Runnable onRefresh, final Runnable onRemove) { + return (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) { + // only refresh if there is a current case. + try { + Case.getCurrentCaseThrows(); + if (onRefresh != null) { + onRefresh.run(); + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null && onRemove != null) { + onRemove.run(); + } + } else if (CASE_EVENTS_OF_INTEREST_STRS.contains(eventType)) { + // only refresh if there is a current case. + try { + Case.getCurrentCaseThrows(); + if (onRefresh != null) { + onRefresh.run(); + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + }; + } + + /** + * The sql where statement for the content. + * + * @param filter The filter type. + * @param objIdAlias The alias for the object id of the content. Must be sql + * safe. + * @param dsIdAlias The alias for the data source id. Must be sql safe. + * @param filteringDSObjId The data source object id to filter on if > 0. + * @return The sql where statement. + * @throws IllegalArgumentException + */ + private static String getFilter(ScoreContent.ScoreContentFilter filter, String objIdAlias, String dsIdAlias, long filteringDSObjId) throws IllegalArgumentException { + String aggregateScoreFilter = getScoreFilter(filter); + String query = " " + objIdAlias + " IN (SELECT tsk_aggregate_score.obj_id FROM tsk_aggregate_score WHERE " + aggregateScoreFilter + ") "; + + if (filteringDSObjId > 0) { + query += " AND " + dsIdAlias + " = " + filteringDSObjId; + } + return query; + } + + private static String getScoreFilter(ScoreContentFilter filter) throws IllegalArgumentException { + switch (filter) { + case SUS_ITEM_FILTER: + return " tsk_aggregate_score.significance = " + Significance.LIKELY_NOTABLE.getId() + + " AND (tsk_aggregate_score.priority = " + Priority.NORMAL.getId() + " OR tsk_aggregate_score.priority = " + Priority.OVERRIDE.getId() + " )"; + case BAD_ITEM_FILTER: + return " tsk_aggregate_score.significance = " + Significance.NOTABLE.getId() + + " AND (tsk_aggregate_score.priority = " + Priority.NORMAL.getId() + " OR tsk_aggregate_score.priority = " + Priority.OVERRIDE.getId() + " )"; + default: + throw new IllegalArgumentException(MessageFormat.format("Unsupported filter type to get suspect content: {0}", filter)); + } + } + + /** + * Returns a sql where statement for files. + * + * @param filter The filter type. + * @param filteringDSObjId The data source object id to filter on if > 0. + * @return The sql where statement. + * @throws IllegalArgumentException + */ + private static String getFileFilter(ScoreContent.ScoreContentFilter filter, long filteringDsObjId) throws IllegalArgumentException { + return getFilter(filter, "obj_id", "data_source_obj_id", filteringDsObjId); + } + + /** + * Returns a sql where statement for files. + * + * @param filter The filter type. + * @param filteringDSObjId The data source object id to filter on if > 0. + * @return The sql where statement. + * @throws IllegalArgumentException + */ + private static String getDataArtifactFilter(ScoreContent.ScoreContentFilter filter, long filteringDsObjId) throws IllegalArgumentException { + return getFilter(filter, "artifacts.artifact_obj_id", "artifacts.data_source_obj_id", filteringDsObjId); + } + + /** + * Checks for analysis results added to the case that could affect the + * aggregate score of the file. + * + * @param evt The event. + * @return True if has an analysis result. + */ + private static boolean isRefreshRequired(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { + // check if current case is active before updating + try { + Case.getCurrentCaseThrows(); + final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); + if (null != event && Category.ANALYSIS_RESULT.equals(event.getBlackboardArtifactType().getCategory())) { + return true; + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + return false; + } + + /** + * Parent node in views section for content with score. + */ + public static class ScoreContentsNode extends DisplayableItemNode { + + @NbBundle.Messages("ScoreContent_ScoreContentNode_name=Score") + private static final String NAME = Bundle.ScoreContent_ScoreContentNode_name(); + + ScoreContentsNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new ScoreContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); + super.setName(NAME); + super.setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + @NbBundle.Messages({ + "ScoreContent_createSheet_name_displayName=Name", + "ScoreContent_createSheet_name_desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("Name", //NON-NLS + Bundle.ScoreContent_createSheet_name_displayName(), + Bundle.ScoreContent_createSheet_name_desc(), + NAME)); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * Children that display a node for Bad Items and Score Items. + */ + public static class ScoreContentsChildren extends ChildFactory.Detachable<ScoreContent.ScoreContentFilter> implements RefreshThrottler.Refresher { + + private SleuthkitCase skCase; + private final long datasourceObjId; + + private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); + + private final PropertyChangeListener pcl = getPcl( + () -> ScoreContentsChildren.this.refresh(false), + () -> ScoreContentsChildren.this.removeNotify()); + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + private final Map<ScoreContentFilter, ScoreContentsChildren.ScoreContentNode> typeNodeMap = new HashMap<>(); + + public ScoreContentsChildren(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + @Override + protected void addNotify() { + super.addNotify(); + refreshThrottler.registerForIngestModuleEvents(); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); + } + + @Override + protected void removeNotify() { + refreshThrottler.unregisterEventListener(); + IngestManager.getInstance().removeIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); + typeNodeMap.clear(); + } + + @Override + public void refresh() { + refresh(false); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + return ScoreContent.isRefreshRequired(evt); + } + + @Override + protected boolean createKeys(List<ScoreContent.ScoreContentFilter> list) { + list.addAll(Arrays.asList(ScoreContent.ScoreContentFilter.values())); + typeNodeMap.values().forEach(nd -> nd.updateDisplayName()); + return true; + } + + @Override + protected Node createNodeForKey(ScoreContent.ScoreContentFilter key) { + ScoreContentsChildren.ScoreContentNode nd = new ScoreContentsChildren.ScoreContentNode(skCase, key, datasourceObjId); + typeNodeMap.put(key, nd); + return nd; + } + + /** + * Parent node showing files matching a score filter. + */ + public class ScoreContentNode extends DisplayableItemNode { + + private static final Logger logger = Logger.getLogger(ScoreContentNode.class.getName()); + private final ScoreContent.ScoreContentFilter filter; + private final long datasourceObjId; + + ScoreContentNode(SleuthkitCase skCase, ScoreContent.ScoreContentFilter filter, long dsObjId) { + super(Children.create(new ScoreContentChildren(filter, skCase, dsObjId), true), Lookups.singleton(filter.getDisplayName())); + this.filter = filter; + this.datasourceObjId = dsObjId; + init(); + } + + private void init() { + super.setName(filter.getName()); + + String tooltip = filter.getDisplayName(); + this.setShortDescription(tooltip); + switch (this.filter) { + case SUS_ITEM_FILTER: + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/yellow-circle-yield.png"); //NON-NLS + break; + default: + case BAD_ITEM_FILTER: + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS + break; + } + + updateDisplayName(); + } + + void updateDisplayName() { + //get count of children without preloading all child nodes + long count = 0; + try { + count = calculateItems(skCase, filter, datasourceObjId); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "An error occurred while fetching file counts", ex); + } + super.setDisplayName(filter.getDisplayName() + " (" + count + ")"); + } + + /** + * Get children count without actually loading all nodes + * + * @param sleuthkitCase + * @param filter + * + * @return + */ + private static long calculateItems(SleuthkitCase sleuthkitCase, ScoreContent.ScoreContentFilter filter, long datasourceObjId) throws TskCoreException { + AtomicLong retVal = new AtomicLong(0L); + AtomicReference<SQLException> exRef = new AtomicReference(null); + + String query = " COUNT(tsk_aggregate_score.obj_id) AS count FROM tsk_aggregate_score WHERE\n" + + getScoreFilter(filter) + "\n" + + ((datasourceObjId > 0) ? "AND tsk_aggregate_score.data_source_obj_id = \n" + datasourceObjId : "") + + " AND tsk_aggregate_score.obj_id IN\n" + + " (SELECT tsk_files.obj_id AS obj_id FROM tsk_files UNION\n" + + " SELECT blackboard_artifacts.artifact_obj_id AS obj_id FROM blackboard_artifacts WHERE blackboard_artifacts.artifact_type_id IN\n" + + " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + Category.DATA_ARTIFACT.getID() + ")) "; + sleuthkitCase.getCaseDbAccessManager().select(query, (rs) -> { + try { + if (rs.next()) { + retVal.set(rs.getLong("count")); + } + } catch (SQLException ex) { + exRef.set(ex); + } + }); + + SQLException sqlEx = exRef.get(); + if (sqlEx != null) { + throw new TskCoreException( + MessageFormat.format("A sql exception occurred fetching results with query: SELECT {0}", query), + sqlEx); + } else { + return retVal.get(); + } + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + @NbBundle.Messages({ + "ScoreContent_createSheet_filterType_displayName=Type", + "ScoreContent_createSheet_filterType_desc=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("Type", //NON_NLS + Bundle.ScoreContent_createSheet_filterType_displayName(), + Bundle.ScoreContent_createSheet_filterType_desc(), + filter.getDisplayName())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + return DisplayableItemNode.FILE_PARENT_NODE_KEY; + } + } + + /** + * Children showing files for a score filter. + */ + static class ScoreContentChildren extends BaseChildFactory<Content> implements RefreshThrottler.Refresher { + + private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); + + private final PropertyChangeListener pcl = getPcl( + () -> ScoreContentChildren.this.refresh(false), + () -> ScoreContentChildren.this.removeNotify()); + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + private final SleuthkitCase skCase; + private final ScoreContent.ScoreContentFilter filter; + private static final Logger logger = Logger.getLogger(ScoreContentChildren.class.getName()); + + private final long datasourceObjId; + + ScoreContentChildren(ScoreContent.ScoreContentFilter filter, SleuthkitCase skCase, long datasourceObjId) { + super(filter.getName(), new ViewsKnownAndSlackFilter<>()); + this.skCase = skCase; + this.filter = filter; + this.datasourceObjId = datasourceObjId; + } + + @Override + protected void onAdd() { + refreshThrottler.registerForIngestModuleEvents(); + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); + } + + @Override + protected void onRemove() { + refreshThrottler.unregisterEventListener(); + IngestManager.getInstance().removeIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); + } + + @Override + public void refresh() { + refresh(false); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + return ScoreContent.isRefreshRequired(evt); + } + + private List<Content> runFsQuery() { + List<Content> ret = new ArrayList<>(); + + String fileFilter = null; + String dataArtifactFilter = null; + try { + fileFilter = getFileFilter(filter, datasourceObjId); + dataArtifactFilter = getDataArtifactFilter(filter, datasourceObjId); + ret.addAll(skCase.findAllFilesWhere(fileFilter)); + ret.addAll(skCase.getBlackboard().getDataArtifactsWhere(dataArtifactFilter)); + } catch (TskCoreException | IllegalArgumentException e) { + logger.log(Level.SEVERE, MessageFormat.format( + "Error getting files for the deleted content view using file filter: {0} data artifact filter: {1}", + StringUtils.defaultString(fileFilter, "<null>"), + StringUtils.defaultString(dataArtifactFilter, "<null>")), e); //NON-NLS + } + + return ret; + + } + + @Override + protected List<Content> makeKeys() { + return runFsQuery(); + } + + @Override + protected Node createNodeForKey(Content key) { + return key.accept(new ContentVisitor.Default<AbstractNode>() { + public FileNode visit(AbstractFile f) { + return new ScoreFileNode(f, false); + } + + public FileNode visit(FsContent f) { + return new ScoreFileNode(f, false); + } + + @Override + public FileNode visit(LayoutFile f) { + return new ScoreFileNode(f, false); + } + + @Override + public FileNode visit(File f) { + return new ScoreFileNode(f, false); + } + + @Override + public FileNode visit(Directory f) { + return new ScoreFileNode(f, false); + } + + @Override + public FileNode visit(VirtualDirectory f) { + return new ScoreFileNode(f, false); + } + + @Override + public AbstractNode visit(SlackFile sf) { + return new ScoreFileNode(sf, false); + } + + @Override + public AbstractNode visit(LocalFile lf) { + return new ScoreFileNode(lf, false); + } + + @Override + public AbstractNode visit(DerivedFile df) { + return new ScoreFileNode(df, false); + } + + @Override + public AbstractNode visit(BlackboardArtifact ba) { + return new ScoreArtifactNode(ba); + } + + @Override + protected AbstractNode defaultVisit(Content di) { + if (di instanceof AbstractFile) { + return visit((AbstractFile) di); + } else { + throw new UnsupportedOperationException("Not supported for this type of Displayable Item: " + di.toString()); + } + } + }); + } + } + } + + private static final String SOURCE_PROP = "Source"; + private static final String TYPE_PROP = "Type"; + private static final String PATH_PROP = "Path"; + private static final String DATE_PROP = "Created Date"; + + private static Sheet createScoreSheet(String type, String path, Long time) { + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + + List<NodeProperty<?>> properties = new ArrayList<>(); + properties.add(new NodeProperty<>( + SOURCE_PROP, + SOURCE_PROP, + NO_DESCR, + StringUtils.defaultString(path))); + + properties.add(new NodeProperty<>( + TYPE_PROP, + TYPE_PROP, + NO_DESCR, + type)); + + if (StringUtils.isNotBlank(path)) { + properties.add(new NodeProperty<>( + PATH_PROP, + PATH_PROP, + NO_DESCR, + path)); + } + + if (time != null && time > 0) { + properties.add(new NodeProperty<>( + DATE_PROP, + DATE_PROP, + NO_DESCR, + TimeZoneUtils.getFormattedTime(time))); + } + + properties.forEach((property) -> { + sheetSet.put(property); + }); + + return sheet; + } + + public static class ScoreArtifactNode extends BlackboardArtifactNode { + + private static final Logger logger = Logger.getLogger(ScoreArtifactNode.class.getName()); + + private static final List<BlackboardAttribute.Type> TIME_ATTRS = Arrays.asList( + BlackboardAttribute.Type.TSK_DATETIME, + BlackboardAttribute.Type.TSK_DATETIME_ACCESSED, + BlackboardAttribute.Type.TSK_DATETIME_RCVD, + BlackboardAttribute.Type.TSK_DATETIME_SENT, + BlackboardAttribute.Type.TSK_DATETIME_CREATED, + BlackboardAttribute.Type.TSK_DATETIME_MODIFIED, + BlackboardAttribute.Type.TSK_DATETIME_START, + BlackboardAttribute.Type.TSK_DATETIME_END, + BlackboardAttribute.Type.TSK_DATETIME_DELETED, + BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_RESET, + BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_FAIL + ); + + private static final Map<Integer, Integer> TIME_ATTR_IMPORTANCE = IntStream.range(0, TIME_ATTRS.size()) + .mapToObj(idx -> Pair.of(TIME_ATTRS.get(idx).getTypeID(), idx)) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v1)); + + public ScoreArtifactNode(BlackboardArtifact artifact) { + super(artifact); + } + + private Long getTime(BlackboardArtifact artifact) { + try { + BlackboardAttribute timeAttr = artifact.getAttributes().stream() + .filter((attr) -> TIME_ATTR_IMPORTANCE.keySet().contains(attr.getAttributeType().getTypeID())) + .sorted(Comparator.comparing(attr -> TIME_ATTR_IMPORTANCE.get(attr.getAttributeType().getTypeID()))) + .findFirst() + .orElse(null); + + if (timeAttr != null) { + return timeAttr.getValueLong(); + } else { + return (artifact.getParent() instanceof AbstractFile) ? ((AbstractFile) artifact.getParent()).getCtime() : null; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "An exception occurred while fetching time for artifact", ex); + return null; + } + } + + @Override + protected synchronized Sheet createSheet() { + try { + return createScoreSheet( + this.content.getType().getDisplayName(), + this.content.getUniquePath(), + getTime(this.content) + ); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "An error occurred while fetching sheet data for score artifact.", ex); + return new Sheet(); + } + } + } + + @Messages("ScoreContent_ScoreFileNode_type=File") + public static class ScoreFileNode extends FileNode { + + private static final Logger logger = Logger.getLogger(ScoreFileNode.class.getName()); + + public ScoreFileNode(AbstractFile af, boolean directoryBrowseMode) { + super(af, directoryBrowseMode); + } + + @Override + protected synchronized Sheet createSheet() { + try { + return createScoreSheet( + Bundle.ScoreContent_ScoreFileNode_type(), + this.content.getUniquePath(), + this.content.getCtime() + ); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "An error occurred while fetching sheet data for score file.", ex); + return new Sheet(); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java index c65ec6a5816ee09302daaa30e8c1add280f6105c..b4d3783a13a3e794055818b00c04a2d0de3cf26d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/SlackFileNode.java @@ -100,6 +100,11 @@ public Action[] getActions(boolean popup) { return actionsList.toArray(new Action[actionsList.size()]); } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java new file mode 100755 index 0000000000000000000000000000000000000000..9653c44d0467dc2c34d62ad4d5147abbb280e7c4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java @@ -0,0 +1,128 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020-2020 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.datamodel.Content; + +/** + * An abstract superclass for a node that represents a tag, uses the name of a + * given Content object as its display name, and has a property sheet with an + * original name property when machine translation is enabled. + * + * The translation of the Content name is done in a background thread. The + * translated name is made the display name of the node and the untranslated + * name is put into both the original name property and into the node's tooltip. + * + * TODO (Jira-6174): Consider modifying this class to be able to use it more broadly + * within the Autopsy data model (i.e., AbstractNode suclasses). It's not really + * specific to a tag node. + */ +@NbBundle.Messages({ + "TagNode.propertySheet.origName=Original Name", + "TagNode.propertySheet.origNameDisplayName=Original Name" +}) +abstract class TagNode extends DisplayableItemNode { + + private final static String ORIG_NAME_PROP_NAME = Bundle.TagNode_propertySheet_origName(); + private final static String ORIG_NAME_PROP_DISPLAY_NAME = Bundle.TagNode_propertySheet_origNameDisplayName(); + + private final String originalName; + private volatile String translatedName; + + /** + * An abstract superclass for a node that represents a tag, uses the name of + * a given Content object as its display name, and has a property sheet with + * an untranslated file name property when machine translation is enabled. + * + * @param lookup The Lookup of the node. + * @param content The Content to use for the node display name. + */ + TagNode(Lookup lookup, Content content) { + super(Children.LEAF, lookup); + originalName = content.getName(); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + abstract public String getItemType(); + + @Override + abstract public <T> T accept(DisplayableItemNodeVisitor<T> visitor); + + /** + * Adds an original name property to the node's property sheet and submits + * an original name translation task. + * + * The translation of the original name is done in a background thread. The + * translated name is made the display name of the node and the untranslated + * name is put into both the original name property and into the node's + * tooltip. + * + * @param properties The node's property sheet. + */ + protected void addOriginalNameProp(Sheet.Set properties) { + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { + properties.put(new NodeProperty<>( + ORIG_NAME_PROP_NAME, + ORIG_NAME_PROP_DISPLAY_NAME, + "", + translatedName != null ? originalName : "")); + if (translatedName == null) { + new FileNameTransTask(originalName, this, new NameTranslationListener()).submit(); + } + } + } + + /** + * A listener for PropertyChangeEvents from a background task used to + * translate the original display name associated with the node. + */ + private class NameTranslationListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(FileNameTransTask.getPropertyName())) { + translatedName = evt.getNewValue().toString(); + String originalName = evt.getOldValue().toString(); + setDisplayName(translatedName); + setShortDescription(originalName); + updatePropertySheet(new NodeProperty<>( + ORIG_NAME_PROP_NAME, + ORIG_NAME_PROP_DISPLAY_NAME, + "", + originalName)); + } + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index c2239340ca984e900f2a09ace3af31a945faf0ef..23e39383aadf28515769787396f020f0ec3c474a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -18,20 +18,51 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.mainui.nodes.TagNameFactory; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.tags.TagUtils; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; /** * Instances of this class act as keys for use by instances of the * RootContentChildren class. RootContentChildren is a NetBeans child node * factory built on top of the NetBeans Children.Keys class. */ -public class Tags { +public class Tags implements AutopsyVisitableItem { + // Creation of a RootNode object corresponding to a Tags object is done + // by a CreateAutopsyNodeVisitor dispatched from the AbstractContentChildren + // override of Children.Keys<T>.createNodes(). + private final static String DISPLAY_NAME = NbBundle.getMessage(RootNode.class, "TagsNode.displayName.text"); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final String USER_NAME_PROPERTY = "user.name"; //NON-NLS + private final TagResults tagResults = new TagResults(); + private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final long filteringDSObjId; // 0 if not filtering/grouping by data source Tags() { @@ -54,24 +85,38 @@ public static String getTagsDisplayName() { long filteringDataSourceObjId() { return this.filteringDSObjId; } - + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * This class largely does nothing except act as a top-level object that the + * other nodes can listen to. This mimics what other nodes have (keword + * search, etc.), but theirs stores data. + */ + private class TagResults extends Observable { + + public void update() { + setChanged(); + notifyObservers(); + } + } + /** * Instances of this class are the root nodes of tree that is a sub-tree of * the Autopsy presentation of the SleuthKit data model. The sub-tree * consists of content and blackboard artifact tags, grouped first by tag * type, then by tag name. */ - public static class RootNode extends DisplayableItemNode { - - private final static String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS - private final Long dataSourceObjId; + public class RootNode extends DisplayableItemNode { - public RootNode(Long dsId) { - super(Children.create(new TagNameFactory(dsId != null && dsId> 0 ? dsId : null), true), Lookups.singleton(DISPLAY_NAME)); + public RootNode(long objId) { + super(Children.create(new TagNameNodeFactory(objId), true), Lookups.singleton(DISPLAY_NAME)); super.setName(DISPLAY_NAME); super.setDisplayName(DISPLAY_NAME); this.setIconBaseWithExtension(ICON_PATH); - this.dataSourceObjId = dsId != null && dsId> 0 ? dsId : null; } @Override @@ -101,16 +146,501 @@ protected Sheet createSheet() { public String getItemType() { return getClass().getName(); } - - public Node clone() { - return new RootNode(dataSourceObjId); - } - + /** * Cause the contents of the RootNode and its children to be updated. */ public void refresh() { - this.refresh(); + tagResults.update(); + } + + } + + private class TagNameNodeFactory extends ChildFactory.Detachable<TagName> implements Observer { + + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + private final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, + Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, + Case.Events.CONTENT_TAG_ADDED, + Case.Events.CONTENT_TAG_DELETED, + Case.Events.CURRENT_CASE); + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString()) + || eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString()) + || eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString()) + || eventType.equals(Case.Events.CONTENT_TAG_DELETED.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. + */ + try { + Case.getCurrentCaseThrows(); + refresh(true); + tagResults.update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + refresh(true); + tagResults.update(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that this can be garbage collected + if (evt.getNewValue() == null) { + removeNotify(); + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + /** + * Constructor + * + * @param objId data source object id + */ + TagNameNodeFactory(long objId) { + this.filteringDSObjId = objId; + } + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); + tagResults.update(); + tagResults.addObserver(this); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); + tagResults.deleteObserver(this); + } + + @Override + protected boolean createKeys(List<TagName> keys) { + try { + List<TagName> tagNamesInUse; + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + tagNamesInUse = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUseForUser(filteringDSObjId, userName) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUseForUser(userName); + } else { + tagNamesInUse = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(filteringDSObjId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); + } + Collections.sort(tagNamesInUse, new Comparator<TagName>() { + @Override + public int compare(TagName o1, TagName o2) { + return TagUtils.getDecoratedTagDisplayName(o1).compareTo(TagUtils.getDecoratedTagDisplayName(o2)); + } + }); + keys.addAll(tagNamesInUse); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(TagNameNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + return true; + } + + @Override + protected Node createNodeForKey(TagName key) { + return new TagNameNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + /** + * Instances of this class are elements of Node hierarchies consisting of + * content and blackboard artifact tags, grouped first by tag type, then by + * tag name. + */ + public class TagNameNode extends DisplayableItemNode implements Observer { + + private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final String BOOKMARK_TAG_ICON_PATH = "org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"; //NON-NLS + private final TagName tagName; + + public TagNameNode(TagName tagName) { + super(Children.create(new TagTypeNodeFactory(tagName), true), Lookups.singleton(NbBundle.getMessage(TagNameNode.class, "TagNameNode.namePlusTags.text", tagName.getDisplayName()))); + this.tagName = tagName; + setName(TagUtils.getDecoratedTagDisplayName(tagName)); + updateDisplayName(); + if (tagName.getDisplayName().equals(TagsManager.getBookmarkTagDisplayName())) { + setIconBaseWithExtension(BOOKMARK_TAG_ICON_PATH); + } else { + setIconBaseWithExtension(ICON_PATH); + } + tagResults.addObserver(this); + } + + private void updateDisplayName() { + long tagsCount = 0; + try { + TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + if (filteringDSObjId > 0) { + tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, filteringDSObjId, userName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, filteringDSObjId, userName); + } else { + tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, userName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); + } + } else { + if (filteringDSObjId > 0) { + tagsCount = tm.getContentTagsCountByTagName(tagName, filteringDSObjId); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, filteringDSObjId); + } else { + tagsCount = tm.getContentTagsCountByTagName(tagName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + } + } + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS + } + setDisplayName(TagUtils.getDecoratedTagDisplayName(tagName) + " (" + tagsCount + ")"); + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "TagNameNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "TagNameNode.createSheet.name.displayName"), tagName.getDescription(), getName())); + return propertySheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + // See classes derived from DisplayableItemNodeVisitor<AbstractNode> + // for behavior added using the Visitor pattern. + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * Creates nodes for the two types of tags: file and artifact. Does not need + * observer / messages since it always has the same children + */ + private class TagTypeNodeFactory extends ChildFactory<String> { + + private final TagName tagName; + private final String CONTENT_TAG_TYPE_NODE_KEY = NbBundle.getMessage(TagNameNode.class, "TagNameNode.contentTagTypeNodeKey.text"); + private final String BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY = NbBundle.getMessage(TagNameNode.class, "TagNameNode.bbArtTagTypeNodeKey.text"); + + TagTypeNodeFactory(TagName tagName) { + super(); + this.tagName = tagName; + } + + @Override + protected boolean createKeys(List<String> keys) { + keys.add(CONTENT_TAG_TYPE_NODE_KEY); + keys.add(BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + if (CONTENT_TAG_TYPE_NODE_KEY.equals(key)) { + return new ContentTagTypeNode(tagName); + } else if (BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY.equals(key)) { + return new BlackboardArtifactTagTypeNode(tagName); + } else { + Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "{0} not a recognized key", key); //NON-NLS + return null; + } + } + } + + private final String CONTENT_DISPLAY_NAME = NbBundle.getMessage(ContentTagTypeNode.class, "ContentTagTypeNode.displayName.text"); + + /** + * Node for the content tags. Children are specific tags. Instances of this + * class are are elements of a directory tree sub-tree consisting of content + * and blackboard artifact tags, grouped first by tag type, then by tag + * name. + */ + public class ContentTagTypeNode extends DisplayableItemNode implements Observer { + + private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final TagName tagName; + + public ContentTagTypeNode(TagName tagName) { + super(Children.create(new ContentTagNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " " + CONTENT_DISPLAY_NAME)); + this.tagName = tagName; + super.setName(CONTENT_DISPLAY_NAME); + updateDisplayName(); + this.setIconBaseWithExtension(ICON_PATH); + tagResults.addObserver(this); + } + + private void updateDisplayName() { + long tagsCount = 0; + try { + + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + tagsCount = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, filteringDSObjId, userName) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, userName); + } else { + tagsCount = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, filteringDSObjId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + } + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS + } + super.setDisplayName(CONTENT_DISPLAY_NAME + " (" + tagsCount + ")"); + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagTypeNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "ContentTagTypeNode.createSheet.name.displayName"), "", getName())); + return propertySheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + private class ContentTagNodeFactory extends ChildFactory<ContentTag> implements Observer { + + private final TagName tagName; + + ContentTagNodeFactory(TagName tagName) { + super(); + this.tagName = tagName; + tagResults.addObserver(this); + } + + @Override + protected boolean createKeys(List<ContentTag> keys) { + // Use the content tags bearing the specified tag name as the keys. + try { + List<ContentTag> contentTags = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName, filteringDSObjId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName); + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + for (ContentTag tag : contentTags) { + if (userName.equals(tag.getUserName())) { + keys.add(tag); + } + } + } else { + keys.addAll(contentTags); + } + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + return true; + } + + @Override + protected Node createNodeForKey(ContentTag key) { + // The content tags to be wrapped are used as the keys. + return new ContentTagNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } + + private final String ARTIFACT_DISPLAY_NAME = NbBundle.getMessage(BlackboardArtifactTagTypeNode.class, "BlackboardArtifactTagTypeNode.displayName.text"); + + /** + * Instances of this class are elements in a sub-tree of the Autopsy + * presentation of the SleuthKit data model. The sub-tree consists of + * content and blackboard artifact tags, grouped first by tag type, then by + * tag name. + */ + public class BlackboardArtifactTagTypeNode extends DisplayableItemNode implements Observer { + + private final TagName tagName; + private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + + public BlackboardArtifactTagTypeNode(TagName tagName) { + super(Children.create(new BlackboardArtifactTagNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " " + ARTIFACT_DISPLAY_NAME)); + this.tagName = tagName; + super.setName(ARTIFACT_DISPLAY_NAME); + this.setIconBaseWithExtension(ICON_PATH); + updateDisplayName(); + tagResults.addObserver(this); + } + + private void updateDisplayName() { + long tagsCount = 0; + try { + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + tagsCount = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, filteringDSObjId, userName) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); + } else { + tagsCount = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, filteringDSObjId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + } + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS + } + super.setDisplayName(ARTIFACT_DISPLAY_NAME + " (" + tagsCount + ")"); + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagTypeNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagTypeNode.createSheet.name.displayName"), "", getName())); + return propertySheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + private class BlackboardArtifactTagNodeFactory extends ChildFactory<BlackboardArtifactTag> implements Observer { + + private final TagName tagName; + + BlackboardArtifactTagNodeFactory(TagName tagName) { + super(); + this.tagName = tagName; + tagResults.addObserver(this); + } + + @Override + protected boolean createKeys(List<BlackboardArtifactTag> keys) { + try { + // Use the blackboard artifact tags bearing the specified tag name as the keys. + List<BlackboardArtifactTag> artifactTags = (filteringDSObjId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName, filteringDSObjId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName); + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + for (BlackboardArtifactTag tag : artifactTags) { + if (userName.equals(tag.getUserName())) { + keys.add(tag); + } + } + } else { + keys.addAll(artifactTags); + } + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifactTag key) { + // The blackboard artifact tags to be wrapped are used as the keys. + return new BlackboardArtifactTagNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TskContentItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/TskContentItem.java index 775ab7ddef23bfbb64069448bd41dcbc75be9d92..21652c85d97bd144a0a2e63ecd4221d26629495a 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/TskContentItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/TskContentItem.java @@ -42,7 +42,7 @@ public class TskContentItem<T extends Content> { * */ @Beta - public TskContentItem(T content) { + TskContentItem(T content) { this.content = content; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java index 8fae271e6700145ea40b6615918fbd3ce51049ca..876f7cb8e68f24d882782466e6ef9d68f67cf227 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java @@ -86,6 +86,11 @@ protected Sheet createSheet() { return sheet; } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java new file mode 100644 index 0000000000000000000000000000000000000000..d2a86716788bdd731bd2f9f70cf3ec945bd6c689 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -0,0 +1,52 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * View nodes support + */ +public class Views implements AutopsyVisitableItem { + + private SleuthkitCase skCase; + private final long datasourceObjId; + + public Views(SleuthkitCase skCase) { + this(skCase, 0); + } + + public Views(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + public SleuthkitCase getSleuthkitCase() { + return skCase; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsKnownAndSlackFilter.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsKnownAndSlackFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..f6b7874122962b2359df06da680d4938216c96aa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsKnownAndSlackFilter.java @@ -0,0 +1,52 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.prefs.PreferenceChangeEvent; +import org.sleuthkit.autopsy.core.UserPreferences; +import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterKnown; +import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterSlack; +import org.sleuthkit.datamodel.Content; + +/** + * Known and Slack filter for Views section of the tree. + * + * @param <T> + */ +class ViewsKnownAndSlackFilter<T extends Content> extends KnownAndSlackFilterBase<T> { + + static { + /** + * Watch for user preference changes and update variables inherited from + * our parent. The actual filtering is provided by our parent class. + */ + UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> { + if (evt.getKey().equals(UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE)) { + filterKnown = UserPreferences.hideKnownFilesInViewsTree(); + } else if (evt.getKey().equals(UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE)) { + filterSlack = UserPreferences.hideSlackFilesInViewsTree(); + } + }); + } + + ViewsKnownAndSlackFilter() { + filterKnown = UserPreferences.hideKnownFilesInViewsTree(); + filterSlack = UserPreferences.hideSlackFilesInViewsTree(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java new file mode 100644 index 0000000000000000000000000000000000000000..423e58e1ed94ce3f93228f96dfce4144079a5f0d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -0,0 +1,89 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2018 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.datamodel; + +import java.util.Arrays; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * + * Node for the views + * + */ +public class ViewsNode extends DisplayableItemNode { + + public static final String NAME = NbBundle.getMessage(ViewsNode.class, "ViewsNode.name.text"); + + public ViewsNode(SleuthkitCase sleuthkitCase) { + this(sleuthkitCase, 0); + } + + public ViewsNode(SleuthkitCase sleuthkitCase, long dsObjId) { + + super( + new RootContentChildren(Arrays.asList( + new FileTypes(dsObjId), + // June '15: Recent Files was removed because it was not useful w/out filtering + // add it back in if we can filter the results to a more managable size. + // new RecentFiles(sleuthkitCase), + new DeletedContent(sleuthkitCase, dsObjId), + new FileSize(sleuthkitCase, dsObjId)) + ), + Lookups.singleton(NAME) + ); + setName(NAME); + setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); //NON-NLS + } + + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.desc"), + NAME)); + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index 05a9a39db89d516976c6b6b9689dff1b3f3a97bc..4092dc599fe00ba152ac9741bad921c6ee0ab5c1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -61,6 +61,11 @@ protected Sheet createSheet() { return defaultSheet; } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index a4267a3a6de29783fdc7fb08e16cfe35cd38065d..54e6d39b7112a7fdd10b29cfcb26190fbe2be6cc 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -207,6 +207,11 @@ protected Sheet createSheet() { return sheet; } + @Override + public <T> T accept(ContentNodeVisitor<T> visitor) { + return visitor.visit(this); + } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index c7b6d6839280b477a0b951d0be03459d83cde6de..ff10dcc9cb5b236ed9a2aa8959fa8702220956d1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -18,15 +18,1966 @@ */ package org.sleuthkit.autopsy.datamodel.accounts; +import com.google.common.collect.Range; +import com.google.common.collect.RangeMap; +import com.google.common.collect.TreeRangeMap; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.SwingUtilities; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.NodeNotFoundException; +import org.openide.nodes.NodeOp; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel.AutopsyVisitableItem; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.CreditCards; +import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.Type; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ACCOUNT; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData.DbType; /** * AutopsyVisitableItem for the Accounts section of the tree. All nodes, * factories, and custom key class related to accounts are inner classes. */ -final public class Accounts { +final public class Accounts implements AutopsyVisitableItem { + private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName()); private static final String ICON_BASE_PATH = "/org/sleuthkit/autopsy/images/"; //NON-NLS + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED); + private static final String DISPLAY_NAME = Bundle.Accounts_RootNode_displayName(); + + @NbBundle.Messages("AccountsRootNode.name=Accounts") //used for the viewArtifact navigation + final public static String NAME = Bundle.AccountsRootNode_name(); + + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus"); + + /* + * Should rejected accounts be shown in the accounts section of the tree. + */ + private boolean showRejected = false; //NOPMD redundant initializer + + private final RejectAccounts rejectActionInstance; + private final ApproveAccounts approveActionInstance; + + // tracks the number of each account type found + private final AccountTypeResults accountTypeResults; + + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + */ + public Accounts() { + this(0); + } + + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + * @param objId Object id of the data source + */ + public Accounts(long objId) { + this.filteringDSObjId = objId; + + this.rejectActionInstance = new RejectAccounts(); + this.approveActionInstance = new ApproveAccounts(); + this.accountTypeResults = new AccountTypeResults(); + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * Get the clause that should be used in order to (not) filter out rejected + * results from db queries. + * + * @return A clause that will or will not filter out rejected artifacts + * based on the state of showRejected. + */ + private String getRejectedArtifactFilterClause() { + return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS + } + + /** + * Returns the clause to filter artifacts by data source. + * + * @return A clause that will or will not filter artifacts by datasource + * based on the CasePreferences groupItemsInTreeByDataSource setting + */ + private String getFilterByDataSourceClause() { + if (filteringDSObjId > 0) { + return " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId + " "; + } + + return " "; + } + + /** + * Gets a new Action that when invoked toggles showing rejected artifacts on + * or off. + * + * @return An Action that will toggle whether rejected artifacts are shown + * in the tree rooted by this Accounts instance. + */ + @Deprecated + public Action newToggleShowRejectedAction() { + return new ToggleShowRejected(); + } + + /** + * Base class for children that are also observers of the reviewStatusBus. + * It + * + * @param <X> The type of keys used by this factory. + */ + private abstract class ObservingChildren<X> extends ChildFactory.Detachable<X> { + + /** + * Override of default constructor to force lazy creation of nodes, by + * concrete instances of ObservingChildren + */ + ObservingChildren() { + super(); + } + + /** + * Create of keys used by this Children object to represent the child + * nodes. + */ + @Override + abstract protected boolean createKeys(List<X> list); + + /** + * Handle a ReviewStatusChangeEvent + * + * @param event the ReviewStatusChangeEvent to handle. + */ + @Subscribe + abstract void handleReviewStatusChange(ReviewStatusChangeEvent event); + + @Subscribe + abstract void handleDataAdded(ModuleDataEvent event); + + @Override + protected void finalize() throws Throwable { + super.finalize(); + reviewStatusBus.unregister(ObservingChildren.this); + } + + @Override + protected void addNotify() { + super.addNotify(); + refresh(true); + reviewStatusBus.register(ObservingChildren.this); + } + } + + /** + * Top-level node for the accounts tree + */ + @NbBundle.Messages({"Accounts.RootNode.displayName=Communication Accounts"}) + final public class AccountsRootNode extends UpdatableCountTypeNode { + + public AccountsRootNode() { + super(Children.create(new AccountTypeFactory(), true), + Lookups.singleton(Accounts.this), + DISPLAY_NAME, + filteringDSObjId, + TSK_ACCOUNT); + + setName(Accounts.NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + protected long fetchChildCount(SleuthkitCase skCase) throws TskCoreException { + String accountTypesInUseQuery + = "SELECT COUNT(*) AS count\n" + + "FROM (\n" + + " SELECT MIN(blackboard_attributes.value_text) AS account_type\n" + + " FROM blackboard_artifacts\n" + + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id\n" + + " WHERE blackboard_artifacts.artifact_type_id = " + TSK_ACCOUNT.getTypeID() + "\n" + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n" + + " AND blackboard_attributes.value_text IS NOT NULL\n" + + getFilterByDataSourceClause() + "\n" + + " -- group by artifact_id to ensure only one account type per artifact\n" + + " GROUP BY blackboard_artifacts.artifact_id\n" + + ") res\n"; + + try (SleuthkitCase.CaseDbQuery executeQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(accountTypesInUseQuery); + ResultSet resultSet = executeQuery.getResultSet()) { + + if (resultSet.next()) { + return resultSet.getLong("count"); + } + + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for count of all account types", ex); + } + + return 0; + } + + } + + /** + * Tracks the account types and the number of account types found. + */ + private class AccountTypeResults { + + private final Map<String, Long> counts = new HashMap<>(); + + AccountTypeResults() { + update(); + } + + /** + * Given the type name of the Account.Type, provides the count of those + * type. + * + * @param accountType The type name of the Account.Type. + * + * @return The number of results found for the given account type. + */ + Long getCount(String accountType) { + return counts.get(accountType); + } + + /** + * Retrieves an alphabetically organized list of all the account types. + * + * @return An alphabetically organized list of all the account types. + */ + List<String> getTypes() { + List<String> types = new ArrayList<>(counts.keySet()); + Collections.sort(types); + return types; + } + + /** + * Queries the database and updates the counts for each account type. + */ + private void update() { + String accountTypesInUseQuery + = "SELECT res.account_type, COUNT(*) AS count\n" + + "FROM (\n" + + " SELECT MIN(blackboard_attributes.value_text) AS account_type\n" + + " FROM blackboard_artifacts\n" + + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id\n" + + " WHERE blackboard_artifacts.artifact_type_id = " + TSK_ACCOUNT.getTypeID() + "\n" + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n" + + getFilterByDataSourceClause() + "\n" + + " -- group by artifact_id to ensure only one account type per artifact\n" + + " GROUP BY blackboard_artifacts.artifact_id\n" + + ") res\n" + + "GROUP BY res.account_type"; + + try (SleuthkitCase.CaseDbQuery executeQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(accountTypesInUseQuery); + ResultSet resultSet = executeQuery.getResultSet()) { + + counts.clear(); + while (resultSet.next()) { + String accountType = resultSet.getString("account_type"); + Long count = resultSet.getLong("count"); + counts.put(accountType, count); + } + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account_types", ex); + } + } + } + + /** + * Creates child nodes for each account type in the db. + */ + private class AccountTypeFactory extends ObservingChildren<String> { + + /* + * The pcl is in this class because it has the easiest mechanisms to add + * and remove itself during its life cycles. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData + && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) { + accountTypeResults.update(); + reviewStatusBus.post(eventData); + } + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + accountTypeResults.update(); + refresh(true); + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Subscribe + @Override + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refresh(true); + } + + @Subscribe + @Override + void handleDataAdded(ModuleDataEvent event) { + refresh(true); + } + + @Override + protected boolean createKeys(List<String> list) { + list.addAll(accountTypeResults.getTypes()); + return true; + } + + /** + * Registers the given node with the reviewStatusBus and returns the + * node wrapped in an array. + * + * @param node The node to be wrapped. + * + * @return The array containing this node. + */ + private Node[] getNodeArr(Node node) { + reviewStatusBus.register(node); + return new Node[]{node}; + } + + @Override + protected Node[] createNodesForKey(String accountTypeName) { + + if (Account.Type.CREDIT_CARD.getTypeName().equals(accountTypeName)) { + return getNodeArr(new CreditCardNumberAccountTypeNode()); + } else { + + try { + Account.Type accountType = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().getAccountType(accountTypeName); + if (accountType != null) { + return getNodeArr(new DefaultAccountTypeNode(accountType)); + } else { + // This can only happen if a TSK_ACCOUNT artifact was created not using CommunicationManager + LOGGER.log(Level.SEVERE, "Unknown account type '" + accountTypeName + "' found - account will not be displayed.\n" + + "Account type names must match an entry in the display_name column of the account_types table.\n" + + "Accounts should be created using the CommunicationManager API."); + } + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error getting display name for account type. ", ex); + } + + return new Node[]{}; + } + } + + @Override + protected void finalize() throws Throwable { + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.finalize(); + } + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.addNotify(); + refresh(true); + } + + } + + final private class DefaultAccountFactory extends ObservingChildren<Long> { + + private final Account.Type accountType; + + private DefaultAccountFactory(Account.Type accountType) { + this.accountType = accountType; + } + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData + && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) { + reviewStatusBus.post(eventData); + } + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + refresh(true); + + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + } + + @Override + protected boolean createKeys(List<Long> list) { + String query + = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS + + getFilterByDataSourceClause() + + getRejectedArtifactFilterClause(); //NON-NLS + try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query); + ResultSet rs = results.getResultSet();) { + List<Long> tempList = new ArrayList<>(); + while (rs.next()) { + tempList.add(rs.getLong("artifact_obj_id")); // NON-NLS + } + list.addAll(tempList); + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + } + + return true; + } + + @Override + protected Node[] createNodesForKey(Long t) { + try { + return new Node[]{new BlackboardArtifactNode(Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().getDataArtifactById(t))}; + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex); + return new Node[0]; + } + } + + @Subscribe + @Override + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refresh(true); + } + + @Subscribe + @Override + void handleDataAdded(ModuleDataEvent event) { + refresh(true); + } + } + + /** + * Default Node class for unknown account types and account types that have + * no special behavior. + */ + final public class DefaultAccountTypeNode extends DisplayableItemNode { + + private final Account.Type accountType; + + private DefaultAccountTypeNode(Account.Type accountType) { + super(Children.create(new DefaultAccountFactory(accountType), true), Lookups.singleton(accountType)); + this.accountType = accountType; + String iconPath = getIconFilePath(accountType); + this.setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); //NON-NLS + setName(accountType.getTypeName()); + updateName(); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateName(); + } + + @Subscribe + void handleDataAdded(ModuleDataEvent event) { + updateName(); + } + + /** + * Gets the latest counts for the account type and then updates the + * name. + */ + public void updateName() { + setDisplayName(String.format("%s (%d)", accountType.getDisplayName(), accountTypeResults.getCount(accountType.getTypeName()))); + } + } + + /** + * Enum for the children under the credit card AccountTypeNode. + */ + private enum CreditCardViewMode { + BY_FILE, + BY_BIN; + } + + final private class ViewModeFactory extends ObservingChildren<CreditCardViewMode> { + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData + && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) { + reviewStatusBus.post(eventData); + } + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + refresh(true); + + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Subscribe + @Override + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refresh(true); + } + + @Subscribe + @Override + void handleDataAdded(ModuleDataEvent event) { + refresh(true); + } + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.addNotify(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.removeNotify(); + } + + /** + * + */ + @Override + protected boolean createKeys(List<CreditCardViewMode> list) { + list.addAll(Arrays.asList(CreditCardViewMode.values())); + + return true; + } + + @Override + protected Node[] createNodesForKey(CreditCardViewMode key) { + switch (key) { + case BY_BIN: + return new Node[]{new ByBINNode()}; + case BY_FILE: + return new Node[]{new ByFileNode()}; + default: + return new Node[0]; + } + } + } + + /** + * Node for the Credit Card account type. * + */ + final public class CreditCardNumberAccountTypeNode extends DisplayableItemNode { + + /** + * ChildFactory that makes nodes for the different account organizations + * (by file, by BIN) + */ + private CreditCardNumberAccountTypeNode() { + super(Children.create(new ViewModeFactory(), true), Lookups.singleton(Account.Type.CREDIT_CARD.getDisplayName())); + setName(Account.Type.CREDIT_CARD.getDisplayName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS + } + + /** + * Gets the latest counts for the account type and then updates the + * name. + */ + public void updateName() { + setName(String.format("%s (%d)", Account.Type.CREDIT_CARD.getDisplayName(), accountTypeResults.getCount(Account.Type.CREDIT_CARD.getTypeName()))); + } + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateName(); + } + + @Subscribe + void handleDataAdded(ModuleDataEvent event) { + updateName(); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + final private class FileWithCCNFactory extends ObservingChildren<FileWithCCN> { + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData + && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) { + reviewStatusBus.post(eventData); + } + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + refresh(true); + + } catch (NoCurrentCaseException notUsed) { + // Case is closed, do nothing. + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.addNotify(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + } + + @Subscribe + @Override + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refresh(true); + } + + @Subscribe + @Override + void handleDataAdded(ModuleDataEvent event) { + refresh(true); + } + + @Override + protected boolean createKeys(List<FileWithCCN> list) { + try { + String query + = "SELECT blackboard_artifacts.obj_id," //NON-NLS + + " solr_attribute.value_text AS solr_document_id, "; //NON-NLS + if (Case.getCurrentCaseThrows().getSleuthkitCase().getDatabaseType().equals(DbType.POSTGRESQL)) { + query += " string_agg(blackboard_artifacts.artifact_id::character varying, ',') AS artifact_IDs, " //NON-NLS + + " string_agg(blackboard_artifacts.review_status_id::character varying, ',') AS review_status_ids, "; + } else { + query += " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS + + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids, "; + } + query += " COUNT( blackboard_artifacts.artifact_id) AS hits " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS + + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS + + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS + + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + + getRejectedArtifactFilterClause() + + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS + + " ORDER BY hits DESC "; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query); + ResultSet resultSet = results.getResultSet();) { + while (resultSet.next()) { + long file_id = resultSet.getLong("obj_id"); + AbstractFile abstractFileById = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(file_id); + if (abstractFileById != null) { + list.add(new FileWithCCN( + abstractFileById, + file_id, //NON-NLS + resultSet.getString("solr_document_id"), //NON-NLS + unGroupConcat(resultSet.getString("artifact_IDs"), Long::valueOf), //NON-NLS + resultSet.getLong("hits"), //NON-NLS + new HashSet<>(unGroupConcat(resultSet.getString("review_status_ids"), reviewStatusID -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(reviewStatusID)))))); //NON-NLS + } + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS + } + + } catch (NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error getting case.", ex); + } + return true; + } + + @Override + protected Node[] createNodesForKey(FileWithCCN key) { + //add all account artifacts for the file and the file itself to the lookup + try { + List<Object> lookupContents = new ArrayList<>(); + for (long artId : key.artifactIDs) { + lookupContents.add(Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(artId)); + } + AbstractFile abstractFileById = key.getFile(); + lookupContents.add(abstractFileById); + return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())}; + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS + return new Node[0]; + } + } + } + + /** + * Node that is the root of the "by file" accounts tree. Its children are + * FileWithCCNNodes. + */ + final public class ByFileNode extends DisplayableItemNode { + + /** + * Factory for the children of the ByFiles Node. + */ + private ByFileNode() { + super(Children.create(new FileWithCCNFactory(), true), Lookups.singleton("By File")); + setName("By File"); //NON-NLS + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS + reviewStatusBus.register(this); + } + + @NbBundle.Messages({ + "# {0} - number of children", + "Accounts.ByFileNode.displayName=By File ({0})"}) + private void updateDisplayName() { + String query + = "SELECT count(*) FROM ( SELECT count(*) AS documents " + + " FROM blackboard_artifacts " //NON-NLS + + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS + + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS + + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS + + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + + getRejectedArtifactFilterClause() + + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo"; + try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query); + ResultSet resultSet = results.getResultSet();) { + while (resultSet.next()) { + if (Case.getCurrentCaseThrows().getSleuthkitCase().getDatabaseType().equals(DbType.POSTGRESQL)) { + setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count"))); + } else { + setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count(*)"))); + } + } + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS + + } + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateDisplayName(); + } + + @Subscribe + void handleDataAdded(ModuleDataEvent event) { + updateDisplayName(); + } + } + + final private class BINFactory extends ObservingChildren<BinResult> { + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_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. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData + && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) { + reviewStatusBus.post(eventData); + } + } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.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. + */ + try { + Case.getCurrentCaseThrows(); + + refresh(true); + } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause + // Case is closed, do nothing. + } + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + super.addNotify(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + IngestManager.getInstance().removeIngestJobEventListener(weakPcl); + IngestManager.getInstance().removeIngestModuleEventListener(weakPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); + } + + @Subscribe + @Override + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refresh(true); + } + + @Subscribe + @Override + void handleDataAdded(ModuleDataEvent event) { + refresh(true); + } + + @Override + protected boolean createKeys(List<BinResult> list) { + + RangeMap<Integer, BinResult> binRanges = TreeRangeMap.create(); + + String query + = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS + + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + + getRejectedArtifactFilterClause() + + " GROUP BY BIN " //NON-NLS + + " ORDER BY BIN "; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query); + ResultSet resultSet = results.getResultSet();) { + //sort all te individual bins in to the ranges + while (resultSet.next()) { + final Integer bin = Integer.valueOf(resultSet.getString("BIN")); + long count = resultSet.getLong("count"); + + BINRange binRange = (BINRange) CreditCards.getBINInfo(bin); + BinResult previousResult = binRanges.get(bin); + + if (previousResult != null) { + binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd())); + count += previousResult.getCount(); + } + + if (binRange == null) { + binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin)); + } else { + binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange)); + } + } + binRanges.asMapOfRanges().values().forEach(list::add); + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS + } + + return true; + } + + @Override + protected Node[] createNodesForKey(BinResult key) { + return new Node[]{new BINNode(key)}; + } + } + + /** + * Node that is the root of the "By BIN" accounts tree. Its children are + * BINNodes. + */ + final public class ByBINNode extends DisplayableItemNode { + + /** + * Factory that generates the children of the ByBin node. + */ + @NbBundle.Messages("Accounts.ByBINNode.name=By BIN") + private ByBINNode() { + super(Children.create(new BINFactory(), true), Lookups.singleton(Bundle.Accounts_ByBINNode_name())); + setName(Bundle.Accounts_ByBINNode_name()); //NON-NLS + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS + reviewStatusBus.register(this); + } + + @NbBundle.Messages({ + "# {0} - number of children", + "Accounts.ByBINNode.displayName=By BIN ({0})"}) + private void updateDisplayName() { + String query + = "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + + getRejectedArtifactFilterClause(); //NON-NLS + try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query); + ResultSet resultSet = results.getResultSet();) { + while (resultSet.next()) { + setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs"))); + } + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS + } + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateDisplayName(); + } + + @Subscribe + void handleDataAdded(ModuleDataEvent event) { + updateDisplayName(); + } + } + + /** + * DataModel for a child of the ByFileNode. Represents a file(chunk) and its + * associated accounts. + */ + @Immutable + final private static class FileWithCCN { + + @Override + public int hashCode() { + int hash = 5; + hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32)); + hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID); + hash = 79 * hash + Objects.hashCode(this.artifactIDs); + hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32)); + hash = 79 * hash + Objects.hashCode(this.statuses); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FileWithCCN other = (FileWithCCN) obj; + if (this.objID != other.objID) { + return false; + } + if (this.hits != other.hits) { + return false; + } + if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) { + return false; + } + if (!Objects.equals(this.artifactIDs, other.artifactIDs)) { + return false; + } + if (!Objects.equals(this.statuses, other.statuses)) { + return false; + } + return true; + } + + private final long objID; + private final String keywordSearchDocID; + private final List<Long> artifactIDs; + private final long hits; + private final Set<BlackboardArtifact.ReviewStatus> statuses; + private final AbstractFile file; + + private FileWithCCN(AbstractFile file, long objID, String solrDocID, List<Long> artifactIDs, long hits, Set<BlackboardArtifact.ReviewStatus> statuses) { + this.objID = objID; + this.keywordSearchDocID = solrDocID; + this.artifactIDs = artifactIDs; + this.hits = hits; + this.statuses = statuses; + this.file = file; + } + + /** + * Get the object ID of the file. + * + * @return the object ID of the file. + */ + public long getObjID() { + return objID; + } + + /** + * Get the keyword search docuement id. This is used for unnalocated + * files to limit results to one chunk/page + * + * @return the keyword search document id. + */ + public String getkeywordSearchDocID() { + return keywordSearchDocID; + } + + /** + * Get the artifact ids of the account artifacts from this file. + * + * @return the artifact ids of the account artifacts from this file. + */ + public List<Long> getArtifactIDs() { + return Collections.unmodifiableList(artifactIDs); + } + + /** + * Get the number of account artifacts from this file. + * + * @return the number of account artifacts from this file. + */ + public long getHits() { + return hits; + } + + /** + * Get the status(s) of the account artifacts from this file. + * + * @return the status(s) of the account artifacts from this file. + */ + public Set<BlackboardArtifact.ReviewStatus> getStatuses() { + return Collections.unmodifiableSet(statuses); + } + + AbstractFile getFile() { + return file; + } + } + + /** + * TODO: this was copy-pasted from timeline. Is there a single accessible + * place it should go? + * + * + * take the result of a group_concat SQLite operation and split it into a + * set of X using the mapper to to convert from string to X + * + * @param <X> the type of elements to return + * @param groupConcat a string containing the group_concat result ( a comma + * separated list) + * @param mapper a function from String to X + * + * @return a Set of X, each element mapped from one element of the original + * comma delimited string + */ + static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) { + return StringUtils.isBlank(groupConcat) ? Collections.emptyList() + : Stream.of(groupConcat.split(",")) //NON-NLS + .map(mapper::apply) + .collect(Collectors.toList()); + } + + /** + * Node that represents a file or chunk of an unallocated space file. + */ + final public class FileWithCCNNode extends DisplayableItemNode { + + private final FileWithCCN fileKey; + private final String fileName; + + /** + * Constructor + * + * @param key The FileWithCCN that backs this node. + * @param content The Content object the key represents. + * @param lookupContents The contents of this Node's lookup. It should + * contain the content object and the account + * artifacts. + */ + @NbBundle.Messages({ + "# {0} - raw file name", + "# {1} - solr chunk id", + "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"}) + private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) { + super(Children.LEAF, Lookups.fixed(lookupContents)); + this.fileKey = key; + this.fileName = (key.getkeywordSearchDocID() == null) + ? content.getName() + : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS + setName(fileName + key.getObjID()); + setDisplayName(fileName); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + @NbBundle.Messages({ + "Accounts.FileWithCCNNode.nameProperty.displayName=File", + "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts", + "Accounts.FileWithCCNNode.statusProperty.displayName=Status", + "Accounts.FileWithCCNNode.noDescription=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set propSet = sheet.get(Sheet.PROPERTIES); + if (propSet == null) { + propSet = Sheet.createPropertiesSet(); + sheet.put(propSet); + } + + propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + fileName)); + propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + fileKey.getHits())); + propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + fileKey.getStatuses().stream() + .map(BlackboardArtifact.ReviewStatus::getDisplayName) + .collect(Collectors.joining(", ")))); //NON-NLS + + return sheet; + } + + @Override + public Action[] getActions(boolean context) { + Action[] actions = super.getActions(context); + ArrayList<Action> arrayList = new ArrayList<>(); + try { + arrayList.addAll(DataModelActionsFactory.getActions(Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(fileKey.getObjID()), false)); + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error gettung content by id", ex); + } + + arrayList.add(approveActionInstance); + arrayList.add(rejectActionInstance); + arrayList.add(null); + arrayList.addAll(Arrays.asList(actions)); + return arrayList.toArray(new Action[arrayList.size()]); + } + } + + final private class CreditCardNumberFactory extends ObservingChildren<DataArtifact> { + + private final BinResult bin; + + private CreditCardNumberFactory(BinResult bin) { + this.bin = bin; + } + + @Subscribe + @Override + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refresh(true); + } + + @Subscribe + @Override + void handleDataAdded(ModuleDataEvent event) { + refresh(true); + } + + @Override + protected boolean createKeys(List<DataArtifact> list) { + + String query + = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + + getRejectedArtifactFilterClause() + + " ORDER BY blackboard_attributes.value_text"; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + list.add(Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().getDataArtifactById(rs.getLong("artifact_obj_id"))); //NON-NLS + } + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + + } + return true; + } + + @Override + protected Node[] createNodesForKey(DataArtifact artifact) { + return new Node[]{new AccountArtifactNode(artifact)}; + } + } + + private String getBinRangeString(BinResult bin) { + if (bin.getBINStart() == bin.getBINEnd()) { + return Integer.toString(bin.getBINStart()); + } else { + return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + ""); + } + } + + final public class BINNode extends DisplayableItemNode { + + /** + * Creates the nodes for the credit card numbers + */ + private final BinResult bin; + + private BINNode(BinResult bin) { + super(Children.create(new CreditCardNumberFactory(bin), true), Lookups.singleton(getBinRangeString(bin))); + this.bin = bin; + setName(getBinRangeString(bin)); + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS + reviewStatusBus.register(this); + } + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateDisplayName(); + updateSheet(); + } + + @Subscribe + void handleDataAdded(ModuleDataEvent event) { + updateDisplayName(); + } + + private void updateDisplayName() { + String query + = "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + + getRejectedArtifactFilterClause(); + try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query); + ResultSet resultSet = results.getResultSet();) { + while (resultSet.next()) { + setDisplayName(getBinRangeString(bin) + " (" + resultSet.getLong("count") + ")"); //NON-NLS + } + } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + + } + + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + private Sheet.Set getPropertySet(Sheet sheet) { + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + return sheetSet; + } + + @Override + @NbBundle.Messages({ + "Accounts.BINNode.binProperty.displayName=Bank Identifier Number", + "Accounts.BINNode.accountsProperty.displayName=Accounts", + "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type", + "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme", + "Accounts.BINNode.brandProperty.displayName=Brand", + "Accounts.BINNode.bankProperty.displayName=Bank", + "Accounts.BINNode.bankCityProperty.displayName=Bank City", + "Accounts.BINNode.bankCountryProperty.displayName=Bank Country", + "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #", + "Accounts.BINNode.bankURLProperty.displayName=Bank URL", + "Accounts.BINNode.noDescription=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set properties = getPropertySet(sheet); + + properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(), + Bundle.Accounts_BINNode_binProperty_displayName(), + Bundle.Accounts_BINNode_noDescription(), + getBinRangeString(bin))); + properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(), + Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bin.getCount())); + + //add optional properties if they are available + if (bin.hasDetails()) { + bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(), + Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + cardType))); + bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(), + Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + scheme))); + bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(), + Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + brand))); + bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(), + Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bankName))); + bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(), + Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bankCity))); + bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(), + Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + country))); + bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), + Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + phoneNumber))); + bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(), + Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + url))); + } + return sheet; + } + + private void updateSheet() { + SwingUtilities.invokeLater(() -> { + this.setSheet(createSheet()); + }); + } + + } + + /** + * Data model item to back the BINNodes in the tree. Has the number of + * accounts found with the BIN. + */ + @Immutable + final static private class BinResult implements CreditCards.BankIdentificationNumber { + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + this.binEnd; + hash = 97 * hash + this.binStart; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BinResult other = (BinResult) obj; + if (this.binEnd != other.binEnd) { + return false; + } + if (this.binStart != other.binStart) { + return false; + } + return true; + } + + /** + * The number of accounts with this BIN + */ + private final long count; + + private final BINRange binRange; + private final int binEnd; + private final int binStart; + + private BinResult(long count, @Nonnull BINRange binRange) { + this.count = count; + this.binRange = binRange; + binStart = binRange.getBINstart(); + binEnd = binRange.getBINend(); + } + + private BinResult(long count, int start, int end) { + this.count = count; + this.binRange = null; + binStart = start; + binEnd = end; + } + + int getBINStart() { + return binStart; + } + + int getBINEnd() { + return binEnd; + } + + long getCount() { + return count; + } + + boolean hasDetails() { + return binRange != null; + } + + @Override + public Optional<Integer> getNumberLength() { + return binRange.getNumberLength(); + } + + @Override + public Optional<String> getBankCity() { + return binRange.getBankCity(); + } + + @Override + public Optional<String> getBankName() { + return binRange.getBankName(); + } + + @Override + public Optional<String> getBankPhoneNumber() { + return binRange.getBankPhoneNumber(); + } + + @Override + public Optional<String> getBankURL() { + return binRange.getBankURL(); + } + + @Override + public Optional<String> getBrand() { + return binRange.getBrand(); + } + + @Override + public Optional<String> getCardType() { + return binRange.getCardType(); + } + + @Override + public Optional<String> getCountry() { + return binRange.getCountry(); + } + + @Override + public Optional<String> getScheme() { + return binRange.getScheme(); + } + } + + final private class AccountArtifactNode extends BlackboardArtifactNode { + + private final BlackboardArtifact artifact; + + private AccountArtifactNode(BlackboardArtifact artifact) { + super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS + this.artifact = artifact; + setName(Long.toString(this.artifact.getArtifactID())); + + reviewStatusBus.register(this); + } + + @Override + public Action[] getActions(boolean context) { + List<Action> actionsList = new ArrayList<>(); + actionsList.addAll(Arrays.asList(super.getActions(context))); + + actionsList.add(approveActionInstance); + actionsList.add(rejectActionInstance); + + return actionsList.toArray(new Action[actionsList.size()]); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set properties = sheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + sheet.put(properties); + } + properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + artifact.getReviewStatus().getDisplayName())); + + return sheet; + } + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + + // Update the node if event includes this artifact + event.artifacts.stream().filter((art) -> (art.getArtifactID() == this.artifact.getArtifactID())).map((_item) -> { + return _item; + }).forEachOrdered((_item) -> { + updateSheet(); + }); + } + + private void updateSheet() { + SwingUtilities.invokeLater(() -> { + this.setSheet(createSheet()); + }); + } + + } + + @Deprecated + private final class ToggleShowRejected extends AbstractAction { + + @NbBundle.Messages("ToggleShowRejected.name=Show Rejected Results") + ToggleShowRejected() { + super(Bundle.ToggleShowRejected_name()); + } + + @Override + public void actionPerformed(ActionEvent e) { + showRejected = !showRejected; + reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null)); + } + } + + /** + * Update the user interface to show or hide rejected artifacts. + * + * @param showRejected Show rejected artifacts? Yes if true; otherwise no. + */ + public void setShowRejected(boolean showRejected) { + this.showRejected = showRejected; + reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null)); + } + + private abstract class ReviewStatusAction extends AbstractAction { + + private final BlackboardArtifact.ReviewStatus newStatus; + + private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) { + super(displayName); + this.newStatus = newStatus; + + } + + @Override + public void actionPerformed(ActionEvent e) { + + /* + * get paths for selected nodes to reselect after applying review + * status change + */ + List<String[]> selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream() + .map(node -> { + String[] createPath; + /* + * If the we are rejecting and not showing rejected + * results, then the selected node, won't exist any + * more, so we select the previous one in stead. + */ + if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) { + List<Node> siblings = Arrays.asList(node.getParentNode().getChildren().getNodes()); + if (siblings.size() > 1) { + int indexOf = siblings.indexOf(node); + //there is no previous for the first node, so instead we select the next one + Node sibling = indexOf > 0 + ? siblings.get(indexOf - 1) + : siblings.get(Integer.max(indexOf + 1, siblings.size() - 1)); + createPath = NodeOp.createPath(sibling, null); + } else { + /* + * if there are no other siblings to select, + * just return null, but note we need to filter + * this out of stream below + */ + return null; + } + } else { + createPath = NodeOp.createPath(node, null); + } + //for the reselect to work we need to strip off the first part of the path. + return Arrays.copyOfRange(createPath, 1, createPath.length); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + //change status of selected artifacts + final Collection<? extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); + artifacts.forEach(artifact -> { + try { + artifact.setReviewStatus(newStatus); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS + } + }); + //post event + reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus)); + + final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing(); + final Node rootNode = directoryListing.getRootNode(); + + //convert paths back to nodes + List<Node> toArray = new ArrayList<>(); + selectedPaths.forEach(path -> { + try { + toArray.add(NodeOp.findPath(rootNode, path)); + } catch (NodeNotFoundException ex) { //NOPMD empty catch clause + //just ingnore paths taht don't exist. this is expected since we are rejecting + } + }); + //select nodes + directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()])); + } + } + + final private class ApproveAccounts extends ReviewStatusAction { + + @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"}) + private ApproveAccounts() { + super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED); + } + } + + final private class RejectAccounts extends ReviewStatusAction { + + @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"}) + private RejectAccounts() { + super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED); + } + } + + static private class ReviewStatusChangeEvent { + + Collection<? extends BlackboardArtifact> artifacts; + BlackboardArtifact.ReviewStatus newReviewStatus; + + ReviewStatusChangeEvent(Collection<? extends BlackboardArtifact> artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) { + this.artifacts = artifacts; + this.newReviewStatus = newReviewStatus; + } + } /** * Get the path of the icon for the given Account Type. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle.properties-MERGED new file mode 100755 index 0000000000000000000000000000000000000000..f7211362ece22a5b43b63362bb61367ef50f6c99 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle.properties-MERGED @@ -0,0 +1,28 @@ +Accounts.BINNode.accountsProperty.displayName=Accounts +Accounts.BINNode.bankCityProperty.displayName=Bank City +Accounts.BINNode.bankCountryProperty.displayName=Bank Country +Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone # +Accounts.BINNode.bankProperty.displayName=Bank +Accounts.BINNode.bankURLProperty.displayName=Bank URL +Accounts.BINNode.binProperty.displayName=Bank Identifier Number +Accounts.BINNode.brandProperty.displayName=Brand +Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type +Accounts.BINNode.noDescription=no description +Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme +# {0} - number of children +Accounts.ByBINNode.displayName=By BIN ({0}) +Accounts.ByBINNode.name=By BIN +# {0} - number of children +Accounts.ByFileNode.displayName=By File ({0}) +Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts +Accounts.FileWithCCNNode.nameProperty.displayName=File +Accounts.FileWithCCNNode.noDescription=no description +Accounts.FileWithCCNNode.statusProperty.displayName=Status +# {0} - raw file name +# {1} - solr chunk id +Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1} +Accounts.RootNode.displayName=Communication Accounts +AccountsRootNode.name=Accounts +ApproveAccountsAction.name=Approve Accounts +RejectAccountsAction.name=Reject Accounts +ToggleShowRejected.name=Show Rejected Results diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle_ja.properties new file mode 100644 index 0000000000000000000000000000000000000000..176ad36b61cd6bb339e6511ce08f7f57715bf7f5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle_ja.properties @@ -0,0 +1,25 @@ +#Thu Sep 30 10:26:59 UTC 2021 +Accounts.BINNode.accountsProperty.displayName=\u30a2\u30ab\u30a6\u30f3\u30c8 +Accounts.BINNode.bankCityProperty.displayName=\u9280\u884c\u6240\u5728\u5730\u5e02\u753a\u6751\u533a +Accounts.BINNode.bankCountryProperty.displayName=\u9280\u884c\u6240\u5728\u56fd +Accounts.BINNode.bankPhoneProperty.displayName=\u9280\u884c\u96fb\u8a71\u756a\u53f7\# +Accounts.BINNode.bankProperty.displayName=\u9280\u884c +Accounts.BINNode.bankURLProperty.displayName=\u9280\u884cURL +Accounts.BINNode.binProperty.displayName=\u9280\u884c\u8b58\u5225\u756a\u53f7 +Accounts.BINNode.brandProperty.displayName=\u30d6\u30e9\u30f3\u30c9 +Accounts.BINNode.cardTypeProperty.displayName=\u652f\u6255\u3044\u30ab\u30fc\u30c9\u306e\u7a2e\u985e +Accounts.BINNode.noDescription=\u8aac\u660e\u306a\u3057 +Accounts.BINNode.schemeProperty.displayName=\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9\u30b9\u30ad\u30fc\u30e0 +Accounts.ByBINNode.displayName=BIN\u5225 ({0}) +Accounts.ByBINNode.name=BIN\u5225 +Accounts.ByFileNode.displayName=\u30d5\u30a1\u30a4\u30eb\u5225 ({0}) +Accounts.FileWithCCNNode.accountsProperty.displayName=\u30a2\u30ab\u30a6\u30f3\u30c8 +Accounts.FileWithCCNNode.nameProperty.displayName=\u30d5\u30a1\u30a4\u30eb +Accounts.FileWithCCNNode.noDescription=\u8aac\u660e\u306a\u3057 +Accounts.FileWithCCNNode.statusProperty.displayName=\u30b9\u30c6\u30fc\u30bf\u30b9 +Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_\u30c1\u30e3\u30f3\u30af_{1} +Accounts.RootNode.displayName=\u901a\u4fe1\u30a2\u30ab\u30a6\u30f3\u30c8 +AccountsRootNode.name=\u30a2\u30ab\u30a6\u30f3\u30c8 +ApproveAccountsAction.name=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u627f\u8a8d +RejectAccountsAction.name=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u62d2\u5426 +ToggleShowRejected.name=\u62d2\u5426\u3055\u308c\u305f\u7d50\u679c\u3092\u8868\u793a diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java index 9d263e6f0fedfc343305ae5b4d0cee9dbfae0377..58bf8b26d2ee062351b8ee12b9ce446743dab198 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java @@ -18,17 +18,12 @@ */ package org.sleuthkit.autopsy.datamodel.utils; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; /** * Utility methods for handling icons */ public final class IconsUtil { - - private static final String ICON_BASE_PATH = "/org/sleuthkit/autopsy/images/"; //NON-NLS - private IconsUtil() { } @@ -138,53 +133,9 @@ public static String getIconFilePath(int typeID) { imageFile = "previously-unseen.png"; //NON-NLS } else if (typeID == ARTIFACT_TYPE.TSK_PREVIOUSLY_NOTABLE.getTypeID()) { imageFile = "red-circle-exclamation.png"; //NON-NLS - } else if (typeID == BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeID()) { - imageFile = "hashset_hits.png"; - } else if (typeID == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) { - imageFile = "keyword_hits.png"; - } else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() - || typeID == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID() - || typeID == BlackboardArtifact.Type.TSK_INTERESTING_ITEM.getTypeID()) { - imageFile = "interesting_item.png"; - } else if (typeID == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) { - imageFile = "accounts.png"; } else { imageFile = "artifact-icon.png"; //NON-NLS } return "/org/sleuthkit/autopsy/images/" + imageFile; } - - /** - * Get the path of the icon for the given Account Type. - * - * @return The path of the icon for the given Account Type. - */ - public static String getIconFilePath(Account.Type type) { - - if (type.equals(Account.Type.CREDIT_CARD)) { - return ICON_BASE_PATH + "credit-card.png"; - } else if (type.equals(Account.Type.DEVICE)) { - return ICON_BASE_PATH + "image.png"; - } else if (type.equals(Account.Type.EMAIL)) { - return ICON_BASE_PATH + "email.png"; - } else if (type.equals(Account.Type.FACEBOOK)) { - return ICON_BASE_PATH + "facebook.png"; - } else if (type.equals(Account.Type.INSTAGRAM)) { - return ICON_BASE_PATH + "instagram.png"; - } else if (type.equals(Account.Type.MESSAGING_APP)) { - return ICON_BASE_PATH + "messaging.png"; - } else if (type.equals(Account.Type.PHONE)) { - return ICON_BASE_PATH + "phone.png"; - } else if (type.equals(Account.Type.TWITTER)) { - return ICON_BASE_PATH + "twitter.png"; - } else if (type.equals(Account.Type.WEBSITE)) { - return ICON_BASE_PATH + "web-file.png"; - } else if (type.equals(Account.Type.WHATSAPP)) { - return ICON_BASE_PATH + "WhatsApp.png"; - } else { - //there could be a default icon instead... - return ICON_BASE_PATH + "face.png"; -// throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName()); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/CollapseAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/CollapseAction.java index fe295ea592ef12e86b33fe5f37a51bf834f7d495..b6c04d9ee2e5d52124e9ebb9ff73325615c50589 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/CollapseAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/CollapseAction.java @@ -29,11 +29,9 @@ * * @author jantonius */ -public class CollapseAction extends AbstractAction { +class CollapseAction extends AbstractAction { - private static final long serialVersionUID = 1L; - - public CollapseAction(String title) { + CollapseAction(String title) { super(title); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 093b250eab2b6a7acbffe23064ab92ccf856805a..d8b2436179b7c7a1fda14fc20137d371059f7740 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -33,7 +33,6 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.corecomponents.SelectionResponder; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; @@ -41,6 +40,7 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.commonpropertiessearch.InstanceCountNode; import org.sleuthkit.autopsy.commonpropertiessearch.InstanceCaseNode; import org.sleuthkit.autopsy.commonpropertiessearch.InstanceDataSourceNode; @@ -48,6 +48,7 @@ import org.sleuthkit.autopsy.commonpropertiessearch.CentralRepoCommonAttributeInstanceNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; +import org.sleuthkit.autopsy.datamodel.Reports; import org.sleuthkit.autopsy.commonpropertiessearch.CaseDBCommonAttributeInstanceNode; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; @@ -127,13 +128,6 @@ public Action[] getActions(boolean popup) { @Override public Action getPreferredAction() { final Node original = this.getOriginal(); - - if (original instanceof SelectionResponder - && original instanceof AbstractNode) { - AbstractNode abstractNode = (AbstractNode)original; - return DirectoryTreeTopComponent.getOpenChildAction(abstractNode.getName(), sourceEm); - } - // Once had a org.openide.nodes.ChildFactory$WaitFilterNode passed in if ((original instanceof DisplayableItemNode) == false) { return null; @@ -245,6 +239,17 @@ public List<Action> visit(BlackboardArtifactNode ban) { return Arrays.asList(ban.getActions(true)); } + @Override + public List<Action> visit(Reports.ReportsListNode ditem) { + // The base class Action is "Collapse All", inappropriate. + return null; + } + + @Override + public List<Action> visit(FileTypesNode fileTypes) { + return defaultVisit(fileTypes); + } + @Override protected List<Action> defaultVisit(DisplayableItemNode ditem) { return Arrays.asList(ditem.getActions(true)); @@ -338,11 +343,21 @@ public AbstractAction visit(LocalFileNode dfn) { } } + @Override + public AbstractAction visit(Reports.ReportNode reportNode) { + return reportNode.getPreferredAction(); + } + @Override protected AbstractAction defaultVisit(DisplayableItemNode c) { return openChild(c); } + @Override + public AbstractAction visit(FileTypesNode fileTypes) { + return openChild(fileTypes); + } + /** * Tell the originating ExplorerManager to display the given * dataModelNode. @@ -352,7 +367,43 @@ protected AbstractAction defaultVisit(DisplayableItemNode c) { * @return */ private AbstractAction openChild(final AbstractNode dataModelNode) { - return DirectoryTreeTopComponent.getOpenChildAction(dataModelNode.getName(), sourceEm); + // get the current selection from the directory tree explorer manager, + // which is a DirectoryTreeFilterNode. One of that node's children + // is a DirectoryTreeFilterNode that wraps the dataModelNode. We need + // to set that wrapped node as the selection and root context of the + // directory tree explorer manager (sourceEm) + if (sourceEm == null || sourceEm.getSelectedNodes().length == 0) { + return null; + } + final Node currentSelectionInDirectoryTree = sourceEm.getSelectedNodes()[0]; + + return new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + if (currentSelectionInDirectoryTree != null) { + // Find the filter version of the passed in dataModelNode. + final org.openide.nodes.Children children = currentSelectionInDirectoryTree.getChildren(); + // This call could break if the DirectoryTree is re-implemented with lazy ChildFactory objects. + Node newSelection = children.findChild(dataModelNode.getName()); + + /* + * We got null here when we were viewing a ZIP file in + * the Views -> Archives area and double clicking on it + * got to this code. It tried to find the child in the + * tree and didn't find it. An exception was then thrown + * from setting the selected node to be null. + */ + if (newSelection != null) { + try { + sourceEm.setExploredContextAndSelection(newSelection, new Node[]{newSelection}); + } catch (PropertyVetoException ex) { + Logger logger = Logger.getLogger(DataResultFilterNode.class.getName()); + logger.log(Level.WARNING, "Error: can't open the selected directory.", ex); //NON-NLS + } + } + } + } + }; } /** @@ -364,7 +415,25 @@ private AbstractAction openChild(final AbstractNode dataModelNode) { * @return */ private AbstractAction openParent(AbstractNode node) { - return DirectoryTreeTopComponent.getOpenParentAction(); + if (sourceEm == null) { + return null; + } + // @@@ Why do we ignore node? + Node[] selectedFilterNodes = sourceEm.getSelectedNodes(); + Node selectedFilterNode = selectedFilterNodes[0]; + final Node parentNode = selectedFilterNode.getParentNode(); + + return new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + try { + sourceEm.setSelectedNodes(new Node[]{parentNode}); + } catch (PropertyVetoException ex) { + Logger logger = Logger.getLogger(DataResultFilterNode.class.getName()); + logger.log(Level.WARNING, "Error: can't open the parent directory.", ex); //NON-NLS + } + } + }; } } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java new file mode 100644 index 0000000000000000000000000000000000000000..e84dd16da6af7f47dd31060fcb867f27ed26b53f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -0,0 +1,330 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.directorytree; + +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.nodes.Children; +import org.sleuthkit.autopsy.datamodel.DirectoryNode; +import org.openide.nodes.FilterNode; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; +import org.sleuthkit.autopsy.datamodel.AbstractContentNode; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; +import org.sleuthkit.autopsy.datamodel.LayoutFileNode; +import org.sleuthkit.autopsy.datamodel.LocalFileNode; +import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; +import org.sleuthkit.autopsy.datamodel.SlackFileNode; +import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; +import org.sleuthkit.autopsy.datamodel.VolumeNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.TskException; +import org.sleuthkit.datamodel.Volume; + +/** + * This class wraps around nodes that are displayed in the directory tree and + * hides files, '..', and other children that should not be displayed. facility + * to customize nodes view in dir tree: hide them or set no children + */ +class DirectoryTreeFilterChildren extends FilterNode.Children { + + private final ShowItemVisitor showItemV = new ShowItemVisitor(); + private final IsLeafItemVisitor isLeafItemV = new IsLeafItemVisitor(); + private final static Logger logger = Logger.getLogger(DirectoryTreeFilterChildren.class.getName()); + + /** + * the constructor + */ + public DirectoryTreeFilterChildren(Node arg) { + super(arg); + } + + @Override + protected Node copyNode(Node arg0) { + return new DirectoryTreeFilterNode(arg0, true); + } + + protected Node copyNode(Node arg0, boolean createChildren) { + return new DirectoryTreeFilterNode(arg0, createChildren); + } + + /* + * This method takes in a node as an argument and will create a new one if + * it should be displayed in the tree. If it is to be displayed, it also + * figures out if it is a leaf or not (i.e. should it have a + sign in the + * tree). + * + * It does NOT create children nodes + */ + @Override + protected Node[] createNodes(Node origNode) { + if (origNode == null || !(origNode instanceof DisplayableItemNode)) { + return new Node[]{}; + } + + // Shoudl this node be displayed in the tree or not + final DisplayableItemNode diNode = (DisplayableItemNode) origNode; + if (diNode.accept(showItemV) == false) { + //do not show + return new Node[]{}; + } + + // If it is going to be displayed, then determine if it should + // have a '+' next to it based on if it has children of itself. + // We will filter out the "." and ".." directories + final boolean isLeaf = diNode.accept(isLeafItemV); + + return new Node[]{this.copyNode(origNode, !isLeaf)}; + } + + /** + * Don't show expansion button on leaves leaf: all children are (file) or + * (directory named "." or "..") + * + * @param node + * + * @return whether node is a leaf + */ + private static boolean isLeafDirectory(DirectoryNode node) { + Directory dir = node.getLookup().lookup(Directory.class); + boolean ret = true; + try { + for (Content c : dir.getChildren()) { + if (c instanceof Directory && (!((Directory) c).getName().equals(".") + && !((Directory) c).getName().equals(".."))) { + ret = false; + break; + } else if(AbstractContentNode.contentHasVisibleContentChildren(c)){ + //fie has children, such as derived files + ret = false; + break; + } + } + } catch (TskException ex) { + Logger.getLogger(DirectoryTreeFilterChildren.class.getName()) + .log(Level.WARNING, "Error getting directory children", ex); //NON-NLS + return false; + } + return ret; + } + + private static boolean isLeafVolume(VolumeNode node) { + Volume vol = node.getLookup().lookup(Volume.class); + boolean ret = true; + + try { + for (Content c : vol.getChildren()) { + if (!(c instanceof LayoutFile)) { + ret = false; + break; + } + } + + } catch (TskException ex) { + Logger.getLogger(DirectoryTreeFilterChildren.class.getName()) + .log(Level.WARNING, "Error getting volume children", ex); //NON-NLS + return false; + } + return ret; + } + + /** + * Helper to ignore the '.' and '..' directories + */ + private static boolean isDotDirectory(DirectoryNode dir) { + String name = dir.getDisplayName(); + return name.equals(DirectoryNode.DOTDIR) || name.equals(DirectoryNode.DOTDOTDIR); + } + + /** + * Return the children based on the current node given. If the node doesn't + * have any directory or volume or image node inside it, it just returns + * leaf. + * + * @param arg the node + * + * @return children the children + */ + public static Children createInstance(Node arg, boolean createChildren) { + if (createChildren) { + return new DirectoryTreeFilterChildren(arg); + } else { + return Children.LEAF; + } + } + + private static class IsLeafItemVisitor extends DisplayableItemNodeVisitor.Default<Boolean> { + + @Override + protected Boolean defaultVisit(DisplayableItemNode c) { + return c.isLeafTypeNode(); + } + + @Override + public Boolean visit(DirectoryNode dn) { + return isLeafDirectory(dn); + } + + private Boolean visitDeep(AbstractAbstractFileNode<? extends AbstractFile> node) { + //is a leaf if has no children, or children are files not dirs + boolean hasChildren = node.hasContentChildren(); + if (!hasChildren) { + return true; + } + List<Content> derivedChildren = node.getContentChildren(); + //child of a file, must be a (derived) file too + for (Content childContent : derivedChildren) { + if ((childContent instanceof AbstractFile) && ((AbstractFile) childContent).isDir()) { + return false; + } else { + if(AbstractContentNode.contentHasVisibleContentChildren(childContent)){ + return false; + } + } + } + return true; + } + + @Override + public Boolean visit(FileNode fn) { + return visitDeep(fn); + } + + @Override + public Boolean visit(LocalFileNode lfn) { + return visitDeep(lfn); + } + + @Override + public Boolean visit(LayoutFileNode fn) { + return visitDeep(fn); + } + + @Override + public Boolean visit(SlackFileNode sfn) { + return visitDeep(sfn); + } + + @Override + public Boolean visit(VolumeNode vn) { + return isLeafVolume(vn); + } + + @Override + public Boolean visit(VirtualDirectoryNode vdn) { + return visitDeep(vdn); + } + + @Override + public Boolean visit(LocalDirectoryNode ldn) { + return visitDeep(ldn); + } + + @Override + public Boolean visit(FileTypesNode ft) { + return defaultVisit(ft); + } + + @Override + public Boolean visit(BlackboardArtifactNode bbafn) { + // Only show Message arttifacts with children + if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) || + (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { + return bbafn.hasContentChildren(); + } + + return false; + } + } + + private static class ShowItemVisitor extends DisplayableItemNodeVisitor.Default<Boolean> { + + @Override + protected Boolean defaultVisit(DisplayableItemNode c) { + return true; + } + + @Override + public Boolean visit(DirectoryNode dn) { + if (isDotDirectory(dn)) { + return false; + } + return true; + } + + @Override + public Boolean visit(FileNode fn) { + return fn.hasVisibleContentChildren(); + } + + @Override + public Boolean visit(LocalFileNode lfn) { + return lfn.hasVisibleContentChildren(); + } + + @Override + public Boolean visit(LayoutFileNode ln) { + return ln.hasVisibleContentChildren(); + } + + @Override + public Boolean visit(SlackFileNode sfn) { + return sfn.hasVisibleContentChildren(); + } + + @Override + public Boolean visit(VirtualDirectoryNode vdn) { + return true; + //return vdn.hasContentChildren(); + } + + + @Override + public Boolean visit(LocalDirectoryNode ldn) { + return true; + } + + @Override + public Boolean visit(FileTypesNode fileTypes) { + return defaultVisit(fileTypes); + } + + @Override + public Boolean visit(BlackboardArtifactNode bbafn) { + + // Only show Message arttifacts with children + if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) || + (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { + return bbafn.hasContentChildren(); + } + + return false; + } + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java new file mode 100644 index 0000000000000000000000000000000000000000..8f8acd7dd3a1ecf10baec8a117d6527d972daaea --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java @@ -0,0 +1,193 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> 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.directorytree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import javax.swing.Action; +import org.openide.nodes.FilterNode; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.openide.util.lookup.ProxyLookup; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.AbstractContentNode; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * A node filter (decorator) that sets the actions for a node in the tree view + * and wraps the Children object of the wrapped node with a + * DirectoryTreeFilterChildren. + */ +class DirectoryTreeFilterNode extends FilterNode { + + private static final Logger logger = Logger.getLogger(DirectoryTreeFilterNode.class.getName()); + private static final Action collapseAllAction = new CollapseAction(NbBundle.getMessage(DirectoryTreeFilterNode.class, "DirectoryTreeFilterNode.action.collapseAll.text")); + + /** + * Constructs node filter (decorator) that sets the actions for a node in + * the tree view and wraps the Children object of the wrapped node with a + * DirectoryTreeFilterChildren. + * + * @param nodeToWrap The node to wrap. + * @param createChildren Whether to create the children of the wrapped node + * or treat it a a leaf node. + */ + DirectoryTreeFilterNode(Node nodeToWrap, boolean createChildren) { + super(nodeToWrap, + DirectoryTreeFilterChildren.createInstance(nodeToWrap, createChildren), + new ProxyLookup(Lookups.singleton(nodeToWrap), nodeToWrap.getLookup())); + } + + /** + * Gets the display name for the wrapped node, possibly including a child + * count in parentheses. + * + * @return The display name for the node. + */ + @Override + public String getDisplayName() { + final Node orig = getOriginal(); + String name = orig.getDisplayName(); + + if (orig instanceof AbstractContentNode) { + AbstractFile file = getLookup().lookup(AbstractFile.class); + if ((file != null) && (false == (orig instanceof BlackboardArtifactNode))) { + try { + int numVisibleChildren = getVisibleChildCount(file); + + name = name + " (" + numVisibleChildren + ")"; //NON-NLS + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting children count to display for file: " + file, ex); //NON-NLS + } + } else if (orig instanceof BlackboardArtifactNode) { + BlackboardArtifact artifact = ((BlackboardArtifactNode) orig).getArtifact(); + try { + int numAttachments = artifact.getChildrenCount(); + name = name + " (" + numAttachments + ")"; //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting chidlren count for atifact: " + artifact, ex); //NON-NLS + } + } + } + return name; + } + + /** + * Gets the number of visible children for a tree view node representing an + * AbstractFile. Depending on the user preferences, known and/or slack files + * will either be included or purged in the count. + * + * @param file The AbstractFile object whose children will be counted. + * + * @return The number of visible children. + */ + private int getVisibleChildCount(AbstractFile file) throws TskCoreException { + List<Content> childList = file.getChildren(); + + int numVisibleChildren = childList.size(); + boolean purgeKnownFiles = UserPreferences.hideKnownFilesInDataSourcesTree(); + boolean purgeSlackFiles = UserPreferences.hideSlackFilesInDataSourcesTree(); + + if (purgeKnownFiles || purgeSlackFiles) { + // Purge known and/or slack files from the file count + for (int i = 0; i < childList.size(); i++) { + Content child = childList.get(i); + if (child instanceof AbstractFile) { + AbstractFile childFile = (AbstractFile) child; + if ((purgeKnownFiles && childFile.getKnown() == TskData.FileKnown.KNOWN) + || (purgeSlackFiles && childFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) { + numVisibleChildren--; + } + } else if (child instanceof BlackboardArtifact) { + + if (FilterNodeUtils.showMessagesInDatasourceTree()) { + // In older versions of Autopsy, attachments were children of email/message artifacts + // and hence email/messages with attachments are shown in the directory tree. + BlackboardArtifact bba = (BlackboardArtifact) child; + // Only message type artifacts are displayed in the tree + if ((bba.getArtifactTypeID() != ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) + && (bba.getArtifactTypeID() != ARTIFACT_TYPE.TSK_MESSAGE.getTypeID())) { + numVisibleChildren--; + } + } else { + numVisibleChildren--; + } + } + } + } + + return numVisibleChildren; + } + + /** + * Gets the context mneu (right click menu) actions for the wrapped node. + * + * @param context Whether to find actions for context meaning or for the + * node itself. + * + * @return + */ + @Override + public Action[] getActions(boolean context) { + List<Action> actions = new ArrayList<>(); + actions.addAll(Arrays.asList(getNodeActions())); + actions.add(collapseAllAction); + return actions.toArray(new Action[actions.size()]); + } + + /** + * @return If lookup node is content, host, or person, returns super + * actions. Otherwise, returns empty array. + */ + private Action[] getNodeActions() { + Lookup lookup = this.getLookup(); + if (lookup.lookup(Content.class) != null + || lookup.lookup(Host.class) != null + || lookup.lookup(Person.class) != null) { + return super.getActions(true); + } else { + return new Action[0]; + } + } + + /** + * Get the wrapped node. + * + * @return + */ + @Override + public Node getOriginal() { + return super.getOriginal(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 31d10605fc57e4db09499613af87615da1f2f4a0..794cc7db9fb0ebd7431e0b13727b2c0c57bae8dd 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2021 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ import java.awt.Cursor; import java.awt.EventQueue; -import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; @@ -32,6 +31,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -41,7 +41,6 @@ import java.util.prefs.PreferenceChangeListener; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; @@ -73,30 +72,25 @@ import org.sleuthkit.autopsy.corecomponents.ViewPreferencesPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.datamodel.AnalysisResults; +import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.CreditCards; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.EmailExtracted; import org.sleuthkit.autopsy.datamodel.EmptyNode; +import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; +import org.sleuthkit.autopsy.datamodel.InterestingHits; +import org.sleuthkit.autopsy.datamodel.KeywordHits; +import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildFactory; +import org.sleuthkit.autopsy.datamodel.DataArtifacts; +import org.sleuthkit.autopsy.datamodel.OsAccounts; +import org.sleuthkit.autopsy.datamodel.PersonNode; import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.corecomponents.SelectionResponder; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; -import org.sleuthkit.autopsy.datamodel.CreditCards; +import org.sleuthkit.autopsy.datamodel.ViewsNode; +import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.nodes.AnalysisResultTypeFactory.KeywordSetNode; -import org.sleuthkit.autopsy.mainui.nodes.AnalysisResultTypeFactory.TreeConfigTypeNode; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo.BlackboardArtifactNodeSelectionInfo; -import org.sleuthkit.autopsy.mainui.nodes.DataArtifactTypeFactory.CreditCardByBinParentNode; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo.OsAccountNodeSelectionInfo; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.AnalysisResultsRootNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.DataArtifactsRootNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.OsAccountsRootNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.PersonNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.ViewsRootNode; -import org.sleuthkit.autopsy.mainui.nodes.TreeNode; -import org.sleuthkit.autopsy.mainui.nodes.ViewsTypeFactory.MimeParentNode; import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.Category; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -125,17 +119,16 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final LinkedList<String[]> forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); + private AutopsyTreeChildFactory autopsyTreeChildFactory; private Children autopsyTreeChildren; + private Accounts accounts; private boolean showRejectedResults; private static final long DEFAULT_DATASOURCE_GROUPING_THRESHOLD = 5; // Threshold for prompting the user about grouping by data source private static final String GROUPING_THRESHOLD_NAME = "GroupDataSourceThreshold"; private static final String SETTINGS_FILE = "CasePreferences.properties"; //NON-NLS // nodes to be opened if present at top level - private static final Set<String> NODES_TO_EXPAND_PREFIXES = Stream.of( - AnalysisResultsRootNode.getNamePrefix(), - DataArtifactsRootNode.getNamePrefix(), - ViewsRootNode.getNamePrefix()) + private static final Set<String> NODES_TO_EXPAND = Stream.of(AnalysisResults.getName(), DataArtifacts.getName(), ViewsNode.NAME) .collect(Collectors.toSet()); /** @@ -204,15 +197,7 @@ private void preExpandNodes(Children rootChildren) { .forEach(tree::expandNode); } else { Stream.of(rootChildrenNodes) - .filter(n -> { - // find any where node name is present in prefixes - return n != null - && n.getName() != null - && NODES_TO_EXPAND_PREFIXES.stream() - .filter(prefix -> n.getName().startsWith(prefix)) - .findAny() - .isPresent(); - }) + .filter(n -> n != null && NODES_TO_EXPAND.contains(n.getName())) .forEach(tree::expandNode); } } @@ -298,6 +283,18 @@ public boolean getShowRejectedResults() { return showRejectedResults; } + /** + * Setter to determine if rejected results should be shown or not. + * + * @param showRejectedResults True if showing rejected results; otherwise + * false. + */ + public void setShowRejectedResults(boolean showRejectedResults) { + this.showRejectedResults = showRejectedResults; + if (accounts != null) { + accounts.setShowRejected(showRejectedResults); + } + } /** * This method is called from within the constructor to initialize the form. @@ -471,7 +468,6 @@ public static synchronized DirectoryTreeTopComponent getDefault() { * * @return getDefault() - the default instance */ - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public static synchronized DirectoryTreeTopComponent findInstance() { WindowManager winManager = WindowManager.getDefault(); TopComponent win = winManager.findTopComponent(PREFERRED_ID); @@ -587,7 +583,36 @@ protected void done() { }.execute(); } - setRootContextChildren(); + // if there's at least one image, load the image and open the top componen + autopsyTreeChildFactory = new AutopsyTreeChildFactory(); + autopsyTreeChildren = Children.create(autopsyTreeChildFactory, true); + Node root = new AbstractNode(autopsyTreeChildren) { + //JIRA-2807: What is the point of these overrides? + /** + * to override the right click action in the white blank space + * area on the directory tree window + */ + @Override + public Action[] getActions(boolean popup) { + return new Action[]{}; + } + + // Overide the AbstractNode use of DefaultHandle to return + // a handle which can be serialized without a parent + @Override + public Node.Handle getHandle() { + return new Node.Handle() { + @Override + public Node getNode() throws IOException { + return em.getRootContext(); + } + }; + } + }; + + root = new DirectoryTreeFilterNode(root, true); + + em.setRootContext(root); em.getRootContext().setName(currentCase.getName()); em.getRootContext().setDisplayName(currentCase.getName()); getTree().setRootVisible(false); // hide the root @@ -646,39 +671,6 @@ protected void done() { } } - /** - * Set root context to the root children. - */ - private void setRootContextChildren() { - // if there's at least one image, load the image and open the top componen - autopsyTreeChildren = RootFactory.getRootChildren(); - Node root = new AbstractNode(autopsyTreeChildren) { - //JIRA-2807: What is the point of these overrides? - /** - * to override the right click action in the white blank space - * area on the directory tree window - */ - @Override - public Action[] getActions(boolean popup) { - return new Action[]{}; - } - - // Overide the AbstractNode use of DefaultHandle to return - // a handle which can be serialized without a parent - @Override - public Node.Handle getHandle() { - return new Node.Handle() { - @Override - public Node getNode() throws IOException { - return em.getRootContext(); - } - }; - } - }; - - em.setRootContext(root); - } - /** * Called only when top component was closed so that now it is closed on all * workspaces in the system. The intent is to provide subclasses information @@ -874,14 +866,11 @@ void respondSelection(final Node[] oldNodes, final Node[] newNodes) { try { Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode(); if (treeNode != null) { - Node originNode = treeNode; - + Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal(); //set node, wrap in filter node first to filter out children Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); - if (originNode instanceof SelectionResponder) { - ((SelectionResponder) originNode).respondSelection(dataResult); - } else if (originNode instanceof MimeParentNode && - originNode.getChildren().getNodesCount(true) <= 0) { + // Create a TableFilterNode with knowledge of the node's type to allow for column order settings + if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) { //Special case for when File Type Identification has not yet been run and //there are no mime types to populate Files by Mime Type Tree EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text()); @@ -901,13 +890,6 @@ void respondSelection(final Node[] oldNodes, final Node[] newNodes) { } } else if (originNode.getLookup().lookup(String.class) != null) { displayName = originNode.getLookup().lookup(String.class); - } else { - if (originNode.getDisplayName() != null) { - // Remove node count from name if present - displayName = originNode.getDisplayName().replaceAll("\\(([0-9]+|\\.\\.\\.)\\)$", ""); - } else { - displayName = originNode.getName(); - } } dataResult.setPath(displayName); } @@ -1039,8 +1021,8 @@ private void rebuildTree() { } // refresh all children of the root. - setRootContextChildren(); - + autopsyTreeChildFactory.refreshChildren(); + // Select the first node and reset the selection history // This should happen on the EDT once the tree has been rebuilt. // hence the SwingWorker that does this in the done() method @@ -1158,13 +1140,9 @@ public boolean hasMenuOpenAction() { private Optional<Node> getCategoryNodeChild(Children children, Category category) { switch (category) { case DATA_ARTIFACT: - return Stream.of(children.getNodes(true)) - .filter(n -> n.getName() != null && n.getName().startsWith(DataArtifactsRootNode.getNamePrefix())) - .findFirst(); + return Optional.ofNullable(children.findChild(DataArtifacts.getName())); case ANALYSIS_RESULT: - return Stream.of(children.getNodes(true)) - .filter(n -> n.getName() != null && n.getName().startsWith(AnalysisResultsRootNode.getNamePrefix())) - .findFirst(); + return Optional.ofNullable(children.findChild(AnalysisResults.getName())); default: LOGGER.log(Level.WARNING, "Unbale to find category of type: " + category.name()); return Optional.empty(); @@ -1274,7 +1252,7 @@ private Optional<Node> getOsAccountListNode(Node node, OsAccount osAccount, Set< } - if (node.getName() != null && node.getName().startsWith(OsAccountsRootNode.getNamePrefix())) { + if (OsAccounts.getListName().equals(node.getName())) { return Optional.of(node); } @@ -1312,21 +1290,16 @@ public void viewOsAccount(OsAccount osAccount) { Node osAccountListNode = osAccountListNodeOpt.get(); - if (osAccountListNode instanceof TreeNode) { - TreeNode<?> treeNode = (TreeNode<?>) osAccountListNode; - treeNode.setNodeSelectionInfo(new OsAccountNodeSelectionInfo(osAccount.getId())); - } - + DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) osAccountListNode).getOriginal(); + undecoratedParentNode.setChildNodeSelectionInfo((osAcctNd) -> { + OsAccount osAcctOfNd = osAcctNd.getLookup().lookup(OsAccount.class); + return osAcctOfNd != null && osAcctOfNd.getId() == osAccount.getId(); + }); getTree().expandNode(osAccountListNode); - if (this.getSelectedNode().equals(osAccountListNode)) { - this.setDirectoryListingActive(); - this.respondSelection(em.getSelectedNodes(), new Node[]{osAccountListNode}); - } else { - try { - em.setExploredContextAndSelection(osAccountListNode, new Node[]{osAccountListNode}); - } catch (PropertyVetoException ex) { - LOGGER.log(Level.WARNING, "Property Veto: ", ex); //NON-NLS - } + try { + em.setExploredContextAndSelection(osAccountListNode, new Node[]{osAccountListNode}); + } catch (PropertyVetoException ex) { + LOGGER.log(Level.WARNING, "Property Veto: ", ex); //NON-NLS } } @@ -1381,8 +1354,16 @@ public void viewArtifact(final BlackboardArtifact art) { Children typesChildren = categoryChildrenOpt.get(); Node treeNode = null; - if (art instanceof AnalysisResult) { - treeNode = getAnalysisResultNode(typesChildren, (AnalysisResult) art); + if (typeID == BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeID()) { + treeNode = getHashsetNode(typesChildren, art); + } else if (typeID == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) { + treeNode = getKeywordHitNode(typesChildren, art); + } else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID()) { + treeNode = getInterestingItemNode(typesChildren, BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, art); + } else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { + treeNode = getInterestingItemNode(typesChildren, BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT, art); + } else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_ITEM.getTypeID()) { + treeNode = getInterestingItemNode(typesChildren, BlackboardArtifact.Type.TSK_INTERESTING_ITEM, art); } else if (typeID == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) { treeNode = getEmailNode(typesChildren, art); } else if (typeID == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) { @@ -1395,10 +1376,8 @@ public void viewArtifact(final BlackboardArtifact art) { return; } - if(treeNode instanceof TreeNode) { - ((TreeNode)treeNode).setNodeSelectionInfo(new BlackboardArtifactNodeSelectionInfo(art)); - } - + DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) treeNode).getOriginal(); + undecoratedParentNode.setChildNodeSelectionInfo(new ArtifactNodeSelectionInfo(art)); getTree().expandNode(treeNode); if (this.getSelectedNode().equals(treeNode)) { this.setDirectoryListingActive(); @@ -1412,33 +1391,35 @@ public void viewArtifact(final BlackboardArtifact art) { } // Another thread is needed because we have to wait for dataResult to populate } - + /** - * Returns the parent tree node of the analysis result if one exists. - * @param analysisResultTypeChildren The analysis result type node children. - * @param analysisResult The analysis result to find. - * @return The parent tree node or null. + * Returns the hashset hit artifact's parent node or null if cannot be + * found. + * + * @param typesChildren The children object of the same category as hashset + * hits. + * @param art The artifact. + * + * @return The hashset hit artifact's parent node or null if cannot be + * found. */ - private Node getAnalysisResultNode(Children analysisResultTypeChildren, final AnalysisResult analysisResult) { - if (BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() == analysisResult.getArtifactTypeID()) { - return getKeywordHitNode(analysisResultTypeChildren, analysisResult); - } - - Node analysisResultTypeNode = analysisResultTypeChildren.findChild(analysisResult.getArtifactTypeName()); - if (analysisResultTypeNode == null) { + private Node getHashsetNode(Children typesChildren, final BlackboardArtifact art) { + Node hashsetRootNode = typesChildren.findChild(art.getArtifactTypeName()); + Children hashsetRootChilds = hashsetRootNode.getChildren(); + try { + String setName = null; + List<BlackboardAttribute> attributes = art.getAttributes(); + for (BlackboardAttribute att : attributes) { + int typeId = att.getAttributeType().getTypeID(); + if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { + setName = att.getValueString(); + } + } + return hashsetRootChilds.findChild(setName); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS return null; } - - Node[] childConfigNodes = analysisResultTypeNode.getChildren() == null ? null : analysisResultTypeNode.getChildren().getNodes(true); - if (childConfigNodes == null || childConfigNodes.length == 0) { - return analysisResultTypeNode; - } - - String expectedName = TreeConfigTypeNode.getNodeName(analysisResult.getArtifactTypeName(), analysisResult.getConfiguration()); - return Stream.of(childConfigNodes) - .filter(childNode -> expectedName.equals(childNode.getName())) - .findFirst() - .orElse(null); } /** @@ -1447,69 +1428,109 @@ private Node getAnalysisResultNode(Children analysisResultTypeChildren, final An * * @param typesChildren The children object of the same category as keyword * hits. - * @param kwh The artifact. + * @param art The artifact. * * @return The keyword hit artifact's parent node or null if cannot be * found. */ - private Node getKeywordHitNode(Children typesChildren, AnalysisResult kwh) { - Node keywordRootNode = typesChildren.findChild(kwh.getArtifactTypeName()); - if (keywordRootNode == null - || keywordRootNode.getChildren() == null - || keywordRootNode.getChildren().getNodesCount(true) == 0) { - - return null; - } - - Node configNode = keywordRootNode.getChildren().findChild(KeywordSetNode.getNodeName(kwh.getConfiguration())); - if (configNode == null) { - return null; - } - - Node toRet = configNode; + private Node getKeywordHitNode(Children typesChildren, BlackboardArtifact art) { + Node keywordRootNode = typesChildren.findChild(art.getArtifactTypeName()); + Children keywordRootChilds = keywordRootNode.getChildren(); try { + String listName = null; String keywordName = null; String regex = null; - List<BlackboardAttribute> attributes = kwh.getAttributes(); + List<BlackboardAttribute> attributes = art.getAttributes(); for (BlackboardAttribute att : attributes) { int typeId = att.getAttributeType().getTypeID(); - if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) { + if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { + listName = att.getValueString(); + } else if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) { keywordName = att.getValueString(); } else if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()) { regex = att.getValueString(); } } - + if (listName == null) { + if (regex == null) { //using same labels used for creation + listName = NbBundle.getMessage(KeywordHits.class, "KeywordHits.simpleLiteralSearch.text"); + } else { + listName = NbBundle.getMessage(KeywordHits.class, "KeywordHits.singleRegexSearch.text"); + } + } + Node listNode = keywordRootChilds.findChild(listName); + if (listNode == null) { + return null; + } + Children listChildren = listNode.getChildren(); + if (listChildren == null) { + return null; + } if (regex != null) { //For support of regex nodes such as URLs, IPs, Phone Numbers, and Email Addrs as they are down another level - Node regexNode = toRet.getChildren() == null || toRet.getChildren().getNodesCount(true) == 0 - ? null - : toRet.getChildren().findChild(regex); - + Node regexNode = listChildren.findChild(listName); + regexNode = (regexNode == null) ? listChildren.findChild(listName + "_" + regex) : regexNode; if (regexNode == null) { - return null; } - - toRet = regexNode; - } - - if (keywordName != null) { - Node keywordNameNode = toRet.getChildren() == null || toRet.getChildren().getNodesCount(true) == 0 - ? null - : toRet.getChildren().findChild(keywordName); - - if (keywordNameNode == null) { + listChildren = regexNode.getChildren(); + if (listChildren == null) { return null; } - - toRet = keywordNameNode; } + + return listChildren.findChild(keywordName); } catch (TskCoreException ex) { LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS return null; } - - return toRet; + } + + /** + * Returns the interesting item artifact's parent node or null if cannot be + * found. + * + * @param typesChildren The children object of the same category as + * interesting item. + * @param artifactType The type of the artifact (interesting hit or + * artifact). + * @param art The artifact. + * + * @return The interesting item artifact's parent node or null if cannot be + * found. + */ + private Node getInterestingItemNode(Children typesChildren, BlackboardArtifact.Type artifactType, BlackboardArtifact art) { + Node interestingItemsRootNode = typesChildren.findChild(artifactType.getDisplayName()); + Children setNodeChildren = (interestingItemsRootNode == null) ? null : interestingItemsRootNode.getChildren(); + + // set node children for type could not be found, so return null. + if (setNodeChildren == null) { + return null; + } + + String setName = null; + try { + setName = art.getAttributes().stream() + .filter(attr -> attr.getAttributeType().getTypeID() == BlackboardAttribute.Type.TSK_SET_NAME.getTypeID()) + .map(attr -> attr.getValueString()) + .findFirst() + .orElse(null); + + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS + return null; + } + + // if no set name, no set node will be identified. + if (setName == null) { + return null; + } + + // make sure data is fully loaded + final String finalSetName = setName; + return Stream.of(setNodeChildren.getNodes(true)) + .filter(setNode -> finalSetName.equals(setNode.getLookup().lookup(String.class))) + .findFirst() + .orElse(null); } /** @@ -1522,36 +1543,23 @@ private Node getKeywordHitNode(Children typesChildren, AnalysisResult kwh) { */ private Node getEmailNode(Children typesChildren, BlackboardArtifact art) { Node emailMsgRootNode = typesChildren.findChild(art.getArtifactTypeName()); - String path = null; + Children emailMsgRootChilds = emailMsgRootNode.getChildren(); + Map<String, String> parsedPath = null; try { List<BlackboardAttribute> attributes = art.getAttributes(); - for (BlackboardAttribute attr : attributes) { - int typeId = attr.getAttributeType().getTypeID(); + for (BlackboardAttribute att : attributes) { + int typeId = att.getAttributeType().getTypeID(); if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID()) { - path = attr.getValueString(); + parsedPath = EmailExtracted.parsePath(att.getValueString()); break; } } - - Node parentNode = null; - Node[] childNodes = emailMsgRootNode.getChildren().getNodes(true); - while (childNodes != null) { - boolean recursing = false; - for (Node child : childNodes) { - if (MainDAO.getInstance().getEmailsDAO().getNextSubFolder(child.getName(), path).isChildInParent()) { - recursing = true; - parentNode = child; - childNodes = child.getChildren().getNodes(true); - break; - } - } - - if (!recursing) { - break; - } + if (parsedPath == null) { + return null; } - - return parentNode; + Node defaultNode = emailMsgRootChilds.findChild(parsedPath.get(NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultAcct.text"))); + Children defaultChildren = defaultNode.getChildren(); + return defaultChildren.findChild(parsedPath.get(NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultFolder.text"))); } catch (TskCoreException ex) { LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS return null; @@ -1568,17 +1576,13 @@ private Node getEmailNode(Children typesChildren, BlackboardArtifact art) { * @return The account artifact's parent node or null if cannot be found. */ private Node getAccountNode(Children typesChildren, BlackboardArtifact art) { + Node accountRootNode = typesChildren.findChild(art.getDisplayName()); + Children accountRootChilds = accountRootNode.getChildren(); + List<BlackboardAttribute> attributes; + String accountType = null; + String ccNumberName = null; try { - Node accountRootNode = typesChildren.findChild(art.getArtifactTypeName()); - if (accountRootNode == null || accountRootNode.getChildren() == null) { - return null; - } - - Children accountRootChilds = accountRootNode.getChildren(); - List<BlackboardAttribute> attributes = art.getAttributes(); - String accountType = null; - String ccNumberName = null; - + attributes = art.getAttributes(); for (BlackboardAttribute att : attributes) { int typeId = att.getAttributeType().getTypeID(); if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()) { @@ -1614,22 +1618,22 @@ private Node getAccountNode(Children typesChildren, BlackboardArtifact art) { * found. */ private Node getCreditCardAccountNode(Children accountRootChildren, String ccNumberName) { - if (ccNumberName == null) { - return null; - } - Node accountNode = accountRootChildren.findChild(Account.Type.CREDIT_CARD.getDisplayName()); - if (accountNode == null || accountNode.getChildren() == null) { + if (accountNode == null) { return null; } Children accountChildren = accountNode.getChildren(); - Node binNode = accountChildren.findChild(CreditCardByBinParentNode.getNameId()); - if (binNode == null || binNode.getChildren() == null) { + if (accountChildren == null) { + return null; + } + Node binNode = accountChildren.findChild(NbBundle.getMessage(Accounts.class, "Accounts.ByBINNode.name")); + if (binNode == null) { return null; } - Children binChildren = binNode.getChildren(); - + if (ccNumberName == null) { + return null; + } //right padded with 0s to 8 digits when single number //when a range of numbers, the first 6 digits are rightpadded with 0s to 8 digits then a dash then 3 digits, the 6,7,8, digits of the end number right padded with 9s String binName = StringUtils.rightPad(ccNumberName, 8, "0"); @@ -1664,114 +1668,5 @@ public void viewArtifactContent(BlackboardArtifact art) { public void addOnFinishedListener(PropertyChangeListener l) { DirectoryTreeTopComponent.this.addPropertyChangeListener(l); } - - /** - * Gets the open child action for the given node name. Will return - * null if the nodeName matches the currently selected tree node. - * - * @param nodeName The child node to open. - * - * @return The openChild action or null if not valid. - */ - public static AbstractAction getOpenChildAction(String nodeName) { - DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); - ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); - return getOpenChildAction(nodeName, treeViewExplorerMgr); - } - - /** - * Gets the open child action for the given node name. Will return - * null if the nodeName matches the currently selected tree node. - * - * @param nodeName The child node to open. - * @param explorerManager The explorer manager for the tree. - * - * @return The openChild action or null if not valid. - */ - static AbstractAction getOpenChildAction(String nodeName, ExplorerManager explorerManager) { - // get the current selection from the directory tree explorer manager, - // which is a DirectoryTreeFilterNode. One of that node's children - // is a DirectoryTreeFilterNode that wraps the dataModelNode. We need - // to set that wrapped node as the selection and root context of the - // directory tree explorer manager (sourceEm) - if (explorerManager == null || explorerManager.getSelectedNodes().length == 0 || nodeName == null) { - return null; - } - final Node currentSelectionInDirectoryTree = explorerManager.getSelectedNodes()[0]; - - // We have several node types that are used in both the tree and the result viewer. - // For tree nodes, we don't want to do the open child action. - // When double-clicking on a tree node, the nodeName to open will be the same - // as the currently seleted node, so don't return an action if this is the case. - if (nodeName.equals(currentSelectionInDirectoryTree.getName())) { - return null; - } - - return new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - if (currentSelectionInDirectoryTree != null) { - // Find the filter version of the passed in dataModelNode. - final org.openide.nodes.Children children = currentSelectionInDirectoryTree.getChildren(); - // This call could break if the DirectoryTree is re-implemented with lazy ChildFactory objects. - Node newSelection = children.findChild(nodeName); - - /* - * We got null here when we were viewing a ZIP file in - * the Views -> Archives area and double clicking on it - * got to this code. It tried to find the child in the - * tree and didn't find it. An exception was then thrown - * from setting the selected node to be null. - */ - if (newSelection != null) { - try { - explorerManager.setExploredContextAndSelection(newSelection, new Node[]{newSelection}); - } catch (PropertyVetoException ex) { - Logger logger = Logger.getLogger(DataResultFilterNode.class.getName()); - logger.log(Level.WARNING, "Error: can't open the selected directory.", ex); //NON-NLS - } - } - } - } - }; - } - - /** - * Gets the open parent action for the currently selected node. - * - * @return The openChild action. - */ - public static AbstractAction getOpenParentAction() { - DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); - ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); - return getOpenParentAction(treeViewExplorerMgr); - } - /** - * Gets the open parent action for the currently selected node. - * - * @param explorerManager The explorer manager for the tree. - * - * @return The open parent action or null if given an invalid ExplorerManager. - */ - static AbstractAction getOpenParentAction(ExplorerManager explorerManager) { - if (explorerManager == null) { - return null; - } - Node[] selectedFilterNodes = explorerManager.getSelectedNodes(); - Node selectedFilterNode = selectedFilterNodes[0]; - final Node parentNode = selectedFilterNode.getParentNode(); - - return new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - try { - explorerManager.setSelectedNodes(new Node[]{parentNode}); - } catch (PropertyVetoException ex) { - Logger logger = Logger.getLogger(DataResultFilterNode.class.getName()); - logger.log(Level.WARNING, "Error: can't open the parent directory.", ex); //NON-NLS - } - } - }; - } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index 872a8c9744ec5aa28e033a1f5c38b3eb8fa0d5ad..5dcd824437db72a705351daa00d2911588a43f0d 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -64,7 +64,7 @@ /** * Extracts all the unallocated space as a single file */ -public final class ExtractUnallocAction extends AbstractAction { +final class ExtractUnallocAction extends AbstractAction { private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); private static final long serialVersionUID = 1L; @@ -84,7 +84,7 @@ public final class ExtractUnallocAction extends AbstractAction { * @param title The title * @param volume The volume set for extraction. */ - public ExtractUnallocAction(String title, Volume volume) { + ExtractUnallocAction(String title, Volume volume) { this(title, null, volume); } @@ -97,11 +97,11 @@ public ExtractUnallocAction(String title, Volume volume) { * * @throws NoCurrentCaseException If no case is open. */ - public ExtractUnallocAction(String title, Image image) { + ExtractUnallocAction(String title, Image image) { this(title, image, null); } - public ExtractUnallocAction(String title, Image image, Volume volume) { + ExtractUnallocAction(String title, Image image, Volume volume) { super(title); this.volume = volume; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java index 6185ad9d5a391d5cf8b85af00be3cf2e2f7421df..6c3c5460180f27e0f2c94dd1f23602a0fc3478ef 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java @@ -22,9 +22,9 @@ import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.CasePreferences; +import org.sleuthkit.autopsy.datamodel.DataSourceFilesNode; +import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import static org.sleuthkit.autopsy.directorytree.Bundle.*; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.AllDataSourcesNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.DataSourceFilesNode; @NbBundle.Messages({"SelectionContext.dataSources=Data Sources", "SelectionContext.dataSourceFiles=Data Source Files", @@ -66,10 +66,8 @@ public static SelectionContext getSelectionContext(Node n) { if (n == null || n.getParentNode() == null) { // Parent of root node or root node. Occurs during case open / close. return SelectionContext.OTHER; - } else if ((!Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) && AllDataSourcesNode.getNameIdentifier().equals(n.getParentNode().getName())) - || (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) - && n.getParentNode().getName() != null - && n.getParentNode().getName().startsWith(DataSourceFilesNode.getNamePrefix()))) { + } else if ((!Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) && DataSourcesNode.getNameIdentifier().equals(n.getParentNode().getName())) + || (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) && DataSourceFilesNode.getNameIdentifier().equals(n.getParentNode().getName()))) { // if group by data type and root is the DataSourcesNode or // if group by persons/hosts and parent of DataSourceFilesNode // then it is a data source node diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewAssociatedContentAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewAssociatedContentAction.java index 1ebb8c778380b9ef626632f2b8ccb7ea68230c3d..b11cd9e6034a1036da3ada34595b7fcd62390077 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewAssociatedContentAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewAssociatedContentAction.java @@ -19,13 +19,10 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.event.ActionEvent; -import java.util.logging.Level; import javax.swing.AbstractAction; -import org.openide.nodes.Node; import org.sleuthkit.autopsy.corecomponents.DataContentTopComponent; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; -import org.sleuthkit.autopsy.datamodel.RootContentChildren; +import org.sleuthkit.autopsy.datamodel.CreateSleuthkitNodeVisitor; import org.sleuthkit.datamodel.Content; /** @@ -33,7 +30,6 @@ */ class ViewAssociatedContentAction extends AbstractAction { - private static final Logger logger = Logger.getLogger(ViewAssociatedContentAction.class.getName()); private final Content content; public ViewAssociatedContentAction(String title, BlackboardArtifactNode node) { @@ -43,11 +39,6 @@ public ViewAssociatedContentAction(String title, BlackboardArtifactNode node) { @Override public void actionPerformed(ActionEvent e) { - Node node = RootContentChildren.createNode(this.content); - if (node == null) { - logger.log(Level.WARNING, "No node created for object: " + this.content); - } else { - DataContentTopComponent.getDefault().setNode(node); - } + DataContentTopComponent.getDefault().setNode(content.accept(new CreateSleuthkitNodeVisitor())); } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 3f7004a7a25b37d90180ca2b737c8a64437d7362..323b340c7f827c0b003fac3afbeb5ba365e51743 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -31,24 +31,27 @@ import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.AbstractNode; import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.TreeView; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; +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.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; -import org.sleuthkit.autopsy.datamodel.TskContentItem; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo.ContentNodeSelectionInfo; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.AllDataSourcesNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.DataSourceFilesNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.DataSourceGroupedNode; -import org.sleuthkit.autopsy.mainui.nodes.RootFactory.PersonNode; -import org.sleuthkit.autopsy.mainui.nodes.TreeNode; +import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo; +import org.sleuthkit.autopsy.datamodel.DataSourcesNode; +import org.sleuthkit.autopsy.datamodel.DataSourceFilesNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.PersonNode; +import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; @@ -57,8 +60,10 @@ import org.sleuthkit.datamodel.FileSystem; import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.UnsupportedContent; import org.sleuthkit.datamodel.VolumeSystem; @@ -237,26 +242,13 @@ private Node getParentNodeGroupedByDataSource(ExplorerManager treeViewExplorerMg // Classic view // Start the search at the DataSourcesNode Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); - long parentContentDsId; - - if (parentContent instanceof AbstractFile) { - parentContentDsId = ((AbstractFile) parentContent).getDataSourceObjectId(); - } else { - try { - parentContentDsId = parentContent.getDataSource().getId(); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "An error occurred while fetching data source for content with id: " + parentContent.getId(), ex); - return null; - } - } - - Node rootDsNode = rootChildren == null ? null : rootChildren.findChild(AllDataSourcesNode.getNameIdentifier()); + Node rootDsNode = rootChildren == null ? null : rootChildren.findChild(DataSourcesNode.getNameIdentifier()); if (rootDsNode != null) { for (Node dataSourceLevelNode : getDataSourceLevelNodes(rootDsNode)) { DataSource dataSource = dataSourceLevelNode.getLookup().lookup(DataSource.class); - if (dataSource != null && dataSource.getId() == parentContentDsId) { + if (dataSource != null) { // the tree view needs to be searched to find the parent treeview node. - Node potentialParentTreeViewNode = findContentNodeInDS(dataSourceLevelNode, parentContent); + Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, dataSourceLevelNode); if (potentialParentTreeViewNode != null) { return potentialParentTreeViewNode; } @@ -279,35 +271,45 @@ private Node getParentNodeGroupedByDataSource(ExplorerManager treeViewExplorerMg * @return The node if found or null. */ private Node getParentNodeGroupedByPersonHost(ExplorerManager treeViewExplorerMgr, Content parentContent) { - // 'Group by Host/Person' view + // 'Group by Data Source' view + SleuthkitCase skCase; + String dsname; try { + // get the objid/name of the datasource of the selected content. + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); long contentDSObjid = parentContent.getDataSource().getId(); - + DataSource datasource = skCase.getDataSource(contentDSObjid); + dsname = datasource.getName(); Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); // the tree view needs to be searched to find the parent treeview node. + /* NOTE: we can't do a lookup by data source name here, becase if there + are multiple data sources with the same name, then "getChildren().findChild(dsname)" + simply returns the first one that it finds. Instead we have to loop over all + data sources with that name, and make sure we find the correct one. + */ List<Node> dataSourceLevelNodes = Stream.of(rootChildren.getNodes(true)) .flatMap(rootNode -> getDataSourceLevelNodes(rootNode).stream()) .collect(Collectors.toList()); for (Node treeNode : dataSourceLevelNodes) { // in the root, look for a data source node with the name of interest - DataSource nodeDs = treeNode.getLookup().lookup(DataSource.class); - - // if not data source continue - if (nodeDs == null || nodeDs.getId() != contentDSObjid) { + if (!(treeNode.getName().equals(dsname))) { continue; } - + + // for this data source, get the "Data Sources" child node + Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourceFilesNode.getNameIdentifier()); + // check whether this is the data source we are looking for - Node parentTreeViewNode = findContentNodeInDS(treeNode, parentContent); + Node parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode); if (parentTreeViewNode != null) { // found the data source node return parentTreeViewNode; } } - } catch (TskCoreException ex) { + } catch (NoCurrentCaseException | TskDataException | TskCoreException ex) { MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS } @@ -330,23 +332,18 @@ private void setNodeSelection(Content content, Node parentTreeViewNode, Director * tree view top component responds to the selection of the parent * node by pushing it into the results view top component. */ - - Long childIdToSelect = content.getId(); - + DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal(); + undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content)); if (content instanceof BlackboardArtifact) { BlackboardArtifact artifact = ((BlackboardArtifact) content); long associatedId = artifact.getObjectID(); try { Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId); - childIdToSelect = associatedFileContent.getId(); + undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId()); } } - - if(parentTreeViewNode instanceof TreeNode) { - ((TreeNode) parentTreeViewNode).setNodeSelectionInfo(new ContentNodeSelectionInfo(childIdToSelect)); - } TreeView treeView = treeViewTopComponent.getTree(); treeView.expandNode(parentTreeViewNode); @@ -381,9 +378,8 @@ private List<Node> getDataSourceLevelNodes(Node node) { return Collections.emptyList(); } else if (node.getLookup().lookup(Host.class) != null || node.getLookup().lookup(Person.class) != null - || AllDataSourcesNode.getNameIdentifier().equals(node.getName()) - || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class)) - || (node.getName() != null && (node.getName().startsWith(DataSourceGroupedNode.getNamePrefix()) || node.getName().startsWith(DataSourceFilesNode.getNamePrefix())))) { + || DataSourcesNode.getNameIdentifier().equals(node.getLookup().lookup(String.class)) + || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) { Children children = node.getChildren(); Node[] childNodes = children == null ? null : children.getNodes(true); if (childNodes == null) { @@ -399,73 +395,70 @@ private List<Node> getDataSourceLevelNodes(Node node) { } /** - * Find content node for specified content within a data source level node. - * - * NOTE: it is probably a good idea to check that the content is in the - * data source for the data source node before traversal (for performance - * reasons). - * - * @param dataSourceNode The data source level node. - * @param content The content whose node is to be found. - * @return The found node or null. + * Searches tree for parent node by getting an ordered list of the ancestors + * of the specified content. + * + * @param parentContent parent content for the content to be searched for + * @param node Node tree to search + * + * @return Node object of the matching parent, NULL if not found */ - private Node findContentNodeInDS(Node dataSourceNode, Content content) { + private Node findParentNodeInTree(Content parentContent, Node node) { /* * Get an ordered list of the ancestors of the specified * content, starting with its data source. + * */ - List<Content> path = content.accept(new AncestorVisitor()); - // last item should be the data source, which can be ignored - if (path.size() == 0) { - return null; - } else { - path = path.subList(0, path.size() - 1); - } - Collections.reverse(path); - + AncestorVisitor ancestorVisitor = new AncestorVisitor(); + List<Content> contentBranch = parentContent.accept(ancestorVisitor); + Collections.reverse(contentBranch); + /** - * For each path element in path, find matching content. + * Convert the list of ancestors into a list of tree nodes. + * + * IMPORTANT: The "dummy" root node used to create this single layer of + * children needs to be wrapped in a DirectoryTreeFilterNode so that its + * child nodes will also be wrapped in DirectoryTreeFilterNodes, via + * DirectoryTreeFilterNodeChildren. Otherwise, the display names of the + * nodes in the branch will not have child node counts and will not + * match the display names of the corresponding nodes in the actual tree + * view. */ - Node curContentNode = dataSourceNode; - for (Content pathElement : path) { - // ensure curContentNode and its children exist - if (curContentNode == null || curContentNode.getChildren() == null) { - return null; - } - - // ensure node array returned from getNodes - Node[] curContentChildNodes = curContentNode.getChildren().getNodes(true); - if (curContentChildNodes == null) { - return null; - } - - // find child node in path to continue traversal - boolean found = false; - for (Node curContentChildNode : curContentChildNodes) { - TskContentItem<?> contentItem = curContentChildNode.getLookup().lookup(TskContentItem.class); - if (contentItem != null && contentItem.getTskContent() != null && contentItem.getTskContent().getId() == pathElement.getId()) { - curContentNode = curContentChildNode; - found = true; + Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true); + Children ancestorChildren = dummyRootNode.getChildren(); + + // if content is the data source provided, return that. + if (ancestorChildren.getNodesCount() == 1 && StringUtils.equals(ancestorChildren.getNodeAt(0).getName(), node.getName())) { + return node; + } + + /* + * Search the tree for the parent node. Note that this algorithm + * simply discards "extra" ancestor nodes not shown in the tree, + * such as the root directory of the file system for file system + * content. + */ + Children treeNodeChildren = node.getChildren(); + Node parentTreeViewNode = null; + for (int i = 0; i < ancestorChildren.getNodesCount(); i++) { + Node ancestorNode = ancestorChildren.getNodeAt(i); + Node[] treeNodeChilds = treeNodeChildren.getNodes(true); + for (int j = 0; j < treeNodeChilds.length; j++) { + Node treeNode = treeNodeChilds[j]; + if (ancestorNode.getName().equals(treeNode.getName())) { + parentTreeViewNode = treeNode; + treeNodeChildren = treeNode.getChildren(); break; } } - - // if not found, we won't be able to identify the path. - if (!found) { - return null; - } } - - return curContentNode; + return parentTreeViewNode; } /** * A ContentVisitor that returns a list of content objects by starting with * a given content and following its chain of ancestors to the root content * of the lineage. - * - * NOTE: This should be kept in sync with - * FileSystemColumnUtils.getDisplayableContentForTableAndTree */ private static class AncestorVisitor extends ContentVisitor.Default<List<Content>> { @@ -473,14 +466,10 @@ private static class AncestorVisitor extends ContentVisitor.Default<List<Content @Override protected List<Content> defaultVisit(Content content) { - if (content instanceof AbstractFile && ((AbstractFile) content).isRoot()) { - return skipToParent(content); - } - lineage.add(content); Content parent = null; try { - parent = content.getParent(); + parent = content.getParent(); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED index 28c8c2c7a5d9a832ba6a3affc5a8f723a2cb929e..ec1a9bc5ccbf2a4e6935b0df64f9160b02143af8 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED @@ -1,9 +1,6 @@ ExtractActionHelper.extractFiles.cantCreateFolderErr.msg=Could not create selected folder. ExtractActionHelper.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite? ExtractActionHelper.confDlg.destFileExist.title=File Exists -# {0} - fileName -ExtractActionHelper.extractOverwrite.msg=A file already exists at {0}. Do you want to overwrite the existing file? -ExtractActionHelper.extractOverwrite.title=Export to csv file ExtractActionHelper.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0} ExtractActionHelper.noOpenCase.errMsg=No open case available. ExtractActionHelper.notifyDlg.noFileToExtr.msg=No file(s) to extract. diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java index 3406b4befc544acd624f32cec5b4ebc481f2d0fe..e94ca0c1a0a5561fae9879c365c195ec068fb10a 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryUiUtils.java @@ -43,7 +43,7 @@ import org.imgscalr.Scalr; import org.netbeans.api.progress.ProgressHandle; import org.opencv.core.Mat; -import org.opencv.videoio.VideoCapture; +import org.opencv.highgui.VideoCapture; import org.openide.util.ImageUtilities; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java b/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java index 14d632a03f3dede7e20239db1b06bc27db346f94..7dfbdacb4cce7ed27d0b3f39accd83cbfb77f310 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java @@ -30,7 +30,6 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.LocalDirectory; -import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.Pool; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskException; @@ -48,12 +47,7 @@ abstract class GetFilesContentVisitor implements ContentVisitor<Collection<Abstr public Collection<AbstractFile> visit(VirtualDirectory ld) { return getAllFromChildren(ld); } - - @Override - public Collection<AbstractFile> visit(LocalFilesDataSource lfds) { - return getAllFromChildren(lfds); - } - + @Override public Collection<AbstractFile> visit(LocalDirectory ld) { return getAllFromChildren(ld); @@ -78,7 +72,7 @@ public Collection<AbstractFile> visit(Volume volume) { public Collection<AbstractFile> visit(VolumeSystem vs) { return getAllFromChildren(vs); } - + @Override public Collection<AbstractFile> visit(Pool pool) { return getAllFromChildren(pool); @@ -102,7 +96,7 @@ protected Collection<AbstractFile> getAllFromChildren(Content parent) { try { for (Content child : parent.getChildren()) { - if (child instanceof AbstractContent) { + if (child instanceof AbstractContent){ all.addAll(child.accept(this)); } } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java index a6721e0ab2ae15a2a1ee362083e458557e6c565f..4698b680069d48b72508b146486b2f1e09d43897 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java @@ -49,7 +49,7 @@ public final class RunIngestModulesAction extends AbstractAction { @Messages("RunIngestModulesAction.name=Run Ingest Modules") private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(RunIngestModulesAction.class.getName()); + private static final Logger logger = Logger.getLogger(SpecialDirectoryNode.class.getName()); /* * Note that the execution context is the name of the dialog that used to be diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AbstractDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AbstractDAO.java deleted file mode 100644 index 8630974377f697c7a43718e033ca99f09a850738..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AbstractDAO.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import java.beans.PropertyChangeEvent; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; - -/** - * Internal methods that DAOs implement. - */ -abstract class AbstractDAO { - - static final int CACHE_SIZE = 5; - static final long CACHE_DURATION = 5; - static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES; - - /** - * Clear any cached data (Due to change in view). - */ - abstract void clearCaches(); - - /** - * Handles an autopsy event (i.e. ingest, case, etc.). This method is - * responsible for clearing internal caches that are effected by the event - * and returning one or more DAOEvents that should be broadcasted to the - * views. - * - * @param evt The Autopsy event that recently came in from Ingest/Case. - * - * @return The list of DAOEvents that should be broadcasted to the views or - * an empty list if the Autopsy events are irrelevant to this DAO. - */ - abstract Set<? extends DAOEvent> processEvent(PropertyChangeEvent evt); - - /** - * Handles the ingest complete or cancelled event. Any events that are - * delayed or batched are flushed and returned. - * - * @return The flushed events that were delayed and batched. - */ - abstract Set<? extends DAOEvent> handleIngestComplete(); - - /** - * Returns any categories that require a tree refresh. For instance, if web - * cache and web bookmarks haven't updated recently, and are currently set - * to an indeterminate amount (i.e. "..."), then broadcast an event forcing - * tree to update to a determinate count. - * - * @return The categories that require a tree refresh. - */ - abstract Set<? extends TreeEvent> shouldRefreshTree(); -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java deleted file mode 100644 index c11c8366507503758cf19166c5ea801c352aafd4..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ /dev/null @@ -1,1426 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.AnalysisResultEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.casemodule.events.TskDataModelObjectsDeletedEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DeleteAnalysisResultEvent; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import org.sleuthkit.autopsy.mainui.datamodel.events.KeywordHitEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.AnalysisResult; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; -import static org.sleuthkit.datamodel.TskData.KeywordSearchQueryType.REGEX; -import static org.sleuthkit.datamodel.TskData.KeywordSearchQueryType.SUBSTRING; - -/** - * DAO for providing data about analysis results to populate the results viewer. - */ -public class AnalysisResultDAO extends BlackboardArtifactDAO { - - private static Logger logger = Logger.getLogger(AnalysisResultDAO.class.getName()); - - private static AnalysisResultDAO instance = null; - - @NbBundle.Messages({ - "AnalysisResultDAO.columnKeys.score.name=Score", - "AnalysisResultDAO.columnKeys.score.displayName=Score", - "AnalysisResultDAO.columnKeys.score.description=Score", - "AnalysisResultDAO.columnKeys.conclusion.name=Conclusion", - "AnalysisResultDAO.columnKeys.conclusion.displayName=Conclusion", - "AnalysisResultDAO.columnKeys.conclusion.description=Conclusion", - "AnalysisResultDAO.columnKeys.justification.name=Justification", - "AnalysisResultDAO.columnKeys.justification.displayName=Justification", - "AnalysisResultDAO.columnKeys.justification.description=Justification", - "AnalysisResultDAO.columnKeys.configuration.name=Configuration", - "AnalysisResultDAO.columnKeys.configuration.displayName=Configuration", - "AnalysisResultDAO.columnKeys.configuration.description=Configuration", - "AnalysisResultDAO.columnKeys.sourceType.name=SourceType", - "AnalysisResultDAO.columnKeys.sourceType.displayName=Source Type", - "AnalysisResultDAO.columnKeys.sourceType.description=Source Type" - }) - static final ColumnKey SCORE_COL = new ColumnKey( - Bundle.AnalysisResultDAO_columnKeys_score_name(), - Bundle.AnalysisResultDAO_columnKeys_score_displayName(), - Bundle.AnalysisResultDAO_columnKeys_score_description() - ); - - static final ColumnKey CONCLUSION_COL = new ColumnKey( - Bundle.AnalysisResultDAO_columnKeys_conclusion_name(), - Bundle.AnalysisResultDAO_columnKeys_conclusion_displayName(), - Bundle.AnalysisResultDAO_columnKeys_conclusion_description() - ); - - static final ColumnKey CONFIGURATION_COL = new ColumnKey( - Bundle.AnalysisResultDAO_columnKeys_configuration_name(), - Bundle.AnalysisResultDAO_columnKeys_configuration_displayName(), - Bundle.AnalysisResultDAO_columnKeys_configuration_description() - ); - - static final ColumnKey JUSTIFICATION_COL = new ColumnKey( - Bundle.AnalysisResultDAO_columnKeys_justification_name(), - Bundle.AnalysisResultDAO_columnKeys_justification_displayName(), - Bundle.AnalysisResultDAO_columnKeys_justification_description() - ); - - static final ColumnKey SOURCE_TYPE_COL = new ColumnKey( - Bundle.AnalysisResultDAO_columnKeys_sourceType_name(), - Bundle.AnalysisResultDAO_columnKeys_sourceType_displayName(), - Bundle.AnalysisResultDAO_columnKeys_sourceType_description() - ); - - synchronized static AnalysisResultDAO getInstance() { - if (instance == null) { - instance = new AnalysisResultDAO(); - } - return instance; - } - - /** - * @return The set of types that are not shown in the tree. - */ - public static Set<BlackboardArtifact.Type> getIgnoredTreeTypes() { - return BlackboardArtifactDAO.getIgnoredTreeTypes(); - } - - private final Cache<SearchParams<BlackboardArtifactSearchParam>, AnalysisResultTableSearchResultsDTO> analysisResultCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final Cache<SearchParams<AnalysisResultSearchParam>, AnalysisResultTableSearchResultsDTO> configHitCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final Cache<SearchParams<KeywordHitSearchParam>, AnalysisResultTableSearchResultsDTO> keywordHitCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - - private final TreeCounts<AnalysisResultEvent> treeCounts = new TreeCounts<>(); - - private AnalysisResultTableSearchResultsDTO fetchAnalysisResultsForTable(SearchParams<BlackboardArtifactSearchParam> cacheKey) throws NoCurrentCaseException, TskCoreException { - - SleuthkitCase skCase = getCase(); - Blackboard blackboard = skCase.getBlackboard(); - BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - - List<BlackboardArtifact> arts = new ArrayList<>(); - String pagedWhereClause = getWhereClause(cacheKey); - arts.addAll(blackboard.getAnalysisResultsWhere(pagedWhereClause)); - blackboard.loadBlackboardAttributes(arts); - - // Get total number of results - long totalResultsCount = getTotalResultsCount(cacheKey, arts.size()); - - TableData tableData = createTableData(artType, arts); - return new AnalysisResultTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), totalResultsCount); - } - - private AnalysisResultTableSearchResultsDTO fetchKeywordHitsForTable(SearchParams<? extends AnalysisResultSearchParam> cacheKey) throws NoCurrentCaseException, TskCoreException { - - SleuthkitCase skCase = getCase(); - Blackboard blackboard = skCase.getBlackboard(); - KeywordHitSearchParam searchParams = (KeywordHitSearchParam) cacheKey.getParamData(); - Long dataSourceId = searchParams.getDataSourceId(); - BlackboardArtifact.Type artType = searchParams.getArtifactType(); - - // get all keyword hits for the search params - List<BlackboardArtifact> allHits = blackboard.getKeywordSearchResults(searchParams.getKeyword(), searchParams.getRegex(), searchParams.getSearchType(), searchParams.getConfiguration(), dataSourceId); - - // populate all attributes in one optimized database call - blackboard.loadBlackboardAttributes(allHits); - - // do paging, if necessary - List<BlackboardArtifact> pagedArtifacts = getPaged(allHits, cacheKey); - TableData tableData = createTableData(artType, pagedArtifacts); - return new AnalysisResultTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), allHits.size()); - } - - // filters results by configuration attr and needs a search param with the configuration - private AnalysisResultTableSearchResultsDTO fetchConfigResultsForTable(SearchParams<? extends AnalysisResultSearchParam> cacheKey) throws NoCurrentCaseException, TskCoreException { - - SleuthkitCase skCase = getCase(); - Blackboard blackboard = skCase.getBlackboard(); - - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - - String expectedConfiguration = cacheKey.getParamData().getConfiguration(); - - // where clause without paging - String originalWhereClause = " artifacts.artifact_type_id = ? AND analysis_results.configuration = ? "; - if (dataSourceId != null) { - originalWhereClause += " AND artifacts.data_source_obj_id = ? "; - } - - // where clause with paging - String pagedWhereClause = originalWhereClause - + " ORDER BY artifacts.obj_id ASC" - + (cacheKey.getMaxResultsCount() != null && cacheKey.getMaxResultsCount() > 0 ? " LIMIT ? " : "") - + (cacheKey.getStartItem() > 0 ? " OFFSET ? " : ""); - - // base from query without where clause - String baseQuery = " FROM blackboard_artifacts artifacts " - + "INNER JOIN tsk_analysis_results analysis_results " - + "ON artifacts.artifact_obj_id = analysis_results.artifact_obj_id WHERE "; - - // query for total count of matching items - int paramIdx = 0; - AtomicLong analysisResultCount = new AtomicLong(0); - try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect( - " COUNT(DISTINCT artifacts.artifact_id) AS count " + baseQuery + originalWhereClause)) { - - preparedStatement.setInt(++paramIdx, artType.getTypeID()); - preparedStatement.setString(++paramIdx, expectedConfiguration); - - if (dataSourceId != null) { - preparedStatement.setLong(++paramIdx, dataSourceId); - } - - getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - if (resultSet.next()) { - analysisResultCount.set(resultSet.getLong("count")); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching results from result set.", ex); - } - - }); - - } catch (SQLException ex) { - throw new TskCoreException(MessageFormat.format( - "An error occurred while fetching analysis result type: {0} with configuration: {1}.", - artType.getTypeName(), - expectedConfiguration), - ex); - } - - List<Long> artifactIds = new ArrayList<>(); - // query to get artifact id's to be displayed if total count exceeds the start item position - if (analysisResultCount.get() > cacheKey.getStartItem()) { - paramIdx = 0; - try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect( - " artifacts.artifact_id AS artifact_id " + baseQuery + pagedWhereClause)) { - - preparedStatement.setInt(++paramIdx, artType.getTypeID()); - preparedStatement.setString(++paramIdx, expectedConfiguration); - - if (dataSourceId != null) { - preparedStatement.setLong(++paramIdx, dataSourceId); - } - - if (cacheKey.getMaxResultsCount() != null && cacheKey.getMaxResultsCount() > 0) { - preparedStatement.setLong(++paramIdx, cacheKey.getMaxResultsCount()); - } - - if (cacheKey.getStartItem() > 0) { - preparedStatement.setLong(++paramIdx, cacheKey.getStartItem()); - } - - getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - while (resultSet.next()) { - artifactIds.add(resultSet.getLong("artifact_id")); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching results from result set.", ex); - } - - }); - - } catch (SQLException ex) { - throw new TskCoreException(MessageFormat.format( - "An error occurred while fetching analysis result type: {0} with configuration: {1}.", - artType.getTypeName(), - expectedConfiguration), - ex); - } - } - - // if there are artifact ids, get the artifacts with attributes - List<BlackboardArtifact> pagedArtifacts = new ArrayList<>(); - if (artifactIds.size() > 0) { - String artifactQueryWhere = " artifacts.artifact_id IN (" + artifactIds.stream().map(l -> Long.toString(l)).collect(Collectors.joining(",")) + ") "; - pagedArtifacts.addAll(blackboard.getAnalysisResultsWhere(artifactQueryWhere)); - blackboard.loadBlackboardAttributes(pagedArtifacts); - } - - TableData tableData = createTableData(artType, pagedArtifacts); - return new AnalysisResultTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), analysisResultCount.get()); - } - - @Override - void addAnalysisResultColumnKeys(List<ColumnKey> columnKeys) { - // Make sure these are in the same order as in addAnalysisResultFields() - columnKeys.add(SOURCE_TYPE_COL); - columnKeys.add(SCORE_COL); - columnKeys.add(CONCLUSION_COL); - columnKeys.add(CONFIGURATION_COL); - columnKeys.add(JUSTIFICATION_COL); - } - - @Override - void addAnalysisResultFields(BlackboardArtifact artifact, List<Object> cells) throws TskCoreException { - if (!(artifact instanceof AnalysisResult)) { - throw new IllegalArgumentException("Can not add fields for artifact with ID: " + artifact.getId() + " - artifact must be an analysis result"); - } - - // Make sure these are in the same order as in addAnalysisResultColumnKeys() - AnalysisResult analysisResult = (AnalysisResult) artifact; - cells.add(getSourceObjType(analysisResult.getParent())); - cells.add(analysisResult.getScore().getSignificance().getDisplayName()); - cells.add(analysisResult.getConclusion()); - cells.add(analysisResult.getConfiguration()); - cells.add(analysisResult.getJustification()); - } - - @Override - RowDTO createRow(BlackboardArtifact artifact, Content srcContent, Content linkedFile, boolean isTimelineSupported, List<Object> cellValues, long id) throws IllegalArgumentException { - if (!(artifact instanceof AnalysisResult)) { - throw new IllegalArgumentException("Can not make row for artifact with ID: " + artifact.getId() + " - artifact must be an analysis result"); - } - return new AnalysisResultRowDTO((AnalysisResult) artifact, srcContent, isTimelineSupported, cellValues, id); - } - - public AnalysisResultTableSearchResultsDTO getAnalysisResultsForTable(AnalysisResultSearchParam artifactKey, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - BlackboardArtifact.Type artType = artifactKey.getArtifactType(); - - if (artType == null || artType.getCategory() != BlackboardArtifact.Category.ANALYSIS_RESULT - || (artifactKey.getDataSourceId() != null && artifactKey.getDataSourceId() < 0)) { - throw new IllegalArgumentException(MessageFormat.format("Illegal data. " - + "Artifact type must be non-null and analysis result. Data source id must be null or > 0. " - + "Received artifact type: {0}; data source id: {1}", artType, artifactKey.getDataSourceId() == null ? "<null>" : artifactKey.getDataSourceId())); - } - - SearchParams<BlackboardArtifactSearchParam> searchParams = new SearchParams<>(artifactKey, startItem, maxCount); - return analysisResultCache.get(searchParams, () -> fetchAnalysisResultsForTable(searchParams)); - } - - private boolean isAnalysisResultsInvalidating(AnalysisResultSearchParam key, DAOEvent eventData) { - - if (eventData instanceof DeleteAnalysisResultEvent) { - return true; - } - - if (!(eventData instanceof AnalysisResultEvent)) { - return false; - } - - AnalysisResultEvent analysisResultEvt = (AnalysisResultEvent) eventData; - return key.getArtifactType().getTypeID() == analysisResultEvt.getArtifactType().getTypeID() - && (key.getDataSourceId() == null || key.getDataSourceId() == analysisResultEvt.getDataSourceId()); - } - - private boolean isAnalysisResultsConfigInvalidating(AnalysisResultSearchParam key, DAOEvent event) { - if (event instanceof DeleteAnalysisResultEvent) { - return true; - } - - if (!(event instanceof AnalysisResultEvent)) { - return false; - } - - AnalysisResultEvent setEvent = (AnalysisResultEvent) event; - return isAnalysisResultsInvalidating(key, setEvent) - && Objects.equals(key.getConfiguration(), setEvent.getConfiguration()); - } - - private boolean isKeywordHitInvalidating(KeywordHitSearchParam parameters, DAOEvent event) { - if (event instanceof DeleteAnalysisResultEvent) { - return true; - } - - if (!(event instanceof KeywordHitEvent)) { - return false; - } - - KeywordHitEvent khEvt = (KeywordHitEvent) event; - return isAnalysisResultsInvalidating(parameters, khEvt) - && (parameters.getKeyword() == null || Objects.equals(parameters.getKeyword(), khEvt.getMatch())) - && (parameters.getRegex() == null || Objects.equals(parameters.getRegex(), khEvt.getSearchString())) - && (parameters.getSearchType() == null || Objects.equals(parameters.getSearchType(), khEvt.getSearchType())); - - } - - public AnalysisResultTableSearchResultsDTO getAnalysisResultConfigResults(AnalysisResultSearchParam artifactKey, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (artifactKey.getDataSourceId() != null && artifactKey.getDataSourceId() < 0) { - throw new IllegalArgumentException(MessageFormat.format("Illegal data. " - + "Data source id must be null or > 0. " - + "Received data source id: {0}", artifactKey.getDataSourceId() == null ? "<null>" : artifactKey.getDataSourceId())); - } - - SearchParams<AnalysisResultSearchParam> searchParams = new SearchParams<>(artifactKey, startItem, maxCount); - return configHitCache.get(searchParams, () -> fetchConfigResultsForTable(searchParams)); - } - - public AnalysisResultTableSearchResultsDTO getKeywordHitsForTable(KeywordHitSearchParam artifactKey, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (artifactKey.getDataSourceId() != null && artifactKey.getDataSourceId() < 0) { - throw new IllegalArgumentException(MessageFormat.format("Illegal data. " - + "Data source id must be null or > 0. " - + "Received data source id: {0}", artifactKey.getDataSourceId() == null ? "<null>" : artifactKey.getDataSourceId())); - } - - SearchParams<KeywordHitSearchParam> searchParams = new SearchParams<>(artifactKey, startItem, maxCount); - return keywordHitCache.get(searchParams, () -> fetchKeywordHitsForTable(searchParams)); - } - - /** - * Returns a search results dto containing rows of counts data. - * - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * - * @return The results where rows are row of AnalysisResultSearchParam. - * - * @throws ExecutionException - */ - public TreeResultsDTO<AnalysisResultSearchParam> getAnalysisResultCounts(Long dataSourceId) throws ExecutionException { - try { - - Set<BlackboardArtifact.Type> indeterminateTypes = this.treeCounts.getEnqueued().stream() - .filter(evt -> dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId)) - .map(evt -> evt.getArtifactType()) - .collect(Collectors.toSet()); - - // get row dto's sorted by display name - Map<BlackboardArtifact.Type, Pair<Long, Boolean>> typeCounts = getCounts(dataSourceId); - List<TreeResultsDTO.TreeItemDTO<AnalysisResultSearchParam>> treeItemRows = typeCounts.entrySet().stream() - .map(entry -> { - TreeDisplayCount displayCount = indeterminateTypes.contains(entry.getKey()) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(entry.getValue().getLeft()); - - return new AnalysisResultTreeItem(entry.getKey(), null, dataSourceId, displayCount, entry.getValue().getRight()); - }) - .sorted(Comparator.comparing(countRow -> countRow.getDisplayName())) - .collect(Collectors.toList()); - - // return results - return new TreeResultsDTO<>(treeItemRows); - - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching analysis result counts.", ex); - } - } - - /** - * Returns the count of each artifact type. - * - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * - * @return The mapping of type to count and whether or not an item has a - * configuration. - * - * @throws NoCurrentCaseException - * @throws TskCoreException - */ - Map<BlackboardArtifact.Type, Pair<Long, Boolean>> getCounts(Long dataSourceId) throws NoCurrentCaseException, TskCoreException { - SleuthkitCase skCase = getCase(); - String query - = "\n r.artifact_type_id\n" - + " ,COUNT(*) AS count\n" - + " ,MAX(r.has_configuration) AS has_configuration\n" - + "FROM\n" - + "(SELECT \n" - + " art.artifact_type_id\n" - + " ,CASE WHEN ar.configuration IS NOT NULL AND ar.configuration <> '' THEN 1 ELSE 0 END AS has_configuration\n" - + "FROM blackboard_artifacts art\n" - + "INNER JOIN blackboard_artifact_types types ON types.category_type = 1 AND art.artifact_type_id = types.artifact_type_id\n" - + "LEFT JOIN tsk_analysis_results ar ON art.artifact_obj_id = ar.artifact_obj_id\n" - + " WHERE art.artifact_type_id NOT IN (" + BlackboardArtifactDAO.IGNORED_TYPES_SQL_SET + ") " - + (dataSourceId == null ? "" : (" AND art.data_source_obj_id = " + dataSourceId + " ")) + "\n" - + ") r GROUP BY r.artifact_type_id"; - - Map<BlackboardArtifact.Type, Pair<Long, Boolean>> typeCounts = new HashMap<>(); - - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - int artifactTypeId = resultSet.getInt("artifact_type_id"); - BlackboardArtifact.Type type = skCase.getBlackboard().getArtifactType(artifactTypeId); - long count = resultSet.getLong("count"); - boolean hasConfiguration = resultSet.getByte("has_configuration") > 0; - typeCounts.put(type, Pair.of(count, hasConfiguration)); - } - } catch (TskCoreException | SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching artifact type counts with query:\nSELECT" + query, ex); - } - }); - - return typeCounts; - } - - /** - * - * @param type The artifact type to filter on. - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * - * @return A mapping of configurations to their counts. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - Map<String, Long> getConfigurationCountsMap(BlackboardArtifact.Type type, Long dataSourceId) throws IllegalArgumentException, ExecutionException { - if (dataSourceId != null && dataSourceId <= 0) { - throw new IllegalArgumentException("Expected data source id to be > 0"); - } - - try { - // get artifact types and counts - SleuthkitCase skCase = getCase(); - String query = "\n ar.configuration AS configuration\n" - + " ,COUNT(*) AS count\n" - + "FROM blackboard_artifacts art\n" - + "LEFT JOIN tsk_analysis_results ar ON art.artifact_obj_id = ar.artifact_obj_id\n" - + " WHERE art.artifact_type_id = " + type.getTypeID() + " \n" - + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = " + dataSourceId + " \n") - + "GROUP BY ar.configuration"; - - Map<String, Long> configurationCounts = new HashMap<>(); - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - String configuration = resultSet.getString("configuration"); - long count = resultSet.getLong("count"); - configurationCounts.put(configuration, count); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching configuration counts with query:\nSELECT" + query, ex); - } - }); - - return configurationCounts; - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching configuration counts", ex); - } - } - - /** - * Get counts for individual configurations of the provided type to be used - * in the tree view. - * - * @param type The blackboard artifact type. - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * @param blankConfigName For artifacts with no configuration, this is the - * name to provide. If null or empty, artifacts - * without a configuration will be ignored. - * - * @return The configurations along with counts to display. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<AnalysisResultSearchParam> getConfigurationCounts( - BlackboardArtifact.Type type, - Long dataSourceId, - String blankConfigName) throws IllegalArgumentException, ExecutionException { - - Set<String> indeterminateConfigCounts = new HashSet<>(); - for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof AnalysisResultEvent - && (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId)) - && evt.getArtifactType().equals(type)) { - indeterminateConfigCounts.add(evt.getConfiguration()); - } - } - - List<TreeItemDTO<AnalysisResultSearchParam>> allConfigurations - = getConfigurationCountsMap(type, dataSourceId).entrySet().stream() - .sorted((a, b) -> compareStrings(a.getKey(), b.getKey())) - .map(entry -> { - TreeDisplayCount displayCount = indeterminateConfigCounts.contains(entry.getKey()) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(entry.getValue()); - - return getConfigTreeItem(type, - dataSourceId, - entry.getKey(), - StringUtils.isBlank(entry.getKey()) ? blankConfigName : entry.getKey(), - displayCount); - }) - .collect(Collectors.toList()); - - return new TreeResultsDTO<>(allConfigurations); - } - - /** - * Get counts for individual sets of the provided type to be used in the - * tree view. - * - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * @param nullSetName For artifacts with no set, this is the name to - * provide. If null, artifacts without a set name will - * be ignored. - * @param converter Means of converting from data source id and set name - * to an AnalysisResultSetSearchParam - * - * @return The sets along with counts to display. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<KeywordListSearchParam> getKwSetCounts( - Long dataSourceId, - String nullSetName) throws IllegalArgumentException, ExecutionException { - - Set<String> indeterminateSetNames = new HashSet<>(); - for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof KeywordHitEvent - && (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))) { - indeterminateSetNames.add(((KeywordHitEvent) evt).getSetName()); - } - } - - List<TreeItemDTO<KeywordListSearchParam>> allSets = new ArrayList<>(); - try { - // get artifact types and counts - SleuthkitCase skCase = getCase(); - String query = " res.set_name, COUNT(*) AS count \n" - + "FROM ( \n" - + " SELECT art.artifact_id, \n" - + " (SELECT value_text \n" - + " FROM blackboard_attributes attr \n" - + " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " LIMIT 1) AS set_name \n" - + " FROM blackboard_artifacts art \n" - + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " \n" - + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = " + dataSourceId + " \n") - + ") res \n" - + "GROUP BY res.set_name\n" - + "ORDER BY res.set_name"; - - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - String setName = resultSet.getString("set_name"); - - TreeDisplayCount displayCount = indeterminateSetNames.contains(setName) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(resultSet.getLong("count")); - - allSets.add(getKeywordListTreeItem( - dataSourceId, - setName, - StringUtils.isBlank(setName) ? nullSetName : setName, - displayCount)); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); - } - }); - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching keyword set hits.", ex); - } - - Collections.sort(allSets, (a, b) -> compareStrings(a.getSearchParams().getSetName(), b.getSearchParams().getSetName())); - - return new TreeResultsDTO<>(allSets); - } - - /** - * Compares strings to properly order for the tree. - * - * @param a The first string. - * @param b The second string. - * - * @return The comparator result. - */ - private int compareStrings(String a, String b) { - if (a == null && b == null) { - return 0; - } else if (a == null) { - return -1; - } else if (b == null) { - return 1; - } else { - return a.compareToIgnoreCase(b); - } - } - - /** - * Returns the search term counts for a set name of keyword search results. - * - * @param setName The set name. - * @param dataSourceId The data source id or null. - * - * @return The search terms and counts. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - @Messages({ - "# {0} - searchTerm", - "AnalysisResultDAO_getKeywordSearchTermCounts_exactMatch={0} (Exact)", - "# {0} - searchTerm", - "AnalysisResultDAO_getKeywordSearchTermCounts_substringMatch={0} (Substring)", - "# {0} - searchTerm", - "AnalysisResultDAO_getKeywordSearchTermCounts_regexMatch={0} (Regex)",}) - public TreeResultsDTO<? extends KeywordSearchTermParams> getKeywordSearchTermCounts(String setName, Long dataSourceId) throws IllegalArgumentException, ExecutionException { - if (dataSourceId != null && dataSourceId <= 0) { - throw new IllegalArgumentException("Expected data source id to be > 0"); - } - - Set<Pair<String, TskData.KeywordSearchQueryType>> indeterminateSearchTerms = new HashSet<>(); - for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof KeywordHitEvent - && (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId)) - && evt.getArtifactType().equals(BlackboardArtifact.Type.TSK_KEYWORD_HIT) - && Objects.equals(((KeywordHitEvent) evt).getSetName(), setName)) { - - KeywordHitEvent keywordEvt = (KeywordHitEvent) evt; - indeterminateSearchTerms.add(Pair.of(keywordEvt.getSearchString(), keywordEvt.getSearchType())); - } - } - - String dataSourceClause = dataSourceId == null - ? "" - : "AND art.data_source_obj_id = ?\n"; - - String setNameClause = setName == null - ? "attr_res.set_name IS NULL" - : "attr_res.set_name = ?"; - - String query = "res.search_term,\n" - + " res.search_type,\n" - // this should be unique for each one - + " MIN(res.configuration) AS configuration,\n" - + " SUM(res.count) AS count,\n" - + " -- when there are multiple keyword groupings, return true for has children\n" - + " CASE\n" - + " WHEN COUNT(*) > 1 THEN 1\n" - + " ELSE 0\n" - + " END AS has_children\n" - + "FROM (\n" - + " -- get keyword value, search type, search term, and count grouped by (keyword, regex, search_type) " - + " -- in order to determine if groupings have children\n" - + " SELECT \n" - + " attr_res.keyword, \n" - + " attr_res.search_type,\n" - + " MIN(attr_res.configuration) AS configuration,\n" - + " COUNT(*) AS count,\n" - + " CASE \n" - + " WHEN attr_res.search_type = 0 OR attr_res.regexp_str IS NULL THEN \n" - + " attr_res.keyword\n" - + " ELSE \n" - + " attr_res.regexp_str\n" - + " END AS search_term\n" - + " FROM (\n" - + " -- get pertinent attribute values for artifacts\n" - + " SELECT art.artifact_id, \n" - + " ar.configuration,\n" - + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " LIMIT 1) AS set_name,\n" - + " (SELECT value_int32 FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " LIMIT 1) AS search_type,\n" - + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " LIMIT 1) AS regexp_str,\n" - + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " LIMIT 1) AS keyword\n" - + " FROM blackboard_artifacts art\n" - + " LEFT JOIN tsk_analysis_results ar ON ar.artifact_obj_id = art.artifact_obj_id\n" - + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + "\n" - + dataSourceClause - + " ) attr_res\n" - + " WHERE " + setNameClause + "\n" - + " GROUP BY attr_res.regexp_str, attr_res.keyword, attr_res.search_type\n" - + ") res\n" - + "GROUP BY res.search_term, res.search_type\n" - + "ORDER BY res.search_term, res.search_type"; - - // get artifact types and counts - try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(query)) { - - int paramIdx = 0; - if (dataSourceId != null) { - preparedStatement.setLong(++paramIdx, dataSourceId); - } - - if (setName != null) { - preparedStatement.setString(++paramIdx, setName); - } - - List<TreeItemDTO<KeywordSearchTermParams>> items = new ArrayList<>(); - getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - while (resultSet.next()) { - String searchTerm = resultSet.getString("search_term"); - int searchType = resultSet.getInt("search_type"); - long count = resultSet.getLong("count"); - boolean hasChildren = resultSet.getBoolean("has_children"); - // only a unique applicable configuration if no child tree nodes - String configuration = resultSet.getString("configuration"); - - TskData.KeywordSearchQueryType searchTypeEnum - = Stream.of(TskData.KeywordSearchQueryType.values()) - .filter(tp -> tp.getType() == searchType) - .findFirst() - .orElse(TskData.KeywordSearchQueryType.LITERAL); - - String searchTermModified = getSearchTermDisplayName(searchTerm, searchTypeEnum); - - TreeDisplayCount displayCount = indeterminateSearchTerms.contains(Pair.of(searchTerm, searchType)) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(count); - - TreeItemDTO<KeywordSearchTermParams> treeItem = new TreeItemDTO<>( - KeywordSearchTermParams.getTypeId(), - new KeywordSearchTermParams(setName, searchTerm, TskData.KeywordSearchQueryType.valueOf(searchType), configuration, hasChildren, dataSourceId), - searchTermModified, - searchTermModified, - displayCount - ); - - items.add(treeItem); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching results from result set.", ex); - } - }); - - return new TreeResultsDTO<>(items); - - } catch (SQLException | NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching set counts", ex); - } - } - - /** - * Returns the UI display name for a search term. - * - * @param searchTerm The search term. - * @param searchType The search type enum value. - * - * @return The display name. - */ - public String getSearchTermDisplayName(String searchTerm, TskData.KeywordSearchQueryType searchType) { - String searchTermModified; - switch (searchType) { - case LITERAL: - searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_exactMatch(searchTerm == null ? "" : searchTerm); - break; - case SUBSTRING: - searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_substringMatch(searchTerm == null ? "" : searchTerm); - break; - case REGEX: - searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_regexMatch(searchTerm == null ? "" : searchTerm); - break; - default: - logger.log(Level.WARNING, MessageFormat.format("Non-standard search type value: {0}.", searchType)); - searchTermModified = searchTerm == null ? "" : searchTerm; - break; - } - return searchTermModified; - } - - /** - * Get counts for string matches of a particular regex/substring search - * term. - * - * @param setName The set name or null if no set name. - * @param regexStr The regex string. Must be non-null. - * @param searchType The value for the search type attribute. - * @param dataSourceId The data source id or null. - * - * @return The results - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<? extends KeywordHitSearchParam> getKeywordMatchCounts(String setName, String regexStr, TskData.KeywordSearchQueryType searchType, Long dataSourceId) throws IllegalArgumentException, ExecutionException { - if (dataSourceId != null && dataSourceId <= 0) { - throw new IllegalArgumentException("Expected data source id to be > 0"); - } - - String dataSourceClause = dataSourceId == null - ? "" - : "AND data_source_obj_id = ?\n"; - - String setNameClause = setName == null - ? "res.set_name IS NULL" - : "res.set_name = ?"; - - String query = "keyword, \n" - + " MIN(configuration) AS configuration,\n" - + " COUNT(*) AS count \n" - + "FROM (\n" - + " SELECT art.artifact_id, \n" - + " ar.configuration," - + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " LIMIT 1) AS set_name,\n" - + " (SELECT value_int32 FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " LIMIT 1) AS search_type,\n" - + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " LIMIT 1) AS regexp_str,\n" - + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " - + BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " LIMIT 1) AS keyword\n" - + " FROM blackboard_artifacts art\n" - + " LEFT JOIN tsk_analysis_results ar ON art.artifact_obj_id = ar.artifact_obj_id\n" - + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + "\n" - + dataSourceClause - + ") res\n" - + "-- TODO replace\n" - + "WHERE " + setNameClause + "\n" - + "AND res.regexp_str = ?\n" - + "AND res.search_type = ?\n" - + "GROUP BY keyword"; - - Set<String> indeterminateMatches = new HashSet<>(); - for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof KeywordHitEvent - && (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId)) - && evt.getArtifactType().equals(BlackboardArtifact.Type.TSK_KEYWORD_HIT)) { - - KeywordHitEvent keywordEvt = (KeywordHitEvent) evt; - if (Objects.equals(keywordEvt.getSetName(), setName) - && Objects.equals(keywordEvt.getSearchString(), regexStr) - && keywordEvt.getSearchType() == searchType) { - - indeterminateMatches.add(keywordEvt.getMatch()); - } - - } - } - - try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(query)) { - // get artifact types and counts - int paramIdx = 0; - if (dataSourceId != null) { - preparedStatement.setLong(++paramIdx, dataSourceId); - } - - if (setName != null) { - preparedStatement.setString(++paramIdx, setName); - } - - preparedStatement.setString(++paramIdx, regexStr); - preparedStatement.setInt(++paramIdx, searchType.getType()); - - List<TreeItemDTO<KeywordHitSearchParam>> items = new ArrayList<>(); - getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - while (resultSet.next()) { - String keyword = resultSet.getString("keyword"); - String configuration = resultSet.getString("configuration"); - long count = resultSet.getLong("count"); - - TreeDisplayCount displayCount = indeterminateMatches.contains(keyword) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(count); - - items.add(createKWHitsTreeItem(dataSourceId, setName, keyword, regexStr, searchType, configuration, displayCount)); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching results from result set.", ex); - } - }); - - return new TreeResultsDTO<>(items); - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - throw new ExecutionException("An error occurred while fetching keyword counts", ex); - } - } - - private static TreeItemDTO<KeywordHitSearchParam> createKWHitsTreeItem( - Long dataSourceId, String setName, String keyword, String regexStr, - TskData.KeywordSearchQueryType searchType, String configuration, TreeDisplayCount displayCount) { - - return new TreeItemDTO<>( - KeywordHitSearchParam.getTypeId(), - new KeywordHitSearchParam(dataSourceId, setName, keyword, regexStr, searchType, configuration), - keyword == null ? "" : keyword, - keyword == null ? "" : keyword, - displayCount - ); - } - - @Override - void clearCaches() { - this.analysisResultCache.invalidateAll(); - this.keywordHitCache.invalidateAll(); - this.configHitCache.invalidateAll(); - this.handleIngestComplete(); - } - - /** - * Returns key data for keyword hit artifacts to be used for clearing caches - * and generating events. - * - * @param art The keyword hit artifact. - * - * @return A pair of the KeywordMatchParams with a null data source and the - * data source id. The params have a null data source so that they - * can be indexed quickly. - * - * @throws TskCoreException - */ - private Pair<KeywordHitSearchParam, Long> getKeywordEvtData(BlackboardArtifact art) throws TskCoreException { - long dataSourceId = art.getDataSourceObjectID(); - String setName = null; - String searchTerm = null; - String keywordMatch = null; - // assume literal unless otherwise specified - TskData.KeywordSearchQueryType searchType = TskData.KeywordSearchQueryType.LITERAL; - - for (BlackboardAttribute attr : art.getAttributes()) { - if (BlackboardAttribute.Type.TSK_SET_NAME.equals(attr.getAttributeType())) { - setName = attr.getValueString(); - } else if (BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.equals(attr.getAttributeType())) { - try { - searchType = TskData.KeywordSearchQueryType.valueOf(attr.getValueInt()); - } catch (IllegalArgumentException ex) { - logger.log(Level.WARNING, "An error occurred while getting search type value for value: " + attr.getValueInt(), ex); - } - } else if (BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.equals(attr.getAttributeType())) { - searchTerm = attr.getValueString(); - } else if (BlackboardAttribute.Type.TSK_KEYWORD.equals(attr.getAttributeType())) { - keywordMatch = attr.getValueString(); - } - } - - String configuration = (art instanceof AnalysisResult) ? ((AnalysisResult) art).getConfiguration() : null; - - // data source id is null for KeywordHitSearchParam so that key lookups can be done without data source id. - return Pair.of(new KeywordHitSearchParam(null, setName, keywordMatch, searchTerm, searchType, configuration), dataSourceId); - } - - @Override - Set<? extends DAOEvent> processEvent(PropertyChangeEvent evt) { - - if (evt.getPropertyName().equals(Case.Events.ANALYSIS_RESULT_DELETED.toString())) { - clearCaches(); - - Set<DeleteAnalysisResultEvent> events = new HashSet<>(); - events.add(new DeleteAnalysisResultEvent(DAOEvent.Type.RESULT, ((TskDataModelObjectsDeletedEvent) evt).getOldValue())); - events.add(new DeleteAnalysisResultEvent(DAOEvent.Type.TREE, ((TskDataModelObjectsDeletedEvent) evt).getOldValue())); - - return events; - } - - // get a grouping of artifacts mapping the artifact type id to data source id. - Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> configMap = new HashMap<>(); - Map<KeywordHitSearchParam, Set<Long>> keywordHitsMap = new HashMap<>(); - Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap = new HashMap<>(); - - ModuleDataEvent dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt); - if (dataEvt != null) { - for (BlackboardArtifact art : dataEvt.getArtifacts()) { - try { - if (art.getArtifactTypeID() == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) { - Pair<KeywordHitSearchParam, Long> keywordData = getKeywordEvtData(art); - keywordHitsMap.computeIfAbsent(keywordData.getKey(), (k) -> new HashSet<>()) - .add(keywordData.getValue()); - } else if (BlackboardArtifact.Category.ANALYSIS_RESULT.equals(art.getType().getCategory())) { - analysisResultMap.computeIfAbsent(art.getType(), (k) -> new HashSet<>()) - .add(art.getDataSourceObjectID()); - - String configuration = (art instanceof AnalysisResult) ? ((AnalysisResult) art).getConfiguration() : null; - - configMap.computeIfAbsent(Pair.of(art.getType(), configuration), (k) -> new HashSet<>()) - .add(art.getDataSourceObjectID()); - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to fetch necessary information for artifact id: " + art.getId(), ex); - } - } - } - - // don't continue if no relevant items found - if (analysisResultMap.isEmpty() && configMap.isEmpty() && keywordHitsMap.isEmpty()) { - return Collections.emptySet(); - } - - SubDAOUtils.invalidateKeys(this.analysisResultCache, ar -> Pair.of(ar.getArtifactType(), ar.getDataSourceId()), analysisResultMap); - SubDAOUtils.invalidateKeys(this.configHitCache, ar -> Pair.of(Pair.of(ar.getArtifactType(), ar.getConfiguration()), ar.getDataSourceId()), configMap); - SubDAOUtils.invalidateKeys(this.keywordHitCache, kw -> Pair.of( - // null data source for lookup - new KeywordHitSearchParam(null, kw.getSetName(), kw.getKeyword(), kw.getRegex(), kw.getSearchType(), kw.getConfiguration()), - kw.getDataSourceId() - ), keywordHitsMap); - - return getResultViewEvents(configMap, keywordHitsMap, IngestManager.getInstance().isIngestRunning()); - } - - /** - * Generate result view events from digest of Autopsy events. - * - * @param resultsWithConfigMap Contains the analysis results that do use a - * set name. A mapping of (analysis result type - * id, set name) to data sources where results - * were created. - * @param keywordHitsMap Contains the keyword hits mapping parameters - * to data source. The data source in the - * parameters is null. - * @param ingestIsRunning Whether or not ingest is running. - * - * @return The list of dao events. - */ - private Set<? extends DAOEvent> getResultViewEvents( - Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> resultsWithConfigMap, - Map<KeywordHitSearchParam, Set<Long>> keywordHitsMap, - boolean ingestIsRunning) { - - List<AnalysisResultEvent> AnalysisResultEvents = resultsWithConfigMap.entrySet().stream() - .flatMap(entry -> entry.getValue().stream().map(dsId -> new AnalysisResultEvent(entry.getKey().getLeft(), entry.getKey().getRight(), dsId))) - .collect(Collectors.toList()); - - // divide into ad hoc searches (null set name) and the rest - Map<Boolean, List<KeywordHitEvent>> keywordHitEvts = keywordHitsMap.entrySet().stream() - .flatMap(entry -> { - KeywordHitSearchParam params = entry.getKey(); - String setName = params.getSetName(); - String searchString = params.getRegex(); - TskData.KeywordSearchQueryType queryType = params.getSearchType(); - String match = params.getKeyword(); - return entry.getValue().stream().map(dsId -> new KeywordHitEvent(setName, searchString, queryType, match, params.getConfiguration(), dsId)); - }) - .collect(Collectors.partitioningBy(kwe -> kwe.getSetName() == null)); - - // include config results in regular events. - List<AnalysisResultEvent> daoEvents = Stream.of(AnalysisResultEvents, keywordHitEvts.get(false)) - .filter(lst -> lst != null) - .flatMap(s -> s.stream()) - .collect(Collectors.toList()); - - // send immediate updates to tree if ingest is not running - Collection<TreeEvent> treeEvents; - if (ingestIsRunning) { - treeEvents = this.treeCounts.enqueueAll(daoEvents).stream() - .map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeDisplayCount.INDETERMINATE), false)) - .collect(Collectors.toList()); - } else { - treeEvents = daoEvents.stream() - .map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toList()); - } - - List<KeywordHitEvent> adHocEvts = keywordHitEvts.get(true); - if (CollectionUtils.isEmpty(adHocEvts)) { - adHocEvts = Collections.emptyList(); - } - - // ad hoc events are always immediate updates. - Collection<TreeEvent> adHocTreeEvents = adHocEvts.stream() - .map(kwEvt -> new TreeEvent(getTreeItem(kwEvt, TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toList()); - - return Stream.of(daoEvents, treeEvents, adHocEvts, adHocTreeEvents) - .flatMap(lst -> lst.stream()) - .collect(Collectors.toSet()); - } - - /** - * Creates a TreeItemDTO instance based on the analysis result event and - * whether or not this event should trigger a full refresh of counts. - * - * @param arEvt The analysis result event. - * @param displayCount The count to display. - * - * @return The tree event. - */ - private TreeItemDTO<?> getTreeItem(AnalysisResultEvent arEvt, TreeDisplayCount displayCount) { - if (arEvt instanceof KeywordHitEvent) { - KeywordHitEvent khEvt = (KeywordHitEvent) arEvt; - return createKWHitsTreeItem( - khEvt.getDataSourceId(), - khEvt.getSetName(), - khEvt.getMatch(), - khEvt.getSearchString(), - khEvt.getSearchType(), - khEvt.getConfiguration(), - displayCount - ); - } else { - return getConfigTreeItem( - arEvt.getArtifactType(), - arEvt.getDataSourceId(), - arEvt.getConfiguration(), - StringUtils.isBlank(arEvt.getConfiguration()) ? arEvt.getArtifactType().getDisplayName() : arEvt.getConfiguration(), - displayCount); - } - } - - private TreeItemDTO<AnalysisResultSearchParam> getConfigTreeItem(BlackboardArtifact.Type type, - Long dataSourceId, String configuration, String displayName, TreeDisplayCount displayCount) { - - return new TreeItemDTO<>( - AnalysisResultSearchParam.getTypeId(), - new AnalysisResultSearchParam(type, configuration, dataSourceId), - configuration == null ? 0 : configuration, - displayName, - displayCount); - } - - private TreeItemDTO<KeywordListSearchParam> getKeywordListTreeItem( - Long dataSourceId, String setName, String displayName, TreeDisplayCount displayCount) { - - return new TreeItemDTO<>( - KeywordListSearchParam.getTypeId(), - // there are one to many for keyword lists to configuration so leave as null - new KeywordListSearchParam(dataSourceId, null, setName), - setName == null ? 0 : setName, - displayName, - displayCount); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return SubDAOUtils.getIngestCompleteEvents(this.treeCounts, (arEvt, count) -> getTreeItem(arEvt, count)); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEvents(this.treeCounts, (arEvt, count) -> getTreeItem(arEvt, count)); - - } - - /** - * Returns all the configurations for keyword hits for the given filtering - * parameters. - * - * @param setName The set name as defined by TSK_SET_NAME. If null, - * assumed to be ad hoc result. - * @param dataSourceId The data source object id. If null, no filtering by - * data source occurs. - * - * @return The distinct configurations. - * - * @throws ExecutionException - */ - public List<String> getKeywordHitConfigurations(String setName, Long dataSourceId) throws ExecutionException { - String kwHitClause = "art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID(); - - String setNameClause = setName == null - // if set name is null, then there should be no set name attribute associated with this - ? "(SELECT " - + " COUNT(*) FROM blackboard_attributes attr " - + " WHERE attr.artifact_id = art.artifact_id " - + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() - + " AND attr.value_text IS NOT NULL " - + " AND attr.value_text <> '' " - + " LIMIT 1) = 0 " - // otherwise, see if the set name attribute matches expected value - : "? IN (SELECT attr.value_text FROM blackboard_attributes attr " - + " WHERE attr.artifact_id = art.artifact_id " - + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() - + " )"; - - String dataSourceClause = dataSourceId == null - ? null - : "art.data_source_obj_id = ?"; - - String clauses = Stream.of(kwHitClause, setNameClause, dataSourceClause) - .filter(s -> s != null) - .map(s -> " (" + s + ") ") - .collect(Collectors.joining("AND\n")); - - String query = "DISTINCT(ar.configuration) AS configuration \n" - + "FROM tsk_analysis_results ar\n" - + "LEFT JOIN blackboard_artifacts art ON ar.artifact_obj_id = art.artifact_obj_id\n" - + "WHERE " + clauses; - - // get artifact types and counts - try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(query)) { - - int paramIdx = 0; - - if (setName != null) { - preparedStatement.setString(++paramIdx, setName); - } - - if (dataSourceId != null) { - preparedStatement.setLong(++paramIdx, dataSourceId); - } - - List<String> configurations = new ArrayList<>(); - getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - while (resultSet.next()) { - configurations.add(resultSet.getString("configuration")); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching results from result set.", ex); - } - }); - - return configurations; - - } catch (SQLException | NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException(MessageFormat.format( - "An error occurred while fetching configurations for counts where setName = {0}", - setName == null ? "<null>" : setName), - ex); - } - } - - /** - * A tree item for an analysis result that can indicate if it has child tree - * nodes due to configuration. - */ - public static class AnalysisResultTreeItem extends TreeItemDTO<AnalysisResultSearchParam> { - - private final Optional<Boolean> hasChildren; - - public AnalysisResultTreeItem(BlackboardArtifact.Type type, String configuration, Long dataSourceId, TreeDisplayCount displayCount, Boolean hasChildren) { - super(AnalysisResultSearchParam.getTypeId(), - new AnalysisResultSearchParam(type, configuration, dataSourceId), - type.getTypeID(), - type.getDisplayName(), - displayCount); - - this.hasChildren = Optional.ofNullable(hasChildren); - } - - /** - * @return Present if known; true if there are nested tree children. - */ - public Optional<Boolean> getHasChildren() { - return hasChildren; - } - } - - /** - * Handles fetching and paging of analysis results. - */ - public static class AnalysisResultFetcher extends DAOFetcher<AnalysisResultSearchParam> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public AnalysisResultFetcher(AnalysisResultSearchParam params) { - super(params); - } - - protected AnalysisResultDAO getDAO() { - return MainDAO.getInstance().getAnalysisResultDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getAnalysisResultsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isAnalysisResultsInvalidating(this.getParameters(), evt); - } - } - - /** - * Handles fetching and paging of configuration filtered results. - */ - public static class AnalysisResultConfigFetcher extends DAOFetcher<AnalysisResultSearchParam> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public AnalysisResultConfigFetcher(AnalysisResultSearchParam params) { - super(params); - } - - protected AnalysisResultDAO getDAO() { - return MainDAO.getInstance().getAnalysisResultDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getAnalysisResultConfigResults(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isAnalysisResultsConfigInvalidating(this.getParameters(), evt); - } - } - - /** - * Handles fetching and paging of keyword hits. - */ - public static class KeywordHitResultFetcher extends DAOFetcher<KeywordHitSearchParam> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public KeywordHitResultFetcher(KeywordHitSearchParam params) { - super(params); - } - - protected AnalysisResultDAO getDAO() { - return MainDAO.getInstance().getAnalysisResultDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getKeywordHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isKeywordHitInvalidating(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultRowDTO.java deleted file mode 100644 index 6065c0b112d8c707e87e0f35fda3dcad4f814d02..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultRowDTO.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.AnalysisResult; - -/** - * A result for an analysis result. - */ -public class AnalysisResultRowDTO extends ArtifactRowDTO<AnalysisResult> { - - private static final String TYPE_ID = "ANALYSIS_RESULT"; - - public static String getTypeIdForClass() { - return TYPE_ID; - } - - public AnalysisResultRowDTO(AnalysisResult analysisResult, Content srcContent, boolean isTimelineSupported, List<Object> cellValues, long id) { - super(analysisResult, srcContent, null, isTimelineSupported, cellValues, TYPE_ID, id); - } - - public AnalysisResult getAnalysisResult() { - return getArtifact(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSearchParam.java deleted file mode 100644 index ccf06dbeeafdb30679027b718231f227bfbf127f..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSearchParam.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Key for analysis result in order to retrieve data from DAO. - */ -public class AnalysisResultSearchParam extends BlackboardArtifactSearchParam { - - private static final String TYPE_ID = BlackboardArtifact.Category.ANALYSIS_RESULT.name(); - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - final String configuration; - - public AnalysisResultSearchParam(BlackboardArtifact.Type artifactType, String configuration, Long dataSourceId) { - super(artifactType, dataSourceId); - this.configuration = configuration; - } - - public String getConfiguration() { - return configuration; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 29 * hash + Objects.hashCode(this.configuration); - hash = 29 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AnalysisResultSearchParam other = (AnalysisResultSearchParam) obj; - if (!Objects.equals(this.configuration, other.configuration)) { - return false; - } - return super.equals(obj); - } - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultTableSearchResultsDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultTableSearchResultsDTO.java deleted file mode 100644 index de243f83eccca6f267c3f3e039518dabf852d027..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultTableSearchResultsDTO.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Search results for analysis results. - */ -public class AnalysisResultTableSearchResultsDTO extends BaseSearchResultsDTO { - - private static final String TYPE_ID = "ANALYSIS_RESULT"; - private static final String SIGNATURE = "analysisresult"; - - private final BlackboardArtifact.Type artifactType; - - public AnalysisResultTableSearchResultsDTO(BlackboardArtifact.Type artifactType, List<ColumnKey> columns, List<RowDTO> items, long startItem, long totalResultsCount) { - super(TYPE_ID, artifactType.getDisplayName(), columns, items, SIGNATURE, startItem, totalResultsCount); - this.artifactType = artifactType; - } - - public BlackboardArtifact.Type getArtifactType() { - return artifactType; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ArtifactRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ArtifactRowDTO.java deleted file mode 100755 index c5e23623ff873056095b45b7e30824f7febc5704..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ArtifactRowDTO.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; - -/** - * Abstract base class for a row result representing a BlackboardArtifact. - * - * @param <T> - */ -public abstract class ArtifactRowDTO<T extends BlackboardArtifact> extends BaseRowDTO{ - - private final T artifact; - private final Content srcContent; - private final Content linkedFile; - private final boolean isTimelineSupported; - - ArtifactRowDTO(T artifact, Content srcContent, Content linkedFile, boolean isTimelineSupported, List<Object> cellValues, String typeId, long id) { - super(cellValues, typeId, id); - this.artifact = artifact; - this.srcContent = srcContent; - this.linkedFile = linkedFile; - this.isTimelineSupported = isTimelineSupported; - } - - /** - * Returns the artifact for this row result. - * - * @return - */ - public T getArtifact() { - return artifact; - } - - /** - * Returns the source content for the artifact row. - * - * @return The source content. - */ - public Content getSrcContent() { - return srcContent; - } - - /** - * Returns the file linked with this artifact row. - * - * @return The linked file. - */ - public Content getLinkedFile() { - return linkedFile; - } - - /** - * Returns whether the artifact supported timeline events. - * - * @return True if timeline is supported for this artifact. - */ - public boolean isTimelineSupported() { - return isTimelineSupported; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BaseRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BaseRowDTO.java deleted file mode 100644 index a81f60d8aa09b879e02f96de8ef8a273a5316747..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BaseRowDTO.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; - -/** - * Base implementation for a row result. - */ -public class BaseRowDTO implements RowDTO { - - private final List<Object> cellValues; - private final long id; - private final String typeId; - - public BaseRowDTO(List<Object> cellValues, String typeId, long id) { - this.cellValues = cellValues; - this.id = id; - this.typeId = typeId; - } - - @Override - public List<Object> getCellValues() { - return cellValues; - } - - @Override - public long getId() { - return id; - } - - @Override - public String getTypeId() { - return typeId; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 23 * hash + (int) (this.id ^ (this.id >>> 32)); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final BaseRowDTO other = (BaseRowDTO) obj; - if (this.id != other.id) { - return false; - } - return true; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BaseSearchResultsDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BaseSearchResultsDTO.java deleted file mode 100644 index 1d0d2aa87e7afa4f31d0863a8fa69f094b66bfc8..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BaseSearchResultsDTO.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.DataSource; - -/** - * Base implementation for a series of results to be displayed in the results viewer. - */ -public class BaseSearchResultsDTO implements SearchResultsDTO { - - private final String typeId; - private final String displayName; - private final List<ColumnKey> columns; - private final List<RowDTO> items; - private final long totalResultsCount; - private final long startItem; - private final String signature; - private final DataSource parentDataSource; - - public BaseSearchResultsDTO(String typeId, String displayName, List<ColumnKey> columns, List<RowDTO> items, String signature) { - this(typeId, displayName, columns, items, signature, 0, items == null ? 0 : items.size()); - } - - public BaseSearchResultsDTO(String typeId, String displayName, List<ColumnKey> columns, List<RowDTO> items, String signature, long startItem, long totalResultsCount) { - this(typeId, displayName, columns, items, signature, startItem, totalResultsCount, null); - } - - public BaseSearchResultsDTO(String typeId, String displayName, List<ColumnKey> columns, List<RowDTO> items, String signature, long startItem, long totalResultsCount, DataSource parentDataSource) { - this.typeId = typeId; - this.displayName = displayName; - this.columns = columns; - this.items = items; - this.startItem = startItem; - this.totalResultsCount = totalResultsCount; - this.signature = signature; - this.parentDataSource = parentDataSource; - } - - @Override - public String getTypeId() { - return typeId; - } - - @Override - public String getDisplayName() { - return displayName; - } - - @Override - public List<ColumnKey> getColumns() { - return columns; - } - - @Override - public List<RowDTO> getItems() { - return items; - } - - @Override - public long getTotalResultsCount() { - return totalResultsCount; - } - - @Override - public long getStartItem() { - return startItem; - } - - @Override - public String getSignature() { - return signature; - } - - @Override - public DataSource getDataSourceParent() { - return parentDataSource; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java deleted file mode 100644 index 02c531798cdb44bd90706304169087379352d080..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java +++ /dev/null @@ -1,526 +0,0 @@ -package org.sleuthkit.autopsy.mainui.datamodel; - -import com.google.common.collect.ImmutableSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.openide.util.NbBundle; -import org.python.google.common.collect.Sets; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.sco.SCOUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_DATA_SOURCE_USAGE; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_GEN_INFO; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.HostAddress; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.Volume; -import org.sleuthkit.datamodel.VolumeSystem; - -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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. - */ -/** - * Base class for common methods. - */ -@NbBundle.Messages({ - "BlackboardArtifactDAO.columnKeys.srcFile.name=Source Name", - "BlackboardArtifactDAO.columnKeys.srcFile.displayName=Source Name", - "BlackboardArtifactDAO.columnKeys.srcFile.description=Source Name", - "BlackboardArtifactDAO.columnKeys.dataSource.name=Data Source", - "BlackboardArtifactDAO.columnKeys.dataSource.displayName=Data Source", - "BlackboardArtifactDAO.columnKeys.dataSource.description=Data Source" -}) -abstract class BlackboardArtifactDAO extends AbstractDAO { - - private static Logger logger = Logger.getLogger(BlackboardArtifactDAO.class.getName()); - - static final int EMAIL_CONTENT_MAX_LEN = 160; - static final int TOOL_TEXT_MAX_LEN = 512; - static final String ELLIPSIS = "..."; - - @SuppressWarnings("deprecation") - static final Set<Integer> HIDDEN_ATTR_TYPES = ImmutableSet.of( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID(), - BlackboardAttribute.Type.TSK_ASSOCIATED_ARTIFACT.getTypeID(), - BlackboardAttribute.Type.TSK_SET_NAME.getTypeID(), - BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.getTypeID(), - BlackboardAttribute.Type.TSK_PATH_ID.getTypeID() - ); - static final Set<Integer> HIDDEN_EMAIL_ATTR_TYPES = ImmutableSet.of( - BlackboardAttribute.Type.TSK_DATETIME_SENT.getTypeID(), - BlackboardAttribute.Type.TSK_EMAIL_CONTENT_HTML.getTypeID(), - BlackboardAttribute.Type.TSK_EMAIL_CONTENT_RTF.getTypeID(), - BlackboardAttribute.Type.TSK_EMAIL_BCC.getTypeID(), - BlackboardAttribute.Type.TSK_EMAIL_CC.getTypeID(), - BlackboardAttribute.Type.TSK_HEADERS.getTypeID() - ); - - static final ColumnKey SRC_FILE_COL = new ColumnKey( - Bundle.BlackboardArtifactDAO_columnKeys_srcFile_name(), - Bundle.BlackboardArtifactDAO_columnKeys_srcFile_displayName(), - Bundle.BlackboardArtifactDAO_columnKeys_srcFile_description() - ); - - static final ColumnKey S_COL = new ColumnKey( - SCOUtils.SCORE_COLUMN_NAME, - SCOUtils.SCORE_COLUMN_NAME, - SCOUtils.SCORE_COLUMN_NAME - ); - - static final ColumnKey C_COL = new ColumnKey( - SCOUtils.COMMENT_COLUMN_NAME, - SCOUtils.COMMENT_COLUMN_NAME, - SCOUtils.COMMENT_COLUMN_NAME - ); - - static final ColumnKey O_COL = new ColumnKey( - SCOUtils.OCCURANCES_COLUMN_NAME, - SCOUtils.OCCURANCES_COLUMN_NAME, - SCOUtils.OCCURANCES_COLUMN_NAME - ); - - static final ColumnKey DATASOURCE_COL = new ColumnKey( - Bundle.BlackboardArtifactDAO_columnKeys_dataSource_name(), - Bundle.BlackboardArtifactDAO_columnKeys_dataSource_displayName(), - Bundle.BlackboardArtifactDAO_columnKeys_dataSource_description() - ); - - /** - * Types that should not be shown in the tree. - */ - @SuppressWarnings("deprecation") - private static final Set<BlackboardArtifact.Type> IGNORED_TYPES = Sets.newHashSet( - // these are shown in other parts of the UI (and different node types) - TSK_DATA_SOURCE_USAGE, - TSK_GEN_INFO, - new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE), - TSK_TL_EVENT, - //This is not meant to be shown in the UI at all. It is more of a meta artifact. - TSK_ASSOCIATED_OBJECT - ); - - static final String IGNORED_TYPES_SQL_SET = IGNORED_TYPES.stream() - .map(tp -> Integer.toString(tp.getTypeID())) - .collect(Collectors.joining(", ")); - - /** - * @return The set of types that are not shown in the tree. - */ - protected static Set<BlackboardArtifact.Type> getIgnoredTreeTypes() { - return IGNORED_TYPES; - } - - TableData createTableData(BlackboardArtifact.Type artType, List<? extends BlackboardArtifact> arts) throws TskCoreException, NoCurrentCaseException { - // A linked hashmap is being used for artifactAttributes to ensure that artifact order - // as well as attribute orders within those artifacts are preserved. This is to maintain - // a consistent ordering of attribute columns as received from BlackboardArtifact.getAttributes - Map<Long, Map<BlackboardAttribute.Type, Object>> artifactAttributes = new LinkedHashMap<>(); - for (BlackboardArtifact art : arts) { - BlackboardArtifact.Type thisArtType = artType != null ? artType : art.getType(); - Map<BlackboardAttribute.Type, Object> attrs = art.getAttributes().stream() - .filter(attr -> isRenderedAttr(thisArtType, attr.getAttributeType())) - .collect(Collectors.toMap(attr -> attr.getAttributeType(), attr -> getAttrValue(thisArtType, attr), (attr1, attr2) -> attr1, LinkedHashMap::new)); - - artifactAttributes.put(art.getId(), attrs); - } - - // NOTE: this has to be in the same order as values are added - List<BlackboardAttribute.Type> attributeTypeKeys = artifactAttributes.values().stream() - .flatMap(attrs -> attrs.keySet().stream()) - .distinct() - .collect(Collectors.toList()); - - List<ColumnKey> columnKeys = new ArrayList<>(); - columnKeys.add(SRC_FILE_COL); - columnKeys.add(S_COL); - columnKeys.add(C_COL); - columnKeys.add(O_COL); - addAnalysisResultColumnKeys(columnKeys); - columnKeys.addAll(attributeTypeKeys.stream() - .map(attrType -> new ColumnKey(attrType.getTypeName(), attrType.getDisplayName(), attrType.getDisplayName())) - .collect(Collectors.toList())); - columnKeys.add(DATASOURCE_COL); - - // determine all different attribute types present as well as row data for each artifact - List<RowDTO> rows = new ArrayList<>(); - - for (BlackboardArtifact artifact : arts) { - List<Object> cellValues = new ArrayList<>(); - - Content srcContent = artifact.getParent(); - cellValues.add(getDisplayNameBySourceContent(srcContent)); - cellValues.add(null); - cellValues.add(null); - cellValues.add(null); - - addAnalysisResultFields(artifact, cellValues); - - long id = artifact.getId(); - Map<BlackboardAttribute.Type, Object> attrValues = artifactAttributes.getOrDefault(id, Collections.emptyMap()); - // NOTE: this has to be in the same order as attribute keys - for (BlackboardAttribute.Type colAttrType : attributeTypeKeys) { - cellValues.add(attrValues.get(colAttrType)); - } - - String dataSourceName = getDataSourceName(srcContent); - cellValues.add(dataSourceName); - - AbstractFile linkedFile = null; - BlackboardArtifact.Type thisArtType = artType != null ? artType : artifact.getType(); - if (thisArtType.getCategory().equals(BlackboardArtifact.Category.DATA_ARTIFACT)) { - // Note that we need to get the attribute from the original artifact since it is not displayed. - if (artifact.getAttribute(BlackboardAttribute.Type.TSK_PATH_ID) != null) { - long linkedId = artifact.getAttribute(BlackboardAttribute.Type.TSK_PATH_ID).getValueLong(); - linkedFile = linkedId >= 0 - ? getCase().getAbstractFileById(linkedId) - : null; - } - } - - boolean isTimelineSupported = isTimelineSupported(attrValues.keySet()); - - rows.add(createRow(artifact, srcContent, linkedFile, isTimelineSupported, cellValues, id)); - } - - return new TableData(columnKeys, rows); - } - - abstract RowDTO createRow(BlackboardArtifact dataArtifact, Content srcContent, Content linkedFile, boolean isTimelineSupported, List<Object> cellValues, long id); - - void addAnalysisResultColumnKeys(List<ColumnKey> columnKeys) { - // By default, do nothing - } - - void addAnalysisResultFields(BlackboardArtifact artifact, List<Object> cells) throws TskCoreException { - // By default, do nothing - } - - SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - boolean isRenderedAttr(BlackboardArtifact.Type artType, BlackboardAttribute.Type attrType) { - // JSON attributes are always hidden - if (BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON.equals(attrType.getValueType())) { - return false; - } - - if (BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID() == artType.getTypeID()) { - return !HIDDEN_EMAIL_ATTR_TYPES.contains(attrType.getTypeID()); - } else { - return !HIDDEN_ATTR_TYPES.contains(attrType.getTypeID()); - } - } - - private String getTruncated(String str, int maxLen) { - return str.length() > maxLen - ? str.substring(0, maxLen) + ELLIPSIS - : str; - } - - boolean isTimelineSupported(Collection<BlackboardAttribute.Type> attrTypes) { - return attrTypes.stream() - .anyMatch(tp -> BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME.equals(tp.getValueType())); - } - - String getWhereClause(SearchParams<BlackboardArtifactSearchParam> cacheKey) { - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - - String originalWhereClause = " artifacts.artifact_type_id = " + artType.getTypeID() + " "; - if (dataSourceId != null) { - originalWhereClause += " AND artifacts.data_source_obj_id = " + dataSourceId + " "; - } - - String pagedWhereClause = originalWhereClause - + " ORDER BY artifacts.obj_id ASC" - + (cacheKey.getMaxResultsCount() != null && cacheKey.getMaxResultsCount() > 0 ? " LIMIT " + cacheKey.getMaxResultsCount() : "") - + (cacheKey.getStartItem() > 0 ? " OFFSET " + cacheKey.getStartItem() : ""); - return pagedWhereClause; - } - - long getTotalResultsCount(SearchParams<BlackboardArtifactSearchParam> cacheKey, long currentPageSize) throws TskCoreException, NoCurrentCaseException { - Blackboard blackboard = getCase().getBlackboard(); - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - - if ((cacheKey.getStartItem() == 0) // offset is zero AND - && ((cacheKey.getMaxResultsCount() != null && currentPageSize < cacheKey.getMaxResultsCount()) // number of results is less than max - || (cacheKey.getMaxResultsCount() == null))) { // OR max number of results was not specified - return currentPageSize; - } else { - if (dataSourceId != null) { - return blackboard.getArtifactsCount(artType.getTypeID(), dataSourceId); - } else { - return blackboard.getArtifactsCount(artType.getTypeID()); - } - } - } - - /** - * Returns the display name to use based on the source content. - * - * @param srcContent The source content. - * - * @return The display name. - */ - String getDisplayNameBySourceContent(Content srcContent) { - if (srcContent instanceof BlackboardArtifact) { - try { - return ((BlackboardArtifact) srcContent).getShortDescription(); - } catch (TskCoreException ex) { - // Log the error, but set the display name to - // Content.getName so there is something visible to the user. - logger.log(Level.WARNING, "Failed to get short description for artifact id = " + srcContent.getId(), ex); - } - } else if (srcContent instanceof OsAccount) { - return ((OsAccount) srcContent).getAddr().orElse(srcContent.getName()); - } - - return srcContent.getName(); - } - - /** - * Returns a displayable type string for the given content object. - * - * If the content object is a artifact of a custom type then this method may - * cause a DB call BlackboardArtifact.getType - * - * @param source The object to determine the type of. - * - * @return A string representing the content type. - */ - String getSourceObjType(Content source) throws TskCoreException { - if (source instanceof BlackboardArtifact) { - BlackboardArtifact srcArtifact = (BlackboardArtifact) source; - return srcArtifact.getType().getDisplayName(); - } else if (source instanceof Volume) { - return TskData.ObjectType.VOL.toString(); - } else if (source instanceof AbstractFile) { - return TskData.ObjectType.ABSTRACTFILE.toString(); - } else if (source instanceof Image) { - return TskData.ObjectType.IMG.toString(); - } else if (source instanceof VolumeSystem) { - return TskData.ObjectType.VS.toString(); - } else if (source instanceof OsAccount) { - return TskData.ObjectType.OS_ACCOUNT.toString(); - } else if (source instanceof HostAddress) { - return TskData.ObjectType.HOST_ADDRESS.toString(); - } else if (source instanceof Pool) { - return TskData.ObjectType.POOL.toString(); - } - return ""; - } - - String getDataSourceName(Content srcContent) throws TskCoreException { - Content dataSource = srcContent.getDataSource(); - if (dataSource != null) { - return dataSource.getName(); - } else { - return getRootAncestorName(srcContent); - } - } - - /** - * Gets the name of the root ancestor of the source content for the artifact - * represented by this node. - * - * @param srcContent The source content. - * - * @return The root ancestor name or the empty string if an error occurs. - */ - private String getRootAncestorName(Content srcContent) throws TskCoreException { - String parentName = srcContent.getName(); - Content parent = srcContent; - - while ((parent = parent.getParent()) != null) { - parentName = parent.getName(); - } - return parentName; - } - - /** - * Returns a displayable type string for the given content object. - * - * If the content object is a artifact of a custom type then this method may - * cause a DB call BlackboardArtifact.getType - * - * @param source The object to determine the type of. - * - * @return A string representing the content type. - */ -// private String getSourceObjType(Content source) throws TskCoreException { -// if (source instanceof BlackboardArtifact) { -// BlackboardArtifact srcArtifact = (BlackboardArtifact) source; -// return srcArtifact.getType().getDisplayName(); -// } else if (source instanceof Volume) { -// return TskData.ObjectType.VOL.toString(); -// } else if (source instanceof AbstractFile) { -// return TskData.ObjectType.ABSTRACTFILE.toString(); -// } else if (source instanceof Image) { -// return TskData.ObjectType.IMG.toString(); -// } else if (source instanceof VolumeSystem) { -// return TskData.ObjectType.VS.toString(); -// } else if (source instanceof OsAccount) { -// return TskData.ObjectType.OS_ACCOUNT.toString(); -// } else if (source instanceof HostAddress) { -// return TskData.ObjectType.HOST_ADDRESS.toString(); -// } else if (source instanceof Pool) { -// return TskData.ObjectType.POOL.toString(); -// } -// return ""; -// } - @SuppressWarnings("deprecation") - Object getAttrValue(BlackboardArtifact.Type artType, BlackboardAttribute attr) { - - // Handle the special cases - if (artType.equals(BlackboardArtifact.Type.TSK_EMAIL_MSG) - && attr.getAttributeType().equals(BlackboardAttribute.Type.TSK_EMAIL_CONTENT_PLAIN)) { - return getTruncated(attr.getValueString(), EMAIL_CONTENT_MAX_LEN); - } - - /* From BlackboardArtifactNode: - * The truncation of text attributes appears to have been - * motivated by the statement that "RegRipper output would - * often cause the UI to get a black line accross it and - * hang if you hovered over large output or selected it. - */ - if ((BlackboardArtifact.ARTIFACT_TYPE.TSK_TOOL_OUTPUT.getTypeID() == artType.getTypeID()) - && attr.getAttributeType().equals(BlackboardAttribute.Type.TSK_TEXT)) { - return getTruncated(attr.getValueString(), TOOL_TEXT_MAX_LEN); - } - - switch (attr.getAttributeType().getValueType()) { - case BYTE: - return attr.getValueBytes(); - case DATETIME: - return new Date(attr.getValueLong() * 1000); - case DOUBLE: - return attr.getValueDouble(); - case INTEGER: - return attr.getValueInt(); - case JSON: - // We shouldn't get here since JSON attribute are not displayed in the table - return attr.getValueString(); - case LONG: - return attr.getValueLong(); - case STRING: - return attr.getValueString(); - default: - throw new IllegalArgumentException("Unknown attribute type value type: " + attr.getAttributeType().getValueType()); - } - } - - /** - * Returns a list of paged artifacts. - * - * @param arts The artifacts. - * @param searchParams The search parameters including the paging. - * - * @return The list of paged artifacts. - */ - List<BlackboardArtifact> getPaged(List<? extends BlackboardArtifact> arts, SearchParams<?> searchParams) { - Stream<? extends BlackboardArtifact> pagedArtsStream = arts.stream() - .sorted(Comparator.comparing((art) -> art.getId())) - .skip(searchParams.getStartItem()); - - if (searchParams.getMaxResultsCount() != null) { - pagedArtsStream = pagedArtsStream.limit(searchParams.getMaxResultsCount()); - } - - return pagedArtsStream.collect(Collectors.toList()); - } - - static class TableData { - - final List<ColumnKey> columnKeys; - final List<RowDTO> rows; - - TableData(List<ColumnKey> columnKeys, List<RowDTO> rows) { - this.columnKeys = columnKeys; - this.rows = rows; - } - } - - /** - * Returns the count of each artifact type in the category. - * - * @param category The artifact type category. - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * - * @return The mapping of type to count. - * - * @throws NoCurrentCaseException - * @throws TskCoreException - */ - Map<BlackboardArtifact.Type, Long> getCounts(BlackboardArtifact.Category category, Long dataSourceId) throws NoCurrentCaseException, TskCoreException { - - // get artifact types and counts - SleuthkitCase skCase = getCase(); - String query = "artifact_type_id, COUNT(*) AS count " - + " FROM blackboard_artifacts " - + " WHERE artifact_type_id NOT IN (" + IGNORED_TYPES_SQL_SET + ") " - + " AND artifact_type_id IN " - + " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + category.getID() + ")" - + (dataSourceId == null ? "" : (" AND data_source_obj_id = " + dataSourceId + " ")) - + " GROUP BY artifact_type_id"; - Map<BlackboardArtifact.Type, Long> typeCounts = new HashMap<>(); - - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - int artifactTypeId = resultSet.getInt("artifact_type_id"); - BlackboardArtifact.Type type = skCase.getBlackboard().getArtifactType(artifactTypeId); - long count = resultSet.getLong("count"); - typeCounts.put(type, count); - } - } catch (TskCoreException | SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching artifact type counts.", ex); - } - }); - - return typeCounts; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactSearchParam.java deleted file mode 100644 index 944354cdd332e546d64a3908b96706c6939a5e0e..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactSearchParam.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo.BlackboardArtifactNodeSelectionInfo; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Key for data artifact in order to retrieve data from DAO. - */ -public class BlackboardArtifactSearchParam { - - private static final String TYPE_ID = "BLACKBOARD_ARTIFACT"; - - private ChildNodeSelectionInfo nodeSelectionInfo; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final BlackboardArtifact.Type artifactType; - private final Long dataSourceId; - - public BlackboardArtifactSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) { - this.artifactType = artifactType; - this.dataSourceId = dataSourceId; - } - - public BlackboardArtifact.Type getArtifactType() { - return artifactType; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.artifactType); - hash = 67 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final BlackboardArtifactSearchParam other = (BlackboardArtifactSearchParam) obj; - if (!Objects.equals(this.artifactType, other.artifactType)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - public ChildNodeSelectionInfo getNodeSelectionInfo() { - return nodeSelectionInfo; - } - - public void setNodeSelectionInfo(ChildNodeSelectionInfo info) { - nodeSelectionInfo = info; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactTagsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactTagsRowDTO.java deleted file mode 100755 index 693618db12e61dcaf49fb9e19208f02976e3e3d9..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactTagsRowDTO.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.BlackboardArtifactTag; - -/** - * A result row for a BlackboardArtifactTag. - */ -public final class BlackboardArtifactTagsRowDTO extends BaseRowDTO { - - private static final String TYPE_ID = "ARTIFACT_TAG"; - - private final BlackboardArtifactTag tag; - - public BlackboardArtifactTagsRowDTO(BlackboardArtifactTag tag, List<Object> cellValues, long id) { - super(cellValues, TYPE_ID, id); - this.tag = tag; - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - - /** - * Returns the tag for this result row. - * - * @return - */ - public BlackboardArtifactTag getTag() { - return tag; - } - - /** - * Returns the tags display name. - * - * @return The display name for this tag. - */ - public String getDisplayName() { - return getCellValues().size() > 0 - ? getCellValues().get(0).toString() - : ""; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED deleted file mode 100644 index 3c931fa727d70d85131f0377e2c0f34853ed9681..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED +++ /dev/null @@ -1,136 +0,0 @@ -AnalysisResultDAO.columnKeys.conclusion.description=Conclusion -AnalysisResultDAO.columnKeys.conclusion.displayName=Conclusion -AnalysisResultDAO.columnKeys.conclusion.name=Conclusion -AnalysisResultDAO.columnKeys.configuration.description=Configuration -AnalysisResultDAO.columnKeys.configuration.displayName=Configuration -AnalysisResultDAO.columnKeys.configuration.name=Configuration -AnalysisResultDAO.columnKeys.justification.description=Justification -AnalysisResultDAO.columnKeys.justification.displayName=Justification -AnalysisResultDAO.columnKeys.justification.name=Justification -AnalysisResultDAO.columnKeys.score.description=Score -AnalysisResultDAO.columnKeys.score.displayName=Score -AnalysisResultDAO.columnKeys.score.name=Score -AnalysisResultDAO.columnKeys.sourceType.description=Source Type -AnalysisResultDAO.columnKeys.sourceType.displayName=Source Type -AnalysisResultDAO.columnKeys.sourceType.name=SourceType -# {0} - searchTerm -AnalysisResultDAO_getKeywordSearchTermCounts_exactMatch={0} (Exact) -# {0} - searchTerm -AnalysisResultDAO_getKeywordSearchTermCounts_regexMatch={0} (Regex) -# {0} - searchTerm -AnalysisResultDAO_getKeywordSearchTermCounts_substringMatch={0} (Substring) -BlackboardArtifactDAO.columnKeys.dataSource.description=Data Source -BlackboardArtifactDAO.columnKeys.dataSource.displayName=Data Source -BlackboardArtifactDAO.columnKeys.dataSource.name=Data Source -BlackboardArtifactDAO.columnKeys.srcFile.description=Source Name -BlackboardArtifactDAO.columnKeys.srcFile.displayName=Source Name -BlackboardArtifactDAO.columnKeys.srcFile.name=Source Name -CommAccounts.name.text=Communication Accounts -CommAccountsDAO.fileColumns.noDescription=No Description -CreditCardByFileRow_accounts_displayName=Accounts -CreditCardByFileRow_file_displayName=File -CreditCardByFileRow_status_displayName=Status -# {0} - raw file name -# {1} - solr chunk id -CreditCardDAO_fetchCreditCardByFile_file_displayName={0}_chunk_{1} -CreditCardDAO_fetchCreditCardByFile_results_displayName=By File -CreditCardDAO_getCreditCardCounts_byBIN_displayName=By BIN -CreditCardDAO_getCreditCardCounts_byFile_displayName=By File -DataArtifactDAO_Accounts_displayName=Communication Accounts -DeletedContent.allDelFilter.text=All -DeletedContent.fsDelFilter.text=File System -EmailsDAO_getFolderDisplayName_defaultName=[Default] -FileExtDocumentFilter_html_displayName=HTML -FileExtDocumentFilter_office_displayName=Office -FileExtDocumentFilter_pdf_displayName=PDF -FileExtDocumentFilter_rtf_displayName=Rich Text -FileExtDocumentFilter_txt_displayName=Plain Text -FileExtRootFilter_archives_displayName=Archives -FileExtRootFilter_audio_displayName=Audio -FileExtRootFilter_databases_displayName=Databases -FileExtRootFilter_documents_displayName=Documents -FileExtRootFilter_executable_displayName=Executable -FileExtRootFilter_image_displayName=Images -FileExtRootFilter_video_displayName=Video -FileSystemColumnUtils.abstractFileColumns.accessTimeColLbl=Access Time -FileSystemColumnUtils.abstractFileColumns.attrAddrColLbl=Attr. Addr. -FileSystemColumnUtils.abstractFileColumns.changeTimeColLbl=Change Time -FileSystemColumnUtils.abstractFileColumns.createdTimeColLbl=Created Time -FileSystemColumnUtils.abstractFileColumns.extensionColLbl=Extension -FileSystemColumnUtils.abstractFileColumns.flagsDirColLbl=Flags(Dir) -FileSystemColumnUtils.abstractFileColumns.flagsMetaColLbl=Flags(Meta) -FileSystemColumnUtils.abstractFileColumns.groupidColLbl=GroupID -FileSystemColumnUtils.abstractFileColumns.knownColLbl=Known -FileSystemColumnUtils.abstractFileColumns.locationColLbl=Location -FileSystemColumnUtils.abstractFileColumns.md5HashColLbl=MD5 Hash -FileSystemColumnUtils.abstractFileColumns.metaAddrColLbl=Meta Addr. -FileSystemColumnUtils.abstractFileColumns.mimeType=MIME Type -FileSystemColumnUtils.abstractFileColumns.modeColLbl=Mode -FileSystemColumnUtils.abstractFileColumns.modifiedTimeColLbl=Modified Time -FileSystemColumnUtils.abstractFileColumns.objectId=Object ID -FileSystemColumnUtils.abstractFileColumns.originalName=Original Name -FileSystemColumnUtils.abstractFileColumns.sha256HashColLbl=SHA-256 Hash -FileSystemColumnUtils.abstractFileColumns.sizeColLbl=Size -FileSystemColumnUtils.abstractFileColumns.typeDirColLbl=Type(Dir) -FileSystemColumnUtils.abstractFileColumns.typeMetaColLbl=Type(Meta) -FileSystemColumnUtils.abstractFileColumns.useridColLbl=UserID -FileSystemColumnUtils.imageColumns.devID=Device ID -FileSystemColumnUtils.imageColumns.sectorSize=Sector Size (Bytes) -FileSystemColumnUtils.imageColumns.size=Size (Bytes) -FileSystemColumnUtils.imageColumns.timezone=Timezone -FileSystemColumnUtils.imageColumns.type=Type -FileSystemColumnUtils.imageColumns.typeValue=Image -FileSystemColumnUtils.nameColumn.name=Name -FileSystemColumnUtils.noDescription=No Description -FileSystemColumnUtils.poolColumns.type=Type -FileSystemColumnUtils.volumeColumns.desc=Description -FileSystemColumnUtils.volumeColumns.flags=Flags -FileSystemColumnUtils.volumeColumns.id=ID -FileSystemColumnUtils.volumeColumns.length=Length in Sectors -FileSystemColumnUtils.volumeColumns.startingSector=Starting Sector -FileTag.name.text=File Tag -FileTypesByMimeType.name.text=By MIME Type -HostPersonDAO_unknownPersons_displayName=Unknown Persons -OsAccounts.name.text=OS Accounts -OsAccountsDAO.fileColumns.noDescription=No Description -OsAccountsDAO_accountHostNameProperty_displayName=Host -OsAccountsDAO_accountNameProperty_displayName=Name -OsAccountsDAO_accountRealmNameProperty_displayName=Realm Name -OsAccountsDAO_accountScopeNameProperty_displayName=Scope -OsAccountsDAO_createdTimeProperty_displayName=Creation Time -OsAccountsDAO_loginNameProperty_displayName=Login Name -ReportsDAO_reports_tableDisplayName=Reports -ReportsRowDTO_createTime_displayName=Created Time -ReportsRowDTO_reportFilePath_displayName=Report File Path -ReportsRowDTO_reportName_displayName=Report Name -ReportsRowDTO_sourceModuleName_displayName=Source Module Name -ResultTag.name.text=Result Tag -ScoreDAO_columns_createdDateLbl=Created Date -ScoreDAO_columns_noDescription=No Description -ScoreDAO_columns_pathLbl=Path -ScoreDAO_columns_sourceLbl=Source -ScoreDAO_columns_typeLbl=Type -ScoreDAO_mainNode_displayName=Score -ScoreDAO_types_filelbl=File -ScoreViewFilter_bad_name=Bad Items -ScoreViewFilter_suspicious_name=Suspicious Items -TagsDAO.fileColumns.accessTimeColLbl=Accessed Time -TagsDAO.fileColumns.changeTimeColLbl=Changed Time -TagsDAO.fileColumns.commentColLbl=Comment -TagsDAO.fileColumns.createdTimeColLbl=Created Time -TagsDAO.fileColumns.filePathColLbl=File Path -TagsDAO.fileColumns.md5HashColLbl=MD5 Hash -TagsDAO.fileColumns.modifiedTimeColLbl=Modified Time -TagsDAO.fileColumns.nameColLbl=Name -TagsDAO.fileColumns.noDescription=No Description -TagsDAO.fileColumns.originalName=Original Name -TagsDAO.fileColumns.sizeColLbl=Size -TagsDAO.fileColumns.userNameColLbl=User Name -TagsDAO.tagColumns.commentColLbl=Comment -TagsDAO.tagColumns.origNameColLbl=Original Name -TagsDAO.tagColumns.sourceNameColLbl=Source Name -TagsDAO.tagColumns.sourcePathColLbl=Source File Path -TagsDAO.tagColumns.typeColLbl=Result Type -TagsDAO.tagColumns.userNameColLbl=User Name -TagType_File_displayName=File Tags -TagType_Result_displayName=Result Tags diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ColumnKey.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ColumnKey.java deleted file mode 100644 index b46aa658c95ac6578da363110f6bc33ecd55e0c6..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ColumnKey.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -/** - * Describes a column to be displayed in the results table including the display - * name and description. - */ -public class ColumnKey { - - private final String fieldName; - private final String displayName; - private final String description; - - public ColumnKey(String fieldName, String displayName, String description) { - this.fieldName = fieldName; - this.displayName = displayName; - this.description = description; - } - - public String getFieldName() { - return fieldName; - } - - public String getDisplayName() { - return displayName; - } - - public String getDescription() { - return description; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsDAO.java deleted file mode 100755 index 751f9ff3f0f945af0edfb5c4d86afb7155e5c560..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsDAO.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; -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.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO.CommAccoutTableSearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.events.CommAccountsEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Provides information to populate the results viewer for data in the - * Communication Accounts section. - */ -@Messages({"CommAccountsDAO.fileColumns.noDescription=No Description"}) -public class CommAccountsDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(CommAccountsDAO.class.getName()); - private final Cache<SearchParams<CommAccountsSearchParams>, SearchResultsDTO> searchParamsCache - = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final TreeCounts<CommAccountsEvent> accountCounts = new TreeCounts<>(); - - private static CommAccountsDAO instance = null; - - synchronized static CommAccountsDAO getInstance() { - if (instance == null) { - instance = new CommAccountsDAO(); - } - - return instance; - } - - SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - public SearchResultsDTO getCommAcounts(CommAccountsSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key.getType() == null) { - throw new IllegalArgumentException("Must have non-null type"); - } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<CommAccountsSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchCommAccountsDTOs(searchParams)); - } - - /** - * Returns a list of paged artifacts. - * - * @param arts The artifacts. - * @param searchParams The search parameters including the paging. - * - * @return The list of paged artifacts. - */ - List<BlackboardArtifact> getPaged(List<? extends BlackboardArtifact> arts, SearchParams<?> searchParams) { - Stream<? extends BlackboardArtifact> pagedArtsStream = arts.stream() - .sorted(Comparator.comparing((art) -> art.getId())) - .skip(searchParams.getStartItem()); - - if (searchParams.getMaxResultsCount() != null) { - pagedArtsStream = pagedArtsStream.limit(searchParams.getMaxResultsCount()); - } - - return pagedArtsStream.collect(Collectors.toList()); - } - - long getTotalResultsCount(SearchParams<CommAccountsSearchParams> cacheKey, long currentPageSize) throws TskCoreException, NoCurrentCaseException { - Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - BlackboardArtifact.Type artType = BlackboardArtifact.Type.TSK_ACCOUNT; - - if ((cacheKey.getStartItem() == 0) // offset is zero AND - && ((cacheKey.getMaxResultsCount() != null && currentPageSize < cacheKey.getMaxResultsCount()) // number of results is less than max - || (cacheKey.getMaxResultsCount() == null))) { // OR max number of results was not specified - return currentPageSize; - } else { - if (dataSourceId != null) { - return blackboard.getArtifactsCount(artType.getTypeID(), dataSourceId); - } else { - return blackboard.getArtifactsCount(artType.getTypeID()); - } - } - } - - @NbBundle.Messages({"CommAccounts.name.text=Communication Accounts"}) - private SearchResultsDTO fetchCommAccountsDTOs(SearchParams<CommAccountsSearchParams> cacheKey) throws NoCurrentCaseException, TskCoreException, SQLException { - - // get current page of communication accounts results - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - Blackboard blackboard = skCase.getBlackboard(); - Account.Type type = cacheKey.getParamData().getType(); - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - List<BlackboardArtifact> allArtifacts = blackboard.getArtifacts(BlackboardArtifact.Type.TSK_ACCOUNT, - BlackboardAttribute.Type.TSK_ACCOUNT_TYPE, type.getTypeName(), dataSourceId, - false); - - // get current page of artifacts - List<BlackboardArtifact> pagedArtifacts = getPaged(allArtifacts, cacheKey); - - // Populate the attributes for paged artifacts in the list. This is done using one database call as an efficient way to - // load many artifacts/attributes at once. - blackboard.loadBlackboardAttributes(pagedArtifacts); - - DataArtifactDAO dataArtDAO = MainDAO.getInstance().getDataArtifactsDAO(); - BlackboardArtifactDAO.TableData tableData = dataArtDAO.createTableData(BlackboardArtifact.Type.TSK_ACCOUNT, pagedArtifacts); - return new CommAccoutTableSearchResultsDTO(type, BlackboardArtifact.Type.TSK_ACCOUNT, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), allArtifacts.size()); - } - - private static TreeResultsDTO.TreeItemDTO<CommAccountsSearchParams> createAccountTreeItem(Account.Type accountType, Long dataSourceId, TreeResultsDTO.TreeDisplayCount count) { - return new TreeResultsDTO.TreeItemDTO<>( - CommAccountsSearchParams.getTypeId(), - new CommAccountsSearchParams(accountType, dataSourceId), - accountType.getTypeName(), - accountType.getDisplayName(), - count); - } - - /** - * Returns the accounts and their counts in the current data source if a - * data source id is provided or all accounts if data source id is null. - * - * @param dataSourceId The data source id or null for no data source filter. - * - * @return The results. - * - * @throws ExecutionException - */ - public TreeResultsDTO<CommAccountsSearchParams> getAccountsCounts(Long dataSourceId) throws ExecutionException { - String innerQuery; - if (dataSourceId != null) { - innerQuery - = " SELECT \n" - + " blackboard_attributes.value_text AS account_type_id,\n" - + " COUNT(*) AS count\n" - + " FROM blackboard_artifacts\n" - + " LEFT JOIN blackboard_attributes \n" - + " ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id\n" - + " WHERE blackboard_attributes.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() + "\n" - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n" - + " AND blackboard_artifacts.data_source_obj_id = " + dataSourceId + " \n" - + " GROUP BY blackboard_attributes.value_text"; - } else { - innerQuery - = " SELECT \n" - + " blackboard_attributes.value_text AS account_type_id,\n" - + " COUNT(*) AS count\n" - + " FROM blackboard_attributes \n" - + " WHERE blackboard_attributes.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() + "\n" - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n" - + " GROUP BY blackboard_attributes.value_text"; - } - - String query = "res.count AS count,\n" - + " acc.display_name AS account_display_name,\n" - + " acc.type_name AS account_type\n" - + "FROM (\n" - + innerQuery + "\n" - + ") res\n" - + "LEFT JOIN account_types acc\n" - + " ON res.account_type_id = acc.type_name\n" - + "ORDER BY acc.display_name"; - - List<TreeResultsDTO.TreeItemDTO<CommAccountsSearchParams>> accountParams = new ArrayList<>(); - try { - Set<Account.Type> indeterminateTypes = this.accountCounts.getEnqueued().stream() - .filter(evt -> dataSourceId == null || evt.getDataSourceId() == dataSourceId) - .map(evt -> evt.getAccountType()) - .collect(Collectors.toSet()); - - getCase().getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - String accountTypeName = resultSet.getString("account_type"); - String accountDisplayName = resultSet.getString("account_display_name"); - Account.Type accountType = new Account.Type(accountTypeName, accountDisplayName); - long count = resultSet.getLong("count"); - TreeDisplayCount treeDisplayCount = indeterminateTypes.contains(accountType) - ? TreeDisplayCount.INDETERMINATE - : TreeResultsDTO.TreeDisplayCount.getDeterminate(count); - - accountParams.add(createAccountTreeItem(accountType, dataSourceId, treeDisplayCount)); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching artifact type counts.", ex); - } - }); - - // return results - return new TreeResultsDTO<>(accountParams); - - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); - } - } - - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - this.handleIngestComplete(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return SubDAOUtils.getIngestCompleteEvents( - this.accountCounts, - (daoEvt, count) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), count) - ); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEvents( - this.accountCounts, - (daoEvt, count) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), count) - ); - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - // get a grouping of artifacts mapping the artifact type id to data source id. - ModuleDataEvent dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt); - if (dataEvt == null) { - return Collections.emptySet(); - } - - Map<Account.Type, Set<Long>> accountTypeMap = new HashMap<>(); - - for (BlackboardArtifact art : dataEvt.getArtifacts()) { - try { - if (art.getType().getTypeID() == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) { - BlackboardAttribute accountTypeAttribute = art.getAttribute(BlackboardAttribute.Type.TSK_ACCOUNT_TYPE); - if (accountTypeAttribute == null) { - continue; - } - - String accountTypeName = accountTypeAttribute.getValueString(); - if (accountTypeName == null) { - continue; - } - - accountTypeMap.computeIfAbsent(getCase().getCommunicationsManager().getAccountType(accountTypeName), (k) -> new HashSet<>()) - .add(art.getDataSourceObjectID()); - } - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.WARNING, "Unable to fetch artifact category for artifact with id: " + art.getId(), ex); - } - } - - // don't do anything else if no relevant events - if (accountTypeMap.isEmpty()) { - return Collections.emptySet(); - } - - SubDAOUtils.invalidateKeys(this.searchParamsCache, - (sp) -> Pair.of(sp.getType(), sp.getDataSourceId()), accountTypeMap); - - List<CommAccountsEvent> accountEvents = new ArrayList<>(); - for (Map.Entry<Account.Type, Set<Long>> entry : accountTypeMap.entrySet()) { - Account.Type accountType = entry.getKey(); - for (Long dsObjId : entry.getValue()) { - CommAccountsEvent newEvt = new CommAccountsEvent(accountType, dsObjId); - accountEvents.add(newEvt); - } - } - - Stream<TreeEvent> treeEvents; - if (IngestManager.getInstance().isIngestRunning()) { - treeEvents = this.accountCounts.enqueueAll(accountEvents).stream() - .map(daoEvt -> new TreeEvent(createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), TreeResultsDTO.TreeDisplayCount.INDETERMINATE), false)); - } else { - treeEvents = accountEvents.stream() - .map(daoEvt -> new TreeEvent(createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), TreeResultsDTO.TreeDisplayCount.INDETERMINATE), false)); - } - - return Stream.of(accountEvents.stream(), treeEvents) - .flatMap(s -> s) - .collect(Collectors.toSet()); - } - - /** - * Returns true if the dao event could update the data stored in the - * parameters. - * - * @param parameters The parameters. - * @param evt The event. - * - * @return True if event invalidates parameters. - */ - private boolean isCommAcctInvalidating(CommAccountsSearchParams parameters, DAOEvent evt) { - if (evt instanceof CommAccountsEvent) { - CommAccountsEvent commEvt = (CommAccountsEvent) evt; - return (parameters.getType().getTypeName().equals(commEvt.getType())) - && (parameters.getDataSourceId() == null || Objects.equals(parameters.getDataSourceId(), commEvt.getDataSourceId())); - } else { - return false; - - } - } - - /** - * Handles fetching and paging of data for communication accounts. - */ - public static class CommAccountFetcher extends DAOFetcher<CommAccountsSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public CommAccountFetcher(CommAccountsSearchParams params) { - super(params); - } - - protected CommAccountsDAO getDAO() { - return MainDAO.getInstance().getCommAccountsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getCommAcounts(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isCommAcctInvalidating(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsSearchParams.java deleted file mode 100755 index 791f28a509c5cc18516bf4fad1e5e8ff38469cdc..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsSearchParams.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Key for accessing data about communication accounts from the DAO. - */ -public class CommAccountsSearchParams extends DataArtifactSearchParam { - - private static final String TYPE_ID = "DATA_ARTIFACT_ACCOUNT"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final Account.Type type; - private final Long dataSourceId; - - public CommAccountsSearchParams(Account.Type type, Long dataSourceId) { - super(BlackboardArtifact.Type.TSK_ACCOUNT, dataSourceId); - this.type = type; - this.dataSourceId = dataSourceId; - } - - public Account.Type getType() { - return type; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 23 * hash + Objects.hashCode(this.type); - hash = 23 * hash + Objects.hashCode(this.dataSourceId); - hash = 23 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final CommAccountsSearchParams other = (CommAccountsSearchParams) obj; - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - if (!Objects.equals(this.type, other.type)) { - return false; - } - return super.equals(obj); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ContentRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ContentRowDTO.java deleted file mode 100755 index 2ec5045eb92b02223430e9b955201fa18128c54a..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ContentRowDTO.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Directory; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LocalDirectory; -import org.sleuthkit.datamodel.LocalFilesDataSource; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.VirtualDirectory; -import org.sleuthkit.datamodel.Volume; - -/* - * A base class for FileSystem table row DTOs. - */ -public abstract class ContentRowDTO<T extends Content> extends BaseRowDTO { - - private final T content; - - /** - * Constructs a new FileSystemRowDTO. - * - * @param content The content represented by this object. - * @param cellValues The table cell values. - * @param typeId The string type id for this DTO. - */ - private ContentRowDTO(T content, List<Object> cellValues, String typeId) { - super(cellValues, typeId, content.getId()); - this.content = content; - } - - /** - * Returns the content object for this row. - * - * @return The content. - */ - public T getContent() { - return content; - } - - /** - * DTO Representing an Volume in the results view. - */ - public static class VolumeRowDTO extends ContentRowDTO<Volume> { - - private static final String TYPE_ID = "VOLUME"; - - /** - * Constructs a new VolumeRowDTO. - * - * @param volume The volume represented by this DTO. - * @param cellValues The table cell values. - */ - public VolumeRowDTO(Volume volume, List<Object> cellValues) { - super(volume, cellValues, TYPE_ID); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - /** - * DTO Representing an Image in the results view. - */ - public static class ImageRowDTO extends ContentRowDTO<Image> { - - private static final String TYPE_ID = "IMAGE"; - - /** - * Constructs a new ImageRowDTO. - * - * @param image The image represented by this DTO. - * @param cellValues The table cell values. - */ - public ImageRowDTO(Image image, List<Object> cellValues) { - super(image, cellValues, TYPE_ID); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - /** - * DTO Representing a LocalDirectory file in the results view. - */ - public static class LocalDirectoryRowDTO extends ContentRowDTO<LocalDirectory> { - - private static final String TYPE_ID = "LOCAL_DIRECTORY"; - - /** - * Constructs a new LocalDirectoryRowDTO. - * - * @param localDir The LocalDirectory represented by this DTO. - * @param cellValues The table cell values. - */ - public LocalDirectoryRowDTO(LocalDirectory localDir, List<Object> cellValues) { - super(localDir, cellValues, TYPE_ID); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - /** - * DTO Representing a VirtualDirectory in the results view. - */ - public static class VirtualDirectoryRowDTO extends ContentRowDTO<VirtualDirectory> { - - private static final String TYPE_ID = "VIRTUAL_DIRECTORY"; - - /** - * Constructs a new VirtualDirectoryRowDTO. - * - * @param virtualDir The VirtualDirectory represented by this DTO. - * @param cellValues The table cell values. - */ - public VirtualDirectoryRowDTO(VirtualDirectory virtualDir, List<Object> cellValues) { - this(virtualDir, cellValues, TYPE_ID); - } - - /** - * Constructs a new VirtualDirectoryRowDTO. - * - * @param virtualDir The VirtualDirectory represented by this DTO. - * @param cellValues The table cell values. - * @param typeId The type id for this object. - */ - private VirtualDirectoryRowDTO(VirtualDirectory localDir, List<Object> cellValues, String typeId) { - super(localDir, cellValues, typeId); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - /** - * DTO Representing LocalFile in the results view. - */ - public static class LocalFileDataSourceRowDTO extends VirtualDirectoryRowDTO { - - private static final String TYPE_ID = "LOCAL_FILE_DATA_SOURCE"; - - /** - * Constructs a new LocalFileDataSourceRowDTO. - * - * @param localFilesDataSource The LocalFilesDataSource represented by - * this DTO. - * @param cellValues The table cell values. - */ - public LocalFileDataSourceRowDTO(LocalFilesDataSource localFilesDataSource, List<Object> cellValues) { - super(localFilesDataSource, cellValues, TYPE_ID); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - /** - * DTO Representing a Directory in the results view. - */ - public static class DirectoryRowDTO extends ContentRowDTO<Directory> { - - private static final String TYPE_ID = "DIRECTORY"; - - /** - * Constructs a new DirectoryRowDTO. - * - * @param dir The directory represented by this DTO. - * @param cellValues The table cell values. - */ - public DirectoryRowDTO(Directory dir, List<Object> cellValues) { - super(dir, cellValues, TYPE_ID); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - /** - * DTO representing a pool in the results view. - */ - public static class PoolRowDTO extends ContentRowDTO<Pool> { - - private static final String TYPE_ID = "POOL"; - - /** - * Constructs a new PoolRowDTO. - * - * @param pool The pool represented by this DTO. - * @param cellValues The table cell values. - */ - public PoolRowDTO(Pool pool, List<Object> cellValues) { - super(pool, cellValues, TYPE_ID); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - public static class OsAccountRowDTO extends ContentRowDTO<OsAccount> { - private static final String TYPE_ID = "OS_ACCOUNT"; - - public OsAccountRowDTO(OsAccount osAccount, List<Object> cellValues) { - super(osAccount, cellValues, TYPE_ID); - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ContentTagsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ContentTagsRowDTO.java deleted file mode 100755 index 94eb0c94535a698d50879a250cbc5a2c50e163f3..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ContentTagsRowDTO.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.ContentTag; - -/** - * A result row for a ContentTag. - */ -public class ContentTagsRowDTO extends BaseRowDTO { - - private static final String TYPE_ID = "CONTENT_TAG"; - - private final ContentTag tag; - - public ContentTagsRowDTO(ContentTag tag, List<Object> cellValues, long id) { - super(cellValues, TYPE_ID, id); - this.tag = tag; - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - - /** - * Return the tag for this result row. - * - * @return The tag for this row. - */ - public ContentTag getTag() { - return tag; - } - - /** - * Returns the tags display name. - * - * @return The display name for this tag. - */ - public String getDisplayName() { - return getCellValues().size() > 0 - ? getCellValues().get(0).toString() - : ""; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardBinSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardBinSearchParams.java deleted file mode 100644 index dea84ea2205161331103c744aa628f47800edf24..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardBinSearchParams.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Search params to fetch credit cards by bin prefix. - */ -public class CreditCardBinSearchParams extends CreditCardSearchParams { - - private static final String TYPE_ID = "CREDIT_CARD_BY_BIN"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final String binPrefix; - - /** - * Main constructor. - * - * @param binPrefix The bin prefix of the credit card. - * @param includeRejected Whether or not to include rejected items in search - * results. - * @param dataSourceId The data source id or null for no data source - * filtering. - */ - public CreditCardBinSearchParams(String binPrefix, boolean includeRejected, Long dataSourceId) { - super(includeRejected, dataSourceId); - this.binPrefix = binPrefix; - } - - public String getBinPrefix() { - return binPrefix; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 47 * hash + Objects.hashCode(this.binPrefix); - hash = 47 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final CreditCardBinSearchParams other = (CreditCardBinSearchParams) obj; - if (!Objects.equals(this.binPrefix, other.binPrefix)) { - return false; - } - return super.equals(obj); - } - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardByFileRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardByFileRowDTO.java deleted file mode 100644 index df7982104f31a1a256e5cf81459cfd2356d0ed01..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardByFileRowDTO.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.collect.ImmutableList; -import java.util.List; -import java.util.Set; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Row of credit card information for a file. - */ -@Messages({ - "CreditCardByFileRow_file_displayName=File", - "CreditCardByFileRow_accounts_displayName=Accounts", - "CreditCardByFileRow_status_displayName=Status" -}) -public class CreditCardByFileRowDTO extends BaseRowDTO { - - private static ColumnKey getColumnKey(String displayName) { - return new ColumnKey(displayName.toUpperCase().replaceAll("\\s", "_"), displayName, ""); - } - - static List<ColumnKey> COLUMNS = ImmutableList.of( - getColumnKey(Bundle.CreditCardByFileRow_file_displayName()), - getColumnKey(Bundle.CreditCardByFileRow_accounts_displayName()), - getColumnKey(Bundle.CreditCardByFileRow_status_displayName()) - ); - - private static final String TYPE_ID = "CREDIT_CARD_BY_FILE"; - - public static String getTypeIdForClass() { - return TYPE_ID; - } - - private final AbstractFile file; - private final Set<BlackboardArtifact> associatedArtifacts; - private final String fileName; - private final long accounts; - private final Set<BlackboardArtifact.ReviewStatus> statuses; - private final String reviewStatusString; - - /** - * Main constructor. - * - * @param file The file where credit cards were found. - * @param associatedArtifacts The associated artifacts. - * @param fileName The name of the file to display in columns. - * @param accounts The number of accounts to display in columns. - * @param statuses The associated statuses. - * @param reviewStatusString The review status string to display. - */ - CreditCardByFileRowDTO(AbstractFile file, Set<BlackboardArtifact> associatedArtifacts, String fileName, long accounts, - Set<BlackboardArtifact.ReviewStatus> statuses, String reviewStatusString) { - super(ImmutableList.of(fileName, accounts, reviewStatusString), TYPE_ID, file.getId()); - this.file = file; - this.associatedArtifacts = associatedArtifacts; - this.fileName = fileName; - this.accounts = accounts; - this.statuses = statuses; - this.reviewStatusString = reviewStatusString; - } - - public AbstractFile getFile() { - return file; - } - - public Set<BlackboardArtifact> getAssociatedArtifacts() { - return associatedArtifacts; - } - - public String getFileName() { - return fileName; - } - - public long getAccounts() { - return accounts; - } - - public Set<BlackboardArtifact.ReviewStatus> getStatuses() { - return statuses; - } - - public String getReviewStatusString() { - return reviewStatusString; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardDAO.java deleted file mode 100644 index ea01bd34389ec788e0cf816f238f9ad72728404f..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardDAO.java +++ /dev/null @@ -1,731 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -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.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.CreditCardEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifact.ReviewStatus; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData.DbType; - -/** - * DAO for fetching credit card information. - */ -public class CreditCardDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(CreditCardDAO.class.getName()); - private static final String LIKE_ESCAPE_CHAR = "\\"; - - // number of digits to include in bin prefix - private static final int BIN_PREFIX_NUM = 8; - - private final Cache<SearchParams<CreditCardSearchParams>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final TreeCounts<CreditCardEvent> creditCardTreeCounts = new TreeCounts<>(); - - private static CreditCardDAO instance = null; - - /** - * @return The singleton instance of this class. - */ - synchronized static CreditCardDAO getInstance() { - if (instance == null) { - instance = new CreditCardDAO(); - } - - return instance; - } - - /** - * @return The current SleuthkitCase. - * - * @throws NoCurrentCaseException - */ - SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - /** - * Returns search results for files containing credit card information. - * - * @param searchParams The search parameters. - * @param startItem The paged start item. - * @param maxCount The maximum number of results to return or null for - * all results. - * - * @return The search results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public SearchResultsDTO getCreditCardByFile(CreditCardFileSearchParams searchParams, long startItem, Long maxCount) throws IllegalArgumentException, ExecutionException { - if (startItem < 0 || (maxCount != null && maxCount < 0)) { - throw new IllegalArgumentException(MessageFormat.format("Start item and max count need to be >= 0 but were [startItem: {0}, maxCount: {1}]", - startItem, - maxCount == null ? "<null>" : maxCount)); - } - - SearchParams<CreditCardSearchParams> pagedSearchParams = new SearchParams<>(searchParams, startItem, maxCount); - return searchParamsCache.get(pagedSearchParams, () -> fetchCreditCardByFile(searchParams, startItem, maxCount)); - } - - /** - * Returns a string providing a sql aggregate concatenating a column into a - * comma separated list. - * - * @param dbType The database type. - * @param field The field to concatenate. - * - * @return The string to be used in a sql statement. - */ - private static String getConcatAggregate(DbType dbType, String field) { - switch (dbType) { - case POSTGRESQL: - return MessageFormat.format("STRING_AGG({0}::character varying, '','')", field); - case SQLITE: - return MessageFormat.format("GROUP_CONCAT({0})", field); - default: - throw new IllegalStateException("Unknown database type: " + dbType); - } - } - - @Messages({ - "# {0} - raw file name", - "# {1} - solr chunk id", - "CreditCardDAO_fetchCreditCardByFile_file_displayName={0}_chunk_{1}", - "CreditCardDAO_fetchCreditCardByFile_results_displayName=By File",}) - private SearchResultsDTO fetchCreditCardByFile(CreditCardFileSearchParams searchParams, long startItem, Long maxCount) throws IllegalStateException, TskCoreException, NoCurrentCaseException, SQLException { - boolean includeRejected = searchParams.isRejectedIncluded(); - Long dataSourceId = searchParams.getDataSourceId(); - - String baseFromAndGroupSql = "FROM blackboard_artifacts art\n" - + "INNER JOIN blackboard_attributes acct ON art.artifact_id = acct.artifact_id \n" - + " AND acct.attribute_type_id = " + BlackboardAttribute.Type.TSK_CARD_NUMBER.getTypeID() + "\n" - + "LEFT JOIN blackboard_attributes solr_doc ON art.artifact_id = solr_doc.artifact_id \n" - + " AND solr_doc.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() + "\n" - + "LEFT JOIN tsk_files f ON art.obj_id = f.obj_id\n" - + "WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() + "\n" - + (dataSourceId == null ? "" : "AND art.data_source_obj_id = " + dataSourceId + "\n") - + (includeRejected ? "" : "AND art.review_status_id <> " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + "\n") - + "GROUP BY art.obj_id, solr_doc.value_text\n"; - - // get the total count of results - String countQuery = "COUNT(*) AS count FROM (SELECT COUNT(*)\n " - + baseFromAndGroupSql + ") q"; - - AtomicLong atomicCount = new AtomicLong(0); - getCase().getCaseDbAccessManager().select(countQuery, (resultSet) -> { - try { - if (resultSet.next()) { - atomicCount.set(resultSet.getLong("count")); - } - } catch (SQLException ex) { - throw new IllegalStateException("An exception occurred while fetching the count with query\n:" + countQuery, ex); - } - }); - - // if result count > 0, return paged data. - List<RowDTO> rows = new ArrayList<>(); - long totalResultCount = atomicCount.get(); - if (totalResultCount > 0) { - String itemQuery = " art.obj_id AS file_id, \n" - + " solr_doc.value_text AS solr_document_id,\n" - + " " + getConcatAggregate(getCase().getDatabaseType(), "art.artifact_id") + " AS artifact_ids,\n" - + " " + getConcatAggregate(getCase().getDatabaseType(), "DISTINCT(art.review_status_id)") + " AS review_status_ids,\n" - + " COUNT(*) AS count\n" - + baseFromAndGroupSql - + (maxCount == null ? "" : "LIMIT " + maxCount + "\n") - + "OFFSET " + startItem; - - getCase().getCaseDbAccessManager().select(itemQuery, (resultSet) -> { - try { - while (resultSet.next()) { - Long fileId = resultSet.getLong("file_id"); - if (resultSet.wasNull()) { - continue; - } - - String solrDocId = resultSet.getString("solr_document_id"); - String artifactIds = resultSet.getString("artifact_ids"); - String reviewStatusIds = resultSet.getString("review_status_ids"); - long itemCount = resultSet.getLong("count"); - - Set<BlackboardArtifact> associatedArtifacts = StringUtils.isBlank(artifactIds) - ? Collections.emptySet() - : getCase().getBlackboard().getDataArtifactsWhere("artifacts.artifact_id IN (" + artifactIds + ")") - .stream() - .collect(Collectors.toSet()); - - AbstractFile file = getCase().getAbstractFileById(fileId); - if(file == null) { - continue; - } - - String fileName = StringUtils.isBlank(solrDocId) - ? file.getName() - : Bundle.CreditCardDAO_fetchCreditCardByFile_file_displayName(file.getName(), StringUtils.substringAfter(solrDocId, "_")); - - // get review status from id - Set<BlackboardArtifact.ReviewStatus> reviewStatuses = StringUtils.isBlank(reviewStatusIds) - ? Collections.emptySet() - : Stream.of(reviewStatusIds.split(",")) - .map(id -> { - try { - String trimmed = id.trim(); - int reviewStatusId = Integer.parseInt(trimmed); - return BlackboardArtifact.ReviewStatus.withID(reviewStatusId); - } catch (NumberFormatException ex) { - return null; - } - }) - .filter(rs -> rs != null) - .collect(Collectors.toSet()); - - String reviewStatusString = reviewStatuses.stream() - .sorted(Comparator.comparing(rs -> rs.getID())) - .map(rs -> rs.getDisplayName()) - .collect(Collectors.joining(", ")); - - rows.add(new CreditCardByFileRowDTO(file, associatedArtifacts, fileName, itemCount, reviewStatuses, reviewStatusString)); - } - } catch (SQLException | NoCurrentCaseException | TskCoreException ex) { - throw new IllegalStateException("An exception occurred while fetching items while running query:\n" + itemQuery, ex); - } - }); - } - - return new BaseSearchResultsDTO(CreditCardByFileRowDTO.getTypeIdForClass(), Bundle.CreditCardDAO_fetchCreditCardByFile_results_displayName(), - CreditCardByFileRowDTO.COLUMNS, rows, CreditCardByFileRowDTO.getTypeIdForClass(), startItem, totalResultCount); - } - - /** - * Returns counts of credit card data found (by file and by bin). - * - * @param dataSourceId The data source id or null for no data source id - * filtering. - * @param includeRejected Whether or not to include rejected accounts. - * - * @return The results to be used in the tree. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - @Messages({ - "CreditCardDAO_getCreditCardCounts_byFile_displayName=By File", - "CreditCardDAO_getCreditCardCounts_byBIN_displayName=By BIN",}) - @SuppressWarnings("unchecked") - public TreeResultsDTO<CreditCardSearchParams> getCreditCardCounts(Long dataSourceId, boolean includeRejected) throws IllegalArgumentException, ExecutionException { - String countsQuery = "\n COUNT(DISTINCT(art.obj_id)) AS file_count,\n" - + " COUNT(DISTINCT(art.artifact_id)) AS bin_count\n" - + " FROM blackboard_artifacts art\n" - + " INNER JOIN blackboard_attributes acct ON art.artifact_id = acct.artifact_id\n" - + " AND acct.attribute_type_id = " + BlackboardAttribute.Type.TSK_CARD_NUMBER.getTypeID() + "\n" - + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() + "\n" - + (dataSourceId == null ? "" : "AND art.data_source_obj_id = " + dataSourceId + "\n") - + (includeRejected ? "" : "AND art.review_status_id <> " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + "\n"); - - boolean isIndeterminate = !this.creditCardTreeCounts.getEnqueued().isEmpty(); - - List<TreeItemDTO<CreditCardSearchParams>> items = new ArrayList<>(); - try { - getCase().getCaseDbAccessManager().select(countsQuery, (resultSet) -> { - try { - if (resultSet.next()) { - TreeDisplayCount fileDisplayCount = isIndeterminate - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(resultSet.getLong("file_count")); - - items.add(createFileTreeItem(includeRejected, dataSourceId, fileDisplayCount)); - - TreeDisplayCount binDisplayCount = isIndeterminate - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(resultSet.getLong("bin_count")); - - items.add((TreeItemDTO<CreditCardSearchParams>) (TreeItemDTO<? extends CreditCardSearchParams>) createBinTreeItem(includeRejected, null, dataSourceId, binDisplayCount)); - - } - } catch (SQLException ex) { - throw new IllegalStateException("An exception occurred while fetching counts:\n" + countsQuery, ex); - } - }); - } catch (NoCurrentCaseException | TskCoreException | IllegalStateException ex) { - throw new ExecutionException("An error occurred while fetching counts while running count query:\n" + countsQuery, ex); - } - - return new TreeResultsDTO<>(items); - } - - /** - * Creates a tree item for the 'By File' parent node. - * - * @param includeRejected Whether or not to include rejected accounts. - * @param dataSourceId The data source object id to filter on or null for - * no filtering. - * @param displayCount The count to display. - * - * @return The tree item dto. - */ - public TreeItemDTO<CreditCardSearchParams> createFileTreeItem(boolean includeRejected, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeItemDTO<>( - CreditCardFileSearchParams.getTypeId(), - new CreditCardFileSearchParams(includeRejected, dataSourceId), - CreditCardFileSearchParams.getTypeId(), - Bundle.CreditCardDAO_getCreditCardCounts_byFile_displayName(), - displayCount); - } - - /** - * Creates a tree item for a bin node. - * - * @param includeRejected Whether or not to include rejected accounts. - * @param binPrefix The bin prefix. If null, a tree item for the - * parent 'By Bin' node is created. - * @param dataSourceId The data source object id to filter on or null for - * no filtering. - * @param displayCount The count to display. - * - * @return - */ - public TreeItemDTO<CreditCardBinSearchParams> createBinTreeItem(boolean includeRejected, String binPrefix, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeItemDTO<>( - CreditCardBinSearchParams.getTypeId(), - new CreditCardBinSearchParams(binPrefix, includeRejected, dataSourceId), - StringUtils.isBlank(binPrefix) ? CreditCardBinSearchParams.getTypeId() : binPrefix, - StringUtils.isBlank(binPrefix) ? Bundle.CreditCardDAO_getCreditCardCounts_byBIN_displayName() : binPrefix, - displayCount); - } - - /** - * Returns search results for querying for credit card accounts by bin - * prefix. - * - * @param searchParams The search parameters. - * @param startItem The paged start item. - * @param maxCount The maximum number of results to return or null for - * all results. - * - * @return The search results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public SearchResultsDTO getCreditCardByBin(CreditCardBinSearchParams searchParams, long startItem, Long maxCount) throws IllegalArgumentException, ExecutionException { - if (startItem < 0 || (maxCount != null && maxCount < 0)) { - throw new IllegalArgumentException(MessageFormat.format("Start item and max count need to be >= 0 but were [startItem: {0}, maxCount: {1}]", - startItem, - maxCount == null ? "<null>" : maxCount)); - } else if (searchParams.getBinPrefix() == null) { - throw new IllegalArgumentException("Expected non-null bin prefix"); - } - - SearchParams<CreditCardSearchParams> pagedSearchParams = new SearchParams<>(searchParams, startItem, maxCount); - - return searchParamsCache.get(pagedSearchParams, () -> fetchCreditCardByBin(searchParams, startItem, maxCount)); - } - - private SearchResultsDTO fetchCreditCardByBin(CreditCardBinSearchParams searchParams, long startItem, Long maxCount) throws TskCoreException, NoCurrentCaseException, IllegalStateException, SQLException { - Long dataSourceId = searchParams.getDataSourceId(); - boolean includeRejected = searchParams.isRejectedIncluded(); - - String baseQuery = "FROM blackboard_artifacts art\n" - + "LEFT JOIN blackboard_attributes attr ON art.artifact_id = attr.artifact_id\n" - + "WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() + "\n" - + "AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_CARD_NUMBER.getTypeID() + "\n" - + (dataSourceId == null ? "" : "AND art.data_source_obj_id = ?\n") - + (includeRejected ? "" : "AND art.review_status_id <> " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + "\n") - + "AND attr.value_text LIKE ? ESCAPE '" + LIKE_ESCAPE_CHAR + "'\n"; - - // get the total count of results - String countQuery = "COUNT(DISTINCT(art.artifact_id)) AS count\n" - + baseQuery; - - String binLikeStatement = SubDAOUtils.likeEscape(searchParams.getBinPrefix(), LIKE_ESCAPE_CHAR) + "%"; - - AtomicLong atomicCount = new AtomicLong(0); - try (CaseDbPreparedStatement countStatement = getCase().getCaseDbAccessManager().prepareSelect(countQuery)) { - int parameterIdx = 0; - if (dataSourceId != null) { - countStatement.setLong(++parameterIdx, dataSourceId); - } - - countStatement.setString(++parameterIdx, binLikeStatement); - - getCase().getCaseDbAccessManager().select(countStatement, (resultSet) -> { - try { - if (resultSet.next()) { - atomicCount.set(resultSet.getLong("count")); - } - } catch (SQLException ex) { - throw new IllegalStateException("Unable to retrieve count with query:\n" + countQuery, ex); - } - }); - } - - // if count is greater than 0, fetch applicable artifact ids. - long totalCount = atomicCount.get(); - List<BlackboardArtifact> artifacts = new ArrayList<>(); - if (totalCount > 0) { - String pagedIdQuery = "art.artifact_id AS artifact_id\n" - + baseQuery - + "GROUP BY art.artifact_id\n" - + "ORDER BY art.artifact_id\n" - + (maxCount == null ? "" : "LIMIT ?\n") - + "OFFSET ?\n"; - - List<Long> artifactIds = new ArrayList<>(); - try (CaseDbPreparedStatement queryStatement = getCase().getCaseDbAccessManager().prepareSelect(pagedIdQuery)) { - int parameterIdx = 0; - if (dataSourceId != null) { - queryStatement.setLong(++parameterIdx, dataSourceId); - } - - queryStatement.setString(++parameterIdx, binLikeStatement); - - if (maxCount != null) { - queryStatement.setLong(++parameterIdx, maxCount); - } - - queryStatement.setLong(++parameterIdx, startItem); - - getCase().getCaseDbAccessManager().select(queryStatement, (resultSet) -> { - try { - while (resultSet.next()) { - artifactIds.add(resultSet.getLong("artifact_id")); - } - } catch (SQLException ex) { - throw new IllegalStateException("Unable to retrieve artifact ids with query:\n" + pagedIdQuery, ex); - } - }); - } - - // get data based on those artifact ids - if (!artifactIds.isEmpty()) { - String artifactIdsStr = artifactIds.stream() - .filter(id -> id != null) - .map(id -> Long.toString(id)) - .collect(Collectors.joining(", ")); - - artifacts.addAll(getCase().getBlackboard().getDataArtifactsWhere("artifact_id IN (" + artifactIdsStr + ")")); - } - } - - getCase().getBlackboard().loadBlackboardAttributes(artifacts); - BlackboardArtifactDAO.TableData tableData = MainDAO.getInstance().getDataArtifactsDAO().createTableData(BlackboardArtifact.Type.TSK_ACCOUNT, artifacts); - return new DataArtifactTableSearchResultsDTO(BlackboardArtifact.Type.TSK_ACCOUNT, tableData.columnKeys, tableData.rows, startItem, totalCount); - } - - /** - * Returns counts of artifacts found for bin prefixes. - * - * @param dataSourceId The data source id to filter on or null for no - * filtering. - * @param includeRejected Whether or not to include rejected accounts. - * - * @return The results to use in the tree. - * - * @throws ExecutionException - */ - public TreeResultsDTO<CreditCardBinSearchParams> getCreditCardBinCounts(Long dataSourceId, boolean includeRejected) throws ExecutionException { - - Set<String> indeterminatePrefixes = this.creditCardTreeCounts.getEnqueued().stream() - .filter(evts -> evts != null - && evts.isRejectedStatus() == includeRejected - && evts.getBinPrefix() != null - && (dataSourceId == null || Objects.equals(evts.getDataSourceId(), dataSourceId))) - .map(params -> params.getBinPrefix()) - .collect(Collectors.toSet()); - - String countsQuery = "\n" - + " SUBSTR(acct.value_text, 1, " + BIN_PREFIX_NUM + ") AS bin_prefix,\n" - + " COUNT(DISTINCT(art.artifact_id)) AS bin_count\n" - + " FROM blackboard_artifacts art\n" - + " INNER JOIN blackboard_attributes acct ON art.artifact_id = acct.artifact_id\n" - + " AND acct.attribute_type_id = " + BlackboardAttribute.Type.TSK_CARD_NUMBER.getTypeID() + "\n" - + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() + "\n" - + (dataSourceId == null ? "" : "AND art.data_source_obj_id = " + dataSourceId + "\n") - + (includeRejected ? "" : "AND art.review_status_id <> " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + "\n") - + "GROUP BY SUBSTR(acct.value_text, 1, " + BIN_PREFIX_NUM + ")\n" - + "ORDER BY SUBSTR(acct.value_text, 1, " + BIN_PREFIX_NUM + ")\n"; - - List<TreeItemDTO<CreditCardBinSearchParams>> items = new ArrayList<>(); - try { - getCase().getCaseDbAccessManager().select(countsQuery, (resultSet) -> { - try { - while (resultSet.next()) { - String binPrefix = resultSet.getString("bin_prefix"); - items.add(new TreeItemDTO<>( - CreditCardBinSearchParams.getTypeId(), - new CreditCardBinSearchParams(binPrefix, includeRejected, dataSourceId), - StringUtils.defaultString(binPrefix), - StringUtils.defaultString(binPrefix), - indeterminatePrefixes.contains(binPrefix) ? TreeDisplayCount.INDETERMINATE : TreeDisplayCount.getDeterminate(resultSet.getLong("bin_count")))); - - } - } catch (SQLException ex) { - throw new IllegalStateException("An Exception occurred while fetching counts with query\n:" + countsQuery, ex); - } - }); - - return new TreeResultsDTO<>(items); - } catch (TskCoreException | IllegalStateException | NoCurrentCaseException ex) { - throw new ExecutionException("There was an error while fetching bin counts with query:\n" + countsQuery, ex); - } - } - - // is account invalidating - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - this.handleIngestComplete(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return SubDAOUtils.getIngestCompleteEventsFromList( - this.creditCardTreeCounts, - (daoEvt, count) -> createTreeItems(daoEvt, count)); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEventsFromList( - this.creditCardTreeCounts, - (daoEvt, count) -> createTreeItems(daoEvt, count)); - } - - @Override - Set<? extends DAOEvent> processEvent(PropertyChangeEvent evt) { - ModuleDataEvent dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt); - if (dataEvt == null) { - return Collections.emptySet(); - } - - // maps bin prefix => isRejected => Data source ids - Map<String, Map<Boolean, Set<Long>>> creditCardBinPrefixMap = new HashMap<>(); - // maintains a set of tuples of data source id and whether or not it is rejected. - Set<Pair<Long, Boolean>> affectedDataSources = new HashSet<>(); - - for (BlackboardArtifact art : dataEvt.getArtifacts()) { - try { - if (art.getType().getTypeID() == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) { - BlackboardAttribute attr = art.getAttribute(BlackboardAttribute.Type.TSK_CARD_NUMBER); - if (attr != null && attr.getValueString() != null) { - - String cardNumber = attr.getValueString().trim(); - String cardPrefix = cardNumber.substring(0, Math.min(cardNumber.length(), BIN_PREFIX_NUM)); - - ReviewStatus reviewStatus = art.getReviewStatus(); - - creditCardBinPrefixMap - .computeIfAbsent(cardPrefix, (k) -> new HashMap<>()) - .computeIfAbsent(ReviewStatus.REJECTED.equals(reviewStatus), (k) -> new HashSet<>()) - .add(art.getDataSourceObjectID()); - - affectedDataSources.add(Pair.of(art.getDataSourceObjectID(), ReviewStatus.REJECTED.equals(reviewStatus))); - } - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "There was an error determining events from artifact with id: " + art.getId(), ex); - } - } - - if (creditCardBinPrefixMap.isEmpty()) { - return Collections.emptySet(); - } - - SubDAOUtils.invalidateKeys(this.searchParamsCache, (paramKey) -> { - if (paramKey instanceof CreditCardBinSearchParams) { - CreditCardBinSearchParams binKey = ((CreditCardBinSearchParams) paramKey); - Map<Boolean, Set<Long>> reviewStatuses = creditCardBinPrefixMap.get(binKey.getBinPrefix()); - if (reviewStatuses != null) { - return (binKey.isRejectedIncluded() - ? Stream.of(reviewStatuses.get(true), reviewStatuses.get(false)) - : Stream.of(reviewStatuses.get(false))) - .filter(dsIds -> dsIds != null) - .flatMap(dsIds -> dsIds.stream()) - .anyMatch(dsId -> binKey.getDataSourceId() == null || Objects.equals(binKey.getDataSourceId(), dsId)); - } - } else if (paramKey instanceof CreditCardFileSearchParams) { - CreditCardFileSearchParams fileKey = ((CreditCardFileSearchParams) paramKey); - return affectedDataSources.stream() - .anyMatch(pr -> (fileKey.isRejectedIncluded() || !pr.getRight()) - && (fileKey.getDataSourceId() == null || Objects.equals(fileKey.getDataSourceId(), pr.getLeft()))); - - } - return false; - }); - - Set<CreditCardEvent> events = new HashSet<>(); - for (Entry<String, Map<Boolean, Set<Long>>> binEntry : creditCardBinPrefixMap.entrySet()) { - String binPrefix = binEntry.getKey(); - for (Entry<Boolean, Set<Long>> isRejectedEntry : binEntry.getValue().entrySet()) { - boolean isRejected = isRejectedEntry.getKey(); - for (Long dsId : isRejectedEntry.getValue()) { - events.add(new CreditCardEvent(binPrefix, isRejected, dsId)); - } - } - } - - Stream<TreeEvent> treeEvents; - if (IngestManager.getInstance().isIngestRunning()) { - treeEvents = this.creditCardTreeCounts.enqueueAll(events).stream() - .flatMap(daoEvt -> { - List<TreeItemDTO<CreditCardSearchParams>> treeItems = createTreeItems(daoEvt, TreeDisplayCount.INDETERMINATE); - return treeItems.stream().map(item -> new TreeEvent(item, false)); - }); - - } else { - treeEvents = events.stream() - .flatMap(daoEvt -> { - List<TreeItemDTO<CreditCardSearchParams>> treeItems = createTreeItems(daoEvt, TreeDisplayCount.UNSPECIFIED); - return treeItems.stream().map(item -> new TreeEvent(item, true)); - }); - } - - return Stream.of(events.stream(), treeEvents) - .flatMap(s -> s) - .collect(Collectors.toSet()); - } - - @SuppressWarnings("unchecked") - private List<TreeItemDTO<CreditCardSearchParams>> createTreeItems(CreditCardEvent daoEvt, TreeDisplayCount count) { - return Arrays.asList( - createFileTreeItem(daoEvt.isRejectedStatus(), daoEvt.getDataSourceId(), count), - (TreeItemDTO<CreditCardSearchParams>) (TreeItemDTO<? extends CreditCardSearchParams>) createBinTreeItem(daoEvt.isRejectedStatus(), daoEvt.getBinPrefix(), daoEvt.getDataSourceId(), count) - ); - } - - private boolean isRefreshRequired(CreditCardBinSearchParams parameters, DAOEvent evt) { - if (!(evt instanceof CreditCardEvent)) { - return false; - } - - CreditCardEvent ccEvt = (CreditCardEvent) evt; - return (parameters.isRejectedIncluded() || !ccEvt.isRejectedStatus()) - && (parameters.getDataSourceId() == null || Objects.equals(parameters.getDataSourceId(), ccEvt.getDataSourceId())) - && Objects.equals(parameters.getBinPrefix(), ccEvt.getBinPrefix()); - } - - private boolean isRefreshRequired(CreditCardFileSearchParams parameters, DAOEvent evt) { - if (!(evt instanceof CreditCardEvent)) { - return false; - } - - CreditCardEvent ccEvt = (CreditCardEvent) evt; - return (parameters.isRejectedIncluded() || !ccEvt.isRejectedStatus()) - && (parameters.getDataSourceId() == null || Objects.equals(parameters.getDataSourceId(), ccEvt.getDataSourceId())); - } - - /** - * Handles fetching and paging of credit cards by bin. - */ - public static class CreditCardByBinFetcher extends DAOFetcher<CreditCardBinSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public CreditCardByBinFetcher(CreditCardBinSearchParams params) { - super(params); - } - - protected CreditCardDAO getDAO() { - return MainDAO.getInstance().getCreditCardDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getCreditCardByBin(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isRefreshRequired(this.getParameters(), evt); - } - } - - /** - * Handles fetching and paging of credit cards by file. - */ - public static class CreditCardByFileFetcher extends DAOFetcher<CreditCardFileSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public CreditCardByFileFetcher(CreditCardFileSearchParams params) { - super(params); - } - - protected CreditCardDAO getDAO() { - return MainDAO.getInstance().getCreditCardDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getCreditCardByFile(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isRefreshRequired(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardFileSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardFileSearchParams.java deleted file mode 100644 index 0fc5eae5c67c3163c800f4aee393eec273585d90..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardFileSearchParams.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -/** - * Search params to fetch credit cards by file. - */ -public class CreditCardFileSearchParams extends CreditCardSearchParams { - - private static final String TYPE_ID = "CREDIT_CARD_BY_FILE"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - /** - * Main constructor. - * - * @param includeRejected Whether or not to include rejected items in search - * results. - * @param dataSourceId The data source id or null for no data source - * filtering. - */ - public CreditCardFileSearchParams(boolean includeRejected, Long dataSourceId) { - super(includeRejected, dataSourceId); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardSearchParams.java deleted file mode 100644 index 2024aafdf6df3b0e5b57febe3307cf767cca9381..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CreditCardSearchParams.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Base credit card search params. - */ -public class CreditCardSearchParams extends DataArtifactSearchParam { - - private static final String TYPE_ID = "CREDIT_CARD"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final boolean rejectedIncluded; - - /** - * Main constructor. - * - * @param includeRejected Whether or not to include rejected items in search - * results. - * @param dataSourceId The data source id or null for no data source - * filtering. - */ - public CreditCardSearchParams(boolean rejectedIncluded, Long dataSourceId) { - super(BlackboardArtifact.Type.TSK_ACCOUNT, dataSourceId); - this.rejectedIncluded = rejectedIncluded; - } - - public boolean isRejectedIncluded() { - return rejectedIncluded; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 79 * hash + (this.rejectedIncluded ? 1 : 0); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final CreditCardSearchParams other = (CreditCardSearchParams) obj; - if (this.rejectedIncluded != other.rejectedIncluded) { - return false; - } - return true; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java deleted file mode 100644 index 9f9debacbc0c9a14bc738eedbc8a93b2b6439f87..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DataArtifactEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; -import org.sleuthkit.autopsy.coreutils.Logger; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.stream.Stream; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * DAO for providing data about data artifacts to populate the results viewer. - */ -@NbBundle.Messages({ - "DataArtifactDAO_Accounts_displayName=Communication Accounts" -}) -public class DataArtifactDAO extends BlackboardArtifactDAO { - - private static Logger logger = Logger.getLogger(DataArtifactDAO.class.getName()); - - private static DataArtifactDAO instance = null; - - synchronized static DataArtifactDAO getInstance() { - if (instance == null) { - instance = new DataArtifactDAO(); - } - - return instance; - } - - /** - * @return The set of types that are not shown in the tree. - */ - public static Set<BlackboardArtifact.Type> getIgnoredTreeTypes() { - return BlackboardArtifactDAO.getIgnoredTreeTypes(); - } - - private final Cache<SearchParams<BlackboardArtifactSearchParam>, DataArtifactTableSearchResultsDTO> dataArtifactCache = - CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final TreeCounts<DataArtifactEvent> treeCounts = new TreeCounts<>(); - - private DataArtifactTableSearchResultsDTO fetchDataArtifactsForTable(SearchParams<BlackboardArtifactSearchParam> cacheKey) throws NoCurrentCaseException, TskCoreException { - - SleuthkitCase skCase = getCase(); - Blackboard blackboard = skCase.getBlackboard(); - BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - - String pagedWhereClause = getWhereClause(cacheKey); - - List<BlackboardArtifact> arts = new ArrayList<>(); - arts.addAll(blackboard.getDataArtifactsWhere(pagedWhereClause)); - blackboard.loadBlackboardAttributes(arts); - - long totalResultsCount = getTotalResultsCount(cacheKey, arts.size()); - - TableData tableData = createTableData(artType, arts); - return new DataArtifactTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), totalResultsCount); - } - - @Override - RowDTO createRow(BlackboardArtifact artifact, Content srcContent, Content linkedFile, boolean isTimelineSupported, List<Object> cellValues, long id) throws IllegalArgumentException { - if (!(artifact instanceof DataArtifact)) { - throw new IllegalArgumentException("Can not make row for artifact with ID: " + artifact.getId() + " - artifact must be a data artifact"); - } - return new DataArtifactRowDTO((DataArtifact) artifact, srcContent, linkedFile, isTimelineSupported, cellValues, id); - } - - public DataArtifactTableSearchResultsDTO getDataArtifactsForTable(DataArtifactSearchParam artifactKey, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - BlackboardArtifact.Type artType = artifactKey.getArtifactType(); - - if (artType == null || artType.getCategory() != BlackboardArtifact.Category.DATA_ARTIFACT - || (artifactKey.getDataSourceId() != null && artifactKey.getDataSourceId() < 0)) { - throw new IllegalArgumentException(MessageFormat.format("Illegal data. " - + "Artifact type must be non-null and data artifact. Data source id must be null or > 0. " - + "Received artifact type: {0}; data source id: {1}", artType, artifactKey.getDataSourceId() == null ? "<null>" : artifactKey.getDataSourceId())); - } - - SearchParams<BlackboardArtifactSearchParam> searchParams = new SearchParams<>(artifactKey, startItem, maxCount); - return dataArtifactCache.get(searchParams, () -> fetchDataArtifactsForTable(searchParams)); - } - - private boolean isDataArtifactInvalidating(DataArtifactSearchParam key, DAOEvent eventData) { - if (!(eventData instanceof DataArtifactEvent)) { - return false; - } else { - DataArtifactEvent dataArtEvt = (DataArtifactEvent) eventData; - return key.getArtifactType().getTypeID() == dataArtEvt.getArtifactType().getTypeID() - && (key.getDataSourceId() == null || (key.getDataSourceId() == dataArtEvt.getDataSourceId())); - } - } - - /** - * Returns a search results dto containing rows of counts data. - * - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * - * @return The results where rows are CountsRowDTO of - * DataArtifactSearchParam. - * - * @throws ExecutionException - */ - public TreeResultsDTO<DataArtifactSearchParam> getDataArtifactCounts(Long dataSourceId) throws ExecutionException { - try { - // get row dto's sorted by display name - Set<BlackboardArtifact.Type> indeterminateTypes = this.treeCounts.getEnqueued().stream() - .filter(evt -> dataSourceId == null || evt.getDataSourceId() == dataSourceId) - .map(evt -> evt.getArtifactType()) - .collect(Collectors.toSet()); - - Map<BlackboardArtifact.Type, Long> typeCounts = getCounts(BlackboardArtifact.Category.DATA_ARTIFACT, dataSourceId); - List<TreeResultsDTO.TreeItemDTO<DataArtifactSearchParam>> treeItemRows = typeCounts.entrySet().stream() - .map(entry -> { - TreeDisplayCount displayCount = indeterminateTypes.contains(entry.getKey()) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(entry.getValue()); - - return createDataArtifactTreeItem(entry.getKey(), dataSourceId, displayCount); - }) - .sorted(Comparator.comparing(countRow -> countRow.getDisplayName())) - .collect(Collectors.toList()); - - // return results - return new TreeResultsDTO<>(treeItemRows); - - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); - } - } - - @Override - void clearCaches() { - this.dataArtifactCache.invalidateAll(); - this.handleIngestComplete(); - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - // get a grouping of artifacts mapping the artifact type id to data source id. - ModuleDataEvent dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt); - if (dataEvt == null) { - return Collections.emptySet(); - } - - Map<BlackboardArtifact.Type, Set<Long>> artifactTypeDataSourceMap = new HashMap<>(); - - for (BlackboardArtifact art : dataEvt.getArtifacts()) { - try { - if (BlackboardArtifact.Category.DATA_ARTIFACT.equals(art.getType().getCategory()) - // accounts are handled in CommAccountsDAO - && art.getType().getTypeID() != BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) { - - artifactTypeDataSourceMap.computeIfAbsent(art.getType(), (k) -> new HashSet<>()) - .add(art.getDataSourceObjectID()); - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to fetch artifact category for artifact with id: " + art.getId(), ex); - } - } - - // don't do anything else if no relevant events - if (artifactTypeDataSourceMap.isEmpty()) { - return Collections.emptySet(); - } - - SubDAOUtils.invalidateKeys(this.dataArtifactCache, (sp) -> Pair.of(sp.getArtifactType(), sp.getDataSourceId()), artifactTypeDataSourceMap); - - // gather dao events based on artifacts - List<DataArtifactEvent> dataArtifactEvents = new ArrayList<>(); - for (Entry<BlackboardArtifact.Type, Set<Long>> entry : artifactTypeDataSourceMap.entrySet()) { - BlackboardArtifact.Type artType = entry.getKey(); - for (Long dsObjId : entry.getValue()) { - DataArtifactEvent newEvt = new DataArtifactEvent(artType, dsObjId); - dataArtifactEvents.add(newEvt); - } - } - - Stream<TreeEvent> dataArtifactTreeEvents; - if (IngestManager.getInstance().isIngestRunning()) { - dataArtifactTreeEvents = this.treeCounts.enqueueAll(dataArtifactEvents).stream() - .map(daoEvt -> new TreeEvent(createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), TreeDisplayCount.INDETERMINATE), false)); - } else { - dataArtifactTreeEvents = dataArtifactEvents.stream() - .map(daoEvt -> new TreeEvent(createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), TreeDisplayCount.UNSPECIFIED), true)); - } - - return Stream.of(dataArtifactEvents.stream(), dataArtifactTreeEvents) - .flatMap(s -> s) - .collect(Collectors.toSet()); - } - - /** - * Returns the display name for an artifact type. - * - * @param artifactType The artifact type. - * - * @return The display name. - */ - public String getDisplayName(BlackboardArtifact.Type artifactType) { - return artifactType.getTypeID() == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() - ? Bundle.DataArtifactDAO_Accounts_displayName() - : artifactType.getDisplayName(); - } - - private TreeItemDTO<DataArtifactSearchParam> createDataArtifactTreeItem(BlackboardArtifact.Type artifactType, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeResultsDTO.TreeItemDTO<>( - DataArtifactSearchParam.getTypeId(), - new DataArtifactSearchParam(artifactType, dataSourceId), - artifactType.getTypeID(), - getDisplayName(artifactType), - displayCount); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return SubDAOUtils.getIngestCompleteEvents(this.treeCounts, - (daoEvt, count) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), count)); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEvents(this.treeCounts, - (daoEvt, count) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), count)); - } - - - /* - * Handles fetching and paging of data artifacts. - */ - public static class DataArtifactFetcher extends DAOFetcher<DataArtifactSearchParam> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public DataArtifactFetcher(DataArtifactSearchParam params) { - super(params); - } - - protected DataArtifactDAO getDAO() { - return MainDAO.getInstance().getDataArtifactsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getDataArtifactsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isDataArtifactInvalidating(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactRowDTO.java deleted file mode 100644 index 23aad40507a29b6f7b071f99920b1dfe2a647eb5..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactRowDTO.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataArtifact; - -/** - * A result for a data artifact. - */ -public class DataArtifactRowDTO extends ArtifactRowDTO<DataArtifact> { - - private static final String TYPE_ID = "DATA_ARTIFACT"; - - public static String getTypeIdForClass() { - return TYPE_ID; - } - - public DataArtifactRowDTO(DataArtifact dataArtifact, Content srcContent, Content linkedFile, boolean isTimelineSupported, List<Object> cellValues, long id) { - super(dataArtifact, srcContent, linkedFile, isTimelineSupported, cellValues, TYPE_ID, id); - } - - public DataArtifact getDataArtifact() { - return getArtifact(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactSearchParam.java deleted file mode 100644 index 9fff59cd28c8266604f787ca1ccffa8440207121..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactSearchParam.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Key for data artifact in order to retrieve data from DAO. - */ -public class DataArtifactSearchParam extends BlackboardArtifactSearchParam { - - private static final String TYPE_ID = BlackboardArtifact.Category.DATA_ARTIFACT.name(); - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - public DataArtifactSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) { - super(artifactType, dataSourceId); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactTableSearchResultsDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactTableSearchResultsDTO.java deleted file mode 100644 index 3f689d384925c3894acc43de99d5411acc5cc4ac..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactTableSearchResultsDTO.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Search results for data artifacts. - */ -public class DataArtifactTableSearchResultsDTO extends BaseSearchResultsDTO { - - private static final String TYPE_ID = "DATA_ARTIFACT"; - private static final String SIGNATURE = "dataartifact"; - - private final BlackboardArtifact.Type artifactType; - - public DataArtifactTableSearchResultsDTO(BlackboardArtifact.Type artifactType, List<ColumnKey> columns, List<RowDTO> items, long startItem, long totalResultsCount) { - super(TYPE_ID, artifactType.getDisplayName(), columns, items, SIGNATURE, startItem, totalResultsCount); - this.artifactType = artifactType; - } - - public BlackboardArtifact.Type getArtifactType() { - return artifactType; - } - - public static class CommAccoutTableSearchResultsDTO extends DataArtifactTableSearchResultsDTO { - - private final Account.Type accountType; - - public CommAccoutTableSearchResultsDTO(Account.Type accountType, BlackboardArtifact.Type artifactType, List<ColumnKey> columns, List<RowDTO> items, long startItem, long totalResultsCount) { - super(artifactType, columns, items, startItem, totalResultsCount); - this.accountType = accountType; - } - - public Account.Type getAccountType() { - return accountType; - } - - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DeletedContentFilter.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DeletedContentFilter.java deleted file mode 100644 index 56b1ef77e5a8c1b934d0542ba2614fffb87dfaf2..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DeletedContentFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.openide.util.NbBundle; - -/** - * Filters for deleted content - */ -@NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System", - "DeletedContent.allDelFilter.text=All"}) -public enum DeletedContentFilter { - - /** - * Names are used in sql query so make sure they are sql friendly. - */ - FS_DELETED_FILTER(0, "FS_DELETED_FILTER", Bundle.DeletedContent_fsDelFilter_text()), - ALL_DELETED_FILTER(1, "ALL_DELETED_FILTER", Bundle.DeletedContent_allDelFilter_text()); - - private int id; - private String name; - private String displayName; - - private DeletedContentFilter(int id, String name, String displayName) { - this.id = id; - this.name = name; - this.displayName = displayName; - - } - - public String getName() { - return this.name; - } - - public int getId() { - return this.id; - } - - public String getDisplayName() { - return this.displayName; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DeletedContentSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DeletedContentSearchParams.java deleted file mode 100644 index 2698fa8ddfdb290afc7cb0964a6ca0c1c3d19ca6..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DeletedContentSearchParams.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Search params for accessing data about deleted content. - */ -public class DeletedContentSearchParams { - - private static final String TYPE_ID = "DELETED_CONTENT"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final DeletedContentFilter filter; - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param filter The filter (if null, indicates full refresh - * required). - * @param dataSourceId The data source id or null. - */ - public DeletedContentSearchParams(DeletedContentFilter filter, Long dataSourceId) { - this.filter = filter; - this.dataSourceId = dataSourceId; - } - - public DeletedContentFilter getFilter() { - return filter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 71 * hash + Objects.hashCode(this.filter); - hash = 71 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final DeletedContentSearchParams other = (DeletedContentSearchParams) obj; - if (this.filter != other.filter) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/EmailSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/EmailSearchParams.java deleted file mode 100644 index b98822f79a852007b3b34704b999830202503dea..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/EmailSearchParams.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Search parameters for email messages. - */ -public class EmailSearchParams extends DataArtifactSearchParam { - - private final String folder; - - /** - * Main constructor. - * - * @param dataSourceId The data source id or null if no data source - * filtering should occur. - * @param folder The folder within the email account. - */ - public EmailSearchParams(Long dataSourceId, String folder) { - super(BlackboardArtifact.Type.TSK_EMAIL_MSG, dataSourceId); - this.folder = folder; - } - - public String getFolder() { - return folder; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 23 * hash + Objects.hashCode(this.folder); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final EmailSearchParams other = (EmailSearchParams) obj; - if (!Objects.equals(this.folder, other.folder)) { - return false; - } - return true; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/EmailsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/EmailsDAO.java deleted file mode 100755 index 40467462361270a867bf207c483d66f4ae0a1401..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/EmailsDAO.java +++ /dev/null @@ -1,851 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle.Messages; -import org.python.icu.text.MessageFormat; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import org.sleuthkit.autopsy.mainui.datamodel.events.EmailEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; - -/** - * Provides information to populate the results viewer for data in the Emails - * section. - */ -public class EmailsDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(EmailsDAO.class.getName()); - - private static final String PATH_DELIMITER = "\\"; - private static final String ESCAPE_CHAR = "$"; - - private final Cache<SearchParams<EmailSearchParams>, SearchResultsDTO> searchParamsCache - = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - - private final TreeCounts<EmailEvent> emailCounts = new TreeCounts<>(); - - private static EmailsDAO instance = null; - - synchronized static EmailsDAO getInstance() { - if (instance == null) { - instance = new EmailsDAO(); - } - - return instance; - } - - SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - public SearchResultsDTO getEmailMessages(EmailSearchParams searchParams, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (searchParams.getDataSourceId() != null && searchParams.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<EmailSearchParams> emailSearchParams = new SearchParams<>(searchParams, startItem, maxCount); - return searchParamsCache.get(emailSearchParams, () -> fetchEmailMessageDTOs(emailSearchParams)); - } - - /** - * Sets the values of a results view prepared statement used in - * fetchEmailMessageDTOs. - * - * @param preparedStatement The prepared statement. - * @param normalizedPath The query path indicated by TSK_PATH. - * @param dataSourceId The data source id. - * - * @throws TskCoreException - */ - private void setResultsViewPreparedStatement(CaseDbPreparedStatement preparedStatement, String normalizedPath, Long dataSourceId) throws TskCoreException { - int paramIdx = 0; - if (normalizedPath != null) { - String noEndingSlash = normalizedPath.endsWith(PATH_DELIMITER) ? normalizedPath.substring(0, normalizedPath.length() - 1) : normalizedPath; - preparedStatement.setString(++paramIdx, noEndingSlash); - } - - if (dataSourceId != null) { - preparedStatement.setLong(++paramIdx, dataSourceId); - } - } - - private SearchResultsDTO fetchEmailMessageDTOs(SearchParams<EmailSearchParams> searchParams) throws NoCurrentCaseException, TskCoreException, SQLException, IllegalStateException { - - // get current page of communication accounts results - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - Blackboard blackboard = skCase.getBlackboard(); - - String normalizedPath = getNormalizedPath(searchParams.getParamData().getFolder()); - String pathWhereStatement = (normalizedPath == null) - // if searching for result without any folder, find items that are not prefixed with '\' or aren't null - ? "AND (attr.value_text IS NULL OR attr.value_text NOT LIKE '" + PATH_DELIMITER + "%' ESCAPE '" + ESCAPE_CHAR + "') \n" - // the path should start with the prescribed folder - : "AND (LOWER(attr.value_text) = LOWER(?))\n"; - - String baseQuery = "FROM blackboard_artifacts art \n" - + "LEFT JOIN (\n" - + " SELECT attr1.artifact_id, MIN(attr1.value_text) AS value_text\n" - + " FROM blackboard_attributes attr1\n" - + " WHERE attr1.attribute_type_id = " + BlackboardAttribute.Type.TSK_PATH.getTypeID() + "\n" - + " GROUP BY attr1.artifact_id\n" - + ") attr ON attr.artifact_id = art.artifact_id \n" - + "WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID() + " \n" - + pathWhereStatement - + (searchParams.getParamData().getDataSourceId() == null ? "" : "AND art.data_source_obj_id = ? \n") - + "GROUP BY art.artifact_id \n"; - - String itemsQuery = " art.artifact_id AS artifact_id \n" - + baseQuery - + "ORDER BY art.artifact_id\n" - + (searchParams.getMaxResultsCount() == null ? "" : "LIMIT " + searchParams.getMaxResultsCount() + "\n") - + "OFFSET " + searchParams.getStartItem() + "\n"; - - String countsQuery = " COUNT(*) AS count FROM (SELECT art.artifact_id\n" + baseQuery + ") res"; - - // this could potentially be done in a query obtaining the artifacts and another retrieving the total count. - List<Long> pagedIds = new ArrayList<>(); - AtomicReference<Long> totalCount = new AtomicReference<>(0L); - - // query for counts - try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(countsQuery)) { - setResultsViewPreparedStatement(preparedStatement, searchParams.getParamData().getFolder(), searchParams.getParamData().getDataSourceId()); - getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - if (resultSet.next()) { - totalCount.set(resultSet.getLong("count")); - } - } catch (SQLException ex) { - throw new IllegalStateException("There was an error fetching count with query:\nSELECT" + countsQuery, ex); - } - - }); - } - - // if there is a result count, get paged artifact ids - List<BlackboardArtifact> allArtifacts = Collections.emptyList(); - if (totalCount.get() > 0) { - try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(itemsQuery)) { - setResultsViewPreparedStatement(preparedStatement, searchParams.getParamData().getFolder(), searchParams.getParamData().getDataSourceId()); - getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - while (resultSet.next()) { - pagedIds.add(resultSet.getLong("artifact_id")); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "There was an error fetching emails for "); - } - - }); - } - - String whereClause = "artifacts.artifact_id IN (" + pagedIds.stream().map(l -> Long.toString(l)).collect(Collectors.joining(", ")) + ")"; - allArtifacts = getDataArtifactsAsBBA(blackboard, whereClause); - - // Populate the attributes for paged artifacts in the list. This is done using one database call as an efficient way to - // load many artifacts/attributes at once. - blackboard.loadBlackboardAttributes(allArtifacts); - } - - DataArtifactDAO dataArtDAO = MainDAO.getInstance().getDataArtifactsDAO(); - BlackboardArtifactDAO.TableData tableData = dataArtDAO.createTableData(BlackboardArtifact.Type.TSK_EMAIL_MSG, allArtifacts); - return new DataArtifactTableSearchResultsDTO(BlackboardArtifact.Type.TSK_EMAIL_MSG, tableData.columnKeys, - tableData.rows, searchParams.getStartItem(), totalCount.get()); - } - - /** - * Converts a list of data artifacts gathered using - * Blackboard.getDataArtifactsWhere into a list of blackboard artifacts. - * - * @param blackboard The TSK blackboard. - * @param whereClause The where clause to use with - * Blackboard.getDataArtifactsWhere. - * - * @return The list of BlackboardArtifacts. - * - * @throws TskCoreException - */ - @SuppressWarnings("unchecked") - private List<BlackboardArtifact> getDataArtifactsAsBBA(Blackboard blackboard, String whereClause) throws TskCoreException { - return (List<BlackboardArtifact>) (List<? extends BlackboardArtifact>) blackboard.getDataArtifactsWhere(whereClause); - } - - /** - * Determines the last non-blank folder segment. - * - * @param fullPath The full path taken from a TSK_PATH. - * - * @return The last non-blank folder segment. - */ - private static String getLastFolderSegment(String fullPath) { - // getNormalizedPath should remove any trailing whitespace or path delimiters, - // so take the last index of the path delimiter if it exists, and use everything after that. - String normalizedPath = getNormalizedPath(fullPath); - if (normalizedPath == null) { - return null; - } - - String pathWithoutEndSlash = normalizedPath.endsWith(PATH_DELIMITER) - ? normalizedPath.substring(0, normalizedPath.length() - 1) - : normalizedPath; - - int lastIdx = pathWithoutEndSlash.lastIndexOf(PATH_DELIMITER); - if (lastIdx >= 0) { - return pathWithoutEndSlash.substring(lastIdx + 1); - } else { - return pathWithoutEndSlash; - } - } - - /** - * Returns the folder display name based on the folder. If blank, returns - * Default folder string. - * - * @param folder The folder. - * - * @return The folder display name. - */ - @Messages({"EmailsDAO_getFolderDisplayName_defaultName=[Default]"}) - public static String getFolderDisplayName(String folder) { - return folder == null - ? Bundle.EmailsDAO_getFolderDisplayName_defaultName() - : folder; - } - - /** - * Normalizes the path. If path is blank or does not start with a path - * delimiter, return an empty string. Otherwise, remove all trailing - * whitespace and path delimiters. - * - * @param origPath The original path. - * - * @return The normalized path. - */ - private static String getNormalizedPath(String origPath) { - if (origPath == null || !origPath.startsWith(PATH_DELIMITER)) { - return null; - } - - if (!origPath.endsWith(PATH_DELIMITER)) { - origPath = origPath + PATH_DELIMITER; - } - - return origPath; - } - - /** - * Creates a tree item dto with the given parameters. - * - * @param fullPath The full TSK_PATH path. - * @param dataSourceId The data source object id. - * @param count The count to display. - * @param hasChildren True if has children. False if no children. Empty if - * unknown. - * - * @return The tree item dto. - */ - private TreeItemDTO<EmailSearchParams> createEmailTreeItem(String fullPath, Long dataSourceId, TreeDisplayCount count, Optional<Boolean> hasChildren) { - String normalizedPath = getNormalizedPath(fullPath); - String displayName = getFolderDisplayName(getLastFolderSegment(getNormalizedPath(fullPath))); - return createEmailTreeItem(normalizedPath, displayName, dataSourceId, count, hasChildren); - } - - /** - * Creates a tree item dto with the given parameters. - * - * @param fullPath The full TSK_PATH path. - * @param displayName The display name. - * @param dataSourceId The data source object id. - * @param count The count to display. - * @param hasChildren True if has children. False if no children. Empty if - * unknown. - * - * @return The tree item dto. - */ - private TreeItemDTO<EmailSearchParams> createEmailTreeItem(String fullPath, String displayName, Long dataSourceId, TreeDisplayCount count, Optional<Boolean> hasChildren) { - String normalizedPath = getNormalizedPath(fullPath); - return new EmailTreeItem(normalizedPath, displayName, dataSourceId, count, hasChildren); - } - - /** - * Creates a tree item dto if full path is a subfolder of parent path. - * - * @param fullPath The full TSK_PATH path. - * @param parentPath The parent path of the tree item. If fullPath is - * equivalent to parentPath, name of this item will be - * default. - * @param dataSourceId The data source object id. - * @param count The count to display. - * - * @return The tree item dto or null if fullPath is not a sub folder of - * parent path. - */ - public TreeItemDTO<EmailSearchParams> createEmailTreeItem(String fullPath, String parentPath, Long dataSourceId, TreeDisplayCount count) { - NextSubFolderResult subFolderOpt = getNextSubFolder(parentPath, fullPath); - if (!subFolderOpt.isChildInParent()) { - return null; - } - - // if full path has further children, then has children is true, otherwise we don't know. - Optional<Boolean> hasChildren = subFolderOpt.hasMoreChildren() ? Optional.empty() : Optional.of(true); - String displayName = subFolderOpt.isMatched() - ? Bundle.EmailsDAO_getFolderDisplayName_defaultName() - : getFolderDisplayName(subFolderOpt.getSubfolder()); - - return new EmailTreeItem(subFolderOpt.getSubfolderPath(), displayName, dataSourceId, count, hasChildren); - - } - - /** - * Result of calling getNextSubFolder. - */ - public static class NextSubFolderResult { - - /** - * The type of result. - */ - public enum NextSubFolderType { - /** - * The normalized child and parent path are an exact match. - */ - MATCHED, - /** - * The child path is not an ancestor of the parent. - */ - CHILD_NOT_IN_PARENT, - /** - * There is a next folder segment moving from the parent to the - * child, but not the last. - */ - NEXT_SEGMENT, - /** - * This is the last folder segment moving from the parent to the - * child. - */ - LAST_SEGMENT - } - - private final NextSubFolderType type; - private final String subFolderPath; - private final String subfolder; - - /** - * Main constructor. - * - * @param type The type of result. - * @param subFolderPath The full sub folder path. This path will be the - * normalized child path provided or a path with - * exactly one more subfolder than the parent if - * there is a subfolder of the child in the parent. - * @param subfolder If there is at least one subfolder of the child - * in the parent, this is the next subfolder of the - * parent in the child. - */ - NextSubFolderResult(NextSubFolderType type, String subFolderPath, String subfolder) { - this.type = type; - this.subFolderPath = subFolderPath; - this.subfolder = subfolder; - } - - /** - * @return The type of result. - */ - NextSubFolderType getType() { - return type; - } - - /** - * @return The full sub folder path. This path will be the normalized - * child path provided or a path with exactly one more subfolder - * than the parent if there is a subfolder of the child in the - * parent. - */ - public String getSubfolderPath() { - return subFolderPath; - } - - /** - * @return If there is at least one subfolder of the child in the - * parent, this is the next subfolder of the parent in the - * child. - */ - public String getSubfolder() { - return subfolder; - } - - /** - * @return True if 1) child path is within parent path, 2) child path - * has > 1 subfolders within parent path. - */ - public boolean hasMoreChildren() { - return type == NextSubFolderType.NEXT_SEGMENT; - } - - /** - * @return True if the normalized child and parent path are an exact - * match. - */ - public boolean isMatched() { - return type == NextSubFolderType.MATCHED; - } - - /** - * @return True if child path is contained within parent path. - */ - public boolean isChildInParent() { - return type != NextSubFolderType.CHILD_NOT_IN_PARENT; - } - - /** - * @return True if 1) child path is within parent path, 2) child path - * has >= 1 subfolders within parent path. - */ - public boolean hasSubfolder() { - return type == NextSubFolderType.NEXT_SEGMENT || type == NextSubFolderType.LAST_SEGMENT; - } - } - - /** - * Returns the next relevant subfolder (the full path) after the parent path - * or empty if child path is not a sub path of parent path. - * - * @param parentPath The parent path. - * @param childPath The child path. - * - * @return The next subfolder or empty. - */ - public NextSubFolderResult getNextSubFolder(String parentPath, String childPath) { - String normalizedParent = getNormalizedPath(parentPath); - String normalizedChild = getNormalizedPath(childPath); - - if (normalizedChild == null) { - return (normalizedParent == null) - ? new NextSubFolderResult(NextSubFolderResult.NextSubFolderType.MATCHED, normalizedChild, null) - : new NextSubFolderResult(NextSubFolderResult.NextSubFolderType.CHILD_NOT_IN_PARENT, normalizedChild, null); - } - - if (normalizedParent == null) { - normalizedParent = PATH_DELIMITER; - } - - // ensure that child is a sub path of parent - if (normalizedChild.toLowerCase().startsWith(normalizedParent.toLowerCase())) { - int nextDelimiter = normalizedChild.indexOf(PATH_DELIMITER, normalizedParent.length()); - - if (nextDelimiter >= 0) { - String nextSubfolderPath = getNormalizedPath(normalizedChild.substring(0, nextDelimiter + 1)); - return new NextSubFolderResult( - NextSubFolderResult.NextSubFolderType.NEXT_SEGMENT, - nextSubfolderPath, - getLastFolderSegment(nextSubfolderPath) - ); - } else { - return new NextSubFolderResult( - NextSubFolderResult.NextSubFolderType.LAST_SEGMENT, - normalizedChild, - getLastFolderSegment(normalizedChild) - ); - } - } else { - return new NextSubFolderResult(NextSubFolderResult.NextSubFolderType.CHILD_NOT_IN_PARENT, normalizedChild, null); - } - } - - /** - * Returns sql to query for email counts. - * - * @param dbType The db type (postgres/sqlite). - * @param folder The parent folder or null for root level. - * @param dataSourceId The data source id to filter on or null for no - * filter. If non-null, a prepared statement parameter - * will need to be provided at index 2. - * - * @return A tuple of the sql and the account like string (or null if no - * account filter). - */ - private static String getFolderChildrenSql(TskData.DbType dbType, String folder, Long dataSourceId) { - // possible and claused depending on whether or not there is an account to filter on and a data source object id to filter on. - - String folderSplitSql; - switch (dbType) { - case POSTGRESQL: - folderSplitSql = "SPLIT_PART(res.subfolders, '" + PATH_DELIMITER + "', 1)"; - break; - case SQLITE: - folderSplitSql = "SUBSTR(res.subfolders, 1, INSTR(res.subfolders, '" + PATH_DELIMITER + "') - 1)"; - break; - default: - throw new IllegalArgumentException("Unknown db type: " + dbType); - } - - String substringFolderSql; - String folderWhereStatement; - if (folder == null) { - substringFolderSql = "CASE WHEN p.path LIKE '" + PATH_DELIMITER + "%' ESCAPE '" + ESCAPE_CHAR + "' THEN SUBSTR(p.path, 2) ELSE NULL END"; - folderWhereStatement = ""; - } else { - // if exact match, - substringFolderSql = " CASE\n" - + " WHEN p.path LIKE ? ESCAPE '" + ESCAPE_CHAR + "' THEN \n" - + " SUBSTR(p.path, LENGTH(?) + 1)\n" - + " ELSE\n" - + " NULL\n" - + " END"; - folderWhereStatement = " WHERE (p.path = ? OR p.path LIKE ? ESCAPE '" + ESCAPE_CHAR + "')\n"; - } - - String query = "\n MAX(grouped_res.folder) AS folder\n" - + " ,COUNT(*) AS count\n" - + " ,MAX(grouped_res.has_children) AS has_children\n" - + "FROM (\n" - + " SELECT\n" - + " (CASE \n" - + " WHEN res.subfolders LIKE '%" + PATH_DELIMITER + "%' ESCAPE '" + ESCAPE_CHAR + "' THEN \n" - + " " + folderSplitSql + "\n" - + " ELSE\n" - + " res.subfolders\n" - + " END) AS folder\n" - + " ,(CASE \n" - + " WHEN res.subfolders LIKE '%" + PATH_DELIMITER + "%' ESCAPE '" + ESCAPE_CHAR + "' THEN 1\n" - + " ELSE 0\n" - + " END) AS has_children\n" - + " FROM (\n" - + " SELECT\n" - + " " + substringFolderSql + " AS subfolders\n" - + " FROM (\n" - + " SELECT MIN(attr.value_text) AS path\n" - + " FROM blackboard_attributes attr \n" - + " LEFT JOIN blackboard_artifacts art \n" - + " ON attr.artifact_id = art.artifact_id\n" - + " WHERE attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_PATH.getTypeID() + " \n" - + " AND attr.artifact_type_id = " + BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID() + " \n" - + (dataSourceId != null ? " AND art.data_source_obj_id = ? \n" : "") - + " GROUP BY attr.artifact_id\n" - + " ) p\n" - + folderWhereStatement - + " ) res\n" - + ") grouped_res\n" - + "GROUP BY LOWER(grouped_res.folder)\n" - + "ORDER BY LOWER(grouped_res.folder)"; - - return query; - } - - /** - * Returns the accounts and their counts in the current data source if a - * data source id is provided or all accounts if data source id is null. - * - * @param dataSourceId The data source id or null for no data source - * filter. - * @param normalizedPath The email folder parent (using '\' as prefix, - * suffix, and delimiter). If null, root level folders - * - * @return The results. - * - * @throws ExecutionException - */ - public TreeResultsDTO<EmailSearchParams> getEmailCounts(Long dataSourceId, String folder) throws ExecutionException { - - String normalizedParent = getNormalizedPath(folder); - - // a series of full folder paths prefixed and delimiter of '\' (no suffix) - Set<String> indeterminateTypes = this.emailCounts.getEnqueued().stream() - .filter(evt -> (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))) - .map(evt -> getNextSubFolder(normalizedParent, evt.getFolder())) - .filter(opt -> opt.isChildInParent()) - .map(opt -> opt.getSubfolder()) - .collect(Collectors.toSet()); - - String query = null; - try { - SleuthkitCase skCase = getCase(); - query = getFolderChildrenSql(skCase.getDatabaseType(), normalizedParent, dataSourceId); - - try (CaseDbPreparedStatement preparedStatement = skCase.getCaseDbAccessManager().prepareSelect(query)) { - int paramIdx = 0; - if (normalizedParent != null) { - String likeStatement = SubDAOUtils.likeEscape(normalizedParent, ESCAPE_CHAR) + "%"; - String normalizedWithoutSlash = normalizedParent.endsWith(PATH_DELIMITER) - ? normalizedParent.substring(0, normalizedParent.length() - 1) - : normalizedParent; - - preparedStatement.setString(++paramIdx, likeStatement); - preparedStatement.setString(++paramIdx, normalizedParent); - preparedStatement.setString(++paramIdx, normalizedWithoutSlash); - preparedStatement.setString(++paramIdx, likeStatement); - } - - if (dataSourceId != null) { - preparedStatement.setLong(++paramIdx, dataSourceId); - } - - // query for data and create tree data - List<TreeResultsDTO.TreeItemDTO<EmailSearchParams>> accumulatedData = new ArrayList<>(); - skCase.getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { - try { - while (resultSet.next()) { - boolean hasChildren = resultSet.getBoolean("has_children"); - String rsFolderSegment = resultSet.getString("folder"); - String rsPath; - if (normalizedParent != null && rsFolderSegment != null) { - // both the parent path and next folder segment are present - rsPath = getNormalizedPath(normalizedParent + rsFolderSegment + PATH_DELIMITER); - } else if (rsFolderSegment == null) { - // the folder segment is not present - rsPath = getNormalizedPath(normalizedParent); - } else { - // the normalized parent is not present but the folder segment is - rsPath = getNormalizedPath(PATH_DELIMITER + rsFolderSegment + PATH_DELIMITER); - } - - TreeDisplayCount treeDisplayCount = indeterminateTypes.contains(rsPath) - ? TreeDisplayCount.INDETERMINATE - : TreeResultsDTO.TreeDisplayCount.getDeterminate(resultSet.getLong("count")); - - accumulatedData.add(createEmailTreeItem(rsPath, getFolderDisplayName(rsFolderSegment), - dataSourceId, treeDisplayCount, Optional.of(hasChildren))); - } - } catch (SQLException ex) { - throw new IllegalStateException("A sql exception occurred.", ex); - } - }); - - // return results - return new TreeResultsDTO<>(accumulatedData); - } - - } catch (SQLException | NoCurrentCaseException | TskCoreException | IllegalStateException ex) { - throw new ExecutionException( - MessageFormat.format("An error occurred while fetching email counts for folder: {0} and sql: \n{1}", - normalizedParent == null ? "<null>" : normalizedParent, - query == null ? "<null>" : query), - ex); - } - } - - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - this.handleIngestComplete(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return SubDAOUtils.getIngestCompleteEvents( - this.emailCounts, - (daoEvt, count) -> createEmailTreeItem( - daoEvt.getFolder(), - daoEvt.getDataSourceId(), - count, - Optional.empty() - )); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEvents( - this.emailCounts, - (daoEvt, count) -> createEmailTreeItem( - daoEvt.getFolder(), - daoEvt.getDataSourceId(), - count, - Optional.empty() - )); - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - // get a grouping of artifacts mapping the artifact type id to data source id. - ModuleDataEvent dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt); - if (dataEvt == null) { - return Collections.emptySet(); - } - - // maps email folder => data source id - Map<String, Set<Long>> emailMap = new HashMap<>(); - - for (BlackboardArtifact art : dataEvt.getArtifacts()) { - try { - if (art.getType().getTypeID() == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) { - BlackboardAttribute attr = art.getAttribute(BlackboardAttribute.Type.TSK_PATH); - String folder = attr == null ? null : getNormalizedPath(attr.getValueString()); - emailMap - .computeIfAbsent(folder, (k) -> new HashSet<>()) - .add(art.getDataSourceObjectID()); - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to fetch email message info for: " + art.getId(), ex); - } - } - - // don't do anything else if no relevant events - if (emailMap.isEmpty()) { - return Collections.emptySet(); - } - - SubDAOUtils.invalidateKeys(this.searchParamsCache, (searchParams) -> Pair.of(searchParams.getFolder(), searchParams.getDataSourceId()), emailMap); - List<EmailEvent> emailEvents = new ArrayList<>(); - for (Entry<String, Set<Long>> folderEntry : emailMap.entrySet()) { - String folder = folderEntry.getKey(); - for (Long dsObjId : folderEntry.getValue()) { - emailEvents.add(new EmailEvent(dsObjId, folder)); - } - } - - Stream<TreeEvent> treeEvents = this.emailCounts.enqueueAll(emailEvents).stream() - .map(daoEvt -> { - return new TreeEvent( - createEmailTreeItem( - daoEvt.getFolder(), - daoEvt.getDataSourceId(), - TreeResultsDTO.TreeDisplayCount.INDETERMINATE, - Optional.empty()), - false); - }); - - return Stream.of(emailEvents.stream(), treeEvents) - .flatMap(s -> s) - .collect(Collectors.toSet()); - } - - /** - * Returns true if the dao event could update the data stored in the - * parameters. - * - * @param parameters The parameters. - * @param evt The event. - * - * @return True if event invalidates parameters. - */ - private boolean isEmailInvalidating(EmailSearchParams parameters, DAOEvent evt) { - if (evt instanceof EmailEvent) { - EmailEvent emailEvt = (EmailEvent) evt; - // determines if sub folder or not. if equivalent, will return present - return (getNextSubFolder(parameters.getFolder(), emailEvt.getFolder()).isChildInParent() - && (parameters.getDataSourceId() == null || Objects.equals(parameters.getDataSourceId(), emailEvt.getDataSourceId()))); - } else { - return false; - - } - } - - /** - * Tree item for emails. - */ - public static class EmailTreeItem extends TreeItemDTO<EmailSearchParams> { - - private final Optional<Boolean> hasChildren; - - /** - * Constructor. - * - * @param normalizedPath The normalized path. - * @param displayName The display name. - * @param dataSourceId The data source id. - * @param count The tree count. - * @param hasChildren True if has children. False if no children. - * Empty if unknown. - */ - EmailTreeItem(String normalizedPath, String displayName, Long dataSourceId, TreeDisplayCount count, Optional<Boolean> hasChildren) { - super( - EmailSearchParams.getTypeId(), - new EmailSearchParams(dataSourceId, normalizedPath), - // path for id to lower case so case insensitive - normalizedPath == null ? 0 : normalizedPath.toLowerCase(), - displayName, - count - ); - this.hasChildren = hasChildren; - } - - /** - * @return True if has children. False if no children. Empty if unknown. - */ - public Optional<Boolean> getHasChildren() { - return hasChildren; - } - } - - /** - * Handles fetching and paging of data for communication accounts. - */ - public static class EmailFetcher extends DAOFetcher<EmailSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public EmailFetcher(EmailSearchParams params) { - super(params); - } - - protected EmailsDAO getDAO() { - return MainDAO.getInstance().getEmailsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getEmailMessages(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isEmailInvalidating(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtDocumentFilter.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtDocumentFilter.java deleted file mode 100644 index c0cb1eca002109de208bcdaf64555657992ece86..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtDocumentFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.collect.ImmutableSet; -import java.util.Set; -import org.openide.util.NbBundle.Messages; - -/** - * Document sub-node filters - */ -@Messages({ - "FileExtDocumentFilter_html_displayName=HTML", - "FileExtDocumentFilter_office_displayName=Office", - "FileExtDocumentFilter_pdf_displayName=PDF", - "FileExtDocumentFilter_txt_displayName=Plain Text", - "FileExtDocumentFilter_rtf_displayName=Rich Text",}) -public enum FileExtDocumentFilter implements FileExtSearchFilter { - AUT_DOC_HTML(0, "AUT_DOC_HTML", Bundle.FileExtDocumentFilter_html_displayName(), ImmutableSet.of(".htm", ".html")), //NON-NLS - AUT_DOC_OFFICE(1, "AUT_DOC_OFFICE", Bundle.FileExtDocumentFilter_office_displayName(), ImmutableSet.of(".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx")), //NON-NLS - AUT_DOC_PDF(2, "AUT_DOC_PDF", Bundle.FileExtDocumentFilter_pdf_displayName(), ImmutableSet.of(".pdf")), //NON-NLS - AUT_DOC_TXT(3, "AUT_DOC_TXT", Bundle.FileExtDocumentFilter_txt_displayName(), ImmutableSet.of(".txt")), //NON-NLS - AUT_DOC_RTF(4, "AUT_DOC_RTF", Bundle.FileExtDocumentFilter_rtf_displayName(), ImmutableSet.of(".rtf")); - //NON-NLS - final int id; - final String name; - final String displayName; - final Set<String> filter; - - private FileExtDocumentFilter(int id, String name, String displayName, Set<String> filter) { - this.id = id; - this.name = name; - this.displayName = displayName; - this.filter = filter; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public int getId() { - return this.id; - } - - @Override - public String getDisplayName() { - return this.displayName; - } - - @Override - public Set<String> getFilter() { - return this.filter; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtExecutableFilter.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtExecutableFilter.java deleted file mode 100644 index 6d97922834be855cb77778ba0d9fe186444423ae..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtExecutableFilter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.collect.ImmutableSet; -import java.util.Collections; -import java.util.Set; - -/** - * Executable sub-node filters. - */ -public enum FileExtExecutableFilter implements FileExtSearchFilter { - ExecutableFilter_EXE(0, "ExecutableFilter_EXE", ".exe", ImmutableSet.of(".exe")), //NON-NLS - ExecutableFilter_DLL(1, "ExecutableFilter_DLL", ".dll", ImmutableSet.of(".dll")), //NON-NLS - ExecutableFilter_BAT(2, "ExecutableFilter_BAT", ".bat", ImmutableSet.of(".bat")), //NON-NLS - ExecutableFilter_CMD(3, "ExecutableFilter_CMD", ".cmd", ImmutableSet.of(".cmd")), //NON-NLS - ExecutableFilter_COM(4, "ExecutableFilter_COM", ".com", ImmutableSet.of(".com")); - //NON-NLS - final int id; - final String name; - final String displayName; - final Set<String> filter; - - private FileExtExecutableFilter(int id, String name, String displayName, Set<String> filter) { - this.id = id; - this.name = name; - this.displayName = displayName; - this.filter = filter; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public int getId() { - return this.id; - } - - @Override - public String getDisplayName() { - return this.displayName; - } - - @Override - public Set<String> getFilter() { - return this.filter; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtRootFilter.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtRootFilter.java deleted file mode 100644 index b57fc77d21080c7598b12b88dc10230c3351063b..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtRootFilter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.collect.ImmutableSet; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import org.apache.commons.collections.set.UnmodifiableSet; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; - -/** - * Root node filters - */ -@Messages({ - "FileExtRootFilter_image_displayName=Images", - "FileExtRootFilter_executable_displayName=Executable", - "FileExtRootFilter_video_displayName=Video", - "FileExtRootFilter_audio_displayName=Audio", - "FileExtRootFilter_archives_displayName=Archives", - "FileExtRootFilter_documents_displayName=Documents", - "FileExtRootFilter_databases_displayName=Databases",}) -public enum FileExtRootFilter implements FileExtSearchFilter { - TSK_IMAGE_FILTER(0, "TSK_IMAGE_FILTER", Bundle.FileExtRootFilter_image_displayName(), - Collections.unmodifiableSet(new HashSet<>(FileTypeExtensions.getImageExtensions()))), - TSK_VIDEO_FILTER(1, "TSK_VIDEO_FILTER", Bundle.FileExtRootFilter_video_displayName(), - Collections.unmodifiableSet(new HashSet<>(FileTypeExtensions.getVideoExtensions()))), - TSK_AUDIO_FILTER(2, "TSK_AUDIO_FILTER", Bundle.FileExtRootFilter_audio_displayName(), - Collections.unmodifiableSet(new HashSet<>(FileTypeExtensions.getAudioExtensions()))), - TSK_ARCHIVE_FILTER(3, "TSK_ARCHIVE_FILTER", Bundle.FileExtRootFilter_archives_displayName(), - Collections.unmodifiableSet(new HashSet<>(FileTypeExtensions.getArchiveExtensions()))), - TSK_DATABASE_FILTER(4, "TSK_DATABASE_FILTER", Bundle.FileExtRootFilter_databases_displayName(), - Collections.unmodifiableSet(new HashSet<>(FileTypeExtensions.getDatabaseExtensions()))), - TSK_DOCUMENT_FILTER(5, "TSK_DOCUMENT_FILTER", Bundle.FileExtRootFilter_documents_displayName(), - ImmutableSet.of(".htm", ".html", ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".rtf")), //NON-NLS - TSK_EXECUTABLE_FILTER(6, "TSK_EXECUTABLE_FILTER", Bundle.FileExtRootFilter_executable_displayName(), - Collections.unmodifiableSet(new HashSet<>(FileTypeExtensions.getExecutableExtensions()))); - //NON-NLS - final int id; - final String name; - final String displayName; - final Set<String> filter; - - private FileExtRootFilter(int id, String name, String displayName, Set<String> filter) { - this.id = id; - this.name = name; - this.displayName = displayName; - this.filter = filter; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public int getId() { - return this.id; - } - - @Override - public String getDisplayName() { - return this.displayName; - } - - @Override - public Set<String> getFilter() { - return this.filter; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtSearchFilter.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtSearchFilter.java deleted file mode 100644 index 264dd3b42eca368c73a19504679a70300b04fce7..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileExtSearchFilter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Set; - -/** - * Interface for filters by file extensions. - */ -public interface FileExtSearchFilter { - - public String getName(); - - public int getId(); - - public String getDisplayName(); - - public Set<String> getFilter(); - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileRowDTO.java deleted file mode 100644 index ddf98f538f67ea1af55d72ddd4c9ede0baa383db..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileRowDTO.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.autopsy.mainui.datamodel.MediaTypeUtils.ExtensionMediaType; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.TskData; - -/** - * DTO Representing an abstract file in the results view. - */ -public class FileRowDTO extends BaseRowDTO { - - private static String TYPE_ID = "FILE"; - - public static String getTypeIdForClass() { - return TYPE_ID; - } - - private final AbstractFile abstractFile; - private final String fileName; - private final String extension; - private final ExtensionMediaType extensionMediaType; - private final boolean allocated; - private final TskData.TSK_DB_FILES_TYPE_ENUM fileType; - - public FileRowDTO(AbstractFile abstractFile, long id, String fileName, String extension, - ExtensionMediaType extensionMediaType, boolean allocated, TskData.TSK_DB_FILES_TYPE_ENUM fileType, - List<Object> cellValues) { - this(abstractFile, id, fileName, extension, extensionMediaType, allocated, fileType, cellValues, TYPE_ID); - } - - public FileRowDTO(AbstractFile abstractFile, long id, String fileName, String extension, - ExtensionMediaType extensionMediaType, boolean allocated, TskData.TSK_DB_FILES_TYPE_ENUM fileType, - List<Object> cellValues, String typeID) { - super(cellValues, typeID, id); - this.abstractFile = abstractFile; - this.fileName = fileName; - this.extension = extension; - this.extensionMediaType = extensionMediaType; - this.allocated = allocated; - this.fileType = fileType; - } - - public ExtensionMediaType getExtensionMediaType() { - return extensionMediaType; - } - - public boolean getAllocated() { - return allocated; - } - - public TskData.TSK_DB_FILES_TYPE_ENUM getFileType() { - return fileType; - } - - public AbstractFile getAbstractFile() { - return abstractFile; - } - - public String getExtension() { - return extension; - } - - public String getFileName() { - return fileName; - } - - /** - * DTO Representing a layout file in the results view. - */ - public static class LayoutFileRowDTO extends FileRowDTO { - - private static String TYPE_ID = "LAYOUT_FILE"; - private final LayoutFile layoutFile; - - public LayoutFileRowDTO(LayoutFile layoutFile, long id, String fileName, String extension, - ExtensionMediaType extensionMediaType, boolean allocated, TskData.TSK_DB_FILES_TYPE_ENUM fileType, - List<Object> cellValues) { - super(layoutFile, id, fileName, extension, extensionMediaType, allocated, fileType, cellValues, TYPE_ID); - this.layoutFile = layoutFile; - } - - public LayoutFile getLayoutFile() { - return layoutFile; - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } - - /** - * DTO Representing an slack file in the results view. - */ - public static class SlackFileRowDTO extends FileRowDTO { - - private static String TYPE_ID = "SLACK_FILE"; - private final SlackFile file; - - public SlackFileRowDTO(SlackFile file, long id, String fileName, String extension, - ExtensionMediaType extensionMediaType, boolean allocated, TskData.TSK_DB_FILES_TYPE_ENUM fileType, - List<Object> cellValues) { - super(file, id, fileName, extension, extensionMediaType, allocated, fileType, cellValues, TYPE_ID); - this.file = file; - } - - public SlackFile getSlackFile() { - return file; - } - - public static String getTypeIdForClass() { - return TYPE_ID; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSizeFilter.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSizeFilter.java deleted file mode 100644 index 246875fffb391714381bda4028e37f7d41ef3477..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSizeFilter.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -/** - * Filters by file size for views. - */ -public enum FileSizeFilter { - SIZE_50_200(0, "SIZE_50_200", "50 - 200MB", 50_000_000L, 200_000_000L), //NON-NLS - SIZE_200_1000(1, "SIZE_200_1GB", "200MB - 1GB", 200_000_000L, 1_000_000_000L), //NON-NLS - SIZE_1000_(2, "SIZE_1000+", "1GB+", 1_000_000_000L, null); - //NON-NLS - private final int id; - private final String name; - private final String displayName; - private long minBound; - private Long maxBound; - - private FileSizeFilter(int id, String name, String displayName, long minBound, Long maxBound) { - this.id = id; - this.name = name; - this.displayName = displayName; - this.minBound = minBound; - this.maxBound = maxBound; - } - - public String getName() { - return this.name; - } - - public int getId() { - return this.id; - } - - public String getDisplayName() { - return this.displayName; - } - - /** - * @return The minimum inclusive bound (non-null). - */ - public long getMinBound() { - return minBound; - } - - /** - * @return The maximum exclusive bound (if null, no upper limit). - */ - public Long getMaxBound() { - return maxBound; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemColumnUtils.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemColumnUtils.java deleted file mode 100644 index dd11bdcfbcc3ee10b4e5edfb4d5b39c58e1c6be1..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemColumnUtils.java +++ /dev/null @@ -1,684 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.logging.Level; -import org.openide.util.NbBundle.Messages; -import org.apache.commons.lang3.StringUtils; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; -import org.sleuthkit.autopsy.mainui.sco.SCOUtils; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.FileSystem; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.Volume; -import org.sleuthkit.datamodel.VolumeSystem; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; - -/** - * Utility class for creating consistent table data. - */ -public class FileSystemColumnUtils { - - private static final Logger logger = Logger.getLogger(FileSystemColumnUtils.class.getName()); - - enum ContentType { - IMAGE, - POOL, - VOLUME, - ABSTRACT_FILE, - UNSUPPORTED; - } - - @Messages({"FileSystemColumnUtils.nameColumn.name=Name", - "FileSystemColumnUtils.abstractFileColumns.originalName=Original Name", - "FileSystemColumnUtils.abstractFileColumns.locationColLbl=Location", - "FileSystemColumnUtils.abstractFileColumns.modifiedTimeColLbl=Modified Time", - "FileSystemColumnUtils.abstractFileColumns.changeTimeColLbl=Change Time", - "FileSystemColumnUtils.abstractFileColumns.accessTimeColLbl=Access Time", - "FileSystemColumnUtils.abstractFileColumns.createdTimeColLbl=Created Time", - "FileSystemColumnUtils.abstractFileColumns.sizeColLbl=Size", - "FileSystemColumnUtils.abstractFileColumns.flagsDirColLbl=Flags(Dir)", - "FileSystemColumnUtils.abstractFileColumns.flagsMetaColLbl=Flags(Meta)", - "FileSystemColumnUtils.abstractFileColumns.modeColLbl=Mode", - "FileSystemColumnUtils.abstractFileColumns.useridColLbl=UserID", - "FileSystemColumnUtils.abstractFileColumns.groupidColLbl=GroupID", - "FileSystemColumnUtils.abstractFileColumns.metaAddrColLbl=Meta Addr.", - "FileSystemColumnUtils.abstractFileColumns.attrAddrColLbl=Attr. Addr.", - "FileSystemColumnUtils.abstractFileColumns.typeDirColLbl=Type(Dir)", - "FileSystemColumnUtils.abstractFileColumns.typeMetaColLbl=Type(Meta)", - "FileSystemColumnUtils.abstractFileColumns.knownColLbl=Known", - "FileSystemColumnUtils.abstractFileColumns.md5HashColLbl=MD5 Hash", - "FileSystemColumnUtils.abstractFileColumns.sha256HashColLbl=SHA-256 Hash", - "FileSystemColumnUtils.abstractFileColumns.objectId=Object ID", - "FileSystemColumnUtils.abstractFileColumns.mimeType=MIME Type", - "FileSystemColumnUtils.abstractFileColumns.extensionColLbl=Extension", - "FileSystemColumnUtils.volumeColumns.id=ID", - "FileSystemColumnUtils.volumeColumns.startingSector=Starting Sector", - "FileSystemColumnUtils.volumeColumns.length=Length in Sectors", - "FileSystemColumnUtils.volumeColumns.desc=Description", - "FileSystemColumnUtils.volumeColumns.flags=Flags", - "FileSystemColumnUtils.imageColumns.type=Type", - "FileSystemColumnUtils.imageColumns.typeValue=Image", - "FileSystemColumnUtils.imageColumns.size=Size (Bytes)", - "FileSystemColumnUtils.imageColumns.sectorSize=Sector Size (Bytes)", - "FileSystemColumnUtils.imageColumns.timezone=Timezone", - "FileSystemColumnUtils.imageColumns.devID=Device ID", - "FileSystemColumnUtils.poolColumns.type=Type", - - "FileSystemColumnUtils.noDescription=No Description"}) - - private static final ColumnKey NAME_COLUMN = getColumnKey(Bundle.FileSystemColumnUtils_nameColumn_name()); - - private static final List<ColumnKey> ABSTRACT_FILE_COLUMNS = Arrays.asList( - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_originalName()), - getColumnKey(SCOUtils.SCORE_COLUMN_NAME), - getColumnKey(SCOUtils.COMMENT_COLUMN_NAME), - getColumnKey(SCOUtils.OCCURANCES_COLUMN_NAME), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_locationColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_modifiedTimeColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_changeTimeColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_accessTimeColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_createdTimeColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_sizeColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_flagsDirColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_flagsMetaColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_modeColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_useridColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_groupidColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_metaAddrColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_attrAddrColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_typeDirColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_typeMetaColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_knownColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_md5HashColLbl()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_sha256HashColLbl()), - // getFileColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_objectId()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_mimeType()), - getColumnKey(Bundle.FileSystemColumnUtils_abstractFileColumns_extensionColLbl())); - - private static final List<ColumnKey> VOLUME_COLUMNS = Arrays.asList( - getColumnKey(Bundle.FileSystemColumnUtils_volumeColumns_id()), - getColumnKey(Bundle.FileSystemColumnUtils_volumeColumns_startingSector()), - getColumnKey(Bundle.FileSystemColumnUtils_volumeColumns_length()), - getColumnKey(Bundle.FileSystemColumnUtils_volumeColumns_desc()), - getColumnKey(Bundle.FileSystemColumnUtils_volumeColumns_flags())); - - private static final List<ColumnKey> IMAGE_COLUMNS = Arrays.asList( - getColumnKey(Bundle.FileSystemColumnUtils_imageColumns_type()), - getColumnKey(Bundle.FileSystemColumnUtils_imageColumns_size()), - getColumnKey(Bundle.FileSystemColumnUtils_imageColumns_sectorSize()), - getColumnKey(Bundle.FileSystemColumnUtils_imageColumns_timezone()), - getColumnKey(Bundle.FileSystemColumnUtils_imageColumns_devID()) - ); - - // Note that Hosts aren't content and will not be combined with other types, so we include the name here - private static final List<ColumnKey> HOST_COLUMNS = Arrays.asList( - NAME_COLUMN - ); - - private static final List<ColumnKey> POOL_COLUMNS = Arrays.asList( - getColumnKey(Bundle.FileSystemColumnUtils_poolColumns_type()) - ); - - /** - * Private constructor for utility class. - */ - private FileSystemColumnUtils() {} - - /** - * Convert a given Content object to an enum. - * - * @param content The Content object. - * - * @return The type corresponding to the content; UNSUPPORTED if the - * content will not be displayed in the file system section of the tree. - */ - private static ContentType getDisplayableContentType(Content content) { - if (content instanceof Image) { - return ContentType.IMAGE; - } else if (content instanceof Volume) { - return ContentType.VOLUME; - } else if (content instanceof Pool) { - return ContentType.POOL; - } else if (content instanceof AbstractFile) { - return ContentType.ABSTRACT_FILE; - } - return ContentType.UNSUPPORTED; - } - - /** - * Check whether a given content object should be displayed in the - * file system section of the tree. - * We can display an object if ContentType is not UNSUPPORTED - * and if it is not the root directory. We can not display - * file systems, volume systems, artifacts, etc. - * - * @param content The content. - * - * @return True if the content is displayable, false otherwise. - */ - static boolean isDisplayable(Content content) { - if (content instanceof AbstractFile) { - // .. directories near the top of the directory structure can - // pass the isRoot() check, so first check if the name is empty - // (real root directories will have a blank name field) - if (!content.getName().isEmpty()) { - return true; - } - return ! ((AbstractFile)content).isRoot(); - } - return (getDisplayableContentType(content) != ContentType.UNSUPPORTED); - } - - /** - * Get a list of the content types from the list that will be displayed. - * Call this before getColumnKeysForContent() and getCellValuesForContent() - * to ensure consistent columns. - * - * @param contentList List of content. - * - * @return List of types that will be displayed. - */ - static List<ContentType> getDisplayableTypesForContentList(List<Content> contentList) { - List<ContentType> displayableTypes = new ArrayList<>(); - for (Content content : contentList) { - ContentType type = getDisplayableContentType(content); - if (type != ContentType.UNSUPPORTED && ! displayableTypes.contains(type)) { - displayableTypes.add(type); - } - } - Collections.sort(displayableTypes); - return displayableTypes; - } - - /** - * Get the column keys corresponding to the given list of types. - * - * @param contentTypes The list of types. - * - * @return The list of column keys. - */ - static List<ColumnKey> getColumnKeysForContent(List<ContentType> contentTypes) { - List<ColumnKey> colKeys = new ArrayList<>(); - colKeys.add(NAME_COLUMN); - - // Make sure content types are processed in the same order as in getCellValuesForContent() - if (contentTypes.contains(ContentType.IMAGE)) { - colKeys.addAll(IMAGE_COLUMNS); - } - if (contentTypes.contains(ContentType.POOL)) { - colKeys.addAll(POOL_COLUMNS); - } - if (contentTypes.contains(ContentType.VOLUME)) { - colKeys.addAll(VOLUME_COLUMNS); - } - if (contentTypes.contains(ContentType.ABSTRACT_FILE)) { - colKeys.addAll(ABSTRACT_FILE_COLUMNS); - } - return colKeys; - } - - /** - * Get the column keys for a Host. - * - * @return The column keys. - */ - static List<ColumnKey> getColumnKeysForHost() { - return Arrays.asList(NAME_COLUMN); - } - - /** - * Get the cell values for a given content object. - * - * @param content The content to display. - * @param contentTypes The content types being displayed in the table. - * - * @return The cell values for this row. - * - * @throws TskCoreException - */ - static List<Object> getCellValuesForContent(Content content, List<ContentType> contentTypes) throws TskCoreException { - List<Object> cellValues = new ArrayList<>(); - cellValues.add(getNameValueForContent(content)); - - // Make sure content types are processed in the same order as in getColumnKeysForContent() - if (contentTypes.contains(ContentType.IMAGE)) { - cellValues.addAll(getNonNameCellValuesForImage(content)); - } - if (contentTypes.contains(ContentType.POOL)) { - cellValues.addAll(getNonNameCellValuesForPool(content)); - } - if (contentTypes.contains(ContentType.VOLUME)) { - cellValues.addAll(getNonNameCellValuesForVolume(content)); - } - if (contentTypes.contains(ContentType.ABSTRACT_FILE)) { - cellValues.addAll(getNonNameCellValuesForAbstractFile(content)); - } - return cellValues; - } - - /** - * Get the value for the name column for the given content. - * - * @param content The content. - * - * @return The display name for the content. - */ - private static String getNameValueForContent(Content content) { - if (content instanceof Image) { - Image image = (Image)content; - return image.getName(); - } else if (content instanceof Volume) { - Volume vol = (Volume)content; - return getVolumeDisplayName(vol); - } else if (content instanceof Pool) { - Pool pool = (Pool)content; - return pool.getType().getName(); // We currently use the type name for both the name and type fields - } - return content.getName(); - } - - /** - * Get the column keys for an abstract file object. - * Only use this method if all rows contain AbstractFile objects. - * Make sure the order here matches that in getCellValuesForAbstractFile(); - * - * @return The list of column keys. - */ - static List<ColumnKey> getColumnKeysForAbstractfile() { - List<ColumnKey> colKeys = new ArrayList<>(); - colKeys.add(NAME_COLUMN); - colKeys.addAll(ABSTRACT_FILE_COLUMNS); - return colKeys; - } - - /** - * Get the cell values for an abstract file. - * Only use this method if all rows contain AbstractFile objects. - * Make sure the order here matches that in getColumnKeysForAbstractfile(); - * - * @param file The file to use to populate the cells. - * - * @return List of cell values. - */ - static List<Object> getCellValuesForAbstractFile(AbstractFile file) throws TskCoreException { - List<Object> cells = new ArrayList<>(); - cells.add(getNameValueForContent(file)); - cells.addAll(getNonNameCellValuesForAbstractFile(file)); - return cells; - } - - /** - * Make sure the order here matches that in ABSTRACT_FILE_COLUMNS - * - * @param content The content to use to populate the cells (may not be an abstract file) - * - * @return List of cell values - */ - private static List<Object> getNonNameCellValuesForAbstractFile(Content content) throws TskCoreException { - final int nColumns = 17; - if (! (content instanceof AbstractFile)) { - return Collections.nCopies(nColumns, null); - } - - // Make sure to update nColumns if the number of columns here changes - AbstractFile file = (AbstractFile) content; - return Arrays.asList( - null, - //GVDTDO replace nulls with SCO - null, - null, - null, - file.getUniquePath(), - TimeZoneUtils.getFormattedTime(file.getMtime()), - TimeZoneUtils.getFormattedTime(file.getCtime()), - TimeZoneUtils.getFormattedTime(file.getAtime()), - TimeZoneUtils.getFormattedTime(file.getCrtime()), - file.getSize(), - file.getDirFlagAsString(), - file.getMetaFlagsAsString(), - // mode, - // userid, - // groupid, - // metaAddr, - // attrAddr, - // typeDir, - // typeMeta, - - file.getKnown().getName(), - StringUtils.defaultString(file.getMd5Hash()), - StringUtils.defaultString(file.getSha256Hash()), - // objectId, - - StringUtils.defaultString(file.getMIMEType()), - file.getNameExtension() - ); - } - - /** - * Make sure the order here matches that in POOL_COLUMNS - * - * @param conent The content to use to populate the cells (may not be a pool) - * - * @return List of cell values - */ - private static List<Object> getNonNameCellValuesForPool(Content content) throws TskCoreException { - final int nColumns = 1; - if (! (content instanceof Pool)) { - return Collections.nCopies(nColumns, null); - } - - // Make sure to update nColumns if the number of columns here changes - Pool pool = (Pool) content; - return Arrays.asList( - pool.getType().getName() // We currently use the type name for both the name and type fields - ); - } - - /** - * Make sure the order here matches that in HOST_COLUMNS - * - * @param host The host to use to populate the cells - * - * @return List of cell values - */ - static List<Object> getCellValuesForHost(Host host) throws TskCoreException { - return Arrays.asList( - host.getName() - ); - } - - /** - * Make sure the order here matches that in IMAGE_COLUMNS - * - * @param content The content to use to populate the cells (may not be an image) - * - * @return List of cell values - */ - private static List<Object> getNonNameCellValuesForImage(Content content) throws TskCoreException { - final int nColumns = 5; - if (! (content instanceof Image)) { - return Collections.nCopies(nColumns, null); - } - - // Make sure to update nColumns if the number of columns here changes - Image image = (Image) content; - return Arrays.asList( - Bundle.FileSystemColumnUtils_imageColumns_typeValue(), - image.getSize(), - image.getSsize(), - image.getTimeZone(), - image.getDeviceId() - ); - } - - /** - * Make sure the order here matches that in VOLUME_COLUMNS - * - * @param content The content to use to populate the cells (may not be a volume) - * - * @return List of cell values - */ - private static List<Object> getNonNameCellValuesForVolume(Content content) throws TskCoreException { - final int nColumns = 5; - if (! (content instanceof Volume)) { - return Collections.nCopies(nColumns, null); - } - - // Make sure to update nColumns if the number of columns here changes - Volume vol = (Volume) content; - return Arrays.asList( - vol.getAddr(), - vol.getStart(), - vol.getLength(), - vol.getDescription(), - vol.getFlagsAsString() - ); - } - - /** - * Get the display name for a volume. - * - * @param vol The volume. - * - * @return The display name. - */ - public static String getVolumeDisplayName(Volume vol) { - // set name, display name, and icon - String volName = "vol" + Long.toString(vol.getAddr()); - long end = vol.getStart() + (vol.getLength() - 1); - String tempVolName = volName + " (" + vol.getDescription() + ": " + vol.getStart() + "-" + end + ")"; - - // If this is a pool volume use a custom display name - try { - if (vol.getParent() != null - && vol.getParent().getParent() instanceof Pool) { - // Pool volumes are not contiguous so printing a range of blocks is inaccurate - tempVolName = volName + " (" + vol.getDescription() + ": " + vol.getStart() + ")"; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error looking up parent(s) of volume with obj ID = " + vol.getId(), ex); - } - return tempVolName; - } - - /** - * Get the content that should be displayed in the table based on the given object. - * Algorithm: - * - If content is known and known files are being hidden, return an empty list - * - If content is a slack file and slack files are being hidden, return an empty list - * - If content is a displayable type, return it - * - If content is a volume system, return its displayable children - * - If content is a file system, return the displayable children of the root folder - * - If content is the root folder, return the displayable children of the root folder - * - * @param content The base content. - * - * @return List of content to add to the table. - */ - static List<Content> getDisplayableContentForTable(Content content) throws TskCoreException { - - if (content instanceof AbstractFile) { - AbstractFile file = (AbstractFile)content; - // Skip known files if requested - if (UserPreferences.hideKnownFilesInDataSourcesTree() - && file.getKnown().equals(TskData.FileKnown.KNOWN)) { - return new ArrayList<>(); - } - - // Skip slack files if requested - if (UserPreferences.hideSlackFilesInDataSourcesTree() - && file instanceof SlackFile) { - return new ArrayList<>(); - } - } - - return getDisplayableContentForTableAndTree(content); - } - - /** - * Get the content that should be displayed in the table based on the given object. - * Algorithm: - * - If content is a displayable type, return it - * - If content is a volume system, return its displayable children - * - If content is a file system, return the displayable children of the root folder - * - If content is the root folder, return the displayable children of the root folder - * - * NOTE: This should be kept in sync with the visitor ViewContextAction.AncestorVisitor - * - * @param content The base content. - * - * @return List of content to add to the table/tree. - * - * @throws TskCoreException - */ - private static List<Content> getDisplayableContentForTableAndTree(Content content) throws TskCoreException { - // If the given content is displayable, return it - if (FileSystemColumnUtils.isDisplayable(content)) { - return Arrays.asList(content); - } - - List<Content> contentToDisplay = new ArrayList<>(); - if (content instanceof VolumeSystem) { - // Return all children that can be displayed - VolumeSystem vs = (VolumeSystem)content; - for (Content child : vs.getChildren()) { - if (isDisplayable(child)) { - contentToDisplay.add(child); - } - } - } else if (content instanceof FileSystem) { - // Return the children of the root node - FileSystem fs = (FileSystem)content; - for (Content child : fs.getRootDirectory().getChildren()) { - if (isDisplayable(child)) { - contentToDisplay.add(child); - } - } - } else if (content instanceof AbstractFile) { - if (((AbstractFile) content).isRoot()) { - // If we have the root folder, skip it and display the children - for (Content child : content.getChildren()) { - if (isDisplayable(child)) { - contentToDisplay.add(child); - } - } - } else { - return Arrays.asList(content); - } - } - - return contentToDisplay; - } - - /** - * Create a column key from a string. - * - * @param name The column name - * - * @return The column key - */ - private static ColumnKey getColumnKey(String name) { - return new ColumnKey(name, name, Bundle.FileSystemColumnUtils_noDescription()); - } - - /** - * Get the children of a given content ID that will be visible in the tree. - * - * @param contentId The ID of the parent content. - * - * @return The visible children of the given content. - * - * @throws TskCoreException - * @throws NoCurrentCaseException - */ - public static List<Content> getVisibleTreeNodeChildren(Long contentId) throws TskCoreException, NoCurrentCaseException { - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - Content content = skCase.getContentById(contentId); - List<Content> originalChildren = content.getChildren(); - - // First, advance past anything we don't display (volume systems, file systems, root folders) - List<Content> treeChildren = new ArrayList<>(); - for (Content child : originalChildren) { - treeChildren.addAll(FileSystemColumnUtils.getDisplayableContentForTableAndTree(child)); - } - - // Filter out the . and .. directories - for (Iterator<Content> iter = treeChildren.listIterator(); iter.hasNext(); ) { - Content c = iter.next(); - if ((c instanceof AbstractFile) && ContentUtils.isDotDirectory((AbstractFile)c)) { - iter.remove(); - } - } - - // Filter out any files that aren't directories and do not have children - for (Iterator<Content> iter = treeChildren.listIterator(); iter.hasNext(); ) { - Content c = iter.next(); - if (c instanceof AbstractFile - && (! ((AbstractFile)c).isDir()) - && (! hasDisplayableContentChildren((AbstractFile)c))) { - iter.remove(); - } - } - - return treeChildren; - } - - /** - * Check whether a file has displayable children. - * - * @param file The file to check. - * - * @return True if the file has displayable children, false otherwise. - */ - private static boolean hasDisplayableContentChildren(AbstractFile file) { - if (file != null) { - try { - // If the file has no children at all, then it has no displayable children. - // NOTE: AbstractContent.hasChildren() uses in-memory data to determine children - // and no DB query is required. - if (!file.hasChildren()) { - return false; - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error checking if the node has children for file with ID: " + file.getId(), ex); //NON-NLS - return false; - } - - String query = "SELECT COUNT(obj_id) AS count FROM " - + " ( SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + file.getId() + " AND type = " - + TskData.ObjectType.ARTIFACT.getObjectType() - + " INTERSECT SELECT artifact_obj_id FROM blackboard_artifacts WHERE obj_id = " + file.getId() - + " AND (artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() - + " OR artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() + ") " - + " UNION SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + file.getId() - + " AND type = " + TskData.ObjectType.ABSTRACTFILE.getObjectType() + ") AS OBJECT_IDS"; //NON-NLS; - - try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { - ResultSet resultSet = dbQuery.getResultSet(); - if (resultSet.next()) { - return (0 < resultSet.getInt("count")); - } - } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Error checking if the node has children for file with ID: " + file.getId(), ex); //NON-NLS - } - } - return false; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemContentSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemContentSearchParam.java deleted file mode 100644 index f1fb6eac9686d0db8ed2e03a3525ab4c584aadf1..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemContentSearchParam.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo; -/** - * Key for content object in order to retrieve data from DAO. - */ -public class FileSystemContentSearchParam { - - private static final String TYPE_ID = "FILE_SYSTEM_CONTENT"; - - // This param is can change, is not used as part of the search query and - // therefore is not included in the equals and hashcode methods. - private ChildNodeSelectionInfo childNodeSelectionInfo; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final Long contentObjectId; - - public FileSystemContentSearchParam(Long contentObjectId) { - this.contentObjectId = contentObjectId; - } - - public Long getContentObjectId() { - return contentObjectId; - } - - public ChildNodeSelectionInfo getNodeSelectionInfo() { - return childNodeSelectionInfo; - } - - public void setNodeSelectionInfo(ChildNodeSelectionInfo info) { - childNodeSelectionInfo = info; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.contentObjectId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileSystemContentSearchParam other = (FileSystemContentSearchParam) obj; - if (!Objects.equals(this.contentObjectId, other.contentObjectId)) { - return false; - } - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemDAO.java deleted file mode 100644 index bad3d4996e79a1727db47309ba93ce50791a1cf1..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemDAO.java +++ /dev/null @@ -1,771 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import java.beans.PropertyChangeEvent; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent; -import org.sleuthkit.autopsy.casemodule.events.HostsAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.HostsAddedToPersonEvent; -import org.sleuthkit.autopsy.casemodule.events.HostsRemovedFromPersonEvent; -import org.sleuthkit.autopsy.casemodule.events.HostsUpdatedEvent; -import org.sleuthkit.autopsy.coreutils.Logger; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import static org.sleuthkit.autopsy.mainui.datamodel.MediaTypeUtils.getExtensionMediaType; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.DirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.ImageRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VolumeRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.LocalDirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.LocalFileDataSourceRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VirtualDirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.LayoutFileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.SlackFileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.PoolRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemContentEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemHostEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemPersonEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.Directory; -import org.sleuthkit.datamodel.FileSystem; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.LocalDirectory; -import org.sleuthkit.datamodel.LocalFilesDataSource; -import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskDataException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.VirtualDirectory; -import org.sleuthkit.datamodel.Volume; -import org.sleuthkit.datamodel.VolumeSystem; - -/** - * - */ -public class FileSystemDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(FileSystemDAO.class.getName()); - - private static final Set<String> HOST_LEVEL_EVTS = ImmutableSet.of( - Case.Events.DATA_SOURCE_ADDED.toString(), - // this should trigger the case to be reopened - // Case.Events.DATA_SOURCE_DELETED.toString(), - Case.Events.DATA_SOURCE_NAME_CHANGED.toString(), - Case.Events.HOSTS_ADDED.toString(), - Case.Events.HOSTS_DELETED.toString(), - Case.Events.HOSTS_UPDATED.toString() - ); - - private static final Set<String> PERSON_LEVEL_EVTS = ImmutableSet.of( - Case.Events.HOSTS_ADDED_TO_PERSON.toString(), - Case.Events.HOSTS_REMOVED_FROM_PERSON.toString() - ); - - private final Cache<SearchParams<?>, BaseSearchResultsDTO> searchParamsCache = - CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - - private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>(); - - private static final String FILE_SYSTEM_TYPE_ID = "FILE_SYSTEM"; - - private static FileSystemDAO instance = null; - - synchronized static FileSystemDAO getInstance() { - if (instance == null) { - instance = new FileSystemDAO(); - } - return instance; - } - - private boolean isSystemContentInvalidating(FileSystemContentSearchParam key, DAOEvent daoEvent) { - if (!(daoEvent instanceof FileSystemContentEvent)) { - return false; - } - - FileSystemContentEvent contentEvt = (FileSystemContentEvent) daoEvent; - - return contentEvt.getContentObjectId() == null || Objects.equals(key.getContentObjectId(), contentEvt.getContentObjectId()); - } - - private boolean isSystemHostInvalidating(FileSystemHostSearchParam key, DAOEvent daoEvent) { - if (!(daoEvent instanceof FileSystemHostEvent)) { - return false; - } - - return key.getHostObjectId() == ((FileSystemHostEvent) daoEvent).getHost().getHostId(); - } - - private BaseSearchResultsDTO fetchContentForTableFromContent(SearchParams<FileSystemContentSearchParam> cacheKey) throws NoCurrentCaseException, TskCoreException { - - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - Long objectId = cacheKey.getParamData().getContentObjectId(); - List<Content> contentForTable = new ArrayList<>(); - String parentName = ""; - Content parentContent = skCase.getContentById(objectId); - if (parentContent == null) { - throw new TskCoreException("Error loading children of object with ID " + objectId); - } - - parentName = parentContent.getName(); - for (Content content : parentContent.getChildren()) { - contentForTable.addAll(FileSystemColumnUtils.getDisplayableContentForTable(content)); - } - - return fetchContentForTable(cacheKey, contentForTable, parentName, parentContent instanceof DataSource ? (DataSource)parentContent : null); - } - - private BaseSearchResultsDTO fetchContentForTableFromHost(SearchParams<FileSystemHostSearchParam> cacheKey) throws NoCurrentCaseException, TskCoreException { - - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - Long objectId = cacheKey.getParamData().getHostObjectId(); - List<Content> contentForTable = new ArrayList<>(); - String parentName = ""; - Optional<Host> host = skCase.getHostManager().getHostById(objectId); - if (host.isPresent()) { - parentName = host.get().getName(); - contentForTable.addAll(skCase.getHostManager().getDataSourcesForHost(host.get())); - } else { - throw new TskCoreException("Error loading host with ID " + objectId); - } - return fetchContentForTable(cacheKey, contentForTable, parentName, null); - } - - private BaseSearchResultsDTO fetchHostsForTable(SearchParams<FileSystemPersonSearchParam> cacheKey) throws NoCurrentCaseException, TskCoreException { - - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - Long objectId = cacheKey.getParamData().getPersonObjectId(); - List<Host> hostsForTable = new ArrayList<>(); - String parentName = ""; - - if (objectId != null) { - Optional<Person> person = skCase.getPersonManager().getPerson(objectId); - if (person.isPresent()) { - parentName = person.get().getName(); - hostsForTable.addAll(skCase.getPersonManager().getHostsForPerson(person.get())); - } else { - throw new TskCoreException("Error loading person with ID " + objectId); - } - } else { - hostsForTable.addAll(skCase.getPersonManager().getHostsWithoutPersons()); - } - - Stream<Host> pagedHostsStream = hostsForTable.stream() - .sorted(Comparator.comparing((host) -> host.getHostId())) - .skip(cacheKey.getStartItem()); - - if (cacheKey.getMaxResultsCount() != null) { - pagedHostsStream = pagedHostsStream.limit(cacheKey.getMaxResultsCount()); - } - - List<Host> pagedHosts = pagedHostsStream.collect(Collectors.toList()); - List<ColumnKey> columnKeys = FileSystemColumnUtils.getColumnKeysForHost(); - - List<RowDTO> rows = new ArrayList<>(); - for (Host host : pagedHosts) { - List<Object> cellValues = FileSystemColumnUtils.getCellValuesForHost(host); - rows.add(new BaseRowDTO(cellValues, FILE_SYSTEM_TYPE_ID, host.getHostId())); - } - return new BaseSearchResultsDTO(FILE_SYSTEM_TYPE_ID, parentName, columnKeys, rows, Host.class.getName(), cacheKey.getStartItem(), hostsForTable.size()); - } - - private BaseSearchResultsDTO fetchContentForTable(SearchParams<?> cacheKey, List<Content> contentForTable, - String parentName, DataSource parentDataSource) throws NoCurrentCaseException, TskCoreException { - // Ensure consistent columns for each page by doing this before paging - List<FileSystemColumnUtils.ContentType> displayableTypes = FileSystemColumnUtils.getDisplayableTypesForContentList(contentForTable); - - List<Content> pagedContent = getPaged(contentForTable, cacheKey); - List<ColumnKey> columnKeys = FileSystemColumnUtils.getColumnKeysForContent(displayableTypes); - - List<RowDTO> rows = new ArrayList<>(); - for (Content content : pagedContent) { - List<Object> cellValues = FileSystemColumnUtils.getCellValuesForContent(content, displayableTypes); - if (content instanceof Image) { - rows.add(new ImageRowDTO((Image) content, cellValues)); - } else if (content instanceof LocalFilesDataSource) { - rows.add(new LocalFileDataSourceRowDTO((LocalFilesDataSource) content, cellValues)); - } else if (content instanceof LocalDirectory) { - rows.add(new LocalDirectoryRowDTO((LocalDirectory) content, cellValues)); - } else if (content instanceof VirtualDirectory) { - rows.add(new VirtualDirectoryRowDTO((VirtualDirectory) content, cellValues)); - } else if (content instanceof Volume) { - rows.add(new VolumeRowDTO((Volume) content, cellValues)); - } else if (content instanceof Directory) { - rows.add(new DirectoryRowDTO((Directory) content, cellValues)); - } else if (content instanceof Pool) { - rows.add(new PoolRowDTO((Pool) content, cellValues)); - } else if (content instanceof SlackFile) { - AbstractFile file = (AbstractFile) content; - rows.add(new SlackFileRowDTO( - (SlackFile) file, - file.getId(), - file.getName(), - file.getNameExtension(), - getExtensionMediaType(file.getNameExtension()), - file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC), - file.getType(), - cellValues)); - } else if (content instanceof LayoutFile) { - AbstractFile file = (AbstractFile) content; - rows.add(new LayoutFileRowDTO( - (LayoutFile) file, - file.getId(), - file.getName(), - file.getNameExtension(), - getExtensionMediaType(file.getNameExtension()), - file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC), - file.getType(), - cellValues)); - } else if (content instanceof AbstractFile) { - AbstractFile file = (AbstractFile) content; - rows.add(new FileRowDTO( - file, - file.getId(), - file.getName(), - file.getNameExtension(), - getExtensionMediaType(file.getNameExtension()), - file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC), - file.getType(), - cellValues)); - } - } - return new BaseSearchResultsDTO(FILE_SYSTEM_TYPE_ID, parentName, columnKeys, rows, FILE_SYSTEM_TYPE_ID, cacheKey.getStartItem(), contentForTable.size(), parentDataSource); - } - - /** - * Returns a list of paged content. - * - * @param contentObjects The content objects. - * @param searchParams The search parameters including the paging. - * - * @return The list of paged content. - */ - private List<Content> getPaged(List<? extends Content> contentObjects, SearchParams<?> searchParams) { - Stream<? extends Content> pagedArtsStream = contentObjects.stream() - .sorted(Comparator.comparing((conent) -> conent.getId())) - .skip(searchParams.getStartItem()); - - if (searchParams.getMaxResultsCount() != null) { - pagedArtsStream = pagedArtsStream.limit(searchParams.getMaxResultsCount()); - } - - return pagedArtsStream.collect(Collectors.toList()); - } - - public BaseSearchResultsDTO getContentForTable(FileSystemContentSearchParam objectKey, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - SearchParams<FileSystemContentSearchParam> searchParams = new SearchParams<>(objectKey, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchContentForTableFromContent(searchParams)); - } - - public BaseSearchResultsDTO getContentForTable(FileSystemHostSearchParam objectKey, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - SearchParams<FileSystemHostSearchParam> searchParams = new SearchParams<>(objectKey, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchContentForTableFromHost(searchParams)); - } - - public BaseSearchResultsDTO getHostsForTable(FileSystemPersonSearchParam objectKey, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - SearchParams<FileSystemPersonSearchParam> searchParams = new SearchParams<>(objectKey, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchHostsForTable(searchParams)); - } - - private Host getHostFromDs(Content dataSource) { - if (!(dataSource instanceof DataSource)) { - return null; - } - - try { - return ((DataSource) dataSource).getHost(); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "There was an error getting the host for data source with id: " + dataSource.getId(), ex); - return null; - } - } - - /** - * In instances where parents are hidden, refresh the entire tree. - * - * @param parentContent The parent content. - * - * @return True if full tree should be refreshed. - */ - private boolean invalidatesAllFileSystem(Content parentContent) { - if (parentContent instanceof VolumeSystem || parentContent instanceof FileSystem || parentContent instanceof Image) { - return true; - } - - if (parentContent instanceof Directory) { - Directory dir = (Directory) parentContent; - return dir.isRoot() && !dir.getName().equals(".") && !dir.getName().equals(".."); - } - - if (parentContent instanceof LocalDirectory) { - return ((LocalDirectory) parentContent).isRoot(); - } - - return false; - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - Content affectedContent = null; - Content affectedParentContent = null; - Host affectedParentHost = null; - - Optional<Person> affectedParentPerson = Optional.empty(); - - boolean refreshAllContent = false; - - Content content = DAOEventUtils.getDerivedFileContentFromFileEvent(evt); - if (content != null) { - Content parentContent; - try { - parentContent = content.getParent(); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get parent content of content with id: " + content.getId(), ex); - return Collections.emptySet(); - } - - if (invalidatesAllFileSystem(parentContent)) { - refreshAllContent = true; - } else { - affectedContent = content; - affectedParentContent = parentContent; - } - } else if (evt instanceof DataSourceAddedEvent) { - affectedContent = ((DataSourceAddedEvent) evt).getDataSource(); - Host host = getHostFromDs(((DataSourceAddedEvent) evt).getDataSource()); - affectedParentHost = host; - - } else if (evt instanceof DataSourceNameChangedEvent) { - Host host = getHostFromDs(((DataSourceNameChangedEvent) evt).getDataSource()); - affectedParentHost = host; - - } else if (evt instanceof HostsAddedEvent) { - - } else if (evt instanceof HostsUpdatedEvent) { - - } else if (evt instanceof HostsAddedToPersonEvent) { - Person person = ((HostsAddedToPersonEvent) evt).getPerson(); - affectedParentPerson = Optional.of(person); - } else if (evt instanceof HostsRemovedFromPersonEvent) { - Person person = ((HostsRemovedFromPersonEvent) evt).getPerson(); - affectedParentPerson = Optional.of(person); - } - - // if nothing affected, return no events - if (!refreshAllContent && affectedContent == null && affectedParentHost == null && !affectedParentPerson.isPresent()) { - return Collections.emptySet(); - } - - invalidateKeys(affectedParentPerson, affectedParentHost, affectedContent, refreshAllContent); - - return getDAOEvents(affectedParentPerson, affectedParentHost, affectedContent, affectedParentContent, refreshAllContent); - } - - private Set<DAOEvent> getDAOEvents(Optional<Person> affectedPerson, Host affectedHost, Content affectedContent, Content affectedParentContent, boolean triggerFullRefresh) { - List<DAOEvent> daoEvents = new ArrayList<>(); - - if (triggerFullRefresh) { - daoEvents.add(new FileSystemContentEvent(null, null, null)); - } else if (affectedContent != null) { - Host parentHost = null; - - try { - parentHost = (affectedContent instanceof DataSource) - ? ((DataSource) affectedContent).getHost() - : null; - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "An error occurred while fetching content id and host id for content with id of: " + affectedContent.getId(), ex); - } - - daoEvents.add(new FileSystemContentEvent(affectedContent, affectedParentContent == null ? null : affectedParentContent.getId(), parentHost)); - } - - if (affectedHost != null) { - daoEvents.add(new FileSystemHostEvent(affectedHost, affectedContent)); - } - - affectedPerson.ifPresent((person) -> { - daoEvents.add(new FileSystemPersonEvent(person == null ? null : person.getPersonId())); - }); - - List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream() - .map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.INDETERMINATE, false)) - .filter(evt -> evt != null) - .collect(Collectors.toList()); - - return Stream.of(daoEvents, treeEvents) - .flatMap(lst -> lst.stream()) - .collect(Collectors.toSet()); - } - - private void invalidateKeys(Optional<Person> affectedPerson, Host affectedHost, Content affectedContent, boolean triggerFullRefresh) { - ConcurrentMap<SearchParams<?>, ?> concurrentMap = this.searchParamsCache.asMap(); - concurrentMap.forEach((k, v) -> { - Object searchParams = k.getParamData(); - boolean shouldInvalidate = false; - if (searchParams instanceof FileSystemPersonSearchParam && affectedPerson.isPresent()) { - shouldInvalidate = Objects.equals( - ((FileSystemPersonSearchParam) searchParams).getPersonObjectId(), - // to allow for null parent person - affectedPerson.flatMap(p -> Optional.ofNullable(p.getPersonId())).orElse(null) - ); - - } else if (searchParams instanceof FileSystemHostSearchParam && affectedHost != null) { - shouldInvalidate = Objects.equals( - ((FileSystemHostSearchParam) searchParams).getHostObjectId(), - affectedHost.getHostId() - ); - - } else if (searchParams instanceof FileSystemContentSearchParam) { - if (triggerFullRefresh) { - shouldInvalidate = true; - } else if (affectedContent != null) { - shouldInvalidate = Objects.equals( - ((FileSystemContentSearchParam) searchParams).getContentObjectId(), - affectedContent.getId() - ); - } - } - - if (shouldInvalidate) { - concurrentMap.remove(k); - } - }); - } - - /** - * Get all data sources belonging to a given host. - * - * @param host The host. - * - * @return Results containing all data sources for the given host. - * - * @throws ExecutionException - */ - public TreeResultsDTO<FileSystemContentSearchParam> getDataSourcesForHost(Host host) throws ExecutionException { - try { - List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>(); - for (DataSource ds : Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getDataSourcesForHost(host)) { - treeItemRows.add(createDisplayableContentTreeItem(ds, TreeDisplayCount.NOT_SHOWN)); - } - return new TreeResultsDTO<>(treeItemRows); - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching images for host with ID " + host.getHostId(), ex); - } - } - - /** - * Create results for a single given data source ID (not its children). - * - * @param dataSourceObjId The data source object ID. - * - * @return Results containing just this data source. - * - * @throws ExecutionException - */ - public TreeResultsDTO<FileSystemContentSearchParam> getSingleDataSource(long dataSourceObjId) throws ExecutionException { - try { - List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>(); - DataSource ds = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSourceObjId); - treeItemRows.add(createDisplayableContentTreeItem(ds, TreeDisplayCount.NOT_SHOWN)); - return new TreeResultsDTO<>(treeItemRows); - } catch (NoCurrentCaseException | TskCoreException | TskDataException ex) { - throw new ExecutionException("An error occurred while fetching data source with ID " + dataSourceObjId, ex); - } - } - - /** - * Get the children that will be displayed in the tree for a given content - * ID. - * - * @param contentId Object ID of parent content. - * - * @return The results. - * - * @throws ExecutionException - */ - public TreeResultsDTO<FileSystemContentSearchParam> getDisplayableContentChildren(Long contentId) throws ExecutionException { - try { - - List<Content> treeChildren = FileSystemColumnUtils.getVisibleTreeNodeChildren(contentId); - - List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>(); - for (Content child : treeChildren) { - Long countForNode = null; - if ((child instanceof AbstractFile) - && !(child instanceof LocalFilesDataSource)) { - countForNode = getContentForTable(new FileSystemContentSearchParam(child.getId()), 0, null).getTotalResultsCount(); - } - TreeDisplayCount displayCount = countForNode == null ? TreeDisplayCount.NOT_SHOWN : TreeDisplayCount.getDeterminate(countForNode); - treeItemRows.add(createDisplayableContentTreeItem(child, displayCount)); - } - return new TreeResultsDTO<>(treeItemRows); - - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); - } - } - - private FileSystemTreeItem createDisplayableContentTreeItem(Content child, TreeDisplayCount displayCount) { - return new FileSystemTreeItem( - FileSystemContentSearchParam.getTypeId(), - new FileSystemContentSearchParam(child == null ? null : child.getId()), - child, - child == null ? null : getNameForContent(child), - child instanceof AbstractFile ? ((AbstractFile) child).getMetaType() : null, - displayCount - ); - } - - /** - * Get display name for the given content. - * - * @param content The content. - * - * @return Display name for the content. - */ - private String getNameForContent(Content content) { - if (content instanceof Volume) { - return FileSystemColumnUtils.getVolumeDisplayName((Volume)content); - } - return content.getName(); - } - - private TreeEvent createTreeEvent(DAOEvent daoEvent, TreeDisplayCount count, boolean fullRefresh) { - - if (daoEvent instanceof FileSystemContentEvent) { - FileSystemContentEvent contentEvt = (FileSystemContentEvent) daoEvent; - - TreeDisplayCount countToShow = contentEvt.getContent() instanceof DataSource ? TreeDisplayCount.NOT_SHOWN : count; - return new FileSystemTreeEvent( - contentEvt.getParentObjId(), - contentEvt.getParentHost(), - createDisplayableContentTreeItem(contentEvt.getContent(), countToShow), - fullRefresh); - - } else if (daoEvent instanceof FileSystemHostEvent) { - FileSystemHostEvent hostEvt = (FileSystemHostEvent) daoEvent; - - return new FileSystemTreeEvent( - null, - hostEvt.getHost(), - createDisplayableContentTreeItem(hostEvt.getDataSource(), TreeDisplayCount.NOT_SHOWN), - true); - } else if (daoEvent instanceof FileSystemPersonEvent) { - - } - - return null; - } - - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - handleIngestComplete(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return treeCounts.flushEvents().stream() - .map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.UNSPECIFIED, true)) - .filter(evt -> evt != null) - .collect(Collectors.toSet()); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return treeCounts.getEventTimeouts().stream() - .map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.UNSPECIFIED, true)) - .filter(evt -> evt != null) - .collect(Collectors.toSet()); - } - - public static class DataSourceRefreshTreeEvent extends TreeEvent { - - public DataSourceRefreshTreeEvent(boolean refresh) { - super(null, refresh); - } - - } - - public static class FileSystemTreeItem extends TreeItemDTO<FileSystemContentSearchParam> { - - private final TskData.TSK_FS_META_TYPE_ENUM metaType; - - FileSystemTreeItem( - String typeId, - FileSystemContentSearchParam searchParams, - Object id, - String displayName, - TskData.TSK_FS_META_TYPE_ENUM metaType, - TreeDisplayCount count) { - - super(typeId, searchParams, id, displayName, count); - this.metaType = metaType; - } - - public TskData.TSK_FS_META_TYPE_ENUM getMetaType() { - return metaType; - } - - } - - public static class FileSystemTreeEvent extends TreeEvent { - - private final Long parentContentId; - private final Host parentHost; - private final FileSystemTreeItem itemRecord; - - FileSystemTreeEvent(Long parentContentId, Host parentHost, FileSystemTreeItem itemRecord, boolean refreshRequired) { - super(itemRecord, refreshRequired); - this.parentContentId = parentContentId; - this.parentHost = parentHost; - this.itemRecord = itemRecord; - } - - public Long getParentContentId() { - return parentContentId; - } - - public Host getParentHost() { - return parentHost; - } - - @Override - public FileSystemTreeItem getItemRecord() { - // override to be typed and contain extra information - return itemRecord; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 31 * hash + Objects.hashCode(this.itemRecord); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileSystemTreeEvent other = (FileSystemTreeEvent) obj; - if (!Objects.equals(this.itemRecord, other.itemRecord)) { - return false; - } - return true; - } - - } - - /** - * Handles fetching and paging of data for file types by mime type. - */ - public static class FileSystemFetcher extends DAOFetcher<FileSystemContentSearchParam> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public FileSystemFetcher(FileSystemContentSearchParam params) { - super(params); - } - - protected FileSystemDAO getDAO() { - return MainDAO.getInstance().getFileSystemDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getContentForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isSystemContentInvalidating(this.getParameters(), evt); - } - } - - public static class FileSystemHostFetcher extends DAOFetcher<FileSystemHostSearchParam> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public FileSystemHostFetcher(FileSystemHostSearchParam params) { - super(params); - } - - protected FileSystemDAO getDAO() { - return MainDAO.getInstance().getFileSystemDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getContentForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isSystemHostInvalidating(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemHostSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemHostSearchParam.java deleted file mode 100644 index a258b2320d8598af45cc54c99e7961e0bc04971e..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemHostSearchParam.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for content object in order to retrieve data from DAO. - */ -public class FileSystemHostSearchParam { - - private static final String TYPE_ID = "FILE_SYSTEM_HOST"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final Long hostObjectId; - - public FileSystemHostSearchParam(Long hostObjectId) { - this.hostObjectId = hostObjectId; - } - - public Long getHostObjectId() { - return hostObjectId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.hostObjectId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileSystemHostSearchParam other = (FileSystemHostSearchParam) obj; - if (!Objects.equals(this.hostObjectId, other.hostObjectId)) { - return false; - } - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemPersonSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemPersonSearchParam.java deleted file mode 100644 index ae198a89ddc4fe8fd19e3c917520345eb280f793..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileSystemPersonSearchParam.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for person object in order to retrieve data from DAO. - */ -public class FileSystemPersonSearchParam { - - private static final String TYPE_ID = "FILE_SYSTEM_PERSON"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final Long personObjectId; - - /** - * Create search param. - * - * @param personObjectId May be null to fetch hosts not associated with a - * Person - */ - public FileSystemPersonSearchParam(Long personObjectId) { - this.personObjectId = personObjectId; - } - - public Long getPersonObjectId() { - return personObjectId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.personObjectId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileSystemPersonSearchParam other = (FileSystemPersonSearchParam) obj; - if (!Objects.equals(this.personObjectId, other.personObjectId)) { - return false; - } - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeExtensionsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeExtensionsSearchParams.java deleted file mode 100644 index be0bf45de2e5d65d71a11c81ee702e8f920abaed..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeExtensionsSearchParams.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for accessing data about file type extensions from the DAO. - */ -public class FileTypeExtensionsSearchParams { - - private static final String TYPE_ID = "FILE_VIEWS_EXTENSION"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final FileExtSearchFilter filter; - private final Long dataSourceId; - - // TODO: This should ideally take in some kind of ENUM once we redo the tree. - // this assumes that filters implicitly or explicitly implement hashCode and equals to work - public FileTypeExtensionsSearchParams(FileExtSearchFilter filter, Long dataSourceId) { - this.filter = filter; - this.dataSourceId = dataSourceId; - } - - public FileExtSearchFilter getFilter() { - return filter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 31 * hash + Objects.hashCode(this.filter); - hash = 31 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileTypeExtensionsSearchParams other = (FileTypeExtensionsSearchParams) obj; - if (!Objects.equals(this.filter, other.filter)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeMimeSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeMimeSearchParams.java deleted file mode 100755 index 0b89294e958de40161c726e5a159df32ca103e65..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeMimeSearchParams.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for accessing data about file MIME type from the DAO. - */ -public class FileTypeMimeSearchParams { - - private static final String TYPE_ID = "FILE_VIEWS_MIME"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final String mimeType; - private final Long dataSourceId; - - public FileTypeMimeSearchParams(String mimeType, Long dataSourceId) { - this.mimeType = mimeType; - this.dataSourceId = dataSourceId; - } - - public String getMimeType() { - return mimeType; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 29 * hash + Objects.hashCode(this.mimeType); - hash = 29 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileTypeMimeSearchParams other = (FileTypeMimeSearchParams) obj; - if (!Objects.equals(this.mimeType, other.mimeType)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java deleted file mode 100755 index 898f1fca8377dccb284bf731ef5dbf2efb4904dd..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for accessing data about file sizeFilter from the DAO. - */ -public class FileTypeSizeSearchParams { - - private static final String TYPE_ID = "FILE_VIEWS_SIZE"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final FileSizeFilter sizeFilter; - private final Long dataSourceId; - - public FileTypeSizeSearchParams(FileSizeFilter sizeFilter, Long dataSourceId) { - this.sizeFilter = sizeFilter; - this.dataSourceId = dataSourceId; - } - - public FileSizeFilter getSizeFilter() { - return sizeFilter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 53 * hash + Objects.hashCode(this.sizeFilter); - hash = 53 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileTypeSizeSearchParams other = (FileTypeSizeSearchParams) obj; - if (this.sizeFilter != other.sizeFilter) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostPersonDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostPersonDAO.java deleted file mode 100644 index a5d1b512e1ed6ac24417fda0a96ae901d4468264..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostPersonDAO.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.beans.PropertyChangeEvent; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.HostPersonEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - * Dao for hosts and persons. - */ -@Messages({"HostPersonDAO_unknownPersons_displayName=Unknown Persons"}) -public class HostPersonDAO extends AbstractDAO { - - private static HostPersonDAO instance = null; - - /** - * @return The singleton instance of this class. - */ - public static HostPersonDAO getInstance() { - if (instance == null) { - instance = new HostPersonDAO(); - } - return instance; - } - - /** - * @return Identifier used for unknown persons. - */ - public static String getUnknownPersonsName() { - return Bundle.HostPersonDAO_unknownPersons_displayName(); - } - - private SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - /** - * Returns tree items for all hosts in the case. - * - * @return All hosts in the case. - * - * @throws ExecutionException - */ - public TreeResultsDTO<HostSearchParams> getAllHosts() throws ExecutionException { - try { - return new TreeResultsDTO<>(getCase().getHostManager().getAllHosts().stream() - .map(h -> createHostTreeItem(h)) - .collect(Collectors.toList())); - } catch (TskCoreException | NoCurrentCaseException ex) { - throw new ExecutionException("Error while fetching all hosts.", ex); - } - } - - /** - * Queries for all hosts belonging to the person or all hosts without a - * person association if person parameter is null. - * - * @param person The person to which hosts belong to or null for hosts with - * no associated person. - * - * @return The results in tree item form. - * - * @throws ExecutionException - */ - public TreeResultsDTO<HostSearchParams> getHosts(Person person) throws ExecutionException { - try { - List<Host> hosts = person == null - ? getCase().getPersonManager().getHostsWithoutPersons() - : getCase().getPersonManager().getHostsForPerson(person); - - return new TreeResultsDTO<>(hosts.stream() - .map(h -> createHostTreeItem(h)) - .collect(Collectors.toList())); - } catch (TskCoreException | NoCurrentCaseException ex) { - throw new ExecutionException("Error while fetching host for person: " + (person == null ? "<null>" : person.getName() + " id: " + person.getPersonId()), ex); - } - } - - /** - * Returns all persons associated with the case. - * @return The person tree results. - * @throws ExecutionException - */ - public TreeResultsDTO<PersonSearchParams> getAllPersons() throws ExecutionException { - try { - List<Person> persons = getCase().getPersonManager().getPersons(); - - List<TreeItemDTO<PersonSearchParams>> personSearchParams = new ArrayList<>(); - for (Person person : persons) { - personSearchParams.add(createPersonTreeItem(person)); - } - - if (!getCase().getPersonManager().getHostsWithoutPersons().isEmpty()) { - personSearchParams.add(createPersonTreeItem(null)); - } - - return new TreeResultsDTO<>(personSearchParams); - } catch (TskCoreException | NoCurrentCaseException ex) { - throw new ExecutionException("Error while fetching all hosts.", ex); - } - } - - private TreeItemDTO<HostSearchParams> createHostTreeItem(Host host) { - return new TreeItemDTO<>( - HostSearchParams.getTypeId(), - new HostSearchParams(host), - host.getHostId(), - host.getName(), - TreeDisplayCount.NOT_SHOWN); - } - - private TreeItemDTO<PersonSearchParams> createPersonTreeItem(Person person) { - return new TreeItemDTO<>( - PersonSearchParams.getTypeId(), - new PersonSearchParams(person), - person == null ? 0 : person.getPersonId(), - person == null ? Bundle.HostPersonDAO_unknownPersons_displayName() : person.getName(), - TreeDisplayCount.NOT_SHOWN); - } - - private static final Set<String> caseEvents = Stream.of( - Case.Events.PERSONS_ADDED, - Case.Events.PERSONS_DELETED, - Case.Events.PERSONS_UPDATED, - Case.Events.HOSTS_ADDED, - Case.Events.HOSTS_ADDED_TO_PERSON, - Case.Events.HOSTS_DELETED, - Case.Events.HOSTS_REMOVED_FROM_PERSON, - Case.Events.HOSTS_UPDATED - ) - .map(caseEvent -> caseEvent.toString()) - .collect(Collectors.toSet()); - - @Override - Set<? extends DAOEvent> processEvent(PropertyChangeEvent evt) { - return caseEvents.contains(evt.getPropertyName()) - ? Collections.singleton(new HostPersonEvent()) - : Collections.emptySet(); - } - - @Override - void clearCaches() { - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return Collections.emptySet(); - } - - @Override - Set<? extends TreeEvent> shouldRefreshTree() { - return Collections.emptySet(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostSearchParams.java deleted file mode 100644 index 70015745a1b3e44dfcdfbf987a5537c966869072..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostSearchParams.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.datamodel.Host; - -/** - * Search parameters for a given host. - */ -public class HostSearchParams { - private static final String TYPE_ID = "Host"; - - public static String getTypeId() { - return TYPE_ID; - } - - private final Host host; - - /** - * Main constructor. - * @param host The host. - */ - public HostSearchParams(Host host) { - this.host = host; - } - - /** - * @return The host. - */ - public Host getHost() { - return host; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 37 * hash + Objects.hashCode(this.host); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final HostSearchParams other = (HostSearchParams) obj; - if (!Objects.equals(this.host, other.host)) { - return false; - } - return true; - } - - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordHitSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordHitSearchParam.java deleted file mode 100644 index 16df045ad41248c05b3e8248aa262bd9ef190781..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordHitSearchParam.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.apache.commons.lang3.StringUtils; -import org.sleuthkit.datamodel.TskData; - -/** - * Key for keyword hits in order to retrieve data from DAO. - */ -public class KeywordHitSearchParam extends KeywordSearchTermParams { - - private static final String TYPE_ID = "KEYWORD_HIT"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final String keyword; - private final String regex; - private final TskData.KeywordSearchQueryType searchType; - - public KeywordHitSearchParam(Long dataSourceId, String setName, String keyword, String regex, TskData.KeywordSearchQueryType searchType, String configuration) { - super(setName, regex, searchType, configuration, StringUtils.isNotBlank(keyword) && !Objects.equals(regex, keyword), dataSourceId); - this.keyword = keyword; - this.regex = regex; - this.searchType = searchType; - } - - public String getRegex() { - return regex; - } - - public String getKeyword() { - return keyword; - } - - public TskData.KeywordSearchQueryType getSearchType() { - return searchType; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 29 * hash + Objects.hashCode(this.keyword); - hash = 29 * hash + Objects.hashCode(this.regex); - hash = 29 * hash + Objects.hashCode(this.searchType); - hash = 29 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final KeywordHitSearchParam other = (KeywordHitSearchParam) obj; - if (!Objects.equals(this.keyword, other.keyword)) { - return false; - } - if (!Objects.equals(this.regex, other.regex)) { - return false; - } - if (this.searchType != other.searchType) { - return false; - } - return super.equals(obj); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordListSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordListSearchParam.java deleted file mode 100644 index 462a3881c842d4666299f5abc8e3f06712be19af..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordListSearchParam.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Base class for search params for analysis results that filter by set name. - */ -public class KeywordListSearchParam extends AnalysisResultSearchParam { - - private static final String TYPE_ID = "ANALYSIS_RESULT_SET"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final String setName; - - public KeywordListSearchParam(Long dataSourceId, String configuration, String setName) { - super(BlackboardArtifact.Type.TSK_KEYWORD_HIT, configuration, dataSourceId); - this.setName = setName; - } - - public String getSetName() { - return setName; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 79 * hash + Objects.hashCode(this.setName); - hash = 79 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final KeywordListSearchParam other = (KeywordListSearchParam) obj; - if (!Objects.equals(this.setName, other.setName)) { - return false; - } - return super.equals(obj); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordSearchTermParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordSearchTermParams.java deleted file mode 100644 index 6bc2f812e59d2b7796e5f67dee32b3910113cf0d..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/KeywordSearchTermParams.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.TskData; - -/** - * Parameters for a keyword search term. - */ -public class KeywordSearchTermParams extends KeywordListSearchParam { - - private static final String TYPE_ID = "KEYWORD_SEARCH_TERMS"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final String searchTerm; - private final Boolean hasChildren; - private final TskData.KeywordSearchQueryType searchType; - - /** - * Main constructor. - * - * @param setName The set name. - * @param searchTerm The search term (determined from regex or keyword). - * @param searchType The keyword search type attribute. - * @param configuration The configuration of the analysis results set if - * hasChildren is false. - * @param hasChildren Whether or not this search term has children tree - * nodes (i.e. url regex search that further divides - * into different urls). - * @param dataSourceId The data source id or null. - */ - public KeywordSearchTermParams(String setName, String searchTerm, TskData.KeywordSearchQueryType searchType, String configuration, boolean hasChildren, Long dataSourceId) { - super(dataSourceId, configuration, setName); - this.searchTerm = searchTerm; - this.hasChildren = hasChildren; - this.searchType = searchType; - } - - /** - * @return The search term (determined from regex or keyword). - */ - public String getRegex() { - return searchTerm; - } - - /** - * @return Whether or not this search term has children tree nodes (i.e. url - * regex search that further divides into different urls). - */ - public boolean hasChildren() { - return hasChildren; - } - - /** - * @return The keyword search type value. - */ - public TskData.KeywordSearchQueryType getSearchType() { - return searchType; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 47 * hash + Objects.hashCode(this.searchTerm); - hash = 47 * hash + Objects.hashCode(this.searchType); - hash = 47 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final KeywordSearchTermParams other = (KeywordSearchTermParams) obj; - if (!Objects.equals(this.searchTerm, other.searchTerm)) { - return false; - } - if (this.searchType != other.searchType) { - return false; - } - return super.equals(obj); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MainDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MainDAO.java deleted file mode 100644 index d3c3f50cb593591084299670d3682354d86af2fb..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MainDAO.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventBatcher; -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.prefs.PreferenceChangeListener; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.commons.collections4.CollectionUtils; -import org.python.google.common.collect.ImmutableSet; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.mainui.datamodel.events.CacheClearEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; - -/** - * Main entry point for DAO for providing data to populate the data results - * viewer. - */ -public class MainDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(MainDAO.class.getName()); - - private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS = EnumSet.of( - IngestManager.IngestJobEvent.COMPLETED, - IngestManager.IngestJobEvent.CANCELLED - ); - - private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS = EnumSet.of( - IngestManager.IngestModuleEvent.CONTENT_CHANGED, - IngestManager.IngestModuleEvent.DATA_ADDED, - IngestManager.IngestModuleEvent.FILE_DONE - ); - - private static final Set<String> QUEUED_CASE_EVENTS = ImmutableSet.of( - Case.Events.OS_ACCOUNTS_ADDED.toString(), - Case.Events.OS_ACCOUNTS_UPDATED.toString(), - Case.Events.OS_ACCOUNTS_DELETED.toString(), - Case.Events.OS_ACCT_INSTANCES_ADDED.toString() - ); - - private static final long WATCH_RESOLUTION_MILLIS = 30 * 1000; - - private static final long RESULT_BATCH_MILLIS = 5 * 1000; - - private static MainDAO instance = null; - - public synchronized static MainDAO getInstance() { - if (instance == null) { - instance = new MainDAO(); - instance.init(); - } - - return instance; - } - - /** - * The case event listener. - */ - private final PropertyChangeListener caseEventListener = (evt) -> { - try { - if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { - this.clearCaches(); - } else if (QUEUED_CASE_EVENTS.contains(evt.getPropertyName())) { - handleEvent(evt, false); - } else { - // handle case events immediately - handleEvent(evt, true); - } - } catch (Throwable ex) { - // firewall exception - logger.log(Level.WARNING, "An exception occurred while handling case events", ex); - } - }; - - /** - * The user preference listener. - */ - private final PreferenceChangeListener userPreferenceListener = (evt) -> { - try { - this.clearCaches(); - } catch (Throwable ex) { - // firewall exception - logger.log(Level.WARNING, "An exception occurred while handling user preference change", ex); - } - - }; - - /** - * The ingest module event listener. - */ - private final PropertyChangeListener ingestModuleEventListener = (evt) -> { - try { - handleEvent(evt, false); - } catch (Throwable ex) { - // firewall exception - logger.log(Level.WARNING, "An exception occurred while handling ingest module event", ex); - } - }; - - /** - * The ingest job event listener. - */ - private final PropertyChangeListener ingestJobEventListener = (evt) -> { - try { - handleEventFlush(); - } catch (Throwable ex) { - // firewall exception - logger.log(Level.WARNING, "An exception occurred while handling ingest job event", ex); - } - - }; - - private final ScheduledThreadPoolExecutor timeoutExecutor - = new ScheduledThreadPoolExecutor(1, - new ThreadFactoryBuilder().setNameFormat(MainDAO.class.getName()).build()); - - private final PropertyChangeManager resultEventsManager = new PropertyChangeManager(); - private final PropertyChangeManager treeEventsManager = new PropertyChangeManager(); - - private final DAOEventBatcher<DAOEvent> eventBatcher = new DAOEventBatcher<>( - (evts) -> { - try { - fireResultEvts(evts); - } catch (Throwable ex) { - // firewall exception - logger.log(Level.WARNING, "An exception occurred while handling batched dao events", ex); - } - }, - RESULT_BATCH_MILLIS); - - private final DataArtifactDAO dataArtifactDAO = DataArtifactDAO.getInstance(); - private final AnalysisResultDAO analysisResultDAO = AnalysisResultDAO.getInstance(); - private final ViewsDAO viewsDAO = ViewsDAO.getInstance(); - private final FileSystemDAO fileSystemDAO = FileSystemDAO.getInstance(); - private final TagsDAO tagsDAO = TagsDAO.getInstance(); - private final OsAccountsDAO osAccountsDAO = OsAccountsDAO.getInstance(); - private final CommAccountsDAO commAccountsDAO = CommAccountsDAO.getInstance(); - private final CreditCardDAO creditCardDAO = CreditCardDAO.getInstance(); - private final EmailsDAO emailsDAO = EmailsDAO.getInstance(); - private final HostPersonDAO hostPersonDAO = HostPersonDAO.getInstance(); - private final ReportsDAO reportsDAO = ReportsDAO.getInstance(); - private final ScoreDAO scoreDAO = ScoreDAO.getInstance(); - - // NOTE: whenever adding a new sub-dao, it should be added to this list for event updates. - private final List<AbstractDAO> allDAOs = ImmutableList.of(dataArtifactDAO, - analysisResultDAO, - viewsDAO, - fileSystemDAO, - tagsDAO, - osAccountsDAO, - commAccountsDAO, - creditCardDAO, - emailsDAO, - hostPersonDAO, - reportsDAO, - scoreDAO); - - /** - * Registers listeners with autopsy event publishers and starts internal - * threads. - */ - void init() { - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS, ingestModuleEventListener); - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS, ingestJobEventListener); - Case.addPropertyChangeListener(caseEventListener); - UserPreferences.addChangeListener(userPreferenceListener); - - this.timeoutExecutor.scheduleAtFixedRate( - () -> { - try { - handleTreeEventTimeouts(); - } catch (Throwable ex) { - // firewall exception - logger.log(Level.WARNING, "An exception occurred while handling tree event timeouts", ex); - } - }, - WATCH_RESOLUTION_MILLIS, - WATCH_RESOLUTION_MILLIS, - TimeUnit.MILLISECONDS); - } - - /** - * Unregisters listeners from autopsy event publishers. - */ - void unregister() { - IngestManager.getInstance().removeIngestModuleEventListener(INGEST_MODULE_EVENTS, ingestModuleEventListener); - IngestManager.getInstance().removeIngestJobEventListener(INGEST_JOB_EVENTS, ingestJobEventListener); - Case.removePropertyChangeListener(caseEventListener); - UserPreferences.removeChangeListener(userPreferenceListener); - } - - public DataArtifactDAO getDataArtifactsDAO() { - return dataArtifactDAO; - } - - public AnalysisResultDAO getAnalysisResultDAO() { - return analysisResultDAO; - } - - public ViewsDAO getViewsDAO() { - return viewsDAO; - } - - public FileSystemDAO getFileSystemDAO() { - return fileSystemDAO; - } - - public TagsDAO getTagsDAO() { - return tagsDAO; - } - - public OsAccountsDAO getOsAccountsDAO() { - return osAccountsDAO; - } - - public CommAccountsDAO getCommAccountsDAO() { - return commAccountsDAO; - } - - public EmailsDAO getEmailsDAO() { - return emailsDAO; - } - - public CreditCardDAO getCreditCardDAO() { - return creditCardDAO; - } - - public HostPersonDAO getHostPersonDAO() { - return hostPersonDAO; - } - - public ReportsDAO getReportsDAO() { - return reportsDAO; - } - - public ScoreDAO getScoreDAO() { - return scoreDAO; - } - - public PropertyChangeManager getResultEventsManager() { - return this.resultEventsManager; - } - - public PropertyChangeManager getTreeEventsManager() { - return treeEventsManager; - } - - @Override - void clearCaches() { - allDAOs.forEach((subDAO) -> subDAO.clearCaches()); - resultEventsManager.firePropertyChange("DATA_CLEAR", null, CacheClearEvent.getInstance()); - treeEventsManager.firePropertyChange("TREE_CLEAR", null, CacheClearEvent.getInstance()); - - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - return allDAOs.stream() - .map(subDAO -> subDAO.processEvent(evt)) - .flatMap(evts -> evts == null ? Stream.empty() : evts.stream()) - .filter(e -> e != null) - .collect(Collectors.toSet()); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return allDAOs.stream() - .map((subDAO) -> subDAO.shouldRefreshTree()) - .flatMap(evts -> evts == null ? Stream.empty() : evts.stream()) - .filter(e -> e != null) - .collect(Collectors.toSet()); - } - - @Override - Set<DAOEvent> handleIngestComplete() { - List<Collection<? extends DAOEvent>> daoStreamEvts = allDAOs.stream() - .map((subDAO) -> subDAO.handleIngestComplete()) - .collect(Collectors.toList()); - - daoStreamEvts.add(eventBatcher.flushEvents()); - - return daoStreamEvts.stream() - .flatMap(evts -> evts == null ? Stream.empty() : evts.stream()) - .filter(evt -> evt != null) - .collect(Collectors.toSet()); - } - - /** - * Processes and handles an autopsy event. - * - * @param evt The event. - * @param immediateResultAction If true, result events are immediately - * fired. Otherwise, the result events are batched. - */ - private void handleEvent(PropertyChangeEvent evt, boolean immediateResultAction) { - Collection<DAOEvent> daoEvts = processEvent(evt); - - Map<DAOEvent.Type, Set<DAOEvent>> daoEvtsByType = daoEvts.stream() - .collect(Collectors.groupingBy(e -> e.getType(), Collectors.toSet())); - - fireTreeEvts(daoEvtsByType.get(DAOEvent.Type.TREE)); - - Set<DAOEvent> resultEvts = daoEvtsByType.get(DAOEvent.Type.RESULT); - if (immediateResultAction) { - fireResultEvts(resultEvts); - } else { - eventBatcher.enqueueAllEvents(resultEvts); - } - } - - private void handleEventFlush() { - Collection<DAOEvent> daoEvts = handleIngestComplete(); - - Map<DAOEvent.Type, Set<DAOEvent>> daoEvtsByType = daoEvts.stream() - .collect(Collectors.groupingBy(e -> e.getType(), Collectors.toSet())); - - fireTreeEvts(daoEvtsByType.get(DAOEvent.Type.TREE)); - - Set<DAOEvent> resultEvts = daoEvtsByType.get(DAOEvent.Type.RESULT); - fireResultEvts(resultEvts); - } - - private void fireResultEvts(Set<DAOEvent> resultEvts) { - if (CollectionUtils.isNotEmpty(resultEvts)) { - resultEventsManager.firePropertyChange("DATA_CHANGE", null, new DAOAggregateEvent(resultEvts)); - } - } - - private void fireTreeEvts(Set<? extends DAOEvent> treeEvts) { - if (CollectionUtils.isNotEmpty(treeEvts)) { - treeEventsManager.firePropertyChange("TREE_CHANGE", null, new DAOAggregateEvent(treeEvts)); - } - } - - private void handleTreeEventTimeouts() { - fireTreeEvts(this.shouldRefreshTree()); - } - - @Override - protected void finalize() throws Throwable { - unregister(); - } - - /** - * A wrapper around property change support that exposes - * addPropertyChangeListener and removePropertyChangeListener so that - * netbeans weak listeners can automatically unregister. - */ - public static class PropertyChangeManager { - - private final PropertyChangeSupport support = new PropertyChangeSupport(this); - - public void addPropertyChangeListener(PropertyChangeListener listener) { - support.addPropertyChangeListener(listener); - } - - public void removePropertyChangeListener(PropertyChangeListener listener) { - support.removePropertyChangeListener(listener); - } - - PropertyChangeListener[] getPropertyChangeListeners() { - return support.getPropertyChangeListeners(); - } - - void firePropertyChange(String propertyName, Object oldValue, Object newValue) { - support.firePropertyChange(propertyName, oldValue, newValue); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MediaTypeUtils.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MediaTypeUtils.java deleted file mode 100644 index f6c44d24332c94a4b0aa945bc8d6599114b1df4c..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MediaTypeUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.apache.commons.lang3.StringUtils; -import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; - -/** - * - */ -public class MediaTypeUtils { - - public enum ExtensionMediaType { - IMAGE, VIDEO, AUDIO, DOC, EXECUTABLE, TEXT, WEB, PDF, ARCHIVE, UNCATEGORIZED - } - - public static ExtensionMediaType getExtensionMediaType(String ext) { - if (StringUtils.isBlank(ext)) { - return ExtensionMediaType.UNCATEGORIZED; - } else { - ext = "." + ext; - } - if (FileTypeExtensions.getImageExtensions().contains(ext)) { - return ExtensionMediaType.IMAGE; - } else if (FileTypeExtensions.getVideoExtensions().contains(ext)) { - return ExtensionMediaType.VIDEO; - } else if (FileTypeExtensions.getAudioExtensions().contains(ext)) { - return ExtensionMediaType.AUDIO; - } else if (FileTypeExtensions.getDocumentExtensions().contains(ext)) { - return ExtensionMediaType.DOC; - } else if (FileTypeExtensions.getExecutableExtensions().contains(ext)) { - return ExtensionMediaType.EXECUTABLE; - } else if (FileTypeExtensions.getTextExtensions().contains(ext)) { - return ExtensionMediaType.TEXT; - } else if (FileTypeExtensions.getWebExtensions().contains(ext)) { - return ExtensionMediaType.WEB; - } else if (FileTypeExtensions.getPDFExtensions().contains(ext)) { - return ExtensionMediaType.PDF; - } else if (FileTypeExtensions.getArchiveExtensions().contains(ext)) { - return ExtensionMediaType.ARCHIVE; - } else { - return ExtensionMediaType.UNCATEGORIZED; - } - } - - /** - * Gets the path to the icon file that should be used to visually represent - * an AbstractFile, using the file name extension to select the icon. - * - * @param file An AbstractFile. - * - * @return An icon file path. - */ - public static String getIconForFileType(ExtensionMediaType fileType) { - if (fileType == null) { - return "org/sleuthkit/autopsy/images/file-icon.png"; - } - - switch (fileType) { - case IMAGE: - return "org/sleuthkit/autopsy/images/image-file.png"; - case VIDEO: - return "org/sleuthkit/autopsy/images/video-file.png"; - case AUDIO: - return "org/sleuthkit/autopsy/images/audio-file.png"; - case DOC: - return "org/sleuthkit/autopsy/images/doc-file.png"; - case EXECUTABLE: - return "org/sleuthkit/autopsy/images/exe-file.png"; - case TEXT: - return "org/sleuthkit/autopsy/images/text-file.png"; - case WEB: - return "org/sleuthkit/autopsy/images/web-file.png"; - case PDF: - return "org/sleuthkit/autopsy/images/pdf-file.png"; - case ARCHIVE: - return "org/sleuthkit/autopsy/images/archive-file.png"; - default: - case UNCATEGORIZED: - return "org/sleuthkit/autopsy/images/file-icon.png"; - } - } - - private MediaTypeUtils() { - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/OsAccountsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/OsAccountsDAO.java deleted file mode 100755 index d9237d457ca48d8e66cc8e2ed05ba71b523ba5c0..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/OsAccountsDAO.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; -import org.python.google.common.collect.ImmutableSet; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.events.OsAccountEvent; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.OsAccountRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.autopsy.mainui.sco.SCOUtils; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Provides information to populate the results viewer for data in the OS - * Accounts section. - */ -@Messages({ - "OsAccountsDAO_accountNameProperty_displayName=Name", - "OsAccountsDAO_accountRealmNameProperty_displayName=Realm Name", - "OsAccountsDAO_accountHostNameProperty_displayName=Host", - "OsAccountsDAO_accountScopeNameProperty_displayName=Scope", - "OsAccountsDAO_createdTimeProperty_displayName=Creation Time", - "OsAccountsDAO_loginNameProperty_displayName=Login Name", - "OsAccountsDAO.fileColumns.noDescription=No Description",}) -public class OsAccountsDAO extends AbstractDAO { - - public static String HOST_COLUMN_NAME = Bundle.OsAccountsDAO_accountHostNameProperty_displayName(); - public static String SCOPE_COLUMN_NAME = Bundle.OsAccountsDAO_accountScopeNameProperty_displayName(); - public static String REALM_COLUMN_NAME = Bundle.OsAccountsDAO_accountRealmNameProperty_displayName(); - - private final Cache<SearchParams<?>, SearchResultsDTO> searchParamsCache = - CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - - private static final String OS_ACCOUNTS_TYPE_ID = "OS_ACCOUNTS"; - - private static final List<ColumnKey> OS_ACCOUNTS_WITH_SCO_COLUMNS = Arrays.asList( - getFileColumnKey(Bundle.OsAccountsDAO_accountNameProperty_displayName()), - getFileColumnKey(SCOUtils.SCORE_COLUMN_NAME), - getFileColumnKey(SCOUtils.COMMENT_COLUMN_NAME), - getFileColumnKey(SCOUtils.OCCURANCES_COLUMN_NAME), - getFileColumnKey(Bundle.OsAccountsDAO_loginNameProperty_displayName()), - getFileColumnKey(Bundle.OsAccountsDAO_accountHostNameProperty_displayName()), - getFileColumnKey(Bundle.OsAccountsDAO_accountScopeNameProperty_displayName()), - getFileColumnKey(Bundle.OsAccountsDAO_accountRealmNameProperty_displayName()), - getFileColumnKey(Bundle.OsAccountsDAO_createdTimeProperty_displayName())); - - private static final Set<String> OS_EVENTS = ImmutableSet.of( - Case.Events.OS_ACCOUNTS_ADDED.toString(), - Case.Events.OS_ACCOUNTS_DELETED.toString(), - Case.Events.OS_ACCOUNTS_UPDATED.toString(), - Case.Events.OS_ACCT_INSTANCES_ADDED.toString() - ); - - private static OsAccountsDAO instance = null; - - synchronized static OsAccountsDAO getInstance() { - if (instance == null) { - instance = new OsAccountsDAO(); - } - - return instance; - } - - private static ColumnKey getFileColumnKey(String name) { - return new ColumnKey(name, name, Bundle.OsAccountsDAO_fileColumns_noDescription()); - } - - public SearchResultsDTO getAccounts(OsAccountsSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key == null) { - throw new IllegalArgumentException("Search parameters are null"); - } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<OsAccountsSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchAccountsDTOs(searchParams)); - } - - private boolean isOSAccountInvalidatingEvt(OsAccountsSearchParams searchParams, DAOEvent evt) { - return evt instanceof OsAccountEvent; - } - - /** - * Returns a list of paged OS Accounts results. - * - * @param accounts The OS Accounts results. - * @param searchParams The search parameters including the paging. - * - * @return The list of paged OS Accounts results. - */ - List<OsAccount> getPaged(List<OsAccount> accounts, SearchParams<?> searchParams) { - Stream<OsAccount> pagedAccountsStream = accounts.stream() - .sorted(Comparator.comparing((acct) -> acct.getId())) - .skip(searchParams.getStartItem()); - - if (searchParams.getMaxResultsCount() != null) { - pagedAccountsStream = pagedAccountsStream.limit(searchParams.getMaxResultsCount()); - } - - return pagedAccountsStream.collect(Collectors.toList()); - } - - @NbBundle.Messages({"OsAccounts.name.text=OS Accounts"}) - private SearchResultsDTO fetchAccountsDTOs(SearchParams<OsAccountsSearchParams> cacheKey) throws NoCurrentCaseException, TskCoreException { - - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - - // get all accounts - List<OsAccount> allAccounts = (dataSourceId != null && dataSourceId > 0) - ? Case.getCurrentCaseThrows().getSleuthkitCase().getOsAccountManager().getOsAccountsByDataSourceObjId(dataSourceId) - : Case.getCurrentCaseThrows().getSleuthkitCase().getOsAccountManager().getOsAccounts(); - - // get current page of accounts results - List<OsAccount> pagedAccounts = getPaged(allAccounts, cacheKey); - - List<RowDTO> fileRows = new ArrayList<>(); - for (OsAccount account : pagedAccounts) { - - Optional<String> optional = account.getLoginName(); - Optional<Long> creationTimeValue = account.getCreationTime(); - String timeDisplayStr - = creationTimeValue.isPresent() ? TimeZoneUtils.getFormattedTime(creationTimeValue.get()) : ""; - List<Object> cellValues = Arrays.asList( - account.getName() != null ? account.getName() : "", - null, - null, - null, - optional.isPresent() ? optional.get() : "", - "", - "", - "", - timeDisplayStr); - - fileRows.add(new OsAccountRowDTO( - account, - cellValues)); - }; - - return new BaseSearchResultsDTO(OS_ACCOUNTS_TYPE_ID, Bundle.OsAccounts_name_text(), OS_ACCOUNTS_WITH_SCO_COLUMNS, fileRows, OS_ACCOUNTS_TYPE_ID, 0, allAccounts.size()); - } - - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - } - - @Override - Set<DAOEvent> handleIngestComplete() { - return Collections.emptySet(); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return Collections.emptySet(); - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - if (!OS_EVENTS.contains(evt.getPropertyName())) { - return Collections.emptySet(); - } - - this.searchParamsCache.invalidateAll(); - - return Collections.singleton(new OsAccountEvent()); - } - - /** - * Handles fetching and paging of data for accounts. - */ - public static class AccountFetcher extends DAOFetcher<OsAccountsSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public AccountFetcher(OsAccountsSearchParams params) { - super(params); - } - - protected OsAccountsDAO getDAO() { - return MainDAO.getInstance().getOsAccountsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getAccounts(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isOSAccountInvalidatingEvt(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/OsAccountsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/OsAccountsSearchParams.java deleted file mode 100755 index 61801fbb3d3117fbef29cc70eb1c31dee523dc56..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/OsAccountsSearchParams.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for accessing data about OS Accounts from the DAO. - */ -public class OsAccountsSearchParams { - - private static final String TYPE_ID = "OS_ACCOUNTS"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final Long dataSourceId; - - public OsAccountsSearchParams(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 23 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final OsAccountsSearchParams other = (OsAccountsSearchParams) obj; - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsDAO.java deleted file mode 100644 index 67363ab944502846e7485d16dab4bbecd23843ad..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsDAO.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.ReportsEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.Report; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - * Dao for reports. - */ -public class ReportsDAO extends AbstractDAO { - - private static ReportsDAO instance = null; - - /** - * @return A singleton instance of this class. - */ - public static ReportsDAO getInstance() { - if (instance == null) { - instance = new ReportsDAO(); - } - return instance; - } - - private final Cache<SearchParams<ReportsSearchParams>, SearchResultsDTO> cache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - - - private SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - @Messages({ - "ReportsDAO_reports_tableDisplayName=Reports" - }) - private SearchResultsDTO fetchReports(SearchParams<ReportsSearchParams> params) throws NoCurrentCaseException, TskCoreException { - long startItem = params.getStartItem(); - - List<Report> reports = getCase().getAllReports(); - - Stream<? extends ReportsRowDTO> pagedReportsStream = reports.stream() - .sorted(Comparator.comparing((report) -> report.getId())) - .map(report -> { - return new ReportsRowDTO( - report, - report.getId(), - report.getSourceModuleName(), - report.getReportName(), - new Date(report.getCreatedTime() * 1000), - report.getPath()); - }) - .skip(params.getStartItem()); - - if (params.getMaxResultsCount() != null) { - pagedReportsStream = pagedReportsStream.limit(params.getMaxResultsCount()); - } - - return new BaseSearchResultsDTO( - ReportsRowDTO.getTypeIdForClass(), - Bundle.ReportsDAO_reports_tableDisplayName(), - ReportsRowDTO.COLUMNS, - pagedReportsStream.collect(Collectors.toList()), - ReportsRowDTO.getTypeIdForClass(), - startItem, - reports.size()); - } - - /** - * Queries the case based on the report search params and returns search results. - * @param repSearchParams The report search params. - * @param startItem The paged starting item. - * @param maxResultsCount The maximum result count for the page. - * @return The search results. - * @throws ExecutionException - * @throws IllegalArgumentException - */ - public SearchResultsDTO getReports(ReportsSearchParams repSearchParams, long startItem, Long maxResultsCount) throws ExecutionException, IllegalArgumentException { - SearchParams<ReportsSearchParams> searchParams = new SearchParams<>(repSearchParams, startItem, maxResultsCount); - return cache.get(searchParams, () -> fetchReports(searchParams)); - } - - @Override - Set<? extends DAOEvent> processEvent(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(Case.Events.REPORT_ADDED.toString()) || eventType.equals(Case.Events.REPORT_DELETED.toString())) { - cache.invalidateAll(); - return Collections.singleton(new ReportsEvent()); - } - - return Collections.emptySet(); - } - - @Override - void clearCaches() { - cache.invalidateAll(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return Collections.emptySet(); - } - - @Override - Set<? extends TreeEvent> shouldRefreshTree() { - return Collections.emptySet(); - } - - private boolean isReportInvalidatingEvent(ReportsSearchParams parameters, DAOEvent evt) { - return evt instanceof ReportsEvent; - } - - /** - * Handles fetching and paging of data for all Reports. - */ - public static class ReportsFetcher extends DAOFetcher<ReportsSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public ReportsFetcher(ReportsSearchParams params) { - super(params); - } - - protected ReportsDAO getDAO() { - return MainDAO.getInstance().getReportsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getReports(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isReportInvalidatingEvent(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsRowDTO.java deleted file mode 100644 index 281a446d0c126cd3af813a5911e72a2eb5d7bb76..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsRowDTO.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.collect.ImmutableList; -import java.util.Date; -import java.util.List; -import java.util.Set; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Report; - -/** - * Row of credit card information for a file. - */ -@Messages({ - "ReportsRowDTO_sourceModuleName_displayName=Source Module Name", - "ReportsRowDTO_reportName_displayName=Report Name", - "ReportsRowDTO_createTime_displayName=Created Time", - "ReportsRowDTO_reportFilePath_displayName=Report File Path" -}) -public class ReportsRowDTO extends BaseRowDTO { - - private static ColumnKey getColumnKey(String displayName) { - return new ColumnKey(displayName.toUpperCase().replaceAll("\\s", "_"), displayName, ""); - } - - static List<ColumnKey> COLUMNS = ImmutableList.of( - getColumnKey(Bundle.ReportsRowDTO_sourceModuleName_displayName()), - getColumnKey(Bundle.ReportsRowDTO_reportName_displayName()), - getColumnKey(Bundle.ReportsRowDTO_createTime_displayName()), - getColumnKey(Bundle.ReportsRowDTO_reportFilePath_displayName()) - ); - - private static final String TYPE_ID = "REPORTS"; - - /** - * @return The type identifier of this class. - */ - public static String getTypeIdForClass() { - return TYPE_ID; - } - - private final String sourceModuleName; - private final String reportName; - private final Date createdTime; - private final String reportFilePath; - private final Report report; - - /** - * Main constructor. - * @param report The report. - * @param id The report id. - * @param sourceModuleName The source module name. - * @param reportName The report name. - * @param createdTime The created time. - * @param reportFilePath The report file path. - */ - public ReportsRowDTO(Report report, long id, String sourceModuleName, String reportName, Date createdTime, String reportFilePath) { - super(ImmutableList.of(sourceModuleName, reportName, createdTime, reportFilePath), TYPE_ID, id); - this.sourceModuleName = sourceModuleName; - this.reportName = reportName; - this.createdTime = createdTime; - this.reportFilePath = reportFilePath; - this.report = report; - } - - /** - * @return The source module name. - */ - public String getSourceModuleName() { - return sourceModuleName; - } - - /** - * @return The report name. - */ - public String getReportName() { - return reportName; - } - - /** - * @return The created time. - */ - public Date getCreatedTime() { - return createdTime; - } - - /** - * @return The report file path. - */ - public String getReportFilePath() { - return reportFilePath; - } - - /** - * @return The report. - */ - public Report getReport() { - return report; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsSearchParams.java deleted file mode 100755 index 017c951be7003f0fdc627a65176f0bd5985dca1a..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ReportsSearchParams.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for accessing data about reports from the DAO. - */ -public class ReportsSearchParams { - - private static final String TYPE_ID = "REPORTS"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private static ReportsSearchParams instance = null; - - /** - * @return A singleton instance of this class. - */ - public static ReportsSearchParams getInstance() { - if (instance == null) { - instance = new ReportsSearchParams(); - } - return instance; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - return Objects.equals(getClass(), obj.getClass()); - } - - @Override - public int hashCode() { - return 7; - } - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/RowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/RowDTO.java deleted file mode 100644 index ce0ad1b6039a6b7df9064ceabde8f54175db6afd..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/RowDTO.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; - -/** - * DTO representing an individual row in search results. - */ -public interface RowDTO { - - List<Object> getCellValues(); - - long getId(); - - String getTypeId(); -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreDAO.java deleted file mode 100644 index adbf077e39c7b210d5b89d7d73ca37a236440b7c..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreDAO.java +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2023 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import java.beans.PropertyChangeEvent; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; -import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.BlackboardArtifactDAO.TableData; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import org.sleuthkit.autopsy.mainui.datamodel.events.ScoreContentEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifact.Category; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.Score; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; - -/** - * Provides information to populate the results viewer for data in the views - * section. - */ -@Messages({ - "ScoreDAO_columns_sourceLbl=Source", - "ScoreDAO_columns_typeLbl=Type", - "ScoreDAO_columns_pathLbl=Path", - "ScoreDAO_columns_createdDateLbl=Created Date", - "ScoreDAO_columns_noDescription=No Description", - "ScoreDAO_types_filelbl=File", - "ScoreDAO_mainNode_displayName=Score" -}) -public class ScoreDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(ScoreDAO.class.getName()); - - private static final List<BlackboardAttribute.Type> TIME_ATTRS = Arrays.asList( - BlackboardAttribute.Type.TSK_DATETIME, - BlackboardAttribute.Type.TSK_DATETIME_ACCESSED, - BlackboardAttribute.Type.TSK_DATETIME_RCVD, - BlackboardAttribute.Type.TSK_DATETIME_SENT, - BlackboardAttribute.Type.TSK_DATETIME_CREATED, - BlackboardAttribute.Type.TSK_DATETIME_MODIFIED, - BlackboardAttribute.Type.TSK_DATETIME_START, - BlackboardAttribute.Type.TSK_DATETIME_END, - BlackboardAttribute.Type.TSK_DATETIME_DELETED, - BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_RESET, - BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_FAIL - ); - - private static final Map<Integer, Integer> TIME_ATTR_IMPORTANCE = IntStream.range(0, TIME_ATTRS.size()) - .mapToObj(idx -> Pair.of(TIME_ATTRS.get(idx).getTypeID(), idx)) - .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v1)); - - private static final List<ColumnKey> RESULT_SCORE_COLUMNS = Arrays.asList( - getFileColumnKey(Bundle.ScoreDAO_columns_sourceLbl()), - getFileColumnKey(Bundle.ScoreDAO_columns_typeLbl()), - getFileColumnKey(Bundle.ScoreDAO_columns_pathLbl()), - getFileColumnKey(Bundle.ScoreDAO_columns_createdDateLbl()) - ); - - private static final String SCORE_TYPE_ID = ScoreDAO.class.getName() + "_SIGNATURE_ID"; - - private static final String BASE_AGGR_SCORE_QUERY - = "FROM tsk_aggregate_score aggr_score\n" - + "INNER JOIN (\n" - + " SELECT obj_id, data_source_obj_id, 'f' AS type FROM tsk_files\n" - + " UNION SELECT artifact_obj_id AS obj_id, data_source_obj_id, 'a' AS type FROM blackboard_artifacts\n" - + " WHERE blackboard_artifacts.artifact_type_id IN\n" - + " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + Category.DATA_ARTIFACT.getID() + ")\n" - + ") art_files ON aggr_score.obj_id = art_files.obj_id\n"; - - private static ScoreDAO instance = null; - - synchronized static ScoreDAO getInstance() { - if (instance == null) { - instance = new ScoreDAO(); - } - - return instance; - } - - private final Cache<SearchParams<Object>, SearchResultsDTO> searchParamsCache - = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>(); - - private static ColumnKey getFileColumnKey(String name) { - return new ColumnKey(name, name, Bundle.ScoreDAO_columns_noDescription()); - } - - private SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - public SearchResultsDTO getFilesByScore(ScoreViewSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchScoreSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount)); - } - - private boolean isScoreContentInvalidating(ScoreViewSearchParams params, DAOEvent eventData) { - if (!(eventData instanceof ScoreContentEvent)) { - return false; - } - - ScoreContentEvent scoreContentEvt = (ScoreContentEvent) eventData; - - ScoreViewFilter evtFilter = scoreContentEvt.getFilter(); - ScoreViewFilter paramsFilter = params.getFilter(); - - Long evtDsId = scoreContentEvt.getDataSourceId(); - Long paramsDsId = params.getDataSourceId(); - - return (evtFilter == null || evtFilter.equals(paramsFilter)) - && (paramsDsId == null || evtDsId == null - || Objects.equals(paramsDsId, evtDsId)); - } - - /** - * Returns counts for deleted content categories. - * - * @param dataSourceId The data source object id or null if no data source - * filtering should occur. - * - * @return The results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<ScoreViewSearchParams> getScoreContentCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { - Set<ScoreViewFilter> indeterminateFilters = new HashSet<>(); - for (DAOEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof ScoreContentEvent) { - ScoreContentEvent scoreEvt = (ScoreContentEvent) evt; - if (dataSourceId == null || scoreEvt.getDataSourceId() == null || Objects.equals(scoreEvt.getDataSourceId(), dataSourceId)) { - if (scoreEvt.getFilter() == null) { - // if null filter, indicates full refresh and all file sizes need refresh. - indeterminateFilters.addAll(Arrays.asList(ScoreViewFilter.values())); - break; - } else { - indeterminateFilters.add(scoreEvt.getFilter()); - } - } - } - } - - String dsClause = getDsFilter(dataSourceId); - - String queryStrSelects = Stream.of(ScoreViewFilter.values()) - .map((filter) -> Pair.of(filter.name(), getScoreFilter(filter.getScores()))) - .map((filterSqlPair) -> { - String filterSql = Stream.of(filterSqlPair.getRight(), dsClause) - .filter(StringUtils::isNotBlank) - .collect(Collectors.joining("\nAND ")); - - if (StringUtils.isNotBlank(filterSql)) { - filterSql = "\n WHERE " + filterSql; - } - - return "(SELECT COUNT(aggr_score.obj_id) " - + BASE_AGGR_SCORE_QUERY - + filterSql - + ") AS " - + filterSqlPair.getLeft(); - }) - .collect(Collectors.joining(",\n")); - - String queryStr = "\n" + queryStrSelects; - - try { - SleuthkitCase skCase = getCase(); - - List<TreeItemDTO<ScoreViewSearchParams>> treeList = new ArrayList<>(); - skCase.getCaseDbAccessManager().select(queryStr, (resultSet) -> { - try { - if (resultSet.next()) { - for (ScoreViewFilter filter : ScoreViewFilter.values()) { - long count = resultSet.getLong(filter.name()); - TreeDisplayCount displayCount = indeterminateFilters.contains(filter) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(count); - - treeList.add(createScoreContentTreeItem(filter, dataSourceId, displayCount)); - } - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex); - } - }); - - return new TreeResultsDTO<>(treeList); - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex); - } - } - - private static TreeItemDTO<ScoreViewSearchParams> createScoreContentTreeItem(ScoreViewFilter filter, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeItemDTO<>( - "SCORE_CONTENT", - new ScoreViewSearchParams(filter, dataSourceId), - filter, - filter == null ? "" : filter.getDisplayName(), - displayCount); - } - - private static String getScoreFilter(Collection<Score> scores) { - if (CollectionUtils.isEmpty(scores)) { - return null; - } else { - return "(" - + scores.stream() - .map(s -> MessageFormat.format( - " (aggr_score.significance = {0} AND aggr_score.priority = {1}) ", - s.getSignificance().getId(), - s.getPriority().getId())) - .collect(Collectors.joining(" OR ")) - + ")"; - } - } - - private static String getDsFilter(Long dataSourceId) { - return (dataSourceId == null || dataSourceId <= 0) - ? null - : "aggr_score.data_source_obj_id = " + dataSourceId; - } - - private SearchResultsDTO fetchScoreSearchResultsDTOs(ScoreViewFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - String scoreClause = getScoreFilter(filter.getScores()); - String dsClause = getDsFilter(dataSourceId); - - String filterSql = Stream.of(scoreClause, dsClause) - .filter(str -> str != null) - .collect(Collectors.joining(" AND ")); - - filterSql = StringUtils.isNotEmpty(filterSql) ? " WHERE " + filterSql + "\n" : ""; - - String baseQuery = BASE_AGGR_SCORE_QUERY - + filterSql - + "ORDER BY art_files.obj_id"; - - String countQuery = " COUNT(art_files.obj_id) AS count\n" + baseQuery; - - AtomicLong totalCountRef = new AtomicLong(0); - AtomicReference<SQLException> countException = new AtomicReference<>(null); - getCase().getCaseDbAccessManager() - .select(countQuery, (rs) -> { - try { - if (rs.next()) { - totalCountRef.set(rs.getLong("count")); - } - } catch (SQLException ex) { - countException.set(ex); - } - } - ); - - SQLException sqlEx = countException.get(); - if (sqlEx != null) { - throw new TskCoreException( - MessageFormat.format("A sql exception occurred fetching results with query: SELECT {0}", countQuery), - sqlEx); - } - - String objIdQuery = " art_files.obj_id, art_files.type\n" + baseQuery + "\n" - + (maxResultCount != null && maxResultCount > 0 ? " LIMIT " + maxResultCount : "") - + (startItem > 0 ? " OFFSET " + startItem : "");; - - List<Long> fileIds = new ArrayList<>(); - List<Long> artifactIds = new ArrayList<>(); - AtomicReference<SQLException> objIdException = new AtomicReference<>(null); - getCase().getCaseDbAccessManager() - .select(objIdQuery, (rs) -> { - try { - while (rs.next()) { - String type = rs.getString("type"); - if ("f".equalsIgnoreCase(type)) { - fileIds.add(rs.getLong("obj_id")); - } else { - artifactIds.add(rs.getLong("obj_id")); - } - } - } catch (SQLException ex) { - objIdException.set(ex); - } - } - ); - - sqlEx = objIdException.get(); - if (sqlEx != null) { - throw new TskCoreException( - MessageFormat.format("A sql exception occurred fetching results with query: SELECT {0}", objIdQuery), - sqlEx); - } - - List<RowDTO> dataRows = new ArrayList<>(); - - if (!fileIds.isEmpty()) { - String joinedFileIds = fileIds.stream() - .map(l -> Long.toString(l)) - .collect(Collectors.joining(", ")); - - List<AbstractFile> files = getCase().findAllFilesWhere("obj_id IN (" + joinedFileIds + ")"); - - for (AbstractFile file : files) { - List<Object> cellValues = Arrays.asList( - file.getName(), - Bundle.ScoreDAO_types_filelbl(), - file.getUniquePath(), - file.getCtime() <= 0 - ? null - : TimeZoneUtils.getFormattedTime(file.getCtime()) - ); - - dataRows.add(new ScoreResultRowDTO( - new FileRowDTO( - file, - file.getId(), - file.getName(), - file.getNameExtension(), - MediaTypeUtils.getExtensionMediaType(file.getNameExtension()), - file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.ALLOC), - file.getType(), - cellValues), - // the modified column types: source name, type, path, created time - cellValues, - file.getId())); - } - } - - if (!artifactIds.isEmpty()) { - String joinedArtifactIds = artifactIds.stream() - .map(l -> Long.toString(l)) - .collect(Collectors.joining(", ")); - - List<DataArtifact> dataArtifacts = getCase().getBlackboard().getDataArtifactsWhere("artifacts.artifact_obj_id IN (" + joinedArtifactIds + ")"); - TableData artTableData = MainDAO.getInstance().getDataArtifactsDAO().createTableData(null, dataArtifacts); - - // all rows should be data artifact rows, and can be appended accordingly - for (RowDTO rowDTO : artTableData.rows) { - if (rowDTO instanceof DataArtifactRowDTO dataArtRow) { - BlackboardArtifact.Type artifactType = dataArtRow.getArtifact().getType(); - List<Object> cellValues = Arrays.asList( - dataArtRow.getSrcContent().getName(), - artifactType.getDisplayName(), - dataArtRow.getArtifact().getUniquePath(), - getTimeStamp(dataArtRow.getArtifact()) - ); - - dataRows.add(new ScoreResultRowDTO( - new DataArtifactRowDTO( - dataArtRow.getArtifact(), - dataArtRow.getSrcContent(), - dataArtRow.getLinkedFile(), - dataArtRow.isTimelineSupported(), - cellValues, - dataArtRow.getId()), - artifactType, - cellValues, - dataArtRow.getId())); - } - - } - } - - return new BaseSearchResultsDTO( - SCORE_TYPE_ID, - Bundle.ScoreDAO_mainNode_displayName(), - RESULT_SCORE_COLUMNS, - dataRows, - SCORE_TYPE_ID, - startItem, - totalCountRef.get()); - } - - private String getTimeStamp(BlackboardArtifact artifact) { - Long time = getTime(artifact); - if (time == null || time <= 0) { - return null; - } else { - return TimeZoneUtils.getFormattedTime(time); - } - } - - private Long getTime(BlackboardArtifact artifact) { - try { - BlackboardAttribute timeAttr = artifact.getAttributes().stream() - .filter((attr) -> TIME_ATTR_IMPORTANCE.keySet().contains(attr.getAttributeType().getTypeID())) - .sorted(Comparator.comparing(attr -> TIME_ATTR_IMPORTANCE.get(attr.getAttributeType().getTypeID()))) - .findFirst() - .orElse(null); - - if (timeAttr != null) { - return timeAttr.getValueLong(); - } else { - return (artifact.getParent() instanceof AbstractFile) ? ((AbstractFile) artifact.getParent()).getCtime() : null; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "An exception occurred while fetching time for artifact", ex); - return null; - } - } - - private TreeItemDTO<?> createTreeItem(DAOEvent daoEvent, TreeDisplayCount count) { - - if (daoEvent instanceof ScoreContentEvent) { - ScoreContentEvent scoreEvt = (ScoreContentEvent) daoEvent; - return createScoreContentTreeItem(scoreEvt.getFilter(), scoreEvt.getDataSourceId(), count); - } else { - return null; - } - } - - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - handleIngestComplete(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - SubDAOUtils.invalidateKeys(this.searchParamsCache, - (searchParams) -> searchParamsMatchEvent(true, null, true, searchParams)); - - Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts, - (daoEvt, count) -> createTreeItem(daoEvt, count)); - - Set<? extends DAOEvent> fileViewRefreshEvents = getFileViewRefreshEvents(null); - - List<? extends DAOEvent> fileViewRefreshTreeEvents = fileViewRefreshEvents.stream() - .map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toList()); - - return Stream.of(treeEvts, fileViewRefreshEvents, fileViewRefreshTreeEvents) - .flatMap(c -> c.stream()) - .collect(Collectors.toSet()); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEvents(this.treeCounts, - (daoEvt, count) -> createTreeItem(daoEvt, count)); - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - AbstractFile af; - if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) { - Long dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null; - return invalidateScoreParamsAndReturnEvents(dsId); - - } else if ((af = DAOEventUtils.getFileFromFileEvent(evt)) != null) { - return invalidateScoreParamsAndReturnEvents(af.getDataSourceObjectId()); - } - - ModuleDataEvent dataEvt; - if (Case.Events.CONTENT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof ContentTagAddedEvent) && ((ContentTagAddedEvent) evt).getAddedTag().getContent() instanceof AbstractFile) { - ContentTagAddedEvent tagAddedEvt = (ContentTagAddedEvent) evt; - return invalidateScoreParamsAndReturnEvents(((AbstractFile) tagAddedEvt.getAddedTag().getContent()).getDataSourceObjectId()); - } else if (Case.Events.CONTENT_TAG_DELETED.toString().equals(evt.getPropertyName())) { - return invalidateScoreParamsAndReturnEvents(null); - } else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof BlackBoardArtifactTagAddedEvent)) { - BlackBoardArtifactTagAddedEvent artifactAddedEvt = (BlackBoardArtifactTagAddedEvent) evt; - return invalidateScoreParamsAndReturnEvents(artifactAddedEvt.getAddedTag().getArtifact().getDataSourceObjectID()); - } else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString().equals(evt.getPropertyName())) { - return invalidateScoreParamsAndReturnEvents(null); - } else if ((dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt)) != null - && Category.ANALYSIS_RESULT.equals(dataEvt.getBlackboardArtifactType().getCategory())) { - Set<Long> dsIds = dataEvt.getArtifacts().stream().map(ar -> ar.getDataSourceObjectID()).distinct().collect(Collectors.toSet()); - return invalidateScoreParamsAndReturnEventsFromSet(dsIds); - - } else { - return Collections.emptySet(); - } - } - - private Set<DAOEvent> invalidateScoreParamsAndReturnEvents(Long dataSourceId) { - return invalidateScoreParamsAndReturnEventsFromSet(dataSourceId != null ? Collections.singleton(dataSourceId) : null); - } - - private Set<DAOEvent> invalidateScoreParamsAndReturnEventsFromSet(Set<Long> dataSourceIds) { - - SubDAOUtils.invalidateKeys(this.searchParamsCache, - (searchParams) -> { - if (searchParams instanceof ScoreViewSearchParams) { - ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams; - return (CollectionUtils.isEmpty(dataSourceIds) || scoreParams.getDataSourceId() == null || dataSourceIds.contains(scoreParams.getDataSourceId())); - } else { - return false; - } - }); - - Set<DAOEvent> dataEvents = CollectionUtils.isEmpty(dataSourceIds) - ? Collections.singleton(new ScoreContentEvent(null, null)) - : dataSourceIds.stream().map(dsId -> new ScoreContentEvent(null, dsId)).collect(Collectors.toSet()); - - Set<DAOEvent> treeEvents = CollectionUtils.isEmpty(dataSourceIds) - ? Collections.singleton(new TreeEvent(createScoreContentTreeItem( - null, - null, - TreeDisplayCount.UNSPECIFIED), - true)) - : dataSourceIds.stream().map(dsId -> new TreeEvent( - createScoreContentTreeItem( - null, - dsId, - TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toSet()); - - return Stream.of(dataEvents, treeEvents).flatMap(s -> s.stream()).collect(Collectors.toSet()); - } - - private boolean searchParamsMatchEvent( - boolean invalidatesScore, - Long dsId, - boolean dataSourceAdded, - Object searchParams) { - - if (searchParams instanceof ScoreViewSearchParams) { - ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams; - return (dataSourceAdded || (invalidatesScore && (scoreParams.getDataSourceId() == null || dsId == null || Objects.equals(scoreParams.getDataSourceId(), dsId)))); - } else { - return false; - } - } - - /** - * Returns events for when a full refresh is required because module content - * events will not necessarily provide events for files (i.e. data source - * added, ingest cancelled/completed). - * - * @param dataSourceId The data source id or null if not applicable. - * - * @return The set of events that apply in this situation. - */ - private Set<DAOEvent> getFileViewRefreshEvents(Long dataSourceId) { - return ImmutableSet.of( - new ScoreContentEvent(null, dataSourceId) - ); - - } - - /** - * Handles fetching and paging of data for deleted content. - */ - public static class ScoreContentFetcher extends DAOFetcher<ScoreViewSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public ScoreContentFetcher(ScoreViewSearchParams params) { - super(params); - } - - protected ScoreDAO getDAO() { - return MainDAO.getInstance().getScoreDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getFilesByScore(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isScoreContentInvalidating(this.getParameters(), evt); - } - - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreResultRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreResultRowDTO.java deleted file mode 100644 index 62de00559c24404a871bf3a197904d63f44d8a49..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreResultRowDTO.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2023 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Score results dto. - */ -public class ScoreResultRowDTO extends BaseRowDTO { - private static final String TYPE_ID = "SCORE_RESULT_ROW"; - - public static String getTypeIdForClass() { - return TYPE_ID; - } - - - private final FileRowDTO fileDTO; - private final DataArtifactRowDTO artifactDTO; - private final BlackboardArtifact.Type artifactType; - - - public ScoreResultRowDTO(FileRowDTO fileDTO, List<Object> cellValues, long id) { - super(cellValues, TYPE_ID, id); - this.fileDTO = fileDTO; - this.artifactDTO = null; - this.artifactType = null; - } - - public ScoreResultRowDTO(DataArtifactRowDTO artifactDTO, BlackboardArtifact.Type artifactType, List<Object> cellValues, long id) { - super(cellValues, TYPE_ID, id); - this.fileDTO = null; - this.artifactDTO = artifactDTO; - this.artifactType = artifactType; - } - - public FileRowDTO getFileDTO() { - return fileDTO; - } - - public DataArtifactRowDTO getArtifactDTO() { - return artifactDTO; - } - - public BlackboardArtifact.Type getArtifactType() { - return artifactType; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreViewFilter.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreViewFilter.java deleted file mode 100644 index 3221f26e0bc393eb0c1be1ec139b9ecceb4821dd..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreViewFilter.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2023 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.datamodel.Score; -import org.sleuthkit.datamodel.Score.Priority; -import org.sleuthkit.datamodel.Score.Significance; - -/** - * - * Filters for the score view - */ -@Messages({ - "ScoreViewFilter_bad_name=Bad Items", - "ScoreViewFilter_suspicious_name=Suspicious Items",}) -public enum ScoreViewFilter { - BAD(Arrays.asList( - new Score(Significance.NOTABLE, Priority.NORMAL), - new Score(Significance.NOTABLE, Priority.OVERRIDE)), - 1, - Bundle.ScoreViewFilter_bad_name()), - SUSPICIOUS(Arrays.asList( - new Score(Significance.LIKELY_NOTABLE, Priority.NORMAL), - new Score(Significance.LIKELY_NOTABLE, Priority.OVERRIDE)), - 2, - Bundle.ScoreViewFilter_suspicious_name()); - - private final Collection<Score> scores; - private final String displayName; - private final int id; - - private ScoreViewFilter(Collection<Score> scores, int id, String displayName) { - this.scores = scores == null ? Collections.emptyList() : Collections.unmodifiableCollection(scores); - this.id = id; - this.displayName = displayName; - } - - public Collection<Score> getScores() { - return scores; - } - - public String getDisplayName() { - return displayName; - } - - public int getId() { - return id; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreViewSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreViewSearchParams.java deleted file mode 100644 index 0e7d8980fa57cbc9b77950717e0fb4f15e1cddce..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreViewSearchParams.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2023 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Key for accessing data about files with a score from the DAO. - */ -public class ScoreViewSearchParams { - - private static final String TYPE_ID = "FILE_VIEWS_SCORE"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final ScoreViewFilter filter; - private final Long dataSourceId; - - public ScoreViewSearchParams(ScoreViewFilter filter, Long dataSourceId) { - this.filter = filter; - this.dataSourceId = dataSourceId; - } - - public ScoreViewFilter getFilter() { - return filter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.filter); - hash = 53 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ScoreViewSearchParams other = (ScoreViewSearchParams) obj; - if (this.filter != other.filter) { - return false; - } - return Objects.equals(this.dataSourceId, other.dataSourceId); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SearchParams.java deleted file mode 100644 index 7cd5e4ebc5ce5bc134c0d7b1d8dcc9a1615409a7..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SearchParams.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; - -/** - * Base implementation of search parameters to provide to a DAO. - */ -class SearchParams<T> { - private final T paramData; - private final long startItem; - private final Long maxResultsCount; - - public SearchParams(T paramData) { - this(paramData, 0, null); - } - - public SearchParams(T paramData, long startItem, Long maxResultsCount) { - this.paramData = paramData; - this.startItem = startItem; - this.maxResultsCount = maxResultsCount; - } - - public T getParamData() { - return paramData; - } - - public long getStartItem() { - return startItem; - } - - public Long getMaxResultsCount() { - return maxResultsCount; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 41 * hash + Objects.hashCode(this.paramData); - hash = 41 * hash + (int) (this.startItem ^ (this.startItem >>> 32)); - hash = 41 * hash + Objects.hashCode(this.maxResultsCount); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final SearchParams<?> other = (SearchParams<?>) obj; - if (this.startItem != other.startItem) { - return false; - } - if (!Objects.equals(this.paramData, other.paramData)) { - return false; - } - if (!Objects.equals(this.maxResultsCount, other.maxResultsCount)) { - return false; - } - return true; - } - - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SearchResultsDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SearchResultsDTO.java deleted file mode 100644 index 94fe1929724e70d7964a0065486256e7fc1273b0..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SearchResultsDTO.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import org.sleuthkit.datamodel.DataSource; - -/** - * Interface for all search results that are used to display in the table/DataResultViewer area. - */ -public interface SearchResultsDTO { - - // returns the type of data - String getTypeId(); - - // Returns a unique signature for the type of data. Used keep track of custom column ordering. - String getSignature(); - - // Text to display at top of the table about the type of the results. - String getDisplayName(); - - // Sorted list of column headers. The RowDTO column values will be in the same order - List<ColumnKey> getColumns(); - - // Page-sized, sorted list of rows to display - List<RowDTO> getItems(); - - // total number of results (could be bigger than what is in the results) - long getTotalResultsCount(); - - // Index in the total results that this set/page starts at - long getStartItem(); - - // Will only return a DataSource if the parent ie the selected node was a - // data source. This was added to support the DataSourceSummaryResultViewer. - default DataSource getDataSourceParent() { - return null; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SubDAOUtils.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SubDAOUtils.java deleted file mode 100644 index 5bae5d2f19cbb3044d10b2e34fae53ff80b3ca2b..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/SubDAOUtils.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import com.google.common.cache.Cache; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import org.apache.commons.lang3.tuple.Pair; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; - -/** - * Utilities for common actions in the sub DAOs. - */ -public class SubDAOUtils { - - /** - * Using a digest of event information, clears keys in a cache that may be - * effected by events. - * - * @param cache The cache. - * @param getKeys Using a key from a cache, provides a tuple - * of the relevant key in the data source - * mapping and the data source id (or null if - * no data source filtering). - * @param itemDataSourceMapping The event digest. - */ - static <T, K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Function<K, Pair<T, Long>> getKeys, Map<T, Set<Long>> itemDsMapping) { - invalidateKeys(cache, (keyParams) -> { - Pair<T, Long> pairItems = getKeys.apply(keyParams); - T searchParamsKey = pairItems.getLeft(); - Long searchParamsDsId = pairItems.getRight(); - Set<Long> dsIds = itemDsMapping.get(searchParamsKey); - return (dsIds != null && (searchParamsDsId == null || dsIds.contains(searchParamsDsId))); - }); - } - - /** - * Determines what keys should be kept in the cache while iterating through - * all the keys. - * - * @param cache The cache. - * @param shouldInvalidate If the key should be removed from the cache. - */ - static <K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Predicate<K> shouldInvalidate) { - ConcurrentMap<SearchParams<K>, ?> concurrentMap = cache.asMap(); - concurrentMap.forEach((k, v) -> { - if (shouldInvalidate.test(k.getParamData())) { - concurrentMap.remove(k); - } - }); - } - - /** - * Returns a set of tree events gathered from the TreeCounts instance after - * calling flushEvents. - * - * @param treeCounts The tree counts instance. - * @param converter The means of acquiring a tree item dto to be placed in - * the TreeEvent. - * - * @return The generated tree events. - */ - static <E, T> Set<TreeEvent> getIngestCompleteEvents(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, TreeItemDTO<T>> converter) { - return treeCounts.flushEvents().stream() - .map(daoEvt -> new TreeEvent(converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toSet()); - } - - /** - * Returns a set of tree events gathered from the TreeCounts instance after - * calling flushEvents. - * - * @param treeCounts The tree counts instance. - * @param converter The means of acquiring a list of tree item dtos to be placed in - * the TreeEvents. - * - * @return The generated tree events. - */ - static <E, T> Set<TreeEvent> getIngestCompleteEventsFromList(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, List<TreeItemDTO<T>>> converter) { - return treeCounts.flushEvents().stream() - .flatMap(daoEvt -> converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED).stream().map(item -> new TreeEvent(item, true))) - .collect(Collectors.toSet()); - } - - /** - * Returns a set of tree events gathered from the TreeCounts instance after - * calling getEventTimeouts. - * - * @param treeCounts The tree counts instance. - * @param converter The means of acquiring a tree item dto to be placed in - * the TreeEvent. - * - * @return The generated tree events. - */ - static <E, T> Set<TreeEvent> getRefreshEvents(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, TreeItemDTO<T>> converter) { - return treeCounts.getEventTimeouts().stream() - .map(daoEvt -> new TreeEvent(converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toSet()); - } - - /** - * Returns a set of tree events gathered from the TreeCounts instance after - * calling getEventTimeouts. - * - * @param treeCounts The tree counts instance. - * @param converter The means of acquiring a tree item dtos to be placed in - * the TreeEvent. - * - * @return The generated tree events. - */ - static <E, T> Set<TreeEvent> getRefreshEventsFromList(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, List<TreeItemDTO<T>>> converter) { - return treeCounts.getEventTimeouts().stream() - .flatMap(daoEvt -> converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED).stream().map(item -> new TreeEvent(item , true))) - .collect(Collectors.toSet()); - } - - /** - * Escape a string literal to be used in a like statement with a prepared - * statement. - * - * @param toBeEscaped The string to be escaped. - * @param escapeChar The like escape character. - * - * @return The escaped string. - */ - static String likeEscape(String toBeEscaped, String escapeChar) { - if (toBeEscaped == null) { - return ""; - } - - return toBeEscaped - .replace("%", escapeChar + "%") - .replace("_", escapeChar + "_") - .replace(escapeChar, escapeChar + escapeChar); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagNameSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagNameSearchParams.java deleted file mode 100644 index 424cc61a8173691fba0f43aa895edc01e787934a..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagNameSearchParams.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.sleuthkit.datamodel.TagName; - -/** - * - * Search param for a tag name. - */ -public class TagNameSearchParams { - - private static final String TYPE_ID = "TAG_TYPE"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - private final TagName tagName; - private final Long dataSourceId; - - public TagNameSearchParams(TagName tagName, Long dataSourceId) { - this.dataSourceId = dataSourceId; - this.tagName = tagName; - } - - public TagName getTagName() { - return tagName; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 67 * hash + Objects.hashCode(this.tagName); - hash = 67 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final TagNameSearchParams other = (TagNameSearchParams) obj; - if (!Objects.equals(this.tagName, other.tagName)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java deleted file mode 100755 index 9f94b0d5d97216c9dbfbaa13ae2eb7cf51949895..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ /dev/null @@ -1,647 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import java.beans.PropertyChangeEvent; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ExecutionException; -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; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; -import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; -import org.sleuthkit.autopsy.casemodule.services.TagsManager; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; -import org.sleuthkit.autopsy.datamodel.Tags; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.TagsEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.autopsy.tags.TagUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.CaseDbAccessManager; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.TagName; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Provides information to populate the results viewer for data in the allTags - * section. - */ -@Messages({"TagsDAO.fileColumns.nameColLbl=Name", - "TagsDAO.fileColumns.originalName=Original Name", - "TagsDAO.fileColumns.filePathColLbl=File Path", - "TagsDAO.fileColumns.commentColLbl=Comment", - "TagsDAO.fileColumns.modifiedTimeColLbl=Modified Time", - "TagsDAO.fileColumns.changeTimeColLbl=Changed Time", - "TagsDAO.fileColumns.accessTimeColLbl=Accessed Time", - "TagsDAO.fileColumns.createdTimeColLbl=Created Time", - "TagsDAO.fileColumns.sizeColLbl=Size", - "TagsDAO.fileColumns.md5HashColLbl=MD5 Hash", - "TagsDAO.fileColumns.userNameColLbl=User Name", - "TagsDAO.fileColumns.noDescription=No Description", - "TagsDAO.tagColumns.sourceNameColLbl=Source Name", - "TagsDAO.tagColumns.origNameColLbl=Original Name", - "TagsDAO.tagColumns.sourcePathColLbl=Source File Path", - "TagsDAO.tagColumns.typeColLbl=Result Type", - "TagsDAO.tagColumns.commentColLbl=Comment", - "TagsDAO.tagColumns.userNameColLbl=User Name"}) -public class TagsDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(TagsDAO.class.getName()); - - private static final String USER_NAME_PROPERTY = "user.name"; //NON-NLS - - private static final List<ColumnKey> FILE_TAG_COLUMNS = Arrays.asList( - getFileColumnKey(Bundle.TagsDAO_fileColumns_nameColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_originalName()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_filePathColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_commentColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_modifiedTimeColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_changeTimeColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_accessTimeColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_createdTimeColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_sizeColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_md5HashColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_userNameColLbl())); - - private static final List<ColumnKey> RESULT_TAG_COLUMNS = Arrays.asList( - getFileColumnKey(Bundle.TagsDAO_tagColumns_sourceNameColLbl()), - getFileColumnKey(Bundle.TagsDAO_tagColumns_origNameColLbl()), - getFileColumnKey(Bundle.TagsDAO_tagColumns_sourcePathColLbl()), - getFileColumnKey(Bundle.TagsDAO_tagColumns_typeColLbl()), - getFileColumnKey(Bundle.TagsDAO_tagColumns_commentColLbl()), - getFileColumnKey(Bundle.TagsDAO_tagColumns_userNameColLbl())); - - private static TagsDAO instance = null; - - synchronized static TagsDAO getInstance() { - if (instance == null) { - instance = new TagsDAO(); - } - - return instance; - } - - private static ColumnKey getFileColumnKey(String name) { - return new ColumnKey(name, name, Bundle.TagsDAO_fileColumns_noDescription()); - } - - private final Cache<SearchParams<TagsSearchParams>, SearchResultsDTO> searchParamsCache - = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final TreeCounts<TagsEvent> treeCounts = new TreeCounts<>(); - - public SearchResultsDTO getTags(TagsSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } else if (key.getTagType() == null) { - throw new IllegalArgumentException("Must have non-null tag type"); - } - - SearchParams<TagsSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchTagsDTOs(searchParams)); - } - - @NbBundle.Messages({"FileTag.name.text=File Tag", - "ResultTag.name.text=Result Tag"}) - private SearchResultsDTO fetchTagsDTOs(SearchParams<TagsSearchParams> cacheKey) throws NoCurrentCaseException, TskCoreException { - switch (cacheKey.getParamData().getTagType()) { - case FILE: - return fetchFileTags(cacheKey); - case RESULT: - return fetchResultTags(cacheKey); - default: - throw new IllegalArgumentException("Unsupported tag type"); - } - } - - /** - * Returns a list of paged tag results. - * - * @param tags The tag results. - * @param searchParams The search parameters including the paging. - * - * @return The list of paged tag results. - */ - List<? extends Tag> getPaged(List<? extends Tag> tags, SearchParams<?> searchParams) { - Stream<? extends Tag> pagedTagsStream = tags.stream() - .sorted(Comparator.comparing((tag) -> tag.getId())) - .skip(searchParams.getStartItem()); - - if (searchParams.getMaxResultsCount() != null) { - pagedTagsStream = pagedTagsStream.limit(searchParams.getMaxResultsCount()); - } - - return pagedTagsStream.collect(Collectors.toList()); - } - - private SearchResultsDTO fetchResultTags(SearchParams<TagsSearchParams> cacheKey) throws NoCurrentCaseException, TskCoreException { - - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - TagName tagNameId = cacheKey.getParamData().getTagName(); - - TagsManager tm = Case.getCurrentCase().getServices().getTagsManager(); - // get all tag results - List<BlackboardArtifactTag> allTags = new ArrayList<>(); - List<BlackboardArtifactTag> artifactTags = (dataSourceId != null && dataSourceId > 0) - ? tm.getBlackboardArtifactTagsByTagName(tagNameId, dataSourceId) - : tm.getBlackboardArtifactTagsByTagName(tagNameId); - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - for (BlackboardArtifactTag tag : artifactTags) { - if (userName.equals(tag.getUserName())) { - allTags.add(tag); - } - } - } else { - allTags.addAll(artifactTags); - } - - // get current page of tag results - List<? extends Tag> pagedTags = getPaged(allTags, cacheKey); - - List<RowDTO> fileRows = new ArrayList<>(); - for (Tag tag : pagedTags) { - BlackboardArtifactTag blackboardTag = (BlackboardArtifactTag) tag; - - String name = blackboardTag.getContent().getName(); // As a backup. - try { - name = blackboardTag.getArtifact().getShortDescription(); - } catch (TskCoreException ignore) { - // it's a WARNING, skip - } - - String contentPath; - try { - contentPath = blackboardTag.getContent().getUniquePath(); - } catch (TskCoreException ex) { - contentPath = NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.unavail.text"); - } - - List<Object> cellValues = Arrays.asList(name, - null, - contentPath, - blackboardTag.getArtifact().getDisplayName(), - blackboardTag.getComment(), - blackboardTag.getUserName()); - - fileRows.add(new BlackboardArtifactTagsRowDTO( - blackboardTag, - cellValues, - blackboardTag.getId())); - } - - return new BaseSearchResultsDTO(BlackboardArtifactTagsRowDTO.getTypeIdForClass(), Bundle.ResultTag_name_text(), RESULT_TAG_COLUMNS, fileRows, BlackboardArtifactTag.class.getName(), 0, allTags.size()); - } - - private SearchResultsDTO fetchFileTags(SearchParams<TagsSearchParams> cacheKey) throws NoCurrentCaseException, TskCoreException { - - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - TagName tagNameId = cacheKey.getParamData().getTagName(); - - TagsManager tm = Case.getCurrentCase().getServices().getTagsManager(); - - // get all tag results - List<ContentTag> allTags = new ArrayList<>(); - List<ContentTag> contentTags = (dataSourceId != null && dataSourceId > 0) - ? tm.getContentTagsByTagName(tagNameId, dataSourceId) - : tm.getContentTagsByTagName(tagNameId); - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - for (ContentTag tag : contentTags) { - if (userName.equals(tag.getUserName())) { - allTags.add(tag); - } - } - } else { - allTags.addAll(contentTags); - } - - // get current page of tag results - List<? extends Tag> pagedTags = getPaged(allTags, cacheKey); - - List<RowDTO> fileRows = new ArrayList<>(); - for (Tag tag : pagedTags) { - ContentTag contentTag = (ContentTag) tag; - Content content = contentTag.getContent(); - String contentPath = content.getUniquePath(); - AbstractFile file = content instanceof AbstractFile ? (AbstractFile) content : null; - - List<Object> cellValues = Arrays.asList( - content.getName(), - null, - contentPath, - contentTag.getComment(), - file != null ? TimeZoneUtils.getFormattedTime(file.getMtime()) : "", - file != null ? TimeZoneUtils.getFormattedTime(file.getCtime()) : "", - file != null ? TimeZoneUtils.getFormattedTime(file.getAtime()) : "", - file != null ? TimeZoneUtils.getFormattedTime(file.getCrtime()) : "", - content.getSize(), - file != null ? StringUtils.defaultString(file.getMd5Hash()) : "", - contentTag.getUserName()); - - fileRows.add(new ContentTagsRowDTO( - contentTag, - cellValues, - file.getId())); - } - - return new BaseSearchResultsDTO(ContentTagsRowDTO.getTypeIdForClass(), Bundle.FileTag_name_text(), FILE_TAG_COLUMNS, fileRows, ContentTag.class.getName(), 0, allTags.size()); - } - - /** - * Returns true if the DAO event could have an impact on the given search - * params. - * - * @param tagParams The tag params. - * @param daoEvt The DAO event. - * - * @return True if the event could affect the results of the search params. - */ - private boolean isTagsInvalidatingEvent(TagsSearchParams tagParams, DAOEvent daoEvt) { - if (!(daoEvt instanceof TagsEvent)) { - return false; - } - - TagsEvent tagEvt = (TagsEvent) daoEvt; - return (Objects.equals(tagParams.getTagName(), tagEvt.getTagName()) - && tagParams.getTagType().equals(tagEvt.getTagType()) - && (tagParams.getDataSourceId() == null - || tagEvt.getDataSourceId() == null - || tagParams.getDataSourceId() == tagEvt.getDataSourceId())); - } - - private TreeItemDTO<TagsSearchParams> getTreeItem(TagsEvent evt, TreeResultsDTO.TreeDisplayCount count) { - return new TreeItemDTO<>( - TagsSearchParams.getTypeId(), - new TagsSearchParams(evt.getTagName(), evt.getTagType(), evt.getDataSourceId()), - evt.getTagName().getId(), - evt.getTagName().getDisplayName(), - count); - } - - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - handleIngestComplete(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - return SubDAOUtils.getIngestCompleteEvents(this.treeCounts, (evt, count) -> getTreeItem(evt, count)); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEvents(this.treeCounts, (evt, count) -> getTreeItem(evt, count)); - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - TagsEvent data = getTagData(evt); - if (data == null) { - return Collections.emptySet(); - } - - SubDAOUtils.invalidateKeys(this.searchParamsCache, (searchParams) -> { - return (Objects.equals(searchParams.getTagType(), data.getTagType()) - && Objects.equals(searchParams.getTagName(), data.getTagName()) - && (searchParams.getDataSourceId() == null || Objects.equals(searchParams.getDataSourceId(), data.getDataSourceId()))); - }); - - Collection<TagsEvent> daoEvents = Collections.singletonList(data); - - Collection<TreeEvent> treeEvents = daoEvents.stream() - .map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeResultsDTO.TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toSet()); - - return Stream.of(daoEvents, treeEvents) - .flatMap(lst -> lst.stream()) - .collect(Collectors.toSet()); - } - - /** - * Returns tag information from an event or null if no tag information - * found. - * - * @param evt The autopsy event. - * - * @return tag type, tag name id, data source id (or null if none determined - * from event). - */ - private TagsEvent getTagData(PropertyChangeEvent evt) { - if (evt instanceof BlackBoardArtifactTagAddedEvent) { - BlackBoardArtifactTagAddedEvent event = (BlackBoardArtifactTagAddedEvent) evt; - // ensure tag added event has a valid content id - if (event.getAddedTag() != null - && event.getAddedTag().getContent() != null - && event.getAddedTag().getArtifact() != null) { - return new TagsEvent(TagType.RESULT, event.getAddedTag().getName(), event.getAddedTag().getArtifact().getDataSourceObjectID()); - } - - } else if (evt instanceof BlackBoardArtifactTagDeletedEvent) { - BlackBoardArtifactTagDeletedEvent event = (BlackBoardArtifactTagDeletedEvent) evt; - BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo deletedTagInfo = event.getDeletedTagInfo(); - if (deletedTagInfo != null) { - return new TagsEvent(TagType.RESULT, deletedTagInfo.getName(), null); - } - } else if (evt instanceof ContentTagAddedEvent) { - ContentTagAddedEvent event = (ContentTagAddedEvent) evt; - // ensure tag added event has a valid content id - if (event.getAddedTag() != null && event.getAddedTag().getContent() != null) { - Content content = event.getAddedTag().getContent(); - Long dsId = content instanceof AbstractFile ? ((AbstractFile) content).getDataSourceObjectId() : null; - return new TagsEvent(TagType.FILE, event.getAddedTag().getName(), dsId); - } - } else if (evt instanceof ContentTagDeletedEvent) { - ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt; - // ensure tag deleted event has a valid content id - ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo(); - if (deletedTagInfo != null) { - return new TagsEvent(TagType.FILE, deletedTagInfo.getName(), null); - } - } - return null; - } - - /** - * Returns the counts of each tag name. - * - * @param dataSourceId The data source object id to filter on. - * - * @return The tree item results. - * - * @throws ExecutionException - */ - public TreeResultsDTO<? extends TagNameSearchParams> getNameCounts(Long dataSourceId) throws ExecutionException { - Set<TagName> indeterminateTagNameIds = this.treeCounts.getEnqueued().stream() - .filter(evt -> dataSourceId == null || evt.getDataSourceId() == dataSourceId) - .map(evt -> evt.getTagName()) - .collect(Collectors.toSet()); - - Map<TagName, TreeDisplayCount> tagNameCount = new HashMap<>(); - try { - TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); - - List<TagName> tagNamesInUse; - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - tagNamesInUse = (dataSourceId != null) - ? tm.getTagNamesInUseForUser(dataSourceId, userName) - : tm.getTagNamesInUseForUser(userName); - } else { - tagNamesInUse = (dataSourceId != null) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(dataSourceId) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); - } - - for (TagName tagName : tagNamesInUse) { - if (indeterminateTagNameIds.contains(tagName)) { - tagNameCount.put(tagName, TreeDisplayCount.INDETERMINATE); - continue; - } - - long tagsCount; - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - if (dataSourceId != null) { - tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, dataSourceId, userName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, dataSourceId, userName); - } else { - tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, userName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); - } - } else { - if (dataSourceId != null) { - tagsCount = tm.getContentTagsCountByTagName(tagName, dataSourceId); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, dataSourceId); - } else { - tagsCount = tm.getContentTagsCountByTagName(tagName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); - } - } - - tagNameCount.put(tagName, TreeDisplayCount.getDeterminate(tagsCount)); - } - - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); - } - - List<TreeResultsDTO.TreeItemDTO<TagNameSearchParams>> tagNameParams = tagNameCount.entrySet().stream() - .map(e -> createTagNameTreeItem(e.getKey(), dataSourceId, e.getValue())) - .collect(Collectors.toList()); - - // return results - return new TreeResultsDTO<>(tagNameParams); - } - - /** - * Creates a tag name tree item. - * - * @param tagName The tag name. - * @param dataSourceId The data source object id or null if not present. - * @param treeDisplayCount The tree display count. - * - * @return The tree item dto. - */ - public TreeItemDTO<TagNameSearchParams> createTagNameTreeItem(TagName tagName, Long dataSourceId, TreeResultsDTO.TreeDisplayCount treeDisplayCount) { - return new TreeItemDTO<>( - TagNameSearchParams.getTypeId(), - new TagNameSearchParams(tagName, dataSourceId), - tagName.getId(), - tagName.getDisplayName(), - treeDisplayCount - ); - } - - /** - * The count of content tags. - * - * @param dataSourceId The data source id where the content tag should - * appear or null. - * @param tagName The tag name. - * - * @return The count. - * - * @throws NoCurrentCaseException - * @throws TskCoreException - */ - private long getContentTagCount(Long dataSourceId, TagName tagName) throws NoCurrentCaseException, TskCoreException { - - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - return (dataSourceId != null) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, dataSourceId, userName) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, userName); - } else { - return (dataSourceId != null) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, dataSourceId) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); - } - } - - /** - * The count of result tags. - * - * @param dataSourceId The data source id where the result tag should appear - * or null. - * @param tagName The tag name. - * - * @return The count. - * - * @throws NoCurrentCaseException - * @throws TskCoreException - */ - private long getArtifactTagCount(Long dataSourceId, TagName tagName) throws NoCurrentCaseException, TskCoreException { - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - return (dataSourceId != null) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, dataSourceId, userName) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); - } else { - return (dataSourceId != null) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, dataSourceId) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); - } - } - - /** - * Returns the counts of file and result type given the search params. - * - * @param searchParams The tag name search params. - * - * @return The tree item results. - * - * @throws ExecutionException - */ - public TreeResultsDTO<? extends TagsSearchParams> getTypeCounts(TagNameSearchParams searchParams) throws ExecutionException { - Long dataSourceId = searchParams.getDataSourceId(); - TagName tagName = searchParams.getTagName(); - - Set<TagType> indeterminateTagTypes = this.treeCounts.getEnqueued().stream() - .filter(evt -> (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId)) && Objects.equals(tagName, evt.getTagName())) - .map(evt -> evt.getTagType()) - .collect(Collectors.toSet()); - - try { - return new TreeResultsDTO<>(Arrays.asList( - createTagTypeTreeItem( - tagName, - TagType.FILE, - dataSourceId, - indeterminateTagTypes.contains(TagType.FILE) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(getContentTagCount(dataSourceId, tagName))), - createTagTypeTreeItem( - tagName, - TagType.RESULT, - dataSourceId, - indeterminateTagTypes.contains(TagType.RESULT) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(getArtifactTagCount(dataSourceId, tagName))) - )); - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching tag type counts.", ex); - } - } - - /** - * Creates a tag type tree item. - * - * @param tagName The tag name. - * @param tagType The tag type. - * @param dataSourceId The data source object id or null if not present. - * @param treeDisplayCount The tree display count. - * - * @return The tree item dto. - */ - public TreeItemDTO<TagsSearchParams> createTagTypeTreeItem(TagName tagName, TagType tagType, Long dataSourceId, TreeResultsDTO.TreeDisplayCount treeDisplayCount) { - return new TreeItemDTO<>( - TagsSearchParams.getTypeId(), - new TagsSearchParams(tagName, tagType, dataSourceId), - tagName.getId() + "_" + tagType.name(), - tagType.getDisplayName(), - treeDisplayCount - ); - } - - /** - * Handles fetching and paging of data for allTags. - */ - public static class TagFetcher extends DAOFetcher<TagsSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public TagFetcher(TagsSearchParams params) { - super(params); - } - - protected TagsDAO getDAO() { - return MainDAO.getInstance().getTagsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getTags(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isTagsInvalidatingEvent(this.getParameters(), evt); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java deleted file mode 100755 index b0153d2810dbeb88048562329a2d4346943dc640..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.Objects; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.datamodel.TagName; - -/** - * Key for accessing data about tags from the DAO. - */ -public class TagsSearchParams extends TagNameSearchParams { - - private static final String TYPE_ID = "TAG"; - - /** - * @return The type id for this search parameter. - */ - public static String getTypeId() { - return TYPE_ID; - } - - @Messages({ - "TagType_File_displayName=File Tags", - "TagType_Result_displayName=Result Tags",}) - public enum TagType { - FILE(Bundle.TagType_File_displayName()), - RESULT(Bundle.TagType_Result_displayName()); - - private final String displayName; - - TagType(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return displayName; - } - } - - private final TagType type; - - public TagsSearchParams(TagName tagName, TagType type, Long dataSourceId) { - super(tagName, dataSourceId); - this.type = type; - } - - public TagType getTagType() { - return type; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 97 * hash + Objects.hashCode(this.type); - hash = 97 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final TagsSearchParams other = (TagsSearchParams) obj; - if (this.type != other.type) { - return false; - } - return super.equals(obj); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java deleted file mode 100644 index d2a92871bf9e2bb28993c3a538402842079ec11d..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.List; -import java.util.Objects; - -/** - * A list of items to display in the tree. - */ -public class TreeResultsDTO<T> { - - private final List<TreeItemDTO<T>> items; - - /** - * Main constructor. - * - * @param items The items to display. - */ - public TreeResultsDTO(List<TreeItemDTO<T>> items) { - this.items = items; - } - - /** - * @return The items to display. - */ - public List<TreeItemDTO<T>> getItems() { - return items; - } - - /** - * Captures the count to be displayed in the UI. - */ - public static class TreeDisplayCount { - - public enum Type { - DETERMINATE, - INDETERMINATE, - NOT_SHOWN, - UNSPECIFIED - } - - private final Type type; - private final long count; - - public static final TreeDisplayCount INDETERMINATE = new TreeDisplayCount(Type.INDETERMINATE, -1); - public static final TreeDisplayCount NOT_SHOWN = new TreeDisplayCount(Type.NOT_SHOWN, -1); - public static final TreeDisplayCount UNSPECIFIED = new TreeDisplayCount(Type.UNSPECIFIED, -1); - - public static TreeDisplayCount getDeterminate(long count) { - return new TreeDisplayCount(Type.DETERMINATE, count); - } - - private TreeDisplayCount(Type type, long count) { - this.type = type; - this.count = count; - } - - public Type getType() { - return type; - } - - public long getCount() { - return count; - } - - /** - * Returns the suffix to be added to a display string when displaying - * this count. - * - * NOTE: If this code changes, regex code in - * DirectoryTreeTopComponent.respondSelection will need to be updated as - * well. - * - * @return The suffix to be added to a display string when displaying - * this count. - */ - public String getDisplaySuffix() { - switch (this.type) { - case DETERMINATE: - return " (" + count + ")"; - case INDETERMINATE: - return " (...)"; - case NOT_SHOWN: - case UNSPECIFIED: - default: - return ""; - } - } - - @Override - public int hashCode() { - int hash = 5; - hash = 97 * hash + Objects.hashCode(this.type); - hash = 97 * hash + (int) (this.count ^ (this.count >>> 32)); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final TreeDisplayCount other = (TreeDisplayCount) obj; - if (this.count != other.count) { - return false; - } - if (this.type != other.type) { - return false; - } - return true; - } - - } - - /** - * A result providing a category and a count for that category. Equals and - * hashCode are based on id, type id, and type data. - */ - public static class TreeItemDTO<T> { - - private final String displayName; - private final String typeId; - private final TreeDisplayCount count; - private final T searchParams; - private final Object id; - - /** - * Main constructor. - * - * @param typeId The id of this item type. - * @param searchParams Search params for this tree item that can be used - * to display results. - * @param id The id of this row. Can be any object that - * implements equals and hashCode. - * @param displayName The display name of this row. - * @param count The count of results for this row or null if not - * applicable. - */ - public TreeItemDTO(String typeId, T searchParams, Object id, String displayName, TreeDisplayCount count) { - this.typeId = typeId; - this.id = id; - this.displayName = displayName; - this.count = count; - this.searchParams = searchParams; - } - - /** - * @return The display name of this row. - */ - public String getDisplayName() { - return displayName; - } - - /** - * @return The count of results for this row or null if not applicable. - */ - public TreeDisplayCount getDisplayCount() { - return count; - } - - /** - * - * @return Search params for this tree item that can be used to display - * results. - */ - public T getSearchParams() { - return searchParams; - } - - /** - * @return The id of this row. Can be any object that implements equals - * and hashCode. - */ - public Object getId() { - return id; - } - - /** - * @return The id of this item type. - */ - public String getTypeId() { - return typeId; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ViewsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ViewsDAO.java deleted file mode 100644 index a9f61d7e967d47c3cca65e9af3faf26886da2296..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/ViewsDAO.java +++ /dev/null @@ -1,1379 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import java.beans.PropertyChangeEvent; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; -import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; -import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils; -import org.sleuthkit.autopsy.mainui.datamodel.events.DeletedContentEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeExtensionsEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeMimeEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeSizeEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskData.FileKnown; -import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; -import org.sleuthkit.datamodel.TskData.TSK_FS_META_FLAG_ENUM; -import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; -import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_TYPE_ENUM; - -/** - * Provides information to populate the results viewer for data in the views - * section. - */ -public class ViewsDAO extends AbstractDAO { - - private static final Logger logger = Logger.getLogger(ViewsDAO.class.getName()); - - private static final Map<String, Set<FileExtSearchFilter>> EXTENSION_FILTER_MAP - = Stream.of((FileExtSearchFilter[]) FileExtRootFilter.values(), FileExtDocumentFilter.values(), FileExtExecutableFilter.values()) - .flatMap(arr -> Stream.of(arr)) - .flatMap(filter -> filter.getFilter().stream().map(ext -> Pair.of(ext, filter))) - .collect(Collectors.groupingBy( - pair -> pair.getKey(), - Collectors.mapping(pair -> pair.getValue(), - Collectors.toSet()))); - - private static final int NODE_COUNT_FILE_TABLE_THRESHOLD = 1_000_000; - - private static final String FILE_VIEW_EXT_TYPE_ID = "FILE_VIEW_BY_EXT"; - - private static ViewsDAO instance = null; - - synchronized static ViewsDAO getInstance() { - if (instance == null) { - instance = new ViewsDAO(); - } - - return instance; - } - - private final Cache<SearchParams<Object>, SearchResultsDTO> searchParamsCache - = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); - private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>(); - - private boolean exceededFileThreshold = false; - - private SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - public SearchResultsDTO getFilesByExtension(FileTypeExtensionsSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key.getFilter() == null) { - throw new IllegalArgumentException("Must have non-null filter"); - } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchExtensionSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount)); - } - - public SearchResultsDTO getFilesByMime(FileTypeMimeSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key.getMimeType() == null) { - throw new IllegalArgumentException("Must have non-null filter"); - } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchMimeSearchResultsDTOs(key.getMimeType(), key.getDataSourceId(), startItem, maxCount)); - } - - public SearchResultsDTO getFilesBySize(FileTypeSizeSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key.getSizeFilter() == null) { - throw new IllegalArgumentException("Must have non-null filter"); - } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchSizeSearchResultsDTOs(key.getSizeFilter(), key.getDataSourceId(), startItem, maxCount)); - } - - /** - * Returns search results for the given deleted content search params. - * - * @param params The deleted content search params. - * @param startItem The starting item to start returning at. - * @param maxCount The maximum number of items to return. - * - * @return The search results. - * - * @throws ExecutionException - * @throws IllegalArgumentException - */ - public SearchResultsDTO getDeletedContent(DeletedContentSearchParams params, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (params.getFilter() == null) { - throw new IllegalArgumentException("Must have non-null filter"); - } else if (params.getDataSourceId() != null && params.getDataSourceId() <= 0) { - throw new IllegalArgumentException("Data source id must be greater than 0 or null"); - } - - SearchParams<Object> searchParams = new SearchParams<>(params, startItem, maxCount); - return searchParamsCache.get(searchParams, () -> fetchDeletedSearchResultsDTOs(params.getFilter(), params.getDataSourceId(), startItem, maxCount)); - } - - private boolean isFilesByExtInvalidating(FileTypeExtensionsSearchParams key, DAOEvent eventData) { - if (!(eventData instanceof FileTypeExtensionsEvent)) { - return false; - } - - FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) eventData; - return (extEvt.getExtensionFilter() == null || key.getFilter().equals(extEvt.getExtensionFilter())) - && (key.getDataSourceId() == null || extEvt.getDataSourceId() == null || key.getDataSourceId().equals(extEvt.getDataSourceId())); - } - - private boolean isFilesByMimeInvalidating(FileTypeMimeSearchParams key, DAOEvent eventData) { - if (!(eventData instanceof FileTypeMimeEvent)) { - return false; - } - - FileTypeMimeEvent mimeEvt = (FileTypeMimeEvent) eventData; - return mimeEvt.getMimeType().startsWith(key.getMimeType()) - && (key.getDataSourceId() == null || Objects.equals(key.getDataSourceId(), mimeEvt.getDataSourceId())); - } - - private boolean isFilesBySizeInvalidating(FileTypeSizeSearchParams key, DAOEvent eventData) { - if (!(eventData instanceof FileTypeSizeEvent)) { - return false; - } - - FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) eventData; - return (sizeEvt.getSizeFilter() == null || sizeEvt.getSizeFilter().equals(key.getSizeFilter())) - && (key.getDataSourceId() == null || sizeEvt.getDataSourceId() == null || Objects.equals(key.getDataSourceId(), sizeEvt.getDataSourceId())); - } - - private boolean isDeletedContentInvalidating(DeletedContentSearchParams params, DAOEvent eventData) { - if (!(eventData instanceof DeletedContentEvent)) { - return false; - } - - DeletedContentEvent deletedContentEvt = (DeletedContentEvent) eventData; - return (deletedContentEvt.getFilter() == null || deletedContentEvt.getFilter().equals(params.getFilter())) - && (params.getDataSourceId() == null || deletedContentEvt.getDataSourceId() == null - || Objects.equals(params.getDataSourceId(), deletedContentEvt.getDataSourceId())); - } - - /** - * Returns a sql 'and' clause to filter by data source id if one is present. - * - * @param dataSourceId The data source id or null. - * - * @return Returns clause if data source id is present or blank string if - * not. - */ - private static String getDataSourceAndClause(Long dataSourceId) { - return (dataSourceId != null && dataSourceId > 0 - ? " AND data_source_obj_id = " + dataSourceId - : " "); - } - - /** - * Returns clause that will determine if file extension is within the - * filter's set of extensions. - * - * @param filter The filter. - * - * @return The sql clause that will need to be proceeded with 'where' or - * 'and'. - */ - private static String getFileExtensionClause(FileExtSearchFilter filter) { - return "extension IN (" + filter.getFilter().stream() - .map(String::toLowerCase) - .map(s -> "'" + StringUtils.substringAfter(s, ".") + "'") - .collect(Collectors.joining(", ")) + ")"; - } - - /** - * @return If user preference of hide known files, returns sql and clause to - * hide known files or returns empty string otherwise. - */ - private String getHideKnownAndClause() { - return (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known <> " + FileKnown.KNOWN.getFileKnownValue() + ") ") : ""); - } - - /** - * @return A clause (no 'and' or 'where' prefixed) indicating the dir_type - * is regular. - */ - private String getRegDirTypeClause() { - return "(dir_type = " + TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"; - } - - /** - * Returns a clause that will filter out files that aren't to be counted in - * the file extensions view. - * - * @return The filter that will need to be proceeded with 'where' or 'and'. - */ - private String getBaseFileExtensionFilter() { - return getRegDirTypeClause() + getHideKnownAndClause(); - } - - /** - * Returns a statement to be proceeded with 'where' or 'and' that will - * filter results to the provided filter and data source id (if non null). - * - * @param filter The file extension filter. - * @param dataSourceId The data source id or null if no data source - * filtering is to occur. - * - * @return The sql statement to be proceeded with 'and' or 'where'. - */ - private String getFileExtensionWhereStatement(FileExtSearchFilter filter, Long dataSourceId) { - String whereClause = getBaseFileExtensionFilter() - + getDataSourceAndClause(dataSourceId) - + " AND (" + getFileExtensionClause(filter) + ")"; - return whereClause; - } - - /** - * @return The TSK_DB_FILES_TYPE_ENUm values allowed for mime type view - * items. - */ - private Set<TSK_DB_FILES_TYPE_ENUM> getMimeDbFilesTypes() { - return Stream.of( - TSK_DB_FILES_TYPE_ENUM.FS, - TSK_DB_FILES_TYPE_ENUM.CARVED, - TSK_DB_FILES_TYPE_ENUM.DERIVED, - TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE, - TSK_DB_FILES_TYPE_ENUM.LOCAL, - (hideSlackFilesInViewsTree() ? null : (TSK_DB_FILES_TYPE_ENUM.SLACK))) - .filter(ordinal -> ordinal != null) - .collect(Collectors.toSet()); - } - - /** - * Returns a statement to be proceeded with 'where' or 'and' that will - * filter results to the provided filter and data source id (if non null). - * - * @param filter The deleted content filter. - * @param dataSourceId The data source id or null if no data source - * filtering is to occur. - * - * @return The sql statement to be proceeded with 'and' or 'where'. - */ - private String getDeletedContentWhereStatement(DeletedContentFilter filter, Long dataSourceId) { - String whereClause = getDeletedContentClause(filter) + getDataSourceAndClause(dataSourceId); - return whereClause; - } - - /** - * Returns a statement to be proceeded with 'where' or 'and' that will - * filter out results that should not be viewed in mime types view. - * - * @return A statement to be proceeded with 'and' or 'where'. - */ - private String getBaseFileMimeFilter() { - return getRegDirTypeClause() - + getHideKnownAndClause() - + " AND (type IN (" - + getMimeDbFilesTypes().stream().map(v -> Integer.toString(v.ordinal())).collect(Collectors.joining(", ")) - + "))"; - } - - /** - * Returns a sql statement to be proceeded with 'where' or 'and' that will - * filter to the specified mime type. - * - * @param mimeType The mime type. - * @param dataSourceId The data source object id or null if no data source - * filtering is to occur. - * - * @return A statement to be proceeded with 'and' or 'where'. - */ - private String getFileMimeWhereStatement(String mimeType, Long dataSourceId) { - String whereClause = getBaseFileMimeFilter() - + getDataSourceAndClause(dataSourceId) - + " AND mime_type = '" + mimeType + "'"; - return whereClause; - } - - /** - * Returns clause to be proceeded with 'where' or 'and' to filter files to - * those within the bounds of the filter. - * - * @param filter The size filter. - * - * @return The clause to be proceeded with 'where' or 'and'. - */ - private static String getFileSizeClause(FileSizeFilter filter) { - return filter.getMaxBound() == null - ? "(size >= " + filter.getMinBound() + ")" - : "(size >= " + filter.getMinBound() + " AND size < " + filter.getMaxBound() + ")"; - } - - /** - * Returns clause to be proceeded with 'where' or 'and' to filter deleted - * content. - * - * @param filter The deleted content filter. - * - * @return The clause to be proceeded with 'where' or 'and'. - */ - private static String getDeletedContentClause(DeletedContentFilter filter) throws IllegalArgumentException { - switch (filter) { - case FS_DELETED_FILTER: - return "dir_flags = " + TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() //NON-NLS - + " AND meta_flags != " + TSK_FS_META_FLAG_ENUM.ORPHAN.getValue() //NON-NLS - + " AND type = " + TSK_DB_FILES_TYPE_ENUM.FS.getFileType(); //NON-NLS - - case ALL_DELETED_FILTER: - return " ( " - + "( " - + "(dir_flags = " + TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() //NON-NLS - + " OR " //NON-NLS - + "meta_flags = " + TSK_FS_META_FLAG_ENUM.ORPHAN.getValue() //NON-NLS - + ")" - + " AND type = " + TSK_DB_FILES_TYPE_ENUM.FS.getFileType() //NON-NLS - + " )" - + " OR type = " + TSK_DB_FILES_TYPE_ENUM.CARVED.getFileType() //NON-NLS - + " OR (dir_flags = " + TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() - + " AND type = " + TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.getFileType() + " )" - + " )"; - - default: - throw new IllegalArgumentException(MessageFormat.format("Unsupported filter type to get deleted content: {0}", filter)); //NON-NLS - } - } - - /** - * The filter for all files to remove those that should never be seen in the - * file size views. - * - * @return The clause to be proceeded with 'where' or 'and'. - */ - private String getBaseFileSizeFilter() { - // Ignore unallocated block files. - return "(type != " + TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")" + getHideKnownAndClause(); - } - - /** - * Creates a clause to be proceeded with 'where' or 'and' that will show - * files specified by the filter and the specified data source. - * - * @param filter The file size filter. - * @param dataSourceId The id of the data source or null if no data source - * filtering. - * - * @return The clause to be proceeded with 'where' or 'and'. - */ - private String getFileSizesWhereStatement(FileSizeFilter filter, Long dataSourceId) { - String query = getBaseFileSizeFilter() - + " AND " + getFileSizeClause(filter) - + getDataSourceAndClause(dataSourceId); - - return query; - } - - /** - * Returns counts for a collection of file extension search filters. - * - * @param filters The filters. Each one will have an entry in the - * returned results. - * @param dataSourceId The data source object id or null if no data source - * filtering should occur. - * - * @return The results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<FileTypeExtensionsSearchParams> getFileExtCounts(Collection<FileExtSearchFilter> filters, Long dataSourceId) throws IllegalArgumentException, ExecutionException { - List<TreeItemDTO<FileTypeExtensionsSearchParams>> treeList; - String query = null; - try { - // if exceeded file count threshold, show no counts - if (this.exceededFileThreshold || getCase().countFilesWhere("1=1") > NODE_COUNT_FILE_TABLE_THRESHOLD) { - this.exceededFileThreshold = true; - treeList = filters.stream() - .map(f -> createExtensionTreeItem(f, dataSourceId, TreeDisplayCount.NOT_SHOWN)) - .collect(Collectors.toList()); - - } else { - // determine filters to be marked as indeterminate - Set<FileExtSearchFilter> indeterminateFilters = new HashSet<>(); - for (DAOEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof FileTypeExtensionsEvent) { - FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) evt; - if (dataSourceId == null || extEvt.getDataSourceId() == null || Objects.equals(extEvt.getDataSourceId(), dataSourceId)) { - if (extEvt.getExtensionFilter() == null) { - // add all filters if extension filter is null and keep going - indeterminateFilters.addAll(filters); - break; - } else if (filters.contains(extEvt.getExtensionFilter())) { - indeterminateFilters.add(extEvt.getExtensionFilter()); - } - } - } - } - - // create selects that provide counts for each filter name; assumes that filter enum names are sql safe. - String filterSums = filters.stream() - // no point in getting a count for filters that will not display count - .filter(f -> !indeterminateFilters.contains(f)) - // this assumes that filter enum names are safe to use as sql identifiers - .map(f -> MessageFormat.format("\n SUM(CASE WHEN {0} THEN 1 ELSE 0 END) AS {1}", getFileExtensionClause(f), f.getName())) - .collect(Collectors.joining(",")); - - query = filterSums - + "\nFROM tsk_files\nWHERE " - + getBaseFileExtensionFilter() - + getDataSourceAndClause(dataSourceId); - - treeList = new ArrayList<>(); - getCase().getCaseDbAccessManager().select(query, (resultSet) -> { - try { - if (resultSet.next()) { - for (FileExtSearchFilter filter : filters) { - // if filter should be indeterminate, don't bother with count - if (indeterminateFilters.contains(filter)) { - treeList.add(createExtensionTreeItem(filter, dataSourceId, TreeDisplayCount.INDETERMINATE)); - } else { - // otherwise get the count which should be labeled by the enum name in the sql query - treeList.add(createExtensionTreeItem(filter, dataSourceId, TreeDisplayCount.getDeterminate(resultSet.getLong(filter.getName())))); - } - } - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex); - } - }); - - } - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching file counts with query:\n" + (query == null ? "<null>" : query), ex); - } - - treeList.sort(Comparator.comparing(item -> item.getSearchParams().getFilter().getId())); - return new TreeResultsDTO<>(treeList); - } - - /** - * Creates an extension tree item. - * - * @param filter The extension filter. - * @param dataSourceId The data source id or null. - * @param displayCount The count to display. - * - * @return The extension tree item. - */ - private TreeItemDTO<FileTypeExtensionsSearchParams> createExtensionTreeItem(FileExtSearchFilter filter, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeItemDTO<>( - FileTypeExtensionsSearchParams.getTypeId(), - new FileTypeExtensionsSearchParams(filter, dataSourceId), - filter, - filter == null ? "" : filter.getDisplayName(), - displayCount); - } - - /** - * Returns counts for file size categories. - * - * @param dataSourceId The data source object id or null if no data source - * filtering should occur. - * - * @return The results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<FileTypeSizeSearchParams> getFileSizeCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { - Set<FileSizeFilter> indeterminateFilters = new HashSet<>(); - for (DAOEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof FileTypeSizeEvent) { - FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) evt; - if (dataSourceId == null || sizeEvt.getDataSourceId() == null || Objects.equals(sizeEvt.getDataSourceId(), dataSourceId)) { - if (sizeEvt.getSizeFilter() == null) { - // if null size filter, indicates full refresh and all file sizes need refresh. - indeterminateFilters.addAll(Arrays.asList(FileSizeFilter.values())); - break; - } else { - indeterminateFilters.add(sizeEvt.getSizeFilter()); - } - } - } - } - - Map<FileSizeFilter, String> whereClauses = Stream.of(FileSizeFilter.values()) - .collect(Collectors.toMap( - filter -> filter, - filter -> getFileSizeClause(filter))); - - Map<FileSizeFilter, Long> countsByFilter = getFilesCounts(whereClauses, getBaseFileSizeFilter(), dataSourceId, true); - - List<TreeItemDTO<FileTypeSizeSearchParams>> treeList = countsByFilter.entrySet().stream() - .map(entry -> { - TreeDisplayCount displayCount = indeterminateFilters.contains(entry.getKey()) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(entry.getValue()); - - return createSizeTreeItem(entry.getKey(), dataSourceId, displayCount); - }) - .sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())) - .collect(Collectors.toList()); - - return new TreeResultsDTO<>(treeList); - } - - /** - * Returns counts for deleted content categories. - * - * @param dataSourceId The data source object id or null if no data source - * filtering should occur. - * - * @return The results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<DeletedContentSearchParams> getDeletedContentCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { - Set<DeletedContentFilter> indeterminateFilters = new HashSet<>(); - for (DAOEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof DeletedContentEvent) { - DeletedContentEvent deletedEvt = (DeletedContentEvent) evt; - if (dataSourceId == null || deletedEvt.getDataSourceId() == null || Objects.equals(deletedEvt.getDataSourceId(), dataSourceId)) { - if (deletedEvt.getFilter() == null) { - // if null filter, indicates full refresh and all file sizes need refresh. - indeterminateFilters.addAll(Arrays.asList(DeletedContentFilter.values())); - break; - } else { - indeterminateFilters.add(deletedEvt.getFilter()); - } - } - } - } - - String queryStr = Stream.of(DeletedContentFilter.values()) - .map((filter) -> { - String clause = getDeletedContentClause(filter); - return MessageFormat.format(" (SELECT COUNT(*) FROM tsk_files WHERE {0}) AS {1}", clause, filter.name()); - }) - .collect(Collectors.joining(", \n")); - - try { - SleuthkitCase skCase = getCase(); - - List<TreeItemDTO<DeletedContentSearchParams>> treeList = new ArrayList<>(); - skCase.getCaseDbAccessManager().select(queryStr, (resultSet) -> { - try { - if (resultSet.next()) { - for (DeletedContentFilter filter : DeletedContentFilter.values()) { - long count = resultSet.getLong(filter.name()); - TreeDisplayCount displayCount = indeterminateFilters.contains(filter) - ? TreeDisplayCount.INDETERMINATE - : TreeDisplayCount.getDeterminate(count); - - treeList.add(createDeletedContentTreeItem(filter, dataSourceId, displayCount)); - } - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex); - } - }); - - return new TreeResultsDTO<>(treeList); - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex); - } - } - - private static TreeItemDTO<DeletedContentSearchParams> createDeletedContentTreeItem(DeletedContentFilter filter, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeItemDTO<>( - "DELETED_CONTENT", - new DeletedContentSearchParams(filter, dataSourceId), - filter, - filter == null ? "" : filter.getDisplayName(), - displayCount); - } - - /** - * Creates a size tree item. - * - * @param filter The file size filter. - * @param dataSourceId The data source id. - * @param displayCount The display count. - * - * @return The tree item. - */ - private TreeItemDTO<FileTypeSizeSearchParams> createSizeTreeItem(FileSizeFilter filter, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeItemDTO<>( - FileTypeSizeSearchParams.getTypeId(), - new FileTypeSizeSearchParams(filter, dataSourceId), - filter, - filter == null ? "" : filter.getDisplayName(), - displayCount); - } - - /** - * Returns counts for file mime type categories. - * - * @param prefix The prefix mime type (i.e. 'application', 'audio'). - * If null, prefix counts are gathered. - * @param dataSourceId The data source object id or null if no data source - * filtering should occur. - * - * @return The results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public TreeResultsDTO<FileTypeMimeSearchParams> getFileMimeCounts(String prefix, Long dataSourceId) throws IllegalArgumentException, ExecutionException { - String prefixWithSlash = StringUtils.isNotBlank(prefix) ? prefix.replaceAll("/", "") + "/" : null; - String likeItem = StringUtils.isNotBlank(prefixWithSlash) ? prefixWithSlash.replaceAll("%", "") + "%" : null; - - String baseFilter = "WHERE " + getBaseFileMimeFilter() - + getDataSourceAndClause(dataSourceId) - + (StringUtils.isNotBlank(prefix) ? " AND mime_type LIKE ? " : " AND mime_type IS NOT NULL "); - - String mimeTypeSql; - SleuthkitCase skCase; - try { - skCase = getCase(); - } catch (NoCurrentCaseException ex) { - throw new ExecutionException("An error occurred while getting the current case.", ex); - } - - // get the sql identifier for the mime type segment to find - if (StringUtils.isNotBlank(prefix)) { - mimeTypeSql = "mime_type"; - } else { - switch (skCase.getDatabaseType()) { - case POSTGRESQL: - mimeTypeSql = "SPLIT_PART(mime_type, '/', 1)"; - break; - case SQLITE: - mimeTypeSql = "SUBSTR(mime_type, 0, instr(mime_type, '/'))"; - break; - default: - throw new IllegalArgumentException("Unknown database type: " + skCase.getDatabaseType()); - } - } - - Map<String, TreeDisplayCount> typeCounts = new HashMap<>(); - try { - // if exceeded file count threshold, only show different mime types - if (this.exceededFileThreshold || skCase.countFilesWhere("1=1") > NODE_COUNT_FILE_TABLE_THRESHOLD) { - this.exceededFileThreshold = true; - String query = "DISTINCT(" + mimeTypeSql + ") AS mime_type\n" - + "FROM tsk_files\n" - + baseFilter; - - typeCounts = fetchMimeTypeData(query, likeItem, Collections.emptySet(), false); - } else { - String query = mimeTypeSql + " AS mime_type, COUNT(*) AS count\n" - + "FROM tsk_files\n" - + baseFilter + "\n" - + "GROUP BY " + mimeTypeSql; - - // get types that should be shown as indeterminate - Set<String> indeterminateMimeTypes = new HashSet<>(); - for (DAOEvent evt : this.treeCounts.getEnqueued()) { - if (evt instanceof FileTypeMimeEvent) { - FileTypeMimeEvent mimeEvt = (FileTypeMimeEvent) evt; - if ((dataSourceId == null || Objects.equals(mimeEvt.getDataSourceId(), dataSourceId)) - && (prefixWithSlash == null || mimeEvt.getMimeType().startsWith(prefixWithSlash))) { - - String mimePortion = prefixWithSlash != null - ? mimeEvt.getMimeType().substring(prefixWithSlash.length()) - : mimeEvt.getMimeType().substring(0, mimeEvt.getMimeType().indexOf("/")); - - indeterminateMimeTypes.add(mimePortion); - } - } - } - - typeCounts = fetchMimeTypeData(query, likeItem, indeterminateMimeTypes, true); - } - } catch (TskCoreException ex) { - throw new ExecutionException("An error occurred while determining file counts.", ex); - } - - // get tree count items to return - List<TreeItemDTO<FileTypeMimeSearchParams>> treeList = typeCounts.entrySet().stream() - .map(entry -> { - String name = prefixWithSlash != null && entry.getKey().startsWith(prefixWithSlash) - ? entry.getKey().substring(prefixWithSlash.length()) - : entry.getKey(); - - return createMimeTreeItem(entry.getKey(), name, dataSourceId, entry.getValue()); - }) - .sorted((a, b) -> stringCompare(a.getSearchParams().getMimeType(), b.getSearchParams().getMimeType())) - .collect(Collectors.toList()); - - return new TreeResultsDTO<>(treeList); - } - - /** - * Helper method that fetches data with a prepared statement specifically - * for mime types. - * - * @param query The sql query. - * @param likeItem The String to be used in the prepared - * statement for a like item or null. - * @param indeterminateMimeTypes The items that should be indeterminate or - * empty. - * @param countShown Whether or not a count should be shown. - * - * @return The map of mime types to counts to display. - * - * @throws ExecutionException - */ - private Map<String, TreeDisplayCount> fetchMimeTypeData(String query, String likeItem, Set<String> indeterminateMimeTypes, boolean countShown) throws ExecutionException { - try (CaseDbPreparedStatement casePreparedStatement = getCase().getCaseDbAccessManager().prepareSelect(query)) { - - if (likeItem != null) { - casePreparedStatement.setString(1, likeItem); - } - - Map<String, TreeDisplayCount> typeCounts = new HashMap<>(); - getCase().getCaseDbAccessManager().select(casePreparedStatement, (resultSet) -> { - try { - while (resultSet.next()) { - String mimeTypeId = resultSet.getString("mime_type"); - if (mimeTypeId != null) { - TreeDisplayCount displayCount; - if (!countShown) { - displayCount = TreeDisplayCount.NOT_SHOWN; - } else if (indeterminateMimeTypes != null && indeterminateMimeTypes.contains(mimeTypeId)) { - displayCount = TreeDisplayCount.INDETERMINATE; - } else { - displayCount = TreeDisplayCount.getDeterminate(resultSet.getLong("count")); - } - - typeCounts.put(mimeTypeId, displayCount); - } - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching file mime type counts.", ex); - } - }); - return typeCounts; - } catch (TskCoreException | NoCurrentCaseException | SQLException ex) { - throw new ExecutionException("An error occurred while fetching file counts with query:\n" + query, ex); - } - } - - /** - * Creates a mime type tree item. - * - * @param fullMime The full mime type. - * @param mimeName The mime type segment that will be displayed (suffix - * or prefix). - * @param dataSourceId The data source id. - * @param displayCount The count to display. - * - * @return The created tree item. - */ - private TreeItemDTO<FileTypeMimeSearchParams> createMimeTreeItem(String fullMime, String mimeName, Long dataSourceId, TreeDisplayCount displayCount) { - return new TreeItemDTO<>( - FileTypeMimeSearchParams.getTypeId(), - new FileTypeMimeSearchParams(fullMime, dataSourceId), - mimeName, - mimeName, - displayCount); - } - - /** - * Provides case insensitive comparator integer for strings that may be - * null. - * - * @param a String that may be null. - * @param b String that may be null. - * - * @return The comparator value placing null first. - */ - private int stringCompare(String a, String b) { - if (a == null && b == null) { - return 0; - } else if (a == null) { - return -1; - } else if (b == null) { - return 1; - } else { - return a.compareToIgnoreCase(b); - } - } - - /** - * Determines counts for files in multiple categories. - * - * @param whereClauses A mapping of objects to their respective where - * clauses. - * @param baseFilter A filter for files applied before performing - * groupings and counts. It shouldn't have a leading - * 'AND' or 'WHERE'. - * @param dataSourceId The data source object id or null if no data - * source filtering. - * @param includeZeroCount Whether or not to return an item if there are 0 - * matches. - * - * @return A mapping of the keys in the 'whereClauses' mapping to their - * respective counts. - * - * @throws ExecutionException - */ - private <T> Map<T, Long> getFilesCounts(Map<T, String> whereClauses, String baseFilter, Long dataSourceId, boolean includeZeroCount) throws ExecutionException { - // get artifact types and counts - - Map<Integer, T> types = new HashMap<>(); - String whenClauses = ""; - - int idx = 0; - for (Entry<T, String> e : whereClauses.entrySet()) { - types.put(idx, e.getKey()); - whenClauses += " WHEN " + e.getValue() + " THEN " + idx + " \n"; - idx++; - } - - String switchStatement = " CASE \n" - + whenClauses - + " ELSE -1 \n" - + " END AS type_id \n"; - - String dataSourceClause = dataSourceId != null && dataSourceId > 0 ? "data_source_obj_id = " + dataSourceId : null; - - String baseWhereClauses = Stream.of(dataSourceClause, baseFilter) - .filter(s -> StringUtils.isNotBlank(s)) - .collect(Collectors.joining(" AND ")); - - String query = "res.type_id, COUNT(*) AS count FROM \n" - + "(SELECT \n" - + switchStatement - + "FROM tsk_files \n" - + (StringUtils.isNotBlank(baseWhereClauses) ? ("WHERE " + baseWhereClauses) : "") + ") res \n" - + "WHERE res.type_id >= 0 \n" - + "GROUP BY res.type_id"; - - Map<T, Long> typeCounts = new HashMap<>(); - try { - SleuthkitCase skCase = getCase(); - - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - int typeIdx = resultSet.getInt("type_id"); - T type = types.remove(typeIdx); - if (type != null) { - long count = resultSet.getLong("count"); - typeCounts.put(type, count); - } - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex); - } - }); - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching file counts with query:\n" + query, ex); - } - - if (includeZeroCount) { - for (T remaining : types.values()) { - typeCounts.put(remaining, 0L); - } - } - - return typeCounts; - } - - private SearchResultsDTO fetchDeletedSearchResultsDTOs(DeletedContentFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - String whereStatement = getDeletedContentWhereStatement(filter, dataSourceId); - return fetchFileViewFiles(whereStatement, filter.getDisplayName(), startItem, maxResultCount); - } - - private SearchResultsDTO fetchExtensionSearchResultsDTOs(FileExtSearchFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - String whereStatement = getFileExtensionWhereStatement(filter, dataSourceId); - return fetchFileViewFiles(whereStatement, filter.getDisplayName(), startItem, maxResultCount); - } - - @NbBundle.Messages({"FileTypesByMimeType.name.text=By MIME Type"}) - private SearchResultsDTO fetchMimeSearchResultsDTOs(String mimeType, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - String whereStatement = getFileMimeWhereStatement(mimeType, dataSourceId); - final String MIME_TYPE_DISPLAY_NAME = Bundle.FileTypesByMimeType_name_text(); - return fetchFileViewFiles(whereStatement, MIME_TYPE_DISPLAY_NAME, startItem, maxResultCount); - } - - private SearchResultsDTO fetchSizeSearchResultsDTOs(FileSizeFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - String whereStatement = getFileSizesWhereStatement(filter, dataSourceId); - return fetchFileViewFiles(whereStatement, filter.getDisplayName(), startItem, maxResultCount); - } - - private SearchResultsDTO fetchFileViewFiles(String originalWhereStatement, String displayName, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - - // Add offset and/or paging, if specified - String modifiedWhereStatement = originalWhereStatement - + " ORDER BY obj_id ASC" - + (maxResultCount != null && maxResultCount > 0 ? " LIMIT " + maxResultCount : "") - + (startItem > 0 ? " OFFSET " + startItem : ""); - - List<AbstractFile> files = getCase().findAllFilesWhere(modifiedWhereStatement); - - long totalResultsCount; - // get total number of results - if ((startItem == 0) // offset is zero AND - && ((maxResultCount != null && files.size() < maxResultCount) // number of results is less than max - || (maxResultCount == null))) { // OR max number of results was not specified - totalResultsCount = files.size(); - } else { - // do a query to get total number of results - totalResultsCount = getCase().countFilesWhere(originalWhereStatement); - } - - List<RowDTO> fileRows = new ArrayList<>(); - for (AbstractFile file : files) { - - List<Object> cellValues = FileSystemColumnUtils.getCellValuesForAbstractFile(file); - - fileRows.add(new FileRowDTO( - file, - file.getId(), - file.getName(), - file.getNameExtension(), - MediaTypeUtils.getExtensionMediaType(file.getNameExtension()), - file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.ALLOC), - file.getType(), - cellValues)); - } - - return new BaseSearchResultsDTO(FILE_VIEW_EXT_TYPE_ID, displayName, FileSystemColumnUtils.getColumnKeysForAbstractfile(), fileRows, AbstractFile.class.getName(), startItem, totalResultsCount); - } - - private Pair<String, String> getMimePieces(String mimeType) { - int idx = mimeType.indexOf("/"); - String mimePrefix = idx > 0 ? mimeType.substring(0, idx) : mimeType; - String mimeSuffix = idx > 0 ? mimeType.substring(idx + 1) : null; - return Pair.of(mimePrefix, mimeSuffix); - } - - private TreeItemDTO<?> createTreeItem(DAOEvent daoEvent, TreeDisplayCount count) { - if (daoEvent instanceof FileTypeExtensionsEvent) { - FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) daoEvent; - return createExtensionTreeItem(extEvt.getExtensionFilter(), extEvt.getDataSourceId(), count); - } else if (daoEvent instanceof FileTypeMimeEvent) { - FileTypeMimeEvent mimeEvt = (FileTypeMimeEvent) daoEvent; - Pair<String, String> mimePieces = getMimePieces(mimeEvt.getMimeType()); - String mimeName = mimePieces.getRight() == null ? mimePieces.getLeft() : mimePieces.getRight(); - return createMimeTreeItem(mimeEvt.getMimeType(), mimeName, mimeEvt.getDataSourceId(), count); - } else if (daoEvent instanceof FileTypeSizeEvent) { - FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) daoEvent; - return createSizeTreeItem(sizeEvt.getSizeFilter(), sizeEvt.getDataSourceId(), count); - } else if (daoEvent instanceof DeletedContentEvent) { - DeletedContentEvent sizeEvt = (DeletedContentEvent) daoEvent; - return createDeletedContentTreeItem(sizeEvt.getFilter(), sizeEvt.getDataSourceId(), count); - } else { - return null; - } - } - - @Override - void clearCaches() { - this.searchParamsCache.invalidateAll(); - this.exceededFileThreshold = false; - handleIngestComplete(); - } - - @Override - Set<? extends DAOEvent> handleIngestComplete() { - SubDAOUtils.invalidateKeys(this.searchParamsCache, - (searchParams) -> searchParamsMatchEvent(null, null, null, null, null, true, searchParams)); - - Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts, - (daoEvt, count) -> createTreeItem(daoEvt, count)); - - Set<? extends DAOEvent> fileViewRefreshEvents = getFileViewRefreshEvents(null); - - List<? extends DAOEvent> fileViewRefreshTreeEvents = fileViewRefreshEvents.stream() - .map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toList()); - - return Stream.of(treeEvts, fileViewRefreshEvents, fileViewRefreshTreeEvents) - .flatMap(c -> c.stream()) - .collect(Collectors.toSet()); - } - - @Override - Set<TreeEvent> shouldRefreshTree() { - return SubDAOUtils.getRefreshEvents(this.treeCounts, - (daoEvt, count) -> createTreeItem(daoEvt, count)); - } - - @Override - Set<DAOEvent> processEvent(PropertyChangeEvent evt) { - Long dsId = null; - boolean dataSourceAdded = false; - Set<FileExtSearchFilter> evtExtFilters = null; - Set<DeletedContentFilter> deletedContentFilters = null; - String evtMimeType = null; - FileSizeFilter evtFileSize = null; - - if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) { - dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null; - dataSourceAdded = true; - } else { - AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt); - if (af == null) { - return Collections.emptySet(); - } else if (hideKnownFilesInViewsTree() && TskData.FileKnown.KNOWN.equals(af.getKnown())) { - return Collections.emptySet(); - } - - dsId = af.getDataSourceObjectId(); - - // create an extension mapping if extension present - if (!StringUtils.isBlank(af.getNameExtension()) && TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType())) { - evtExtFilters = EXTENSION_FILTER_MAP.getOrDefault("." + af.getNameExtension(), Collections.emptySet()); - } - - deletedContentFilters = getMatchingDeletedContentFilters(af); - - // create a mime type mapping if mime type present - if (!StringUtils.isBlank(af.getMIMEType()) && TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType()) && getMimeDbFilesTypes().contains(af.getType())) { - evtMimeType = af.getMIMEType(); - } - - // create a size mapping if size present in filters - if (!TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.equals(af.getType())) { - evtFileSize = Stream.of(FileSizeFilter.values()) - .filter(filter -> af.getSize() >= filter.getMinBound() && (filter.getMaxBound() == null || af.getSize() < filter.getMaxBound())) - .findFirst() - .orElse(null); - } - - if (evtExtFilters == null || evtExtFilters.isEmpty() && deletedContentFilters.isEmpty() && evtMimeType == null && evtFileSize == null) { - return Collections.emptySet(); - } - } - - return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, deletedContentFilters, dsId, dataSourceAdded); - } - - /** - * Handles invalidating caches and returning events based on digest. - * - * @param evtExtFilters The file extension filters or empty set. - * @param evtMimeType The mime type or null. - * @param evtFileSize The file size filter or null. - * @param deletedContentFilters The set of affected deleted content filters. - * @param dsId The data source id or null. - * @param dataSourceAdded Whether or not this is a data source added - * event. - * - * @return The set of dao events to be fired. - */ - private Set<DAOEvent> invalidateAndReturnEvents(Set<FileExtSearchFilter> evtExtFilters, String evtMimeType, - FileSizeFilter evtFileSize, Set<DeletedContentFilter> deletedContentFilters, Long dsId, boolean dataSourceAdded) { - - SubDAOUtils.invalidateKeys(this.searchParamsCache, - (searchParams) -> searchParamsMatchEvent(evtExtFilters, deletedContentFilters, - evtMimeType, evtFileSize, dsId, dataSourceAdded, searchParams)); - - return getDAOEvents(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, dsId, dataSourceAdded); - } - - private boolean searchParamsMatchEvent(Set<FileExtSearchFilter> evtExtFilters, - Set<DeletedContentFilter> deletedContentFilters, - String evtMimeType, - FileSizeFilter evtFileSize, - Long dsId, - boolean dataSourceAdded, - Object searchParams) { - - if (searchParams instanceof FileTypeExtensionsSearchParams) { - FileTypeExtensionsSearchParams extParams = (FileTypeExtensionsSearchParams) searchParams; - // if data source added or evtExtFilters contain param filter - return (dataSourceAdded || (evtExtFilters != null && evtExtFilters.contains(extParams.getFilter()))) - // and data source is either null or they are equal data source ids - && (extParams.getDataSourceId() == null || dsId == null || Objects.equals(extParams.getDataSourceId(), dsId)); - - } else if (searchParams instanceof FileTypeMimeSearchParams) { - FileTypeMimeSearchParams mimeParams = (FileTypeMimeSearchParams) searchParams; - return evtMimeType != null && evtMimeType.startsWith(mimeParams.getMimeType()) - && (mimeParams.getDataSourceId() == null || Objects.equals(mimeParams.getDataSourceId(), dsId)); - - } else if (searchParams instanceof FileTypeSizeSearchParams) { - FileTypeSizeSearchParams sizeParams = (FileTypeSizeSearchParams) searchParams; - // if data source added or size filter is equal to param filter - return (dataSourceAdded || Objects.equals(sizeParams.getSizeFilter(), evtFileSize)) - // and data source is either null or they are equal data source ids - && (sizeParams.getDataSourceId() == null || dsId == null || Objects.equals(sizeParams.getDataSourceId(), dsId)); - } else if (searchParams instanceof DeletedContentSearchParams) { - DeletedContentSearchParams deletedParams = (DeletedContentSearchParams) searchParams; - return (dataSourceAdded || (deletedContentFilters != null && deletedContentFilters.contains(deletedParams.getFilter()))) - && (deletedParams.getDataSourceId() == null || dsId == null || Objects.equals(deletedParams.getDataSourceId(), dsId)); - } else { - return false; - } - } - - /** - * Clears relevant cache entries from cache based on digest of autopsy - * events. - * - * @param extFilters The set of affected extension filters. - * @param deletedContentFilters The set of affected deleted content filters. - * @param mimeType The affected mime type or null. - * @param sizeFilter The affected size filter or null. - * @param dsId The file object id. - * @param dataSourceAdded A data source was added. - * - * @return The list of affected dao events. - */ - private Set<DAOEvent> getDAOEvents(Set<FileExtSearchFilter> extFilters, - Set<DeletedContentFilter> deletedContentFilters, - String mimeType, - FileSizeFilter sizeFilter, - Long dsId, - boolean dataSourceAdded) { - - Stream<DAOEvent> extEvents = extFilters == null - ? Stream.empty() - : extFilters.stream() - .map(extFilter -> new FileTypeExtensionsEvent(extFilter, dsId)); - - Stream<DAOEvent> deletedEvents = deletedContentFilters == null - ? Stream.empty() - : deletedContentFilters.stream() - .map(deletedFilter -> new DeletedContentEvent(deletedFilter, dsId)); - - List<DAOEvent> daoEvents = Stream.concat(extEvents, deletedEvents) - .collect(Collectors.toList()); - - if (mimeType != null) { - daoEvents.add(new FileTypeMimeEvent(mimeType, dsId)); - } - - if (sizeFilter != null) { - daoEvents.add(new FileTypeSizeEvent(sizeFilter, dsId)); - } - - List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream() - .map(daoEvt -> new TreeEvent(createTreeItem(daoEvt, TreeDisplayCount.INDETERMINATE), false)) - .collect(Collectors.toList()); - - // data source added events are not necessarily fired before ingest completed/cancelled, so don't handle dataSourceAdded events with delay. - Set<DAOEvent> forceRefreshEvents = (dataSourceAdded) - ? getFileViewRefreshEvents(dsId) - : Collections.emptySet(); - - List<TreeEvent> forceRefreshTreeEvents = forceRefreshEvents.stream() - .map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true)) - .collect(Collectors.toList()); - - return Stream.of(daoEvents, treeEvents, forceRefreshEvents, forceRefreshTreeEvents) - .flatMap(lst -> lst.stream()) - .collect(Collectors.toSet()); - } - - private Set<DeletedContentFilter> getMatchingDeletedContentFilters(AbstractFile af) { - Set<DeletedContentFilter> toRet = new HashSet<>(); - - TSK_DB_FILES_TYPE_ENUM type = af.getType(); - - if (af.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC) - && !af.isMetaFlagSet(TSK_FS_META_FLAG_ENUM.ORPHAN) - && TSK_DB_FILES_TYPE_ENUM.FS.equals(type)) { - - toRet.add(DeletedContentFilter.FS_DELETED_FILTER); - } - - if ((((af.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC) || af.isMetaFlagSet(TSK_FS_META_FLAG_ENUM.ORPHAN)) && TSK_DB_FILES_TYPE_ENUM.FS.equals(type)) - || TSK_DB_FILES_TYPE_ENUM.CARVED.equals(type) - || (af.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC) && TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.equals(type)))) { - - toRet.add(DeletedContentFilter.ALL_DELETED_FILTER); - } - - return toRet; - } - - /** - * Returns events for when a full refresh is required because module content - * events will not necessarily provide events for files (i.e. data source - * added, ingest cancelled/completed). - * - * @param dataSourceId The data source id or null if not applicable. - * - * @return The set of events that apply in this situation. - */ - private Set<DAOEvent> getFileViewRefreshEvents(Long dataSourceId) { - return ImmutableSet.of( - new DeletedContentEvent(null, dataSourceId), - new FileTypeSizeEvent(null, dataSourceId), - new FileTypeExtensionsEvent(null, dataSourceId) - ); - } - - /** - * Handles fetching and paging of data for file types by extension. - */ - public static class FileTypeExtFetcher extends DAOFetcher<FileTypeExtensionsSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public FileTypeExtFetcher(FileTypeExtensionsSearchParams params) { - super(params); - } - - protected ViewsDAO getDAO() { - return MainDAO.getInstance().getViewsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getFilesByExtension(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isFilesByExtInvalidating(this.getParameters(), evt); - } - } - - /** - * Handles fetching and paging of data for file types by mime type. - */ - public static class FileTypeMimeFetcher extends DAOFetcher<FileTypeMimeSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public FileTypeMimeFetcher(FileTypeMimeSearchParams params) { - super(params); - } - - protected ViewsDAO getDAO() { - return MainDAO.getInstance().getViewsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getFilesByMime(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isFilesByMimeInvalidating(this.getParameters(), evt); - } - } - - /** - * Handles fetching and paging of data for file types by size. - */ - public class FileTypeSizeFetcher extends DAOFetcher<FileTypeSizeSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public FileTypeSizeFetcher(FileTypeSizeSearchParams params) { - super(params); - } - - protected ViewsDAO getDAO() { - return MainDAO.getInstance().getViewsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getFilesBySize(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isFilesBySizeInvalidating(this.getParameters(), evt); - } - } - - /** - * Handles fetching and paging of data for deleted content. - */ - public static class DeletedFileFetcher extends DAOFetcher<DeletedContentSearchParams> { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public DeletedFileFetcher(DeletedContentSearchParams params) { - super(params); - } - - protected ViewsDAO getDAO() { - return MainDAO.getInstance().getViewsDAO(); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException { - return getDAO().getDeletedContent(this.getParameters(), pageIdx * pageSize, (long) pageSize); - } - - @Override - public boolean isRefreshRequired(DAOEvent evt) { - return getDAO().isDeletedContentInvalidating(this.getParameters(), evt); - } - - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/AnalysisResultEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/AnalysisResultEvent.java deleted file mode 100644 index 8fafca4df7d345269a999a9b403ed8a630d94d24..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/AnalysisResultEvent.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * An event for an Analysis Result that is organized by Set names to - * signal that one has been added or removed on a given data source. - */ -public class AnalysisResultEvent extends BlackboardArtifactEvent { - private final String configuration; - - public AnalysisResultEvent(BlackboardArtifact.Type artifactType, String configuration, long dataSourceId) { - super(artifactType, dataSourceId); - this.configuration = configuration; - } - - public String getConfiguration() { - return configuration; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 53 * hash + Objects.hashCode(this.configuration); - hash = 53 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AnalysisResultEvent other = (AnalysisResultEvent) obj; - if (!Objects.equals(this.configuration, other.configuration)) { - return false; - } - return super.equals(obj); - } - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/BlackboardArtifactEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/BlackboardArtifactEvent.java deleted file mode 100644 index a2ecc73bf4e50685697d6ac7cb74b7a0ec8fd8be..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/BlackboardArtifactEvent.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * A base class for DataArtifact and AnalysisResult events to signal that one - * has been added or removed. - */ -public abstract class BlackboardArtifactEvent implements DAOEvent { - private final BlackboardArtifact.Type artifactType; - private final long dataSourceId; - - BlackboardArtifactEvent(BlackboardArtifact.Type artifactType, long dataSourceId) { - this.artifactType = artifactType; - this.dataSourceId = dataSourceId; - } - - public BlackboardArtifact.Type getArtifactType() { - return artifactType; - } - - public long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 17 * hash + Objects.hashCode(this.artifactType); - hash = 17 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32)); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final BlackboardArtifactEvent other = (BlackboardArtifactEvent) obj; - if (this.dataSourceId != other.dataSourceId) { - return false; - } - if (!Objects.equals(this.artifactType, other.artifactType)) { - return false; - } - return true; - } - - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CacheClearEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CacheClearEvent.java deleted file mode 100644 index 4fbb7122ee22c983ab2c1d107c7708ef450d0d9f..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CacheClearEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -/** - * Event fired when all dao caches cleared. - */ -public class CacheClearEvent { - - private final static CacheClearEvent instance = new CacheClearEvent(); - - /** - * @return Singleton instance of this class. - */ - public static CacheClearEvent getInstance() { - return instance; - } - - private final String eventType = "CACHE_CLEAR"; - - - private CacheClearEvent() {} - - /** - * @return The event type string. - */ - public String getEventType() { - return eventType; - } - - - - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CommAccountsEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CommAccountsEvent.java deleted file mode 100755 index 1da76f7f34dd9d61ee3b370429df90ddfea469e2..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CommAccountsEvent.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * An event for handling - */ -public class CommAccountsEvent extends DataArtifactEvent { - - private final Account.Type accountType; - - /** - * Main constructor. - * - * @param accountType The account type identifier. - * @param dataSourceId The data source id to filter on or null. - */ - public CommAccountsEvent(Account.Type accountType, Long dataSourceId) { - super(BlackboardArtifact.Type.TSK_ACCOUNT, dataSourceId); - this.accountType = accountType; - } - - /** - * @return The account type identifier. - */ - public Account.Type getAccountType() { - return accountType; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 29 * hash + Objects.hashCode(this.accountType); - hash = 29 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final CommAccountsEvent other = (CommAccountsEvent) obj; - if (!Objects.equals(this.accountType, other.accountType)) { - return false; - } - return super.equals(obj); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CreditCardEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CreditCardEvent.java deleted file mode 100644 index 2b380b9ab79edb7b1490bb7436a1990044b40e9a..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/CreditCardEvent.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Event for new email messages. - */ -public class CreditCardEvent extends DataArtifactEvent { - - private final String binPrefix; - private final boolean rejectedStatus; - - /** - * Main constructor. - * - * @param binPrefix The bin prefix of the credit card. - * @param includeRejected Whether or not to include rejected items in search - * results. - * @param dataSourceId The data source id or null for no data source - * filtering. - */ - public CreditCardEvent(String binPrefix, boolean rejectedStatus, long dataSourceId) { - super(BlackboardArtifact.Type.TSK_ACCOUNT, dataSourceId); - this.binPrefix = binPrefix; - this.rejectedStatus = rejectedStatus; - } - - public String getBinPrefix() { - return binPrefix; - } - - public boolean isRejectedStatus() { - return rejectedStatus; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 61 * hash + Objects.hashCode(this.binPrefix); - hash = 61 * hash + (this.rejectedStatus ? 1 : 0); - hash = 61 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final CreditCardEvent other = (CreditCardEvent) obj; - if (this.rejectedStatus != other.rejectedStatus) { - return false; - } - if (!Objects.equals(this.binPrefix, other.binPrefix)) { - return false; - } - return super.equals(obj); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOAggregateEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOAggregateEvent.java deleted file mode 100644 index d8930e37ca837a9744b0ca7c5bc204395e63ad0c..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOAggregateEvent.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Collections; -import java.util.Set; - -/** - * A single event containing an aggregate of all affected data. - */ -public class DAOAggregateEvent { - - private final Set<? extends DAOEvent> objects; - - /** - * Main constructor. - * - * @param objects The list of events in this aggregate event. - */ - public DAOAggregateEvent(Set<? extends DAOEvent> objects) { - this.objects = Collections.unmodifiableSet(objects); - } - - /** - * @return The events in this aggregate event. - */ - public Set<? extends DAOEvent> getEvents() { - return objects; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEvent.java deleted file mode 100644 index c4a1c3a3caf290eb6f47f0c7cffca64c5f57a978..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -/** - * An event emitted by the DAO. - */ -public interface DAOEvent { - public enum Type { TREE, RESULT } - - DAOEvent.Type getType(); -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEventBatcher.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEventBatcher.java deleted file mode 100644 index 33789cb0076a8bc3eb011394e378dd163c8d90c2..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEventBatcher.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import org.apache.commons.collections4.CollectionUtils; - -/** - * - * Handles refreshes in DAOs based on incoming events handling throttles - */ -public class DAOEventBatcher<T> { - - /** - * The Refresher interface needs to be implemented by ChildFactory instances - * that wish to take advantage of throttled refresh functionality. - */ - public interface BatchedEventsHandler<T> { - - /** - * Handles a list of aggregated events. - * - * @param events The events to handle. - */ - void handle(Set<T> events); - } - - private final ScheduledThreadPoolExecutor refreshExecutor - = new ScheduledThreadPoolExecutor(1, - new ThreadFactoryBuilder().setNameFormat(DAOEventBatcher.class.getName()).build()); - - private Set<T> aggregateEvents = new HashSet<>(); - private Object eventListLock = new Object(); - private boolean isRunning = false; - - private final BatchedEventsHandler<T> eventsHandler; - private final long batchMillis; - - public DAOEventBatcher(BatchedEventsHandler<T> eventsHandler, long batchMillis) { - this.eventsHandler = eventsHandler; - this.batchMillis = batchMillis; - } - - /** - * Queues an event to be fired as a part of a time-windowed batch. - * - * @param event The event. - */ - public void queueEvent(T event) { - synchronized (this.eventListLock) { - this.aggregateEvents.add(event); - verifyRunning(); - } - } - - /** - * Starts up throttled event runner if not currently running. - */ - private void verifyRunning() { - synchronized (this.eventListLock) { - if (!this.isRunning) { - refreshExecutor.schedule(() -> fireEvents(), this.batchMillis, TimeUnit.MILLISECONDS); - this.isRunning = true; - } - } - } - - /** - * Queues an event to be fired as a part of a time-windowed batch. - * - * @param events The events. - */ - public void enqueueAllEvents(Collection<T> events) { - if (CollectionUtils.isNotEmpty(events)) { - synchronized (this.eventListLock) { - this.aggregateEvents.addAll(events); - verifyRunning(); - } - } - } - - /** - * Flushes any currently batched events emptying queue of batched events. - * - * @return The flushed events. - */ - public Set<T> flushEvents() { - synchronized (this.eventListLock) { - Set<T> evtsToFire = this.aggregateEvents; - this.aggregateEvents = new HashSet<>(); - this.isRunning = false; - return evtsToFire; - } - } - - /** - * Fires all events and clears batch. - */ - private void fireEvents() { - this.eventsHandler.handle(flushEvents()); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEventUtils.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEventUtils.java deleted file mode 100644 index d6789f7b368d26997d35d7505d4aa487833a37cc..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DAOEventUtils.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.beans.PropertyChangeEvent; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.ModuleContentEvent; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; - -/** - * - * Utilities for handling events in DAO - */ -public class DAOEventUtils { - - /** - * Returns the file content from the event. If the event is not a file event - * or the event does not contain file content, null is returned. - * - * @param evt The event - * - * @return The inner content or null if no content. - */ - public static Content getContentFromFileEvent(PropertyChangeEvent evt) { - String eventName = evt.getPropertyName(); - Content derivedContent = getDerivedFileContentFromFileEvent(evt); - if (derivedContent != null) { - return derivedContent; - } else if (IngestManager.IngestModuleEvent.FILE_DONE.toString().equals(eventName) - && (evt.getNewValue() instanceof Content)) { - return (Content) evt.getNewValue(); - } else { - return null; - } - } - - /** - * Returns the content from the ModuleContentEvent. If the event does not - * contain a event or the event does not contain Content, null is returned. - * - * @param evt The event - * - * @return The inner content or null if no content. - */ - public static Content getDerivedFileContentFromFileEvent(PropertyChangeEvent evt) { - String eventName = evt.getPropertyName(); - if (IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString().equals(eventName) - && (evt.getOldValue() instanceof ModuleContentEvent) - && ((ModuleContentEvent) evt.getOldValue()).getSource() instanceof Content) { - - return (Content) ((ModuleContentEvent) evt.getOldValue()).getSource(); - - } else { - return null; - } - } - - /** - * Returns a file in the event if a file is found in the event. - * - * @param evt The autopsy event. - * - * @return The inner file or null if no file found. - */ - public static AbstractFile getFileFromFileEvent(PropertyChangeEvent evt) { - Content content = getContentFromFileEvent(evt); - return (content instanceof AbstractFile) - ? ((AbstractFile) content) - : null; - } - - /** - * Returns the ModuleDataEvent in the event if there is a child - * ModuleDataEvent. If not, null is returned. - * - * @param evt The event. - * - * @return The inner ModuleDataEvent or null. - */ - public static ModuleDataEvent getModuelDataFromArtifactEvent(PropertyChangeEvent evt) { - String eventName = evt.getPropertyName(); - if (IngestManager.IngestModuleEvent.DATA_ADDED.toString().equals(eventName) - && (evt.getOldValue() instanceof ModuleDataEvent)) { - - return (ModuleDataEvent) evt.getOldValue(); - } else { - return null; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DataArtifactEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DataArtifactEvent.java deleted file mode 100644 index 3cbd809414bc0f1cb75779a25146fd42e137c713..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DataArtifactEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * An event for an artifact added or changed of a particular type possibly for a - * particular data source. - */ -public class DataArtifactEvent extends BlackboardArtifactEvent { - - public DataArtifactEvent(BlackboardArtifact.Type artifactType, long dataSourceId) { - super(artifactType, dataSourceId); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DeletedContentEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DeletedContentEvent.java deleted file mode 100755 index 4ecf4d477eb9f58f6cf60a6ed02607fba6a608e1..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/DeletedContentEvent.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentFilter; - -/** - * An event to signal that deleted files have been added to the given case on - * the given data source. - */ -public class DeletedContentEvent implements DAOEvent { - - private final DeletedContentFilter filter; - private final Long dataSourceId; - - public DeletedContentEvent(DeletedContentFilter filter, Long dataSourceId) { - this.filter = filter; - this.dataSourceId = dataSourceId; - } - - public DeletedContentFilter getFilter() { - return filter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 41 * hash + Objects.hashCode(this.filter); - hash = 41 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final DeletedContentEvent other = (DeletedContentEvent) obj; - if (this.filter != other.filter) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/EmailEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/EmailEvent.java deleted file mode 100644 index 0a10913d1bf91667eacae37fffd3eea1babbbf89..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/EmailEvent.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Event for new email messages. - */ -public class EmailEvent extends DataArtifactEvent { - - private final String folder; - - /** - * Main constructor. - * - * @param dataSourceId The data source id that the email message belongs to. - * @param account The email message account. - * @param folder The folder within that account of the email message. - */ - public EmailEvent(long dataSourceId, String folder) { - super(BlackboardArtifact.Type.TSK_EMAIL_MSG, dataSourceId); - this.folder = folder; - } - - public String getFolder() { - return folder; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 89 * hash + Objects.hashCode(this.folder); - hash = 89 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final EmailEvent other = (EmailEvent) obj; - if (!Objects.equals(this.folder, other.folder)) { - return false; - } - return super.equals(obj); - } - - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemContentEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemContentEvent.java deleted file mode 100644 index 8efc3d5ef973f51156ee8058d8b66feaf1127092..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemContentEvent.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Host; - -/** - * An event signaling that children files were added or removed from the given - * parent ID. - */ -public class FileSystemContentEvent implements DAOEvent { - - private final Content content; - private final Long parentObjId; - private final Host parentHost; - - public FileSystemContentEvent(Content content, Long parentObjId, Host parentHost) { - this.content = content; - this.parentObjId = parentObjId; - this.parentHost = parentHost; - } - - public Long getParentObjId() { - return parentObjId; - } - - public Host getParentHost() { - return parentHost; - } - - /** - * @return The content associated with the event, if null, triggers a full - * refresh. - */ - public Content getContent() { - return content; - } - - public Long getContentObjectId() { - return (content == null) ? null : content.getId(); - } - - @Override - public int hashCode() { - int hash = 7; - hash = 71 * hash + Objects.hashCode(this.content); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileSystemContentEvent other = (FileSystemContentEvent) obj; - if (!Objects.equals(this.content, other.content)) { - return false; - } - return true; - } - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemHostEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemHostEvent.java deleted file mode 100644 index e953baf5d7553e5709d356244a81238c1d790b7e..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemHostEvent.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Host; - -/** - * An event signaling that a data source has been added or removed from the - * given Host. - */ -public class FileSystemHostEvent implements DAOEvent { - - private final Host host; - private final Content dataSource; - - public FileSystemHostEvent(Host host, Content dataSource) { - this.host = host; - this.dataSource = dataSource; - } - - public Host getHost() { - return host; - } - - public Content getDataSource() { - return dataSource; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 59 * hash + Objects.hashCode(this.host); - hash = 59 * hash + Objects.hashCode(this.dataSource); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileSystemHostEvent other = (FileSystemHostEvent) obj; - if (!Objects.equals(this.host, other.host)) { - return false; - } - if (!Objects.equals(this.dataSource, other.dataSource)) { - return false; - } - return true; - } - - - - @Override - public DAOEvent.Type getType() { - return DAOEvent.Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemPersonEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemPersonEvent.java deleted file mode 100644 index 110429a6d91368096985e62d0fde75ca16ef653d..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileSystemPersonEvent.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; - -/** - * An event signaling that a host has been added or removed from the given - * Person. - */ -public class FileSystemPersonEvent implements DAOEvent { - - private final Long personObjectId; - - /** - * Main constructor. - * - * @param personObjectId May be null for hosts with no associated Person. - */ - public FileSystemPersonEvent(Long personObjectId) { - this.personObjectId = personObjectId; - } - - public Long getPersonObjectId() { - return personObjectId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.personObjectId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileSystemPersonEvent other = (FileSystemPersonEvent) obj; - if (!Objects.equals(this.personObjectId, other.personObjectId)) { - return false; - } - return true; - } - - @Override - public DAOEvent.Type getType() { - return DAOEvent.Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeExtensionsEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeExtensionsEvent.java deleted file mode 100644 index 354cb250103cc4732e042de7b4e1c8fab8ec6ce3..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeExtensionsEvent.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.autopsy.mainui.datamodel.FileExtSearchFilter; - -/** - * An event to signal that files have been added or removed with the given - * extension on the given data source. - */ -public class FileTypeExtensionsEvent implements DAOEvent { - - private final FileExtSearchFilter extensionFilter; - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param extensionFilter The extension filter. If null, indicates full - * refresh necessary. - * @param dataSourceId The data source id. - */ - public FileTypeExtensionsEvent(FileExtSearchFilter extensionFilter, Long dataSourceId) { - this.extensionFilter = extensionFilter; - this.dataSourceId = dataSourceId; - } - - public FileExtSearchFilter getExtensionFilter() { - return extensionFilter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 89 * hash + Objects.hashCode(this.extensionFilter); - hash = 89 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileTypeExtensionsEvent other = (FileTypeExtensionsEvent) obj; - if (!Objects.equals(this.extensionFilter, other.extensionFilter)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeMimeEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeMimeEvent.java deleted file mode 100755 index 96b884a43253a878ba4c73c81d75cac45f9f353f..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeMimeEvent.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; - -/** - * An event pertaining to MIME types view from the DAO. - */ -public class FileTypeMimeEvent implements DAOEvent { - - private final String mimeType; - private final long dataSourceId; - - public FileTypeMimeEvent(String mimeType, long dataSourceId) { - this.mimeType = mimeType; - this.dataSourceId = dataSourceId; - } - - public String getMimeType() { - return mimeType; - } - - public long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 29 * hash + Objects.hashCode(this.mimeType); - hash = 29 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileTypeMimeEvent other = (FileTypeMimeEvent) obj; - if (!Objects.equals(this.mimeType, other.mimeType)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeSizeEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeSizeEvent.java deleted file mode 100755 index 1fe425613099072704a295413654803f889ab328..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/FileTypeSizeEvent.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.autopsy.mainui.datamodel.FileSizeFilter; - -/** - * An event to signal that files have been added or removed within the given - * size range on the given data source. - */ -public class FileTypeSizeEvent implements DAOEvent { - - private final FileSizeFilter sizeFilter; - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param sizeFilter The size filter. If null, indicates full refresh is - * necessary. - * @param dataSourceId The data source id or null. - */ - public FileTypeSizeEvent(FileSizeFilter sizeFilter, Long dataSourceId) { - this.sizeFilter = sizeFilter; - this.dataSourceId = dataSourceId; - } - - public FileSizeFilter getSizeFilter() { - return sizeFilter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 73 * hash + Objects.hashCode(this.sizeFilter); - hash = 73 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FileTypeSizeEvent other = (FileTypeSizeEvent) obj; - if (this.sizeFilter != other.sizeFilter) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/HostPersonEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/HostPersonEvent.java deleted file mode 100644 index afa011c138b10c01821c1a576a6731a3607a6db8..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/HostPersonEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -/** - * An event that hosts or persons were changed. - */ -public class HostPersonEvent implements DAOEvent { - - @Override - public Type getType() { - return Type.TREE; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/KeywordHitEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/KeywordHitEvent.java deleted file mode 100644 index e2d419a87e28d8d724a92ce8f5fd1ee1cac9ed85..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/KeywordHitEvent.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.TskData; - -/** - * An event for an artifact added or changed of a particular type possibly for a - * particular data source. - */ -public class KeywordHitEvent extends AnalysisResultEvent { - - private final String searchString; - private final String match; - private final TskData.KeywordSearchQueryType searchType; - private final String setName; - private final String configuration; - - /** - * Main constructor. - * - * @param setName The set name. - * @param searchString The search string or regex. - * @param searchType THe search type. - * @param match The match string. - * @param configuration The configuration of the analysis result. - * @param dataSourceId The data source id. - */ - public KeywordHitEvent(String setName, String searchString, TskData.KeywordSearchQueryType searchType, String match, String configuration, long dataSourceId) { - super(BlackboardArtifact.Type.TSK_KEYWORD_HIT, configuration, dataSourceId); - this.setName = setName; - this.searchString = searchString; - this.match = match; - this.searchType = searchType; - this.configuration = configuration; - } - - public String getConfiguration() { - return configuration; - } - - public String getSetName() { - return setName; - } - - public String getSearchString() { - return searchString; - } - - public String getMatch() { - return match; - } - - public TskData.KeywordSearchQueryType getSearchType() { - return searchType; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.searchString); - hash = 67 * hash + Objects.hashCode(this.match); - hash = 67 * hash + Objects.hashCode(this.searchType); - hash = 67 * hash + super.hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final KeywordHitEvent other = (KeywordHitEvent) obj; - if (!Objects.equals(this.searchString, other.searchString)) { - return false; - } - if (!Objects.equals(this.match, other.match)) { - return false; - } - if (this.searchType != other.searchType) { - return false; - } - return super.equals(obj); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/ReportsEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/ReportsEvent.java deleted file mode 100644 index e43bdc331243aa05a8fbe382cba9d593858fabb9..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/ReportsEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -/** - * An event that Reports were changed. - */ -public class ReportsEvent implements DAOEvent { - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/ScoreContentEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/ScoreContentEvent.java deleted file mode 100755 index 0077186630303e0583ccb8636718dc1a69d982e9..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/ScoreContentEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewFilter; - -/** - * An event to signal that deleted files have been added to the given case on - * the given data source. - */ -public class ScoreContentEvent implements DAOEvent { - - private final ScoreViewFilter filter; - private final Long dataSourceId; - - public ScoreContentEvent(ScoreViewFilter filter, Long dataSourceId) { - this.filter = filter; - this.dataSourceId = dataSourceId; - } - - public ScoreViewFilter getFilter() { - return filter; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TagsEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TagsEvent.java deleted file mode 100755 index 802303b7441f2f3d7d6642ef6de8b048364878cd..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TagsEvent.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType; -import org.sleuthkit.datamodel.TagName; - -/** - * An event to signal that tags have been added or removed on the - * given data source with the given types. - */ -public class TagsEvent implements DAOEvent { - - private final TagType type; - private final TagName tagName; - private final Long dataSourceId; - - public TagsEvent(TagType type, TagName tagName, Long dataSourceId) { - this.type = type; - this.tagName = tagName; - this.dataSourceId = dataSourceId; - } - - - public TagType getTagType() { - return type; - } - - public TagName getTagName() { - return tagName; - } - - /** - * @return The data source object id for the tag. Is null if cannot be - * determined. - */ - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 97 * hash + Objects.hashCode(this.type); - hash = 97 * hash + Objects.hashCode(this.tagName); - hash = 97 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final TagsEvent other = (TagsEvent) obj; - if (this.type != other.type) { - return false; - } - if (!Objects.equals(this.tagName, other.tagName)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - - - @Override - public Type getType() { - return Type.RESULT; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TreeCounts.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TreeCounts.java deleted file mode 100644 index 5a04e03bc9240e846400e1c334c20d2500db73af..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TreeCounts.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * This class is in charge of tracking tree events. When an autopsy event comes - * in that affects a tree node, the sub DAO's enqueue the event in this class - * along with the timeout (current time + timeoutMillis). If another autopsy - * event comes in affecting the same tree category, the timeout is reset. Events - * are not removed from tracking until getEventTimeouts is flushEvents are - * called. The MainDAO has a periodically running task to see if any tree events - * have timed out, and broadcasts those events that have reached timeout. - */ -public class TreeCounts<T> { - - private static final long DEFAULT_TIMEOUT_MILLIS = 2 * 60 * 1000; - - private final Object timeoutLock = new Object(); - private final Map<T, Long> eventTimeouts = new HashMap<>(); - - private final long timeoutMillis; - - /** - * Constructor that uses default timeout duration. - */ - public TreeCounts() { - this(DEFAULT_TIMEOUT_MILLIS); - } - - /** - * Main constructor. - * - * @param timeoutMillis How long to track an event before it reaches a - * timeout (in milliseconds). - */ - public TreeCounts(long timeoutMillis) { - this.timeoutMillis = timeoutMillis; - } - - /** - * Returns the current time in milliseconds. - * - * @return The current time in milliseconds. - */ - private long getCurTime() { - return System.currentTimeMillis(); - } - - /** - * Returns the timeout time based on the current time. - * - * @return The current time. - */ - private long getTimeoutTime() { - return getCurTime() + timeoutMillis; - } - - /** - * Adds events to be tracked until they reach timeout. - * - * @param events The events to be tracked. - * - * @return The subset of events that were not already being tracked. - */ - public Collection<T> enqueueAll(Collection<T> events) { - Collection<T> updateToIndeterminate = new ArrayList<>(); - - synchronized (this.timeoutLock) { - for (T event : events) { - this.eventTimeouts.compute(event, (k, v) -> { - if (v == null) { - updateToIndeterminate.add(event); - } - return getTimeoutTime(); - }); - } - } - - return updateToIndeterminate; - } - - /** - * Returns the set of events that are currently being tracked for timeout. - * - * @return The events that are being tracked for timeout. - */ - public Set<T> getEnqueued() { - synchronized (this.timeoutLock) { - return new HashSet<>(eventTimeouts.keySet()); - } - } - - /** - * Returns the events that have reached timeout based on the current time - * stamp and removes them from tracking. - * - * @return The - */ - public Collection<T> getEventTimeouts() { - long curTime = getCurTime(); - List<T> toUpdate; - synchronized (this.timeoutLock) { - toUpdate = this.eventTimeouts.entrySet().stream() - .filter(e -> e.getValue() < curTime) - .map(e -> e.getKey()) - .collect(Collectors.toList()); - - this.eventTimeouts.keySet().removeAll(toUpdate); - } - return toUpdate; - } - - /** - * Returns all currently tracked events despite timeout. This method removes - * all events from tracking. - * - * @return All currently tracked events. - */ - public Collection<T> flushEvents() { - synchronized (this.timeoutLock) { - List<T> toRet = new ArrayList<>(eventTimeouts.keySet()); - eventTimeouts.clear(); - return toRet; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TreeEvent.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TreeEvent.java deleted file mode 100644 index 4fadbbc6724ff4f1727bd1f938a8d8d5eb0d536c..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/events/TreeEvent.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel.events; - -import java.util.Objects; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; - -/** - * An event to signal that an item in the tree has been - * added or changed. - */ -public class TreeEvent implements DAOEvent { - - private final TreeItemDTO<?> itemRecord; // the updated item - private final boolean refreshRequired; // true if tree should request new data from DAO - - /** - * @param itemRecord The updated item - * @param rereshRequired True if the tree should go to the DAO for updated data - */ - public TreeEvent(TreeItemDTO<?> itemRecord, boolean refreshRequired) { - this.itemRecord = itemRecord; - this.refreshRequired = refreshRequired; - } - - public TreeItemDTO<?> getItemRecord() { - return itemRecord; - } - - public boolean isRefreshRequired() { - return refreshRequired; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 89 * hash + Objects.hashCode(this.itemRecord); - hash = 89 * hash + (this.refreshRequired ? 1 : 0); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final TreeEvent other = (TreeEvent) obj; - if (this.refreshRequired != other.refreshRequired) { - return false; - } - if (!Objects.equals(this.itemRecord, other.itemRecord)) { - return false; - } - return true; - } - - - - @Override - public Type getType() { - return Type.TREE; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AbstractAnalysisResultTreeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AbstractAnalysisResultTreeFactory.java deleted file mode 100644 index 5424745003b39afaf5f8c2a6a8b80cea2e8c93ac..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AbstractAnalysisResultTreeFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DeleteAnalysisResultEvent; - -/** - * An abstract analysis result factory that handles 'DeleteAnalysisResultEvent' - * events and updates the tree accordingly. - */ -public abstract class AbstractAnalysisResultTreeFactory<T> extends TreeChildFactory<T> { - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof DeleteAnalysisResultEvent && evt.getType() == DAOEvent.Type.TREE) { - super.update(); - return; - } - } - - super.handleDAOAggregateEvent(aggEvt); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultNode.java deleted file mode 100755 index a63f26e05ac59f2e7b70e140c7d915a61c43a5ff..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultNode.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.logging.Level; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.AnalysisResultItem; -import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; -import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultTableSearchResultsDTO; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory.ActionGroup; -import org.sleuthkit.autopsy.mainui.nodes.actions.DeleteAnalysisResultAction; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.AnalysisResult; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Node to display AnalysResult. - */ -public class AnalysisResultNode extends ArtifactNode<AnalysisResult, AnalysisResultRowDTO> { - - private static final Logger logger = Logger.getLogger(AnalysisResultNode.class.getName()); - - /** - * Construct a new node for the given table and row DTO objects. - * - * @param tableData The table search result DTO. - * @param resultRow The row DTO. - */ - AnalysisResultNode(AnalysisResultTableSearchResultsDTO tableData, AnalysisResultRowDTO resultRow, ExecutorService backgroundTasksPool) { - this(tableData, resultRow, IconsUtil.getIconFilePath(tableData.getArtifactType().getTypeID()), backgroundTasksPool); - } - - /** - * Construct a new node for the given table and row DTO objects. - * - * @param tableData The table search result DTO. - * @param resultRow The row DTO. - * @param iconPath The path for the node icon. - */ - AnalysisResultNode(AnalysisResultTableSearchResultsDTO tableData, AnalysisResultRowDTO resultRow, String iconPath, ExecutorService backgroundTasksPool) { - super(tableData, resultRow, tableData.getColumns(), createLookup(resultRow), iconPath, backgroundTasksPool); - } - - /** - * Create the lookup for the AnalysisResultNode. - * - * @param row The RowDTO data. - * - * @return The lookup for the node. - */ - private static Lookup createLookup(AnalysisResultRowDTO row) { - AnalysisResultItem resultItem = new AnalysisResultItem(row.getAnalysisResult(), row.getSrcContent()); - if (row.getSrcContent() == null) { - return Lookups.fixed(row.getAnalysisResult(), resultItem); - } - - return Lookups.fixed(row.getAnalysisResult(), resultItem, row.getSrcContent()); - } - - @Override - public boolean supportsContentTagAction() { - return getSourceContent().isPresent() && getSourceContent().get() instanceof AbstractFile; - } - - @Override - public Optional<AbstractFile> getExtractArchiveWithPasswordActionFile() { - Optional<Content> optionalSourceContent = getSourceContent(); - // TODO: See JIRA-8099 - boolean encryptionDetected = false; - if (optionalSourceContent.isPresent()) { - if (optionalSourceContent.get() instanceof AbstractFile) { - AbstractFile file = (AbstractFile) optionalSourceContent.get(); - boolean isArchive = FileTypeExtensions.getArchiveExtensions().contains("." + file.getNameExtension().toLowerCase()); - try { - encryptionDetected = isArchive && file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED).size() > 0; - } catch (TskCoreException ex) { - // TODO - } - if (encryptionDetected) { - return Optional.of(file); - } - } - } - return Optional.empty(); - } - - @Override - public Optional<List<Tag>> getAllTagsFromDatabase() { - List<Tag> tags = new ArrayList<>(); - try { - List<BlackboardArtifactTag> artifactTags = ContentNodeUtil.getArtifactTagsFromDatabase(getRowDTO().getArtifact()); - if (!artifactTags.isEmpty()) { - tags.addAll(artifactTags); - } - - List<ContentTag> contentTags = ContentNodeUtil.getContentTagsFromDatabase(getRowDTO().getSrcContent()); - if (!contentTags.isEmpty()) { - tags.addAll(contentTags); - } - - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to get content tags from database for Artifact id=" + getRowDTO().getArtifact().getId(), ex); - } - if (!tags.isEmpty()) { - return Optional.of(tags); - } - return Optional.empty(); - } - - @Override - public Optional<ActionGroup> getNodeSpecificActions() { - ActionGroup group = new ActionGroup(); - - group.add(new DeleteAnalysisResultAction()); - - return Optional.of(group); - } - - @Override - protected boolean shouldUpdateSCOColumns(long eventObjId) { - try { - return eventObjId == getRowDTO().getArtifact().getParent().getId(); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to update comment icon, failed to get parent for artifact id = " + getRowDTO().getArtifact().getId(), ex); - } - return false; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java deleted file mode 100644 index c48a4aef2fffa2be0077aa0a26cdd914be3a8785..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java +++ /dev/null @@ -1,783 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.Collections; -import org.sleuthkit.autopsy.mainui.datamodel.KeywordSearchTermParams; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO.AnalysisResultTreeItem; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.KeywordListSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.KeywordHitSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DeleteAnalysisResultEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import static org.sleuthkit.autopsy.mainui.nodes.TreeNode.getDefaultLookup; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; -import org.sleuthkit.autopsy.mainui.nodes.actions.DeleteAnalysisResultSetAction; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.TskData; - -/** - * Factory for displaying analysis result types in the tree. - */ -public class AnalysisResultTypeFactory extends AbstractAnalysisResultTreeFactory<AnalysisResultSearchParam> { - - private final static Comparator<String> STRING_COMPARATOR = Comparator.nullsFirst(Comparator.naturalOrder()); - - /** - * Returns the path to the icon to use for this artifact type. - * - * @param artType The artifact type. - * - * @return The path to the icon to use for this artifact type. - */ - private static String getIconPath(BlackboardArtifact.Type artType) { - String iconPath = IconsUtil.getIconFilePath(artType.getTypeID()); - return iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath; - } - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source id to filter on or null if no filter. - */ - public AnalysisResultTypeFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeResultsDTO<? extends AnalysisResultSearchParam> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getAnalysisResultCounts(dataSourceId); - } - - @Messages({"AnalysisResultTypeFactory_blankConfigName=(No Configuration)"}) - @Override - protected TreeNode<AnalysisResultSearchParam> createNewNode(TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> rowData) { - if (BlackboardArtifact.Type.TSK_KEYWORD_HIT.equals(rowData.getSearchParams().getArtifactType())) { - return new TreeTypeNode(rowData, new KeywordSetFactory(dataSourceId)); - } else if (rowData instanceof AnalysisResultTreeItem && ((AnalysisResultTreeItem) rowData).getHasChildren().orElse(false)) { - return new TreeTypeNode(rowData, new TreeConfigFactory(rowData.getSearchParams().getArtifactType(), dataSourceId, Bundle.AnalysisResultTypeFactory_blankConfigName())); - } else { - return new AnalysisResultTypeTreeNode(rowData); - } - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) { - - TreeResultsDTO.TreeItemDTO<AnalysisResultSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, AnalysisResultSearchParam.class); - - if (originalTreeItem != null - && !AnalysisResultDAO.getIgnoredTreeTypes().contains(originalTreeItem.getSearchParams().getArtifactType()) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - Boolean hasChildren = null; - if (originalTreeItem instanceof AnalysisResultTreeItem) { - hasChildren = ((AnalysisResultTreeItem) originalTreeItem).getHasChildren().orElse(null); - } else if (StringUtils.isNotBlank(originalTreeItem.getSearchParams().getConfiguration())) { - String setName = originalTreeItem.getSearchParams().getConfiguration(); - hasChildren = StringUtils.isNotBlank(setName); - } - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - AnalysisResultSearchParam searchParam = originalTreeItem.getSearchParams(); - return new AnalysisResultTreeItem(searchParam.getArtifactType(), searchParam.getConfiguration(), - this.dataSourceId, originalTreeItem.getDisplayCount(), hasChildren); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends AnalysisResultSearchParam> o1, TreeItemDTO<? extends AnalysisResultSearchParam> o2) { - return o1.getSearchParams().getArtifactType().getDisplayName().compareTo(o2.getSearchParams().getArtifactType().getDisplayName()); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof DeleteAnalysisResultEvent && evt.getType() == DAOEvent.Type.TREE) { - super.update(); - return; - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - /** - * Display name and count of an analysis result type in the tree. - */ - static class AnalysisResultTypeTreeNode extends TreeNode<AnalysisResultSearchParam> { - - /** - * Main constructor. - * - * @param itemData The data to display. - */ - AnalysisResultTypeTreeNode(TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> itemData) { - super(itemData.getSearchParams().getArtifactType().getTypeName(), - getIconPath(itemData.getSearchParams().getArtifactType()), - itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - dataResultPanel.displayAnalysisResult(this.getItemData().getSearchParams()); - } - - @Override - public Optional<BlackboardArtifact.Type> getAnalysisResultType() { - return Optional.ofNullable(this.getItemData().getSearchParams().getArtifactType()); - } - - @Override - public Optional<Long> getDataSourceIdForActions() { - return Optional.ofNullable(this.getItemData().getSearchParams().getDataSourceId()); - } - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionsFactory.ActionGroup group = new ActionsFactory.ActionGroup(); - - Optional<BlackboardArtifact.Type> type = getAnalysisResultType(); - Optional<Long> dsId = getDataSourceIdForActions(); - if (type.isPresent()) { - group.add(new DeleteAnalysisResultSetAction(type.get(), () -> Collections.emptyList(), dsId.isPresent() ? dsId.get() : null)); - } - - return Optional.of(group); - } - - } - - /** - * An analysis result type node that has nested children. - */ - static class TreeTypeNode extends TreeNode<AnalysisResultSearchParam> { - - /** - * Main constructor. - * - * @param itemData The data to display. - */ - TreeTypeNode(TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> itemData, ChildFactory<?> childFactory) { - super(itemData.getSearchParams().getArtifactType().getTypeName(), - getIconPath(itemData.getSearchParams().getArtifactType()), - itemData, - Children.create(childFactory, true), - getDefaultLookup(itemData)); - } - - @Override - public Optional<Long> getDataSourceIdForActions() { - return Optional.ofNullable(this.getItemData().getSearchParams().getDataSourceId()); - } - - @Override - public Optional<BlackboardArtifact.Type> getAnalysisResultType() { - return Optional.ofNullable(this.getItemData().getSearchParams().getArtifactType()); - } - } - - /** - * Factory displaying all analysis result configurations with count in the - * tree. - */ - static class TreeConfigFactory extends AbstractAnalysisResultTreeFactory<AnalysisResultSearchParam> { - - private final BlackboardArtifact.Type artifactType; - private final Long dataSourceId; - private final String nullSetName; - - /** - * Main constructor. - * - * @param artifactType The type of artifact. - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * @param nullSetName The name of the set for artifacts with no - * configuration value. If null, items are omitted. - */ - TreeConfigFactory(BlackboardArtifact.Type artifactType, Long dataSourceId, String nullSetName) { - this.artifactType = artifactType; - this.dataSourceId = dataSourceId; - this.nullSetName = nullSetName; - } - - protected BlackboardArtifact.Type getArtifactType() { - return artifactType; - } - - protected Long getDataSourceId() { - return dataSourceId; - } - - protected String getNullSetName() { - return nullSetName; - } - - @Override - protected TreeResultsDTO<? extends AnalysisResultSearchParam> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getConfigurationCounts(this.artifactType, this.dataSourceId, this.nullSetName); - } - - @Override - protected TreeNode<AnalysisResultSearchParam> createNewNode(TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> rowData) { - return new TreeConfigTypeNode(rowData); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<AnalysisResultSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, AnalysisResultSearchParam.class); - - if (originalTreeItem != null - && originalTreeItem.getSearchParams().getArtifactType().equals(this.artifactType) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - AnalysisResultSearchParam searchParam = originalTreeItem.getSearchParams(); - return new TreeResultsDTO.TreeItemDTO<>( - AnalysisResultSearchParam.getTypeId(), - new AnalysisResultSearchParam(this.artifactType, searchParam.getConfiguration(), this.dataSourceId), - searchParam.getConfiguration() == null ? 0 : searchParam.getConfiguration(), - searchParam.getConfiguration() == null ? nullSetName : searchParam.getConfiguration(), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends AnalysisResultSearchParam> o1, TreeItemDTO<? extends AnalysisResultSearchParam> o2) { - return STRING_COMPARATOR.compare(o1.getSearchParams().getConfiguration(), o2.getSearchParams().getConfiguration()); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof DeleteAnalysisResultEvent && evt.getType() == DAOEvent.Type.TREE) { - super.update(); - return; - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - } - - /** - * A node for a set within an artifact type. - */ - public static class TreeConfigTypeNode extends TreeNode<AnalysisResultSearchParam> { - - /** - * Returns the node name to be used given the artifact type name and configuration. - * @param artifactTypeName The artifact type name (non-null). - * @param configuration The configuration name (can be null). - * @return The node name. - */ - public static String getNodeName(String artifactTypeName, String configuration) { - return configuration == null ? - artifactTypeName + "_NULL_CONFIG" : - artifactTypeName + "_CONFIG_" + configuration; - } - /** - * Main constructor. - * - * @param itemData The data to display. - */ - TreeConfigTypeNode(TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> itemData) { - super(getNodeName(itemData.getSearchParams().getArtifactType().getTypeName(), itemData.getSearchParams().getConfiguration()), - getIconPath(itemData.getSearchParams().getArtifactType()), - itemData, - Children.LEAF, - getDefaultLookup(itemData)); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - dataResultPanel.displayAnalysisResultConfig(this.getItemData().getSearchParams()); - } - - @Override - public Optional<Long> getDataSourceIdForActions() { - return Optional.ofNullable(this.getItemData().getSearchParams().getDataSourceId()); - } - - @Override - public Optional<BlackboardArtifact.Type> getAnalysisResultType() { - return Optional.ofNullable(this.getItemData().getSearchParams().getArtifactType()); - } - - @Override - public boolean hasAnalysisResultConfigurations() { - return true; - } - - @Override - public List<String> getAnalysisResultConfigurations() { - return Collections.singletonList(this.getItemData().getSearchParams().getConfiguration()); - } - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionsFactory.ActionGroup group = new ActionsFactory.ActionGroup(); - - Optional<BlackboardArtifact.Type> type = getAnalysisResultType(); - Optional<Long> dsId = getDataSourceIdForActions(); - if (type.isPresent()) { - group.add(new DeleteAnalysisResultSetAction( - type.get(), - () -> this.getAnalysisResultConfigurations(), - dsId.isPresent() ? dsId.get() : null)); - } - - return Optional.of(group); - } - } - - /** - * A factory that shows all sets in keyword hits. - */ - @Messages({ - "AnalysisResultTypeFactory_adHocName=Ad Hoc Results" - }) - public static class KeywordSetFactory extends AbstractAnalysisResultTreeFactory<KeywordListSearchParam> { - - private final Long dataSourceId; - - public KeywordSetFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeResultsDTO<? extends KeywordListSearchParam> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getKwSetCounts(this.dataSourceId, Bundle.AnalysisResultTypeFactory_adHocName()); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends KeywordListSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<KeywordListSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, KeywordListSearchParam.class); - - if (originalTreeItem != null - && originalTreeItem.getSearchParams().getArtifactType().equals(BlackboardArtifact.Type.TSK_KEYWORD_HIT) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - KeywordListSearchParam searchParam = originalTreeItem.getSearchParams(); - return new TreeResultsDTO.TreeItemDTO<>( - KeywordListSearchParam.getTypeId(), - new KeywordListSearchParam(this.dataSourceId, searchParam.getConfiguration(), searchParam.getSetName()), - searchParam.getSetName() == null ? 0 : searchParam.getSetName(), - searchParam.getSetName() == null ? Bundle.AnalysisResultTypeFactory_adHocName() : searchParam.getSetName(), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends KeywordListSearchParam> o1, TreeItemDTO<? extends KeywordListSearchParam> o2) { - return STRING_COMPARATOR.compare(o1.getSearchParams().getSetName(), o2.getSearchParams().getSetName()); - } - - @Override - protected TreeNode<KeywordListSearchParam> createNewNode(TreeResultsDTO.TreeItemDTO<? extends KeywordListSearchParam> rowData) { - return new KeywordSetNode(rowData); - } - } - - public static class KeywordSetNode extends TreeNode<KeywordListSearchParam> { - - private static final Logger logger = Logger.getLogger(KeywordSetNode.class.getName()); - - - /** - * Returns the name that will be used with a set node based on the configuration. - * @param config The analysis result configuration. - * @return The name to be used. - */ - public static String getNodeName(String config) { - return (config == null) - ? BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName() + "_ADHOC_SET" - : BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName() + "_SET_" + config; - } - - /** - * Main constructor. - * - * @param itemData The data to display. - */ - public KeywordSetNode(TreeResultsDTO.TreeItemDTO<? extends KeywordListSearchParam> itemData) { - super(getNodeName(itemData.getSearchParams().getSetName()), - getIconPath(itemData.getSearchParams().getArtifactType()), - itemData, - Children.create(new KeywordSearchTermFactory(itemData.getSearchParams()), true), - getDefaultLookup(itemData)); - } - - @Override - public Optional<Long> getDataSourceIdForActions() { - return Optional.ofNullable(this.getItemData().getSearchParams().getDataSourceId()); - } - - @Override - public Optional<BlackboardArtifact.Type> getAnalysisResultType() { - return Optional.ofNullable(this.getItemData().getSearchParams().getArtifactType()); - } - - @Override - public boolean hasAnalysisResultConfigurations() { - return true; - } - - @Override - public List<String> getAnalysisResultConfigurations() { - try { - return MainDAO.getInstance().getAnalysisResultDAO().getKeywordHitConfigurations( - this.getItemData().getSearchParams().getSetName(), - this.getItemData().getSearchParams().getDataSourceId()); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, "An exception occurred while fetching configurations.", ex); - return Collections.emptyList(); - } - } - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionsFactory.ActionGroup group = new ActionsFactory.ActionGroup(); - - Optional<BlackboardArtifact.Type> type = getAnalysisResultType(); - Optional<Long> dsId = getDataSourceIdForActions(); - if (type.isPresent()) { - group.add(new DeleteAnalysisResultSetAction( - type.get(), - () -> this.getAnalysisResultConfigurations(), - dsId.isPresent() ? dsId.get() : null)); - } - - return Optional.of(group); - } - } - - /** - * Factory for displaying all search terms (regex or exact) for a specific - * set. - */ - static class KeywordSearchTermFactory extends AbstractAnalysisResultTreeFactory<KeywordSearchTermParams> { - - private final KeywordListSearchParam setParams; - - /** - * Main constructor. - * - * @param setParams The parameters for the set. - */ - KeywordSearchTermFactory(KeywordListSearchParam setParams) { - this.setParams = setParams; - } - - @Override - protected TreeNode<KeywordSearchTermParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends KeywordSearchTermParams> rowData) { - return new KeywordSearchTermNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends KeywordSearchTermParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getKeywordSearchTermCounts(this.setParams.getSetName(), this.setParams.getDataSourceId()); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends KeywordSearchTermParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<KeywordSearchTermParams> originalTreeItem = super.getTypedTreeItem(treeEvt, KeywordSearchTermParams.class); - - if (originalTreeItem != null - && Objects.equals(originalTreeItem.getSearchParams().getSetName(), this.setParams.getSetName()) - && (this.setParams.getDataSourceId() == null - || Objects.equals(this.setParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) { - - KeywordSearchTermParams searchParam = originalTreeItem.getSearchParams(); - String searchTermDisplayName = MainDAO.getInstance().getAnalysisResultDAO() - .getSearchTermDisplayName(searchParam.getRegex(), searchParam.getSearchType()); - - return new TreeResultsDTO.TreeItemDTO<>( - KeywordSearchTermParams.getTypeId(), - new KeywordSearchTermParams( - this.setParams.getSetName(), - searchParam.getRegex(), - searchParam.getSearchType(), - searchParam.getConfiguration(), - searchParam.hasChildren(), - this.setParams.getDataSourceId() - ), - searchTermDisplayName, - searchTermDisplayName, - originalTreeItem.getDisplayCount() - ); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends KeywordSearchTermParams> o1, TreeItemDTO<? extends KeywordSearchTermParams> o2) { - return STRING_COMPARATOR.compare(o1.getSearchParams().getRegex(), o2.getSearchParams().getRegex()); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof DeleteAnalysisResultEvent && evt.getType() == DAOEvent.Type.TREE) { - super.update(); - return; - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - } - - /** - * A node for an individual search term. - */ - static class KeywordSearchTermNode extends TreeNode<KeywordSearchTermParams> { - - private static final Logger logger = Logger.getLogger(KeywordSearchTermNode.class.getName()); - - /** - * Main constructor. - * - * @param itemData The data for the search term. - */ - KeywordSearchTermNode(TreeResultsDTO.TreeItemDTO<? extends KeywordSearchTermParams> itemData) { - super(itemData.getSearchParams().getRegex(), - getIconPath(BlackboardArtifact.Type.TSK_KEYWORD_HIT), - itemData, - (itemData.getSearchParams().hasChildren() || itemData.getSearchParams().getSearchType() == TskData.KeywordSearchQueryType.REGEX - // for regex queries always create a subtree, even if there is only one child - ? Children.create(new KeywordFoundMatchFactory(itemData.getSearchParams()), true) - : Children.LEAF), - getDefaultLookup(itemData)); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - KeywordSearchTermParams searchTermParams = this.getItemData().getSearchParams(); - - if (!searchTermParams.hasChildren()) { - KeywordHitSearchParam searchParams = new KeywordHitSearchParam(searchTermParams.getDataSourceId(), - searchTermParams.getConfiguration(), - // if literal, keyword is regex - TskData.KeywordSearchQueryType.LITERAL.equals(searchTermParams.getSearchType()) ? searchTermParams.getRegex() : null, - // if literal, no regex - TskData.KeywordSearchQueryType.LITERAL.equals(searchTermParams.getSearchType()) ? null : searchTermParams.getRegex(), - searchTermParams.getSearchType(), - searchTermParams.getConfiguration()); - dataResultPanel.displayKeywordHits(searchParams); - } else { - super.respondSelection(dataResultPanel); - } - } - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionsFactory.ActionGroup group = new ActionsFactory.ActionGroup(); - - Optional<BlackboardArtifact.Type> type = getAnalysisResultType(); - Optional<Long> dsId = getDataSourceIdForActions(); - if (type.isPresent()) { - group.add(new DeleteAnalysisResultSetAction( - type.get(), - () -> this.getAnalysisResultConfigurations(), - dsId.isPresent() ? dsId.get() : null)); - } - - return Optional.of(group); - } - - public Optional<Long> getDataSourceIdForActions() { - return Optional.ofNullable(this.getItemData().getSearchParams().getDataSourceId()); - } - - @Override - public Optional<BlackboardArtifact.Type> getAnalysisResultType() { - return Optional.ofNullable(this.getItemData().getSearchParams().getArtifactType()); - } - - @Override - public boolean hasAnalysisResultConfigurations() { - return true; - } - - @Override - public List<String> getAnalysisResultConfigurations() { - return Collections.singletonList(this.getItemData().getSearchParams().getConfiguration()); - } - } - - /** - * A factory for found keyword matches based on the search term (for - * regex/substring). - */ - public static class KeywordFoundMatchFactory extends AbstractAnalysisResultTreeFactory<KeywordHitSearchParam> { - - private final KeywordSearchTermParams searchTermParams; - - /** - * Main constructor. - * - * @param params The search term parameters. - */ - public KeywordFoundMatchFactory(KeywordSearchTermParams params) { - this.searchTermParams = params; - } - - @Override - protected TreeNode<KeywordHitSearchParam> createNewNode(TreeResultsDTO.TreeItemDTO<? extends KeywordHitSearchParam> rowData) { - return new KeywordFoundMatchNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends KeywordHitSearchParam> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getKeywordMatchCounts( - this.searchTermParams.getSetName(), - this.searchTermParams.getRegex(), - this.searchTermParams.getSearchType(), - this.searchTermParams.getDataSourceId()); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends KeywordHitSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<KeywordHitSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, KeywordHitSearchParam.class); - - if (originalTreeItem != null - && Objects.equals(originalTreeItem.getSearchParams().getRegex(), this.searchTermParams.getRegex()) - && Objects.equals(originalTreeItem.getSearchParams().getSearchType(), this.searchTermParams.getSearchType()) - && Objects.equals(originalTreeItem.getSearchParams().getConfiguration(), this.searchTermParams.getConfiguration()) - && (this.searchTermParams.getDataSourceId() == null - || Objects.equals(this.searchTermParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - KeywordHitSearchParam searchParam = originalTreeItem.getSearchParams(); - return new TreeResultsDTO.TreeItemDTO<>( - KeywordHitSearchParam.getTypeId(), - new KeywordHitSearchParam( - this.searchTermParams.getDataSourceId(), - this.searchTermParams.getSetName(), - searchParam.getKeyword(), - this.searchTermParams.getRegex(), - this.searchTermParams.getSearchType(), - this.searchTermParams.getConfiguration() - ), - searchParam.getKeyword() == null ? "" : searchParam.getKeyword(), - searchParam.getKeyword() == null ? "" : searchParam.getKeyword(), - originalTreeItem.getDisplayCount() - ); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends KeywordHitSearchParam> o1, TreeItemDTO<? extends KeywordHitSearchParam> o2) { - return STRING_COMPARATOR.compare(o1.getSearchParams().getKeyword(), o2.getSearchParams().getKeyword()); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof DeleteAnalysisResultEvent && evt.getType() == DAOEvent.Type.TREE) { - super.update(); - return; - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - } - - /** - * A node signifying a match for a specific keyword given a regex/substring - * search term. - */ - static class KeywordFoundMatchNode extends TreeNode<KeywordHitSearchParam> { - - /** - * Main constructor. - * - * @param itemData The data for the match parameters. - */ - public KeywordFoundMatchNode(TreeResultsDTO.TreeItemDTO<? extends KeywordHitSearchParam> itemData) { - super(itemData.getSearchParams().getKeyword(), - getIconPath(BlackboardArtifact.Type.TSK_KEYWORD_HIT), - itemData, - Children.LEAF, - getDefaultLookup(itemData)); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - dataResultPanel.displayKeywordHits(this.getItemData().getSearchParams()); - } - - @Override - public Optional<Long> getDataSourceIdForActions() { - return Optional.ofNullable(this.getItemData().getSearchParams().getDataSourceId()); - } - - @Override - public Optional<BlackboardArtifact.Type> getAnalysisResultType() { - return Optional.ofNullable(this.getItemData().getSearchParams().getArtifactType()); - } - - @Override - public boolean hasAnalysisResultConfigurations() { - return true; - } - - @Override - public List<String> getAnalysisResultConfigurations() { - return Collections.singletonList(this.getItemData().getSearchParams().getConfiguration()); - } - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ArtifactNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ArtifactNode.java deleted file mode 100755 index 97dd3a8b2e6c7cbe2b416fc3e174cf70e41e3121..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ArtifactNode.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import javax.swing.Action; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.Lookup; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.datamodel.DirectoryNode; -import org.sleuthkit.autopsy.datamodel.LayoutFileNode; -import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; -import org.sleuthkit.autopsy.datamodel.LocalFileNode; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.datamodel.SlackFileNode; -import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; -import org.sleuthkit.autopsy.mainui.datamodel.ArtifactRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.autopsy.mainui.sco.SCOUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.DerivedFile; -import org.sleuthkit.datamodel.Directory; -import org.sleuthkit.datamodel.File; -import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.LocalDirectory; -import org.sleuthkit.datamodel.LocalFile; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.VirtualDirectory; - -public abstract class ArtifactNode<T extends BlackboardArtifact, R extends ArtifactRowDTO<T>> extends BaseNode<SearchResultsDTO, ArtifactRowDTO<?>> implements ActionContext, SCOSupporter { - - private final R rowData; - private final List<ColumnKey> columns; - private Node parentFileNode; - - ArtifactNode(SearchResultsDTO searchResults, R rowData, List<ColumnKey> columns, Lookup lookup, String iconPath, ExecutorService backgroundTasksPool) { - super(Children.LEAF, lookup, searchResults, rowData, backgroundTasksPool); - this.rowData = rowData; - this.columns = columns; - setupNodeDisplay(iconPath); - } - - @Override - public Optional<Content> getSourceContent() { - return Optional.ofNullable(rowData.getSrcContent()); - } - - @Override - public Optional<AbstractFile> getLinkedFile() { - return Optional.ofNullable((AbstractFile) rowData.getLinkedFile()); - } - - @Override - public boolean supportsViewInTimeline() { - return rowData.isTimelineSupported(); - } - - @Override - public Optional<BlackboardArtifact> getArtifactForTimeline() { - return Optional.ofNullable(rowData.getArtifact()); - } - - @Override - public boolean supportsAssociatedFileActions() { - return getLinkedFile().isPresent(); - } - - @Override - public boolean supportsSourceContentActions() { - Content sourceContent = rowData.getSrcContent(); - - return (sourceContent instanceof DataArtifact) - || (sourceContent instanceof OsAccount) - || (sourceContent instanceof AbstractFile || (rowData.getArtifact() instanceof DataArtifact)); - } - - @Override - public Optional<AbstractFile> getSourceFileForTimelineAction() { - return Optional.ofNullable(rowData.getSrcContent() instanceof AbstractFile ? (AbstractFile) rowData.getSrcContent() : null); - } - - @Override - public Optional<BlackboardArtifact> getArtifact() { - return Optional.of(rowData.getArtifact()); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return rowData.getSrcContent() != null; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.ofNullable(getParentFileNode()); - } - - @Override - public Optional<Node> getExternalViewerActionNode() { - return Optional.ofNullable(getParentFileNode()); - } - - @Override - public boolean supportsTableExtractActions() { - return rowData.getSrcContent() instanceof AbstractFile; - } - - @Override - public boolean supportsArtifactTagAction() { - return true; - } - - private Node getParentFileNode() { - if (parentFileNode == null) { - parentFileNode = getParentFileNode(rowData.getSrcContent()); - } - return parentFileNode; - } - - protected void setupNodeDisplay(String iconPath) { - // use first cell value for display name - String displayName = rowData.getCellValues().size() > 0 - ? rowData.getCellValues().get(0).toString() - : ""; - - setDisplayName(displayName); - setShortDescription(displayName); - setName(Long.toString(rowData.getId())); - setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); - } - - @Override - public Action[] getActions(boolean context) { - return ActionsFactory.getActions(this); - } - - @Override - public Optional<Content> getContent() { - return Optional.of(rowData.getArtifact()); - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } - - @Override - public Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute, String defaultDescription) { - return SCOUtils.getCountPropertyAndDescription(attribute, defaultDescription); - } - - @Override - public DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) { - return SCOUtils.getCommentProperty(tags, attributes); - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - return sheet; - } - - /** - * Returns a Node representing the file content if the content is indeed - * some sort of file. Otherwise, return null. - * - * @param content The content. - * - * @return The file node or null if not a file. - */ - private Node getParentFileNode(Content content) { - if (content instanceof File) { - return new org.sleuthkit.autopsy.datamodel.FileNode((AbstractFile) content); - } else if (content instanceof Directory) { - return new DirectoryNode((Directory) content); - } else if (content instanceof VirtualDirectory) { - return new VirtualDirectoryNode((VirtualDirectory) content); - } else if (content instanceof LocalDirectory) { - return new LocalDirectoryNode((LocalDirectory) content); - } else if (content instanceof LayoutFile) { - return new LayoutFileNode((LayoutFile) content); - } else if (content instanceof LocalFile || content instanceof DerivedFile) { - return new LocalFileNode((AbstractFile) content); - } else if (content instanceof SlackFile) { - return new SlackFileNode((AbstractFile) content); - } else { - return null; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/BaseNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/BaseNode.java deleted file mode 100755 index 06db5fa7eb9e2cf79a5f6bdafab2a2b582ab1a94..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/BaseNode.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.lang.ref.WeakReference; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.FutureTask; -import java.util.stream.Collectors; -import javax.swing.Action; -import javax.swing.SwingUtilities; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.Children; -import org.openide.nodes.Sheet; -import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.openide.util.WeakListeners; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; -import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent; -import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; -import org.sleuthkit.autopsy.mainui.datamodel.BaseRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; -import org.sleuthkit.autopsy.mainui.sco.SCOFetcher; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.autopsy.mainui.sco.SCOUtils; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; -import org.sleuthkit.autopsy.texttranslation.TextTranslationService; - -/** - * A a simple starting point for nodes. - */ -abstract class BaseNode<S extends SearchResultsDTO, R extends BaseRowDTO> extends AbstractNode implements ActionContext { - - private final S results; - private final R rowData; - private String translatedSourceName; - - private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of( - Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, - Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, - Case.Events.CONTENT_TAG_ADDED, - Case.Events.CONTENT_TAG_DELETED, - Case.Events.CR_COMMENT_CHANGED, - Case.Events.CURRENT_CASE); - - private final PropertyChangeListener listener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString())) { - if(BaseNode.this instanceof SCOSupporter) { - BlackBoardArtifactTagAddedEvent event = (BlackBoardArtifactTagAddedEvent) evt; - Optional<BlackboardArtifact> optional = getArtifact(); - if (optional.isPresent() - && event.getAddedTag().getArtifact().equals(optional.get())) { - updateSCOColumns(); - } - } - } else if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString())) { - if(BaseNode.this instanceof SCOSupporter) { - BlackBoardArtifactTagDeletedEvent event = (BlackBoardArtifactTagDeletedEvent) evt; - Optional<BlackboardArtifact> optional = getArtifact(); - if (optional.isPresent() && event.getDeletedTagInfo().getArtifactID() == optional.get().getArtifactID()) { - updateSCOColumns(); - } - } - } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) { - if(BaseNode.this instanceof SCOSupporter) { - ContentTagAddedEvent event = (ContentTagAddedEvent) evt; - Optional<Content> optional = ((SCOSupporter)BaseNode.this).getContent(); - if (optional.isPresent() && event.getAddedTag().getContent().equals(optional.get())) { - updateSCOColumns(); - } - } - - } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) { - if(BaseNode.this instanceof SCOSupporter) { - ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt; - Optional<Content> optional = ((SCOSupporter)BaseNode.this).getContent(); - if (optional.isPresent() && event.getDeletedTagInfo().getContentID() == optional.get().getId()) { - updateSCOColumns(); - } - } - } else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) { - if(BaseNode.this instanceof SCOSupporter) { - CommentChangedEvent event = (CommentChangedEvent) evt; - - if (shouldUpdateSCOColumns(event.getContentID())) { - updateSCOColumns(); - } - } - } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { - if (evt.getNewValue() == null) { - /* - * The case has been closed. - */ - unregisterListeners(); - } - } - } - }; - - private PropertyChangeListener weakListener = null; - - /** - * A pool of background tasks to run any long computation needed to populate - * this node. - */ - private final ExecutorService backgroundTasksPool; - - private FutureTask<String> scoFutureTask; - - BaseNode(Children children, Lookup lookup, S results, R rowData, ExecutorService backgroundTasksPool) { - super(children, lookup); - this.results = results; - this.rowData = rowData; - this.backgroundTasksPool = backgroundTasksPool; - - // If the S column is there register the listeners. - if (results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()).contains(SCOUtils.SCORE_COLUMN_NAME)) { - weakListener = WeakListeners.propertyChange(listener, null); - Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakListener); - } - } - - private void unregisterListeners() { - Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakListener); - } - - /** - * Returns the SearchResultDTO object. - * - * @return - */ - S getSearchResultsDTO() { - return results; - } - - /** - * Returns the RowDTO for this node. - * - * @return A RowDTO object. - */ - R getRowDTO() { - return rowData; - } - - @Override - protected Sheet createSheet() { - Sheet sheet = ContentNodeUtil.setSheet(super.createSheet(), results.getColumns(), rowData.getCellValues()); - updateSCOColumns(); - startTranslationTask(); - return sheet; - } - - @Override - public Action[] getActions(boolean context) { - return ActionsFactory.getActions(this); - } - - protected boolean shouldUpdateSCOColumns(long eventObjId) { - if (BaseNode.this instanceof SCOSupporter) { - Optional<Content> optional = ((SCOSupporter) BaseNode.this).getContent(); - return optional.isPresent() && eventObjId == optional.get().getId(); - - } - return false; - } - - private void updateSCOColumns() { - if (scoFutureTask != null && !scoFutureTask.isDone()) { - scoFutureTask.cancel(true); - scoFutureTask = null; - } - - if ((backgroundTasksPool != null && !backgroundTasksPool.isShutdown() && !backgroundTasksPool.isTerminated()) && (scoFutureTask == null || scoFutureTask.isDone()) && this instanceof SCOSupporter) { - scoFutureTask = new FutureTask<>(new SCOFetcher<>(new WeakReference<>((SCOSupporter) this)), ""); - backgroundTasksPool.submit(scoFutureTask); - } - } - - private void startTranslationTask() { - if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { - /* - * If machine translation is configured, add the original name of - * the of the source content of the artifact represented by this - * node to the sheet. - */ - - if (translatedSourceName == null) { - PropertyChangeListener listener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(FileNameTransTask.getPropertyName())) { - displayTranslation(evt.getOldValue().toString(), evt.getNewValue().toString()); - } - } - }; - /* - * NOTE: The task makes its own weak reference to the listener. - */ - // use first cell value for display name - String displayName = rowData.getCellValues().size() > 0 - ? rowData.getCellValues().get(0).toString() - : ""; - new FileNameTransTask(displayName, this, listener).submit(); - } - } - } - - // These strings need to be consistent with what is in FileSystemColumnUtils - @NbBundle.Messages({ - "BaseNode_columnKeys_originalName_name=Original Name", - "BaseNode_columnKeys_originalName_displayName=Original Name", - "BaseNode_columnKeys_originalName_description=Original Name", - }) - private void displayTranslation(String originalName, String translatedSourceName) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - BaseNode.this.translatedSourceName = translatedSourceName; - setDisplayName(translatedSourceName); - setShortDescription(originalName); - updateSheet(Collections.singletonList(new NodeProperty<>( - Bundle.BaseNode_columnKeys_originalName_name(), - Bundle.BaseNode_columnKeys_originalName_displayName(), - Bundle.BaseNode_columnKeys_originalName_description(), - originalName))); - } - }); - - } - - /** - * Updates the values of the properties in the current property sheet with - * the new properties being passed in. Only if that property exists in the - * current sheet will it be applied. That way, we allow for subclasses to - * add their own (or omit some!) properties and we will not accidentally - * disrupt their UI. - * - * Race condition if not synchronized. Only one update should be applied at - * a time. - * - * @param newProps New file property instances to be updated in the current - * sheet. - */ - protected synchronized void updateSheet(List<NodeProperty<?>> newProps) { - SwingUtilities.invokeLater(() -> { - /* - * Refresh ONLY those properties in the sheet currently. Subclasses - * may have only added a subset of our properties or their own - * properties. - */ - Sheet visibleSheet = this.getSheet(); - Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES); - Property<?>[] visibleProps = visibleSheetSet.getProperties(); - for (NodeProperty<?> newProp : newProps) { - for (int i = 0; i < visibleProps.length; i++) { - if (visibleProps[i].getName().equals(newProp.getName())) { - visibleProps[i] = newProp; - } - } - } - visibleSheetSet.put(visibleProps); - visibleSheet.put(visibleSheetSet); - //setSheet() will notify Netbeans to update this node in the UI. - this.setSheet(visibleSheet); - }); - } - - @Override - public Action getPreferredAction() { - return DirectoryTreeTopComponent.getOpenChildAction(getName()); - } - - protected ExecutorService getTaskPool() { - return backgroundTasksPool; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/BlackboardArtifactTagNode.java deleted file mode 100755 index c248a3fa1a21cae8cebe804f24848b8db7781d6b..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/BlackboardArtifactTagNode.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.text.MessageFormat; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.AnalysisResultItem; -import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; -import org.sleuthkit.autopsy.datamodel.DataArtifactItem; -import org.sleuthkit.autopsy.mainui.datamodel.BlackboardArtifactTagsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.AnalysisResult; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * A node representing a BlackboardArtifactTag. - */ -public final class BlackboardArtifactTagNode extends BaseNode<SearchResultsDTO, BlackboardArtifactTagsRowDTO> { - - private static final String ICON_PATH = "org/sleuthkit/autopsy/images/green-tag-icon-16.png"; //NON-NLS - private final BlackboardArtifactTagsRowDTO rowData; - private final List<ColumnKey> columns; - - private static final Logger logger = Logger.getLogger(BlackboardArtifactTagNode.class.getName()); - - public BlackboardArtifactTagNode(SearchResultsDTO results, BlackboardArtifactTagsRowDTO rowData, ExecutorService backgroundTasksPool) { - super(Children.LEAF, createLookup(rowData.getTag()), results, rowData, backgroundTasksPool); - this.rowData = rowData; - this.columns = results.getColumns(); - setDisplayName(rowData.getDisplayName()); - setShortDescription(rowData.getDisplayName()); - setName(Long.toString(rowData.getId())); - setIconBaseWithExtension(ICON_PATH); - } - - @Override - protected Sheet createSheet() { - return ContentNodeUtil.setSheet(super.createSheet(), columns, rowData.getCellValues()); - } - - /** - * Creates the lookup for a BlackboardArtifactTag. - * - * Note: This method comes from dataModel.BlackboardArtifactTag. - * - * @param tag The tag to create a lookup for - * - * @return The lookup. - */ - private static Lookup createLookup(BlackboardArtifactTag tag) { - /* - * Make an Autopsy Data Model wrapper for the artifact. - * - * NOTE: The creation of an Autopsy Data Model independent of the - * NetBeans nodes is a work in progress. At the time this comment is - * being written, this object is only being used to indicate the item - * represented by this BlackboardArtifactTagNode. - */ - Content sourceContent = tag.getContent(); - BlackboardArtifact artifact = tag.getArtifact(); - BlackboardArtifactItem<?> artifactItem; - if (artifact instanceof AnalysisResult) { - artifactItem = new AnalysisResultItem((AnalysisResult) artifact, sourceContent); - } else { - artifactItem = new DataArtifactItem((DataArtifact) artifact, sourceContent); - } - return Lookups.fixed(tag, artifactItem, artifact, sourceContent); - } - - @Override - public Optional<Content> getSourceContent() { - return Optional.ofNullable(rowData.getTag().getContent()); - } - - @Override - public Optional<BlackboardArtifact> getArtifact() { - return Optional.ofNullable(rowData.getTag().getArtifact()); - } - - @Override - public boolean supportsViewInTimeline() { - BlackboardArtifact artifact = rowData.getTag().getArtifact(); - if (artifact != null) { - try { - return ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting arttribute(s) from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS - } - } - - return false; - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public Optional<Node> getExternalViewerActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsTableExtractActions() { - return true; - } - - @Override - public boolean supportsArtifactTagAction() { - return true; - } - - @Override - public boolean supportsReplaceTagAction() { - return true; - } - - @Override - public boolean supportsContentTagAction() { - return rowData.getTag().getContent() instanceof AbstractFile; - } - - @Override - public boolean supportsResultArtifactAction() { - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/mainui/nodes/Bundle.properties-MERGED deleted file mode 100644 index f54e5481cfcf15aaa9a4f5942884de465f7cd838..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/Bundle.properties-MERGED +++ /dev/null @@ -1,36 +0,0 @@ -AnalysisResultTypeFactory_adHocName=Ad Hoc Results -AnalysisResultTypeFactory_blankConfigName=(No Configuration) -BaseNode_columnKeys_originalName_description=Original Name -BaseNode_columnKeys_originalName_displayName=Original Name -BaseNode_columnKeys_originalName_name=Original Name -DataArtifactTypeFactory_AccountTypeParentNode_displayName=Communication Accounts -FileNode.createSheet.comment.displayName=C -# {0} - occurrenceCount -FileNode_createSheet_count.description=There were {0} datasource(s) found with occurrences of the MD5 correlation value -FileNode_createSheet_count.displayName=O -FileNode_createSheet_count_hashLookupNotRun_description=Hash lookup had not been run on this file when the column was populated -FileSystemFactory.FileSystemTreeNode.ExtractUnallocAction.text=Extract Unallocated Space to Single Files -FileSystemFactory.UnsupportedTreeNode.displayName=Unsupported Content -ImageNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files -PersonNode_unknownPersonNode_title=Unknown Persons -RealmFetcher_nodescription_text=No description -RootFactory_AllDataSourcesNode_displayName=Data Sources -RootFactory_AnalysisResultsRootNode_displayName=Analysis Results -RootFactory_DataArtifactsRootNode_displayName=Data Artifacts -RootFactory_DataSourceFilesNode_displayName=Data Source Files -RootFactory_OsAccountsRootNode_displayName=OS Accounts -RootFactory_ReportsRootNode_displayName=Reports -RootFactory_TagsRootNode_displayName=Tags -RootFactory_ViewsRootNode_displayName=Views -ScoreTypeFactory_ScoreParentNode_displayName=Score -SearchResultRootNode_createSheet_childCount_displayName=Child Count -SearchResultRootNode_createSheet_childCount_name=Child Count -SearchResultRootNode_createSheet_type_displayName=Name -SearchResultRootNode_createSheet_type_name=Name -SearchResultRootNode_noDesc=No Description -ViewsTypeFactory_DeletedParentNode_displayName=Deleted Files -ViewsTypeFactory_ExtensionParentNode_displayName=By Extension -ViewsTypeFactory_FileTypesParentNode_displayName=File Types -ViewsTypeFactory_MimeParentNode_displayName=By MIME Type -ViewsTypeFactory_SizeParentNode_displayName=File Size -VolumnNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ChildNodeSelectionInfo.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ChildNodeSelectionInfo.java deleted file mode 100755 index 50de052cd28133d86615f13d809c8d56a6f35aec..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ChildNodeSelectionInfo.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import org.openide.nodes.Node; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.OsAccount; - -/** - * An interface for nodes that support the view selected file\directory. - */ -public interface ChildNodeSelectionInfo { - - /** - * Determine of the given node represents the child content to be selected. - * - * @param node - * - * @return True if there is a match. - */ - boolean matches(Node node); - - public class ContentNodeSelectionInfo implements ChildNodeSelectionInfo { - - private final Long contentId; - - public ContentNodeSelectionInfo(Long contentId) { - this.contentId = contentId; - } - - @Override - public boolean matches(Node node) { - Content content = node.getLookup().lookup(Content.class); - if (content != null && contentId != null) { - return contentId.equals(content.getId()); - } - - return false; - } - } - - public class BlackboardArtifactNodeSelectionInfo implements ChildNodeSelectionInfo { - - private final long objId; - - public BlackboardArtifactNodeSelectionInfo(BlackboardArtifact artifact) { - this.objId = artifact.getId(); - } - - @Override - public boolean matches(Node node) { - BlackboardArtifact nodeArtifact = node.getLookup().lookup(BlackboardArtifact.class); - if (nodeArtifact != null) { - return objId == nodeArtifact.getId(); - } - - return false; - } - } - - /** - * The selection of an os account. - */ - public class OsAccountNodeSelectionInfo implements ChildNodeSelectionInfo { - - private final long osAccountId; - - /** - * Main constructor. - * @param osAccountId The os account id. - */ - public OsAccountNodeSelectionInfo(long osAccountId) { - this.osAccountId = osAccountId; - } - - @Override - public boolean matches(Node node) { - OsAccount osAccount = node.getLookup().lookup(OsAccount.class); - return osAccount != null && osAccount.getId() == osAccountId; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ContentNodeUtil.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ContentNodeUtil.java deleted file mode 100644 index 707b2c3fb5e658e41d987c450719178fcca55628..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ContentNodeUtil.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import org.openide.nodes.Sheet; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; -import org.sleuthkit.autopsy.datamodel.DirectoryNode; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.datamodel.TskContentItem; -import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Utilities for setting up nodes that handle content. - */ -public class ContentNodeUtil { - - public static String getContentDisplayName(String fileName) { - switch (fileName) { - case "..": - return DirectoryNode.DOTDOTDIR; - case ".": - return DirectoryNode.DOTDIR; - default: - return fileName; - } - } - - public static String getContentName(long objId) { - return "content_" + Long.toString(objId); - } - - public static Lookup getLookup(Content content) { - return Lookups.fixed(content, new TskContentItem<>(content)); - } - - public static Sheet setSheet(Sheet sheet, List<ColumnKey> columnKeys, List<Object> values) { - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - - int maxSize = Math.min(columnKeys.size(), values.size()); - - for (int i = 0; i < maxSize; i++) { - ColumnKey columnKey = columnKeys.get(i); - Object cellValue = values.get(i); - - if (cellValue == null) { - sheetSet.put(new NodeProperty<>( - columnKey.getFieldName(), - columnKey.getDisplayName(), - columnKey.getDescription(), - "" - )); - continue; - } - - if (cellValue instanceof Date) { - cellValue = TimeZoneUtils.getFormattedTime(((Date) cellValue).getTime() / 1000); - } - - sheetSet.put(new NodeProperty<>( - columnKey.getFieldName(), - columnKey.getDisplayName(), - columnKey.getDescription(), - cellValue - )); - } - - return sheet; - } - - /** - * Get all tags from the case database that are associated with the file - * - * @return a list of tags that are associated with the file - */ - public static List<ContentTag> getContentTagsFromDatabase(Content content) throws TskCoreException, NoCurrentCaseException{ - List<ContentTag> tags = new ArrayList<>(); - tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content)); - - return tags; - } - - public static List<BlackboardArtifactTag> getArtifactTagsFromDatabase(BlackboardArtifact artifact) throws TskCoreException, NoCurrentCaseException{ - List<BlackboardArtifactTag> tags = new ArrayList<>(); - tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); - return tags; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ContentTagNode.java deleted file mode 100755 index ce3d02e360cb0879db0593f6fe32e086b685547d..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ContentTagNode.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey; -import org.sleuthkit.autopsy.mainui.datamodel.ContentTagsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentTag; - -/** - * A node representing a ContentTag. - */ -public final class ContentTagNode extends BaseNode<SearchResultsDTO, ContentTagsRowDTO> { - - private static final String CONTENT_ICON_PATH = "org/sleuthkit/autopsy/images/blue-tag-icon-16.png"; //NON-NLS - - private final ContentTagsRowDTO rowData; - private final List<ColumnKey> columns; - - /** - * Construct a new node. - * - * @param results Search results. - * @param rowData Row data. - */ - public ContentTagNode(SearchResultsDTO results, ContentTagsRowDTO rowData, ExecutorService backgroundTasksPool) { - super(Children.LEAF, createLookup(rowData.getTag()), results, rowData, backgroundTasksPool); - this.rowData = rowData; - this.columns = results.getColumns(); - setDisplayName(rowData.getDisplayName()); - setName(rowData.getDisplayName()); - setIconBaseWithExtension(CONTENT_ICON_PATH); - } - - @Override - protected Sheet createSheet() { - return ContentNodeUtil.setSheet(super.createSheet(), columns, rowData.getCellValues()); - } - - /** - * Create the Lookup based on the tag type. - * - * @param tag The node tag. - * - * @return The lookup for the tag. - */ - private static Lookup createLookup(ContentTag tag) { - return Lookups.fixed(tag, tag.getContent()); - } - - @Override - public Optional<AbstractFile> getFileForViewInTimelineAction() { - Content tagContent = rowData.getTag().getContent(); - if (tagContent instanceof AbstractFile) { - return Optional.of((AbstractFile) tagContent); - } - - return Optional.empty(); - } - - @Override - public boolean supportsViewInTimeline() { - return true; - } - - @Override - public boolean supportsAssociatedFileActions() { - return true; - } - - @Override - public Optional<AbstractFile> getLinkedFile() { - Content content = rowData.getTag().getContent(); - if (content instanceof AbstractFile) { - return Optional.of((AbstractFile) content); - } - - return Optional.empty(); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public Optional<Node> getExternalViewerActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsTableExtractActions() { - return true; - } - - @Override - public boolean supportsContentTagAction() { - return true; - } - - @Override - public boolean supportsReplaceTagAction() { - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/CreditCardByFileNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/CreditCardByFileNode.java deleted file mode 100644 index cdb3f60e0037d38654cc77abd1411a408aceb573..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/CreditCardByFileNode.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.stream.Stream; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardByFileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; - -/** - * A node representing a single row when viewing credit cards by file. - */ -public class CreditCardByFileNode extends BaseNode<SearchResultsDTO, CreditCardByFileRowDTO> { - - private static Object[] getLookupItems(CreditCardByFileRowDTO rowData) { - return Stream.of(rowData.getAssociatedArtifacts().stream(), Stream.of(rowData.getFile())) - .flatMap(s -> s) - .toArray(); - } - - public CreditCardByFileNode(SearchResultsDTO results, CreditCardByFileRowDTO rowData, ExecutorService backgroundTasksPool) { - super(Children.LEAF, - Lookups.fixed(getLookupItems(rowData)), - results, - rowData, - backgroundTasksPool); - - setName(rowData.getFileName() + rowData.getId()); - setDisplayName(rowData.getFileName()); - setIconBaseWithExtension(NodeIconUtil.FILE.getPath()); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsTableExtractActions() { - return true; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DAOFetcher.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DAOFetcher.java deleted file mode 100644 index 5bff79f50b8391f5155b4537fb0a15ffc2a021f5..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DAOFetcher.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.beans.PropertyChangeEvent; -import java.util.concurrent.ExecutionException; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; - -/** - * Provides a generic interface to perform searches and determine if refreshes - * are needed without needing to know which DAO to use. - */ -public abstract class DAOFetcher<P> { - - private final P parameters; - - /** - * Main constructor. - * - * @param parameters The search parameters. - */ - public DAOFetcher(P parameters) { - this.parameters = parameters; - } - - /** - * Returns the provided search params. - * - * @return The provided search params. - */ - protected P getParameters() { - return parameters; - } - - /** - * Fetches search results data based on paging settings. - * - * - * @param pageSize The number of items per page. - * @param pageIdx The page index. - * - * @return The retrieved data. - * - * @throws ExecutionException - */ - public abstract SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException; - - /** - * Returns true if the ingest module event will require a refresh in the - * data. - * - * @param evt The event. - * - * @return True if the - */ - public abstract boolean isRefreshRequired(DAOEvent evt); -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactNode.java deleted file mode 100644 index b53db86a5925b93d185ca640d013ec4a36dcf5c9..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactNode.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2012-2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.logging.Level; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; -import org.sleuthkit.autopsy.datamodel.DataArtifactItem; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO.CommAccoutTableSearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * node to display a data artifact. - */ -public class DataArtifactNode extends ArtifactNode<DataArtifact, DataArtifactRowDTO> { - - private static final Logger logger = Logger.getLogger(DataArtifactNode.class.getName()); - - private static Lookup createLookup(DataArtifactRowDTO row) { - DataArtifactItem artifactItem = new DataArtifactItem(row.getDataArtifact(), row.getSrcContent()); - if (row.getSrcContent() == null) { - return Lookups.fixed(row.getDataArtifact(), artifactItem); - } else { - return Lookups.fixed(row.getDataArtifact(), artifactItem, row.getSrcContent()); - } - } - - public DataArtifactNode(DataArtifactTableSearchResultsDTO tableData, DataArtifactRowDTO artifactRow, ExecutorService backgroundTasksPool) { - this(tableData, artifactRow, getIconFilePath(tableData), backgroundTasksPool); - } - - public DataArtifactNode(SearchResultsDTO tableData, DataArtifactRowDTO artifactRow, String iconPath, ExecutorService backgroundTasksPool) { - super(tableData, artifactRow, tableData.getColumns(), createLookup(artifactRow), iconPath, backgroundTasksPool); - } - - @Override - public Optional<List<Tag>> getAllTagsFromDatabase() { - try { - List<BlackboardArtifactTag> artifactTags = ContentNodeUtil.getArtifactTagsFromDatabase(getRowDTO().getArtifact()); - if (!artifactTags.isEmpty()) { - List<Tag> tags = new ArrayList<>(); - tags.addAll(artifactTags); - return Optional.of(tags); - } - - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to get content tags from database for Artifact id=" + getRowDTO().getArtifact().getId(), ex); - } - return Optional.empty(); - } - - private static String getIconFilePath(DataArtifactTableSearchResultsDTO tableData) { - if(!(tableData instanceof CommAccoutTableSearchResultsDTO)) { - return IconsUtil.getIconFilePath(tableData.getArtifactType().getTypeID()); - } - - return IconsUtil.getIconFilePath(((CommAccoutTableSearchResultsDTO)tableData).getAccountType()); - } - - @Override - protected boolean shouldUpdateSCOColumns(long eventObjId) { - return false; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java deleted file mode 100644 index 43c9de23c2efc086b191d0eae5f0e6467424344a..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java +++ /dev/null @@ -1,586 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.Children; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.mainui.datamodel.CommAccountsSearchParams; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.datamodel.accounts.Accounts; -import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardBinSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardDAO; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardFileSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactDAO; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.EmailSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.EmailsDAO; -import org.sleuthkit.autopsy.mainui.datamodel.EmailsDAO.EmailTreeItem; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import static org.sleuthkit.autopsy.mainui.nodes.TreeNode.getDefaultLookup; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Factory for displaying data artifact types in the tree. - */ -public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearchParam> { - - private static final String BANK_ICON = "org/sleuthkit/autopsy/images/bank.png"; - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source id to filter on or null if no filter. - */ - public DataArtifactTypeFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeResultsDTO<? extends DataArtifactSearchParam> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getDataArtifactsDAO().getDataArtifactCounts(dataSourceId); - } - - @Override - protected TreeNode<DataArtifactSearchParam> createNewNode(TreeResultsDTO.TreeItemDTO<? extends DataArtifactSearchParam> rowData) { - if (rowData.getSearchParams().getArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) { - return new AccountTypeParentNode(rowData); - } else if (rowData.getSearchParams().getArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) { - return new EmailTypeParentNode(rowData); - } else { - return new DataArtifactTypeTreeNode(rowData); - } - } - - @Override - protected TreeItemDTO<DataArtifactSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) { - - TreeItemDTO<DataArtifactSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, DataArtifactSearchParam.class); - - if (originalTreeItem != null - && !DataArtifactDAO.getIgnoredTreeTypes().contains(originalTreeItem.getSearchParams().getArtifactType()) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - DataArtifactSearchParam searchParam = originalTreeItem.getSearchParams(); - return new TreeItemDTO<>( - DataArtifactSearchParam.getTypeId(), - new DataArtifactSearchParam(searchParam.getArtifactType(), this.dataSourceId), - searchParam.getArtifactType().getTypeID(), - MainDAO.getInstance().getDataArtifactsDAO().getDisplayName(searchParam.getArtifactType()), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends DataArtifactSearchParam> o1, TreeItemDTO<? extends DataArtifactSearchParam> o2) { - DataArtifactDAO dao = MainDAO.getInstance().getDataArtifactsDAO(); - return dao.getDisplayName(o1.getSearchParams().getArtifactType()).compareToIgnoreCase(dao.getDisplayName(o2.getSearchParams().getArtifactType())); - } - - private static String getIconPath(BlackboardArtifact.Type artType) { - String iconPath = IconsUtil.getIconFilePath(artType.getTypeID()); - return iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath; - } - - /** - * Display name and count of a data artifact type in the tree. - */ - public static class DataArtifactTypeTreeNode extends TreeNode<DataArtifactSearchParam> { - - public DataArtifactTypeTreeNode(TreeResultsDTO.TreeItemDTO<? extends DataArtifactSearchParam> itemData) { - super(itemData.getSearchParams().getArtifactType().getTypeName(), - getIconPath(itemData.getSearchParams().getArtifactType()), - itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - dataResultPanel.displayDataArtifact(this.getItemData().getSearchParams()); - } - } - - /** - * Display name and count of email messages in the tree. - */ - public static class EmailTypeParentNode extends TreeNode<DataArtifactSearchParam> { - - public EmailTypeParentNode(TreeResultsDTO.TreeItemDTO<? extends DataArtifactSearchParam> itemData) { - super(itemData.getSearchParams().getArtifactType().getTypeName(), - getIconPath(itemData.getSearchParams().getArtifactType()), - itemData, - Children.create(new EmailFolderFactory(null, itemData.getSearchParams().getDataSourceId()), true), - getDefaultLookup(itemData) - ); - } - } - - /** - * The account node that has nested children of account types. - */ - @Messages({ - "DataArtifactTypeFactory_AccountTypeParentNode_displayName=Communication Accounts" - }) - static class AccountTypeParentNode extends TreeNode<DataArtifactSearchParam> { - - /** - * Sets correct title (not using artifact type display name). - * - * @param itemData The item data. - * - * @return The updated data. - */ - private static TreeItemDTO<? extends DataArtifactSearchParam> createTitledData(TreeResultsDTO.TreeItemDTO<? extends DataArtifactSearchParam> itemData) { - return new TreeItemDTO<>( - itemData.getTypeId(), - itemData.getSearchParams(), - itemData.getId(), - Bundle.DataArtifactTypeFactory_AccountTypeParentNode_displayName(), - itemData.getDisplayCount() - ); - } - - /** - * Main constructor. - * - * @param itemData The data to display. - * @param dataSourceId The data source id to filter on or null if no - * data source filter. - */ - public AccountTypeParentNode(TreeResultsDTO.TreeItemDTO<? extends DataArtifactSearchParam> itemData) { - super(itemData.getSearchParams().getArtifactType().getTypeName(), - getIconPath(itemData.getSearchParams().getArtifactType()), - createTitledData(itemData), - Children.create(new AccountTypeFactory(itemData.getSearchParams().getDataSourceId()), true), - getDefaultLookup(itemData) - ); - } - - @Override - protected void updateDisplayName(TreeItemDTO<? extends DataArtifactSearchParam> prevData, TreeItemDTO<? extends DataArtifactSearchParam> curData) { - super.updateDisplayName(prevData, createTitledData(curData)); - } - - } - - /** - * Factory for displaying account types. - */ - static class AccountTypeFactory extends TreeChildFactory<CommAccountsSearchParams> { - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - */ - public AccountTypeFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeResultsDTO<? extends CommAccountsSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getCommAccountsDAO().getAccountsCounts(this.dataSourceId); - } - - @Override - protected TreeNode<CommAccountsSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends CommAccountsSearchParams> rowData) { - if (Objects.equals(Account.Type.CREDIT_CARD, rowData.getSearchParams().getType())) { - return new CreditCardRootNode(rowData); - } else { - return new AccountTypeNode(rowData); - } - - } - - @Override - protected TreeItemDTO<? extends CommAccountsSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - - TreeItemDTO<CommAccountsSearchParams> originalTreeItem = getTypedTreeItem(treeEvt, CommAccountsSearchParams.class); - - if (originalTreeItem != null - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - CommAccountsSearchParams searchParam = originalTreeItem.getSearchParams(); - return TreeChildFactory.createTreeItemDTO(originalTreeItem, - new CommAccountsSearchParams(searchParam.getType(), this.dataSourceId)); - } - - return null; - } - - @Override - public int compare(TreeItemDTO<? extends CommAccountsSearchParams> o1, TreeItemDTO<? extends CommAccountsSearchParams> o2) { - return o1.getSearchParams().getType().getDisplayName().compareToIgnoreCase(o2.getSearchParams().getType().getDisplayName()); - } - } - - /** - * A node representing a single account type in the tree. - */ - static class AccountTypeNode extends TreeNode<CommAccountsSearchParams> { - - /** - * Main constructor. - * - * @param itemData The data to display. - */ - public AccountTypeNode(TreeResultsDTO.TreeItemDTO<? extends CommAccountsSearchParams> itemData) { - super(itemData.getSearchParams().getType().getTypeName(), - IconsUtil.getIconFilePath(itemData.getSearchParams().getType()), - itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - dataResultPanel.displayAccounts(super.getItemData().getSearchParams()); - } - } - - /** - * Factory for displaying account types. - */ - static class EmailFolderFactory extends TreeChildFactory<EmailSearchParams> { - - private final String folderParent; - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param folderParent The email parent folder for the factory. - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - */ - public EmailFolderFactory(String folderParent, Long dataSourceId) { - this.dataSourceId = dataSourceId; - this.folderParent = folderParent; - } - - private EmailsDAO getDAO() { - return MainDAO.getInstance().getEmailsDAO(); - } - - @Override - protected TreeResultsDTO<? extends EmailSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return getDAO().getEmailCounts(dataSourceId, this.folderParent); - } - - @Override - protected TreeNode<EmailSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> rowData) { - return new EmailNode(rowData); - } - - @Override - protected TreeItemDTO<? extends EmailSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - - TreeItemDTO<EmailSearchParams> originalTreeItem = getTypedTreeItem(treeEvt, EmailSearchParams.class); - - if (originalTreeItem != null - // ensure data source id for factory is null or data sources are equal - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - EmailSearchParams originalSearchParam = originalTreeItem.getSearchParams(); - return getDAO().createEmailTreeItem(originalSearchParam.getFolder(), this.folderParent, dataSourceId, originalTreeItem.getDisplayCount()); - } - - return null; - } - - @Override - public int compare(TreeItemDTO<? extends EmailSearchParams> o1, TreeItemDTO<? extends EmailSearchParams> o2) { - String safeO1 = o1.getId() instanceof String ? o1.getId().toString() : ""; - String safeO2 = o2.getId() instanceof String ? o2.getId().toString() : ""; - - boolean firstDown = o1.getId() instanceof String; - boolean secondDown = o2.getId() instanceof String; - - if (firstDown == secondDown) { - return safeO1.compareToIgnoreCase(safeO2); - } else { - return Boolean.compare(firstDown, secondDown); - } - } - } - - /** - * A node representing a single account type in the tree. - */ - static class EmailNode extends TreeNode<EmailSearchParams> { - - /** - * Main constructor. - * - * @param itemData The data to display. - */ - public EmailNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> itemData) { - super(itemData.getId().toString(), - "org/sleuthkit/autopsy/images/folder-icon-16.png", - itemData, - getChildren(itemData), - getDefaultLookup(itemData)); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - if (Children.LEAF.equals(getChildren())) { - dataResultPanel.displayEmailMessages(super.getItemData().getSearchParams()); - } else { - super.respondSelection(dataResultPanel); - } - } - - @Override - public void update(TreeItemDTO<? extends EmailSearchParams> updatedData) { - setChildren(updatedData); - super.update(updatedData); - } - - private void setChildren(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> itemData) { - if (Children.LEAF.equals(getChildren())) { - Children newChildren = getChildren(itemData); - if (!Children.LEAF.equals(newChildren)) { - setChildren(newChildren); - } - } - } - - private static Children getChildren(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> itemData) { - if (itemData instanceof EmailTreeItem) { - EmailTreeItem treeItem = (EmailTreeItem) itemData; - if (treeItem.getHasChildren().orElse(false)) { - return Children.create( - new EmailFolderFactory( - treeItem.getSearchParams().getFolder(), - treeItem.getSearchParams().getDataSourceId()), - true); - } - } - - return Children.LEAF; - } - } - - /** - * The root credit card node. - */ - static class CreditCardRootNode extends TreeNode<CommAccountsSearchParams> { - - public CreditCardRootNode(TreeResultsDTO.TreeItemDTO<? extends CommAccountsSearchParams> itemData) { - super(Account.Type.CREDIT_CARD.getDisplayName(), - Accounts.getIconFilePath(Account.Type.CREDIT_CARD), - itemData, - Children.create(new CreditCardTypeChildren(itemData.getSearchParams().getDataSourceId()), true), - getDefaultLookup(itemData)); - } - - } - - /** - * The children underneath the root credit card node (By File, By Bin). - */ - static class CreditCardTypeChildren extends TreeChildFactory<CreditCardSearchParams> { - - private final Long dataSourceId; - private final boolean includeRejected = true; - - CreditCardTypeChildren(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - private CreditCardDAO getDAO() { - return MainDAO.getInstance().getCreditCardDAO(); - } - - @Override - protected TreeResultsDTO<? extends CreditCardSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return getDAO().getCreditCardCounts(this.dataSourceId, this.includeRejected); - } - - @Override - @SuppressWarnings("unchecked") - protected TreeNode<CreditCardSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends CreditCardSearchParams> rowData) { - if (rowData.getSearchParams() instanceof CreditCardFileSearchParams) { - return new CreditCardByFileNode(rowData); - } else if (rowData.getSearchParams() instanceof CreditCardBinSearchParams) { - return new CreditCardByBinParentNode(rowData); - } else { - return null; - } - } - - @Override - protected TreeItemDTO<? extends CreditCardSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - - TreeItemDTO<CreditCardSearchParams> originalTreeItem = getTypedTreeItem(treeEvt, CreditCardSearchParams.class); - - if (originalTreeItem != null - && (this.includeRejected || !originalTreeItem.getSearchParams().isRejectedIncluded()) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - if (originalTreeItem.getSearchParams() instanceof CreditCardFileSearchParams) { - return getDAO().createFileTreeItem(this.includeRejected, this.dataSourceId, originalTreeItem.getDisplayCount()); - } else if (originalTreeItem.getSearchParams() instanceof CreditCardBinSearchParams) { - return getDAO().createBinTreeItem(this.includeRejected, null, this.dataSourceId, originalTreeItem.getDisplayCount()); - } - } - - return null; - } - - @Override - public int compare(TreeItemDTO<? extends CreditCardSearchParams> o1, TreeItemDTO<? extends CreditCardSearchParams> o2) { - // Push the 'By Bin' node lower than the 'By File' node. - boolean isBin1 = o1.getSearchParams() instanceof CreditCardBinSearchParams; - boolean isBin2 = o2.getSearchParams() instanceof CreditCardBinSearchParams; - return Boolean.compare(isBin1, isBin2); - } - } - - /** - * The tree credit card by file node. - */ - static class CreditCardByFileNode extends TreeNode<CreditCardSearchParams> { - /** - * @return The name id of this node. - */ - public static String getNameId() { - return CreditCardFileSearchParams.getTypeId(); - } - - - CreditCardByFileNode(TreeItemDTO<? extends CreditCardSearchParams> rowData) { - super(CreditCardFileSearchParams.getTypeId(), - NodeIconUtil.FILE.getPath(), - rowData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - CreditCardSearchParams baseParams = this.getItemData().getSearchParams(); - dataResultPanel.displayCreditCardsByFile(new CreditCardFileSearchParams(baseParams.isRejectedIncluded(), baseParams.getDataSourceId())); - } - } - - /** - * The root node for credit cards by bin. - */ - public static class CreditCardByBinParentNode extends TreeNode<CreditCardSearchParams> { - /** - * @return The name id of this node. - */ - public static String getNameId() { - return CreditCardBinSearchParams.getTypeId(); - } - - CreditCardByBinParentNode(TreeResultsDTO.TreeItemDTO<? extends CreditCardSearchParams> rowData) { - super(CreditCardBinSearchParams.getTypeId(), - BANK_ICON, - rowData, - Children.create(new CreditCardByBinFactory(rowData.getSearchParams().getDataSourceId(), rowData.getSearchParams().isRejectedIncluded()), true), - getDefaultLookup(rowData)); - } - } - - /** - * Factory for credit card bin prefixes. - */ - static class CreditCardByBinFactory extends TreeChildFactory<CreditCardBinSearchParams> { - - private final Long dataSourceId; - private final boolean includeRejected = true; - - CreditCardByBinFactory(Long dataSourceId, boolean includeRejected) { - this.dataSourceId = dataSourceId; - } - - private CreditCardDAO getDAO() { - return MainDAO.getInstance().getCreditCardDAO(); - } - - @Override - protected TreeResultsDTO<? extends CreditCardBinSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return getDAO().getCreditCardBinCounts(this.dataSourceId, this.includeRejected); - } - - @Override - @SuppressWarnings("unchecked") - protected TreeNode<CreditCardBinSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends CreditCardBinSearchParams> rowData) { - return new CreditCardByBinNode(rowData); - } - - @Override - protected TreeItemDTO<? extends CreditCardBinSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - - TreeItemDTO<CreditCardBinSearchParams> originalTreeItem = getTypedTreeItem(treeEvt, CreditCardBinSearchParams.class); - - if (originalTreeItem != null - && (this.includeRejected || !originalTreeItem.getSearchParams().isRejectedIncluded()) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId())) - && (originalTreeItem.getSearchParams().getBinPrefix() != null)) { - return getDAO().createBinTreeItem( - this.includeRejected, - originalTreeItem.getSearchParams().getBinPrefix(), - this.dataSourceId, - originalTreeItem.getDisplayCount()); - } - - return null; - } - - @Override - public int compare(TreeItemDTO<? extends CreditCardBinSearchParams> o1, TreeItemDTO<? extends CreditCardBinSearchParams> o2) { - return StringUtils.defaultString(o1.getSearchParams().getBinPrefix()).compareToIgnoreCase(StringUtils.defaultString(o2.getSearchParams().getBinPrefix())); - } - } - - /** - * A bin prefix credit card node. - */ - static class CreditCardByBinNode extends TreeNode<CreditCardBinSearchParams> { - - CreditCardByBinNode(TreeResultsDTO.TreeItemDTO<? extends CreditCardBinSearchParams> rowData) { - super(rowData.getDisplayName(), - BANK_ICON, - rowData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - this.getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - dataResultPanel.displayCreditCardsByBin(this.getItemData().getSearchParams()); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DirectoryNode.java deleted file mode 100755 index 0a0f295c2f09074abc09412630232286c6ffeecc..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DirectoryNode.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import org.apache.commons.lang3.tuple.Pair; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import javax.swing.Action; -import org.openide.nodes.Children; -import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.DirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.autopsy.mainui.sco.SCOUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.TskData; - -/** - * A node representing a row for a Directory in the results table. - */ -public class DirectoryNode extends BaseNode<SearchResultsDTO, DirectoryRowDTO> implements SCOSupporter { - - /** - * Simple node constructor. - * - * @param results The search result DTO. - * @param row The table row DTO. - */ - public DirectoryNode(SearchResultsDTO results, DirectoryRowDTO row, ExecutorService backgroundTasksPool) { - super(Children.LEAF, ContentNodeUtil.getLookup(row.getContent()), results, row, backgroundTasksPool); - setName(ContentNodeUtil.getContentName(row.getContent().getId())); - setDisplayName(ContentNodeUtil.getContentDisplayName(row.getContent().getName())); - setShortDescription(ContentNodeUtil.getContentDisplayName(row.getContent().getName())); - setIcon(); - } - - /** - * Sets the Icon that appears for the directory based on the FLAG state. - * - * @param dir - */ - private void setIcon() { - // set name, display name, and icon - if (getRowDTO().getContent().isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - this.setIconBaseWithExtension(NodeIconUtil.DELETED_FOLDER.getPath()); //NON-NLS - } else { - this.setIconBaseWithExtension(NodeIconUtil.FOLDER.getPath()); //NON-NLS - } - } - - @Override - public boolean supportsViewInTimeline() { - return true; - } - - @Override - public Optional<AbstractFile> getFileForViewInTimelineAction() { - return Optional.of(getRowDTO().getContent()); - } - - @Override - public boolean supportsTableExtractActions() { - return true; - } - - @Override - public Optional<Content> getContentForRunIngestionModuleAction() { - return Optional.of(getRowDTO().getContent()); - } - - @Override - public boolean supportsContentTagAction() { - return true; - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } - - @Override - public Optional<Content> getContent() { - return Optional.ofNullable(getRowDTO().getContent()); - } - - @Override - public Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute, String defaultDescription) { - return SCOUtils.getCountPropertyAndDescription(attribute, defaultDescription); - } - - @Override - public DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) { - return SCOUtils.getCommentProperty(tags, attributes); - } - - @Override - public Action getPreferredAction() { - if (getDisplayName().equals(org.sleuthkit.autopsy.datamodel.DirectoryNode.DOTDOTDIR) - || getDisplayName().equals("..")) { - return DirectoryTreeTopComponent.getOpenParentAction(); - } - return DirectoryTreeTopComponent.getOpenChildAction(getName()); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/FileNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/FileNode.java deleted file mode 100644 index dcb1d9a44db7db3ac4111fba5bf2f1a401ad2af5..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/FileNode.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.logging.Level; -import javax.swing.Action; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey; -import org.sleuthkit.autopsy.mainui.datamodel.MediaTypeUtils; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.LayoutFileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.SlackFileRowDTO; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; -import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; - -/** - * A node for representing an AbstractFile. - */ -public class FileNode extends BaseNode<SearchResultsDTO, FileRowDTO> implements SCOSupporter { - - private static final Logger logger = Logger.getLogger(FileNode.class.getName()); - - private final boolean directoryBrowseMode; - private final FileRowDTO fileData; - private final List<ColumnKey> columns; - - public FileNode(SearchResultsDTO results, FileRowDTO file, ExecutorService backgroundTasksPool) { - this(results, file, true, backgroundTasksPool); - } - - public FileNode(SearchResultsDTO results, FileRowDTO file, boolean directoryBrowseMode, ExecutorService backgroundTasksPool) { - super(Children.LEAF, ContentNodeUtil.getLookup(file.getAbstractFile()), results, file, backgroundTasksPool); - setIcon(file); - setName(ContentNodeUtil.getContentName(file.getId())); - setDisplayName(ContentNodeUtil.getContentDisplayName(file.getFileName())); - setShortDescription(ContentNodeUtil.getContentDisplayName(file.getFileName())); - this.directoryBrowseMode = directoryBrowseMode; - this.fileData = file; - this.columns = results.getColumns(); - } - - /* - * Sets the icon for the node, based on properties of the AbstractFile. - */ - void setIcon(FileRowDTO fileData) { - if (fileData.getAbstractFile().isDir()) { - // This is most likely a derived file directory - if (fileData.getAllocated()) { - this.setIconBaseWithExtension(NodeIconUtil.FOLDER.getPath()); - } else { - this.setIconBaseWithExtension(NodeIconUtil.DELETED_FOLDER.getPath()); - } - } else { - if (!fileData.getAllocated()) { - if (TSK_DB_FILES_TYPE_ENUM.CARVED.equals(fileData.getFileType())) { - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-x-icon-16.png"); //NON-NLS - } else { - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS - } - } else { - this.setIconBaseWithExtension(MediaTypeUtils.getIconForFileType(fileData.getExtensionMediaType())); - } - } - } - - @Override - public boolean supportsViewInTimeline() { - return true; - } - - @Override - public Optional<AbstractFile> getFileForViewInTimelineAction() { - return Optional.of(fileData.getAbstractFile()); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public Optional<Node> getExternalViewerActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsTableExtractActions() { - return true; - } - - @Override - public boolean supportsContentTagAction() { - return true; - } - - @Override - public Optional<AbstractFile> getFileForDirectoryBrowseMode() { - if (directoryBrowseMode) { - return Optional.of(fileData.getAbstractFile()); - } - - return Optional.empty(); - } - - @Override - public Optional<AbstractFile> getExtractArchiveWithPasswordActionFile() { - // TODO: See JIRA-8099 - AbstractFile file = this.fileData.getAbstractFile(); - boolean isArchive = FileTypeExtensions.getArchiveExtensions().contains("." + file.getNameExtension().toLowerCase()); - boolean encryptionDetected = false; - try { - encryptionDetected = isArchive && file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED).size() > 0; - } catch (TskCoreException ex) { - // TODO - } - - return encryptionDetected ? Optional.of(fileData.getAbstractFile()) : Optional.empty(); - } - - @Override - public Action[] getActions(boolean context) { - return ActionsFactory.getActions(this); - } - - @Override - public Optional<Content> getContent() { - return Optional.ofNullable(fileData.getAbstractFile()); - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } - - @Override - public Optional<List<Tag>> getAllTagsFromDatabase() { - try { - List<ContentTag> contentTags = ContentNodeUtil.getContentTagsFromDatabase(fileData.getAbstractFile()); - if (!contentTags.isEmpty()) { - List<Tag> tags = new ArrayList<>(); - tags.addAll(contentTags); - return Optional.of(tags); - } - - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to get content tags from database for AbstractFile id=" + fileData.getAbstractFile().getId(), ex); - } - return Optional.empty(); - } - - @NbBundle.Messages({ - "FileNode_createSheet_count.displayName=O", - "FileNode_createSheet_count_hashLookupNotRun_description=Hash lookup had not been run on this file when the column was populated", - "# {0} - occurrenceCount", - "FileNode_createSheet_count.description=There were {0} datasource(s) found with occurrences of the MD5 correlation value"}) - @Override - public Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attributeInstance, String defaultDescription) { - Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting - String description = defaultDescription; - try { - //don't perform the query if there is no correlation value - if (attributeInstance != null && StringUtils.isNotBlank(attributeInstance.getCorrelationValue())) { - count = CentralRepository.getInstance().getCountCasesWithOtherInstances(attributeInstance); - description = Bundle.FileNode_createSheet_count_description(count); - } else if (attributeInstance != null) { - description = Bundle.FileNode_createSheet_count_hashLookupNotRun_description(); - } - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error getting count of datasources with correlation attribute", ex); - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.SEVERE, "Unable to normalize data to get count of datasources with correlation attribute", ex); - } - return Pair.of(count, description); - } - - @NbBundle.Messages({ - "FileNode.createSheet.comment.displayName=C"}) - @Override - public DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) { - DataResultViewerTable.HasCommentStatus status = !tags.isEmpty() ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT; - for (Tag tag : tags) { - if (!StringUtils.isBlank(tag.getComment())) { - //if the tag is null or empty or contains just white space it will indicate there is not a comment - status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT; - break; - } - } - /* - * Is there a comment in the CR for anything that matches the value and - * type of the specified attributes. - */ - try { - if (CentralRepoDbUtil.commentExistsOnAttributes(attributes)) { - if (status == DataResultViewerTable.HasCommentStatus.TAG_COMMENT) { - status = DataResultViewerTable.HasCommentStatus.CR_AND_TAG_COMMENTS; - } else { - status = DataResultViewerTable.HasCommentStatus.CR_COMMENT; - } - } - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Attempted to Query CR for presence of comments in a file node and was unable to perform query, comment column will only reflect caseDB", ex); - } - return status; - } - - @Override - public Action getPreferredAction() { - return DirectoryTreeTopComponent.getOpenChildAction(getName()); - } - - /** - * A node for representing a LayoutFile. - */ - public static class LayoutFileNode extends FileNode { - - private final LayoutFileRowDTO layoutFileRow; - - public LayoutFileNode(SearchResultsDTO results, LayoutFileRowDTO file, ExecutorService backgroundTasksPool) { - super(results, file, true, backgroundTasksPool); - layoutFileRow = file; - } - - @Override - void setIcon(FileRowDTO fileData) { - LayoutFile lf = ((LayoutFileRowDTO) fileData).getLayoutFile(); - switch (lf.getType()) { - case CARVED: - setIconBaseWithExtension(NodeIconUtil.CARVED_FILE.getPath()); - break; - case LAYOUT_FILE: - if (lf.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - setIconBaseWithExtension(NodeIconUtil.DELETED_FILE.getPath()); - } else { - setIconBaseWithExtension(MediaTypeUtils.getIconForFileType(layoutFileRow.getExtensionMediaType())); - } - break; - default: - setIconBaseWithExtension(NodeIconUtil.DELETED_FILE.getPath()); - } - } - } - - /** - * A node for representing a SlackFile. - */ - public static class SlackFileNode extends FileNode { - - public SlackFileNode(SearchResultsDTO results, SlackFileRowDTO file, ExecutorService backgroundTasksPool) { - super(results, file, backgroundTasksPool); - } - - @Override - void setIcon(FileRowDTO fileData) { - AbstractFile file = fileData.getAbstractFile(); - if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) { - this.setIconBaseWithExtension(NodeIconUtil.CARVED_FILE.getPath()); //NON-NLS - } else { - this.setIconBaseWithExtension(NodeIconUtil.DELETED_FILE.getPath()); //NON-NLS - } - } else { - this.setIconBaseWithExtension(MediaTypeUtils.getIconForFileType(fileData.getExtensionMediaType())); - } - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/FileSystemFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/FileSystemFactory.java deleted file mode 100644 index 01d83eec35c2405446d5281d829560ecae21eb96..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/FileSystemFactory.java +++ /dev/null @@ -1,695 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Objects; -import java.util.Optional; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import javax.swing.Action; -import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; -import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; -import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction; -import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemContentSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemColumnUtils; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemTreeEvent; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemTreeItem; -import org.sleuthkit.autopsy.mainui.datamodel.MediaTypeUtils; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.CARVED_FILE; -import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.DELETED_FILE; -import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.DELETED_FOLDER; -import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.FOLDER; -import static org.sleuthkit.autopsy.mainui.nodes.TreeNode.getDefaultLookup; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; -import org.sleuthkit.autopsy.texttranslation.TextTranslationService; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LocalDirectory; -import org.sleuthkit.datamodel.LocalFilesDataSource; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskDataException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.VirtualDirectory; -import org.sleuthkit.datamodel.Volume; - -/** - * Factory for displaying content in the data source section of the tree. - */ -public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchParam> { - - private static final Logger logger = Logger.getLogger(FileSystemFactory.class.getName()); - - private Long contentId = null; - private Host host = null; - - /** - * Create a factory for a given parent content ID. - * - * @param contentId The object ID for this node - */ - public FileSystemFactory(Long contentId) { - this.contentId = contentId; - } - - /** - * Create a factory for a given parent Host. - * - * @param host The parent host for this node - */ - public FileSystemFactory(Host host) { - this.host = host; - } - - @Override - protected TreeResultsDTO<? extends FileSystemContentSearchParam> getChildResults() throws IllegalArgumentException, ExecutionException { - if (host == null) { - TreeResultsDTO<? extends FileSystemContentSearchParam> results = MainDAO.getInstance().getFileSystemDAO().getDisplayableContentChildren(contentId); - return results; - } else { - TreeResultsDTO<? extends FileSystemContentSearchParam> results = MainDAO.getInstance().getFileSystemDAO().getDataSourcesForHost(host); - return results; - } - } - - @Override - protected TreeNode<FileSystemContentSearchParam> createNewNode(TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> rowData) { - return getContentNode(rowData); - } - - /** - * Returns the relevant content tree node type based on the content of the - * row data. - * - * @param rowData The row data. - * - * @return The tree node. - */ - public static TreeNode<FileSystemContentSearchParam> getContentNode(TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> rowData) { - try { - Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(rowData.getSearchParams().getContentObjectId()); - return getContentNode(rowData, content); - } catch (NoCurrentCaseException ex) { - // Case was likely closed while nodes were being created - don't fill the log with errors. - return null; - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error creating new node for content with ID: " + rowData.getSearchParams().getContentObjectId(), ex); - return null; - } - } - - /** - * Returns the relevant content tree node type based on the content of the - * row data. - * - * @param rowData The row data. - * @param content The content. - * - * @return The tree node. - */ - public static TreeNode<FileSystemContentSearchParam> getContentNode(TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> rowData, Content content) { - if (content instanceof Image) { - return new ImageTreeNode((Image) content, rowData); - } else if (content instanceof Volume) { - return new VolumeTreeNode((Volume) content, rowData); - } else if (content instanceof Pool) { - return new PoolTreeNode((Pool) content, rowData); - } else if (content instanceof LocalFilesDataSource) { - return new LocalFilesDataSourceTreeNode((LocalFilesDataSource) content, rowData); - } else if (content instanceof LocalDirectory) { - return new LocalDirectoryTreeNode((LocalDirectory) content, rowData); - } else if (content instanceof VirtualDirectory) { - return new VirtualDirectoryTreeNode((VirtualDirectory) content, rowData); - } else if (content instanceof Volume) { - return new VolumeTreeNode((Volume) content, rowData); - } else if (content instanceof AbstractFile) { - AbstractFile file = (AbstractFile) content; - if (file.isDir()) { - return new DirectoryTreeNode(file, rowData); - } else { - return new FileTreeNode(file, rowData); - } - } else { - return new UnsupportedTreeNode(content, rowData); - } - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) { - if (treeEvt instanceof FileSystemTreeEvent) { - FileSystemTreeEvent fsTreeEvent = (FileSystemTreeEvent) treeEvt; - // when getContentObjectId == null, trigger refresh, otherwise, see if common parent - if (fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId() == null - || (Objects.equals(this.host, fsTreeEvent.getParentHost()) - && Objects.equals(this.contentId, fsTreeEvent.getParentContentId()))) { - - return fsTreeEvent.getItemRecord(); - } - } - - return null; - } - - @Override - public int compare(TreeItemDTO<? extends FileSystemContentSearchParam> o1, TreeItemDTO<? extends FileSystemContentSearchParam> o2) { - if (o1 instanceof FileSystemTreeItem && o2 instanceof FileSystemTreeItem) { - FileSystemTreeItem fs1 = (FileSystemTreeItem) o1; - FileSystemTreeItem fs2 = (FileSystemTreeItem) o2; - - // ordering taken from SELECT_FILES_BY_PARENT in SleuthkitCase - if ((fs1.getMetaType() != null) && (fs2.getMetaType() != null) - && (fs1.getMetaType().getValue() != fs2.getMetaType().getValue())) { - return -Short.compare(fs1.getMetaType().getValue(), fs2.getMetaType().getValue()); - } - - // The case where both meta types are null will fall through to the name comparison. - if (fs1.getMetaType() == null) { - if (fs2.getMetaType() != null) { - return -1; - } - } else if (fs2.getMetaType() != null) { - return 1; - } - } - return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName()); - } - - /** - * This factory is used to produce the single data source node under "Data - * Source Files" when grouping by person/host is selected. - */ - public static class DataSourceFactory extends TreeChildFactory<FileSystemContentSearchParam> { - - private final long dataSourceId; - - /** - * Create the factory for a given data source object ID. - * - * @param dataSourceId The data source object ID. - */ - public DataSourceFactory(long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeResultsDTO<? extends FileSystemContentSearchParam> getChildResults() throws IllegalArgumentException, ExecutionException { - // We're not really getting children here, just creating a node for the data source itself. - return MainDAO.getInstance().getFileSystemDAO().getSingleDataSource(dataSourceId); - } - - @Override - protected TreeNode<FileSystemContentSearchParam> createNewNode(TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> rowData) { - try { - DataSource ds = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSourceId); - if (ds instanceof Image) { - return new ImageTreeNode((Image) ds, rowData); - } else if (ds instanceof LocalFilesDataSource) { - return new LocalFilesDataSourceTreeNode((LocalFilesDataSource) ds, rowData); - } else { - logger.log(Level.SEVERE, "Unexpected data source type (ID: {0})", dataSourceId); - return null; - } - } catch (NoCurrentCaseException ex) { - // Case is likely closing - return null; - } catch (TskCoreException | TskDataException ex) { - logger.log(Level.SEVERE, "Error creating node from data source with ID: " + dataSourceId, ex); - return null; - } - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) { - if (treeEvt instanceof FileSystemTreeEvent) { - FileSystemTreeEvent fsTreeEvent = (FileSystemTreeEvent) treeEvt; - // when getContentObjectId == null, trigger refresh, otherwise, see if common parent - if (fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId() == null - || Objects.equals(fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId(), dataSourceId)) { - - return fsTreeEvent.getItemRecord(); - } - } - - return null; - - } - - @Override - public int compare(TreeItemDTO<? extends FileSystemContentSearchParam> o1, TreeItemDTO<? extends FileSystemContentSearchParam> o2) { - return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName()); - } - } - - /** - * Display name and count of a file system node in the tree. - */ - @NbBundle.Messages({ - "FileSystemFactory.FileSystemTreeNode.ExtractUnallocAction.text=Extract Unallocated Space to Single Files"}) - public abstract static class FileSystemTreeNode extends TreeNode<FileSystemContentSearchParam> { - - private String translatedSourceName; - - protected FileSystemTreeNode(String icon, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData, Children children, Lookup lookup) { - super(ContentNodeUtil.getContentName(itemData.getSearchParams().getContentObjectId()), icon, itemData, children, lookup); - startTranslationTask(); - } - - protected static Children createChildrenForContent(Long contentId) { - try { - if (FileSystemColumnUtils.getVisibleTreeNodeChildren(contentId).isEmpty()) { - return Children.LEAF; - } else { - return Children.create(new FileSystemFactory(contentId), true); - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error creating children for content with ID: " + contentId, ex); - return Children.LEAF; - } catch (NoCurrentCaseException ex) { - return Children.LEAF; - } - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - getItemData().getSearchParams().setNodeSelectionInfo(getNodeSelectionInfo()); - dataResultPanel.displayFileSystemContent(this.getItemData().getSearchParams()); - } - - public abstract Node clone(); - - @Override - public Action[] getActions(boolean context) { - return ActionsFactory.getActions(this); - } - - @Override - protected String getDisplayNameString(String displayName, TreeResultsDTO.TreeDisplayCount displayCount) { - // lock to prevent simultaneous reads and updates of translatedSourceName - synchronized (this) { - // defer to translated source name if present; otherwise use current display name - String displayNameToUse = this.translatedSourceName != null ? this.translatedSourceName : displayName; - return super.getDisplayNameString(displayNameToUse, displayCount); - } - } - - private void startTranslationTask() { - if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { - /* - * If machine translation is configured, add the original name - * of the of the source content of the artifact represented by - * this node to the sheet. - */ - - if (translatedSourceName == null) { - PropertyChangeListener listener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(FileNameTransTask.getPropertyName())) { - // lock to prevent simultaneous reads and updates of translatedSourceName - synchronized (FileSystemTreeNode.this) { - FileSystemTreeNode.this.translatedSourceName = evt.getNewValue().toString(); - } - - TreeItemDTO<? extends FileSystemContentSearchParam> itemData = FileSystemTreeNode.this.getItemData(); - FileSystemTreeNode.this.setDisplayName(getDisplayNameString(itemData.getDisplayName(), itemData.getDisplayCount())); - } - } - }; - /* - * NOTE: The task makes its own weak reference to the - * listener. - */ - new FileNameTransTask(getItemData().getDisplayName(), this, listener).submit(); - } - } - } - } - - static class ImageTreeNode extends FileSystemTreeNode { - - Image image; - - ImageTreeNode(Image image, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(NodeIconUtil.IMAGE.getPath(), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(image)); - this.image = image; - } - - public Node clone() { - return new ImageTreeNode(image, getItemData()); - } - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionsFactory.ActionGroup group = new ActionsFactory.ActionGroup(); - group.add(new ExtractUnallocAction( - Bundle.FileSystemFactory_FileSystemTreeNode_ExtractUnallocAction_text(), image)); - return Optional.of(group); - } - - @Override - public Optional<Content> getDataSourceForActions() { - return Optional.of(image); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public boolean supportsFileSearchAction() { - return true; - } - } - - static class VolumeTreeNode extends FileSystemTreeNode { - - Volume volume; - - VolumeTreeNode(Volume volume, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(NodeIconUtil.VOLUME.getPath(), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(volume)); - this.volume = volume; - } - - public Node clone() { - return new VolumeTreeNode(volume, getItemData()); - } - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionsFactory.ActionGroup group = new ActionsFactory.ActionGroup(); - group.add(new ExtractUnallocAction( - Bundle.VolumnNode_ExtractUnallocAction_text(), volume)); - group.add(new FileSystemDetailsAction(volume)); - return Optional.of(group); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - } - - static class PoolTreeNode extends FileSystemTreeNode { - - Pool pool; - - PoolTreeNode(Pool pool, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(NodeIconUtil.POOL.getPath(), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(pool)); - this.pool = pool; - } - - public Node clone() { - return new PoolTreeNode(pool, getItemData()); - } - } - - static class DirectoryTreeNode extends FileSystemTreeNode { - - AbstractFile dir; - - DirectoryTreeNode(AbstractFile dir, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(getDirectoryIcon(dir), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(dir)); - this.dir = dir; - } - - private static String getDirectoryIcon(AbstractFile dir) { - if (dir.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - return DELETED_FOLDER.getPath(); - } else { - return FOLDER.getPath(); - } - } - - public Node clone() { - return new DirectoryTreeNode(dir, getItemData()); - } - - @Override - public boolean supportsViewInTimeline() { - return true; - } - - @Override - public Optional<AbstractFile> getFileForViewInTimelineAction() { - return Optional.of(dir); - } - - @Override - public boolean supportsTreeExtractActions() { - return true; - } - - @Override - public Optional<Content> getContentForRunIngestionModuleAction() { - return Optional.of(dir); - } - - @Override - public boolean supportsContentTagAction() { - return true; - } - } - - static abstract class SpecialDirectoryTreeNode extends FileSystemTreeNode { - - AbstractFile dir; - - protected SpecialDirectoryTreeNode(AbstractFile dir, String icon, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData, Children children, Lookup lookup) { - super(icon, itemData, children, lookup); - this.dir = dir; - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public boolean supportsTreeExtractActions() { - return true; - } - - @Override - public Optional<Content> getContentForRunIngestionModuleAction() { - return Optional.of(dir); - } - - @Override - public boolean supportsFileSearchAction() { - return true; - } - } - - static class LocalDirectoryTreeNode extends SpecialDirectoryTreeNode { - - LocalDirectoryTreeNode(AbstractFile dir, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(dir, - NodeIconUtil.FOLDER.getPath(), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(dir)); - } - - public Node clone() { - return new LocalDirectoryTreeNode(dir, getItemData()); - } - - @Override - public boolean supportsContentTagAction() { - return true; - } - } - - static class LocalFilesDataSourceTreeNode extends SpecialDirectoryTreeNode { - - LocalFilesDataSourceTreeNode(AbstractFile localFilesDataSource, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(localFilesDataSource, - NodeIconUtil.VOLUME.getPath(), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(localFilesDataSource)); - } - - public Node clone() { - return new LocalFilesDataSourceTreeNode(dir, getItemData()); - } - - @Override - public Optional<Content> getDataSourceForActions() { - return Optional.of(dir); - } - } - - static class VirtualDirectoryTreeNode extends SpecialDirectoryTreeNode { - - VirtualDirectoryTreeNode(AbstractFile dir, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(dir, - NodeIconUtil.VIRTUAL_DIRECTORY.getPath(), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(dir)); - } - - public Node clone() { - return new VirtualDirectoryTreeNode(dir, getItemData()); - } - - @Override - public boolean supportsFileSearchAction() { - return true; - } - } - - static class FileTreeNode extends FileSystemTreeNode { - - AbstractFile file; - - FileTreeNode(AbstractFile file, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(getFileIcon(file), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - ContentNodeUtil.getLookup(file)); - this.file = file; - } - - public Node clone() { - return new FileTreeNode(file, getItemData()); - } - - private static String getFileIcon(AbstractFile file) { - if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - if (file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.CARVED)) { - return CARVED_FILE.getPath(); - } else { - return DELETED_FILE.getPath(); - } - } else { - MediaTypeUtils.ExtensionMediaType mediaType = MediaTypeUtils.getExtensionMediaType(file.getNameExtension()); - return MediaTypeUtils.getIconForFileType(mediaType); - } - } - - @Override - public boolean supportsViewInTimeline() { - return true; - } - - @Override - public Optional<AbstractFile> getFileForViewInTimelineAction() { - return Optional.of(file); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public Optional<Node> getExternalViewerActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsTreeExtractActions() { - return true; - } - - @Override - public boolean supportsContentTagAction() { - return true; - } - - @Override - public Optional<AbstractFile> getFileForDirectoryBrowseMode() { - return Optional.of(file); - } - - @Override - public Optional<AbstractFile> getExtractArchiveWithPasswordActionFile() { - // TODO: See JIRA-8099 - boolean isArchive = FileTypeExtensions.getArchiveExtensions().contains("." + file.getNameExtension().toLowerCase()); - boolean encryptionDetected = false; - try { - encryptionDetected = isArchive && file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED).size() > 0; - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error loading artifacts for file with ID: " + file.getId(), ex); - } - - return encryptionDetected ? Optional.of(file) : Optional.empty(); - } - } - - @NbBundle.Messages({ - "FileSystemFactory.UnsupportedTreeNode.displayName=Unsupported Content",}) - static class UnsupportedTreeNode extends FileSystemTreeNode { - - Content content; - - UnsupportedTreeNode(Content content, TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - super(NodeIconUtil.FILE.getPath(), - itemData, - createChildrenForContent(itemData.getSearchParams().getContentObjectId()), - getDefaultLookup(itemData)); - this.content = content; - } - - public Node clone() { - return new UnsupportedTreeNode(content, getItemData()); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ImageNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ImageNode.java deleted file mode 100755 index 69434ba4f97363807ad6e9badf080fcdbcf77a3d..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ImageNode.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.ImageRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.datamodel.Content; - -/** - * A node representing an Image. - */ -public class ImageNode extends BaseNode<SearchResultsDTO, ImageRowDTO> implements SCOSupporter { - - /** - * Simple node constructor. - * - * @param results The search result DTO. - * @param row The table row DTO. - */ - public ImageNode(SearchResultsDTO results, ImageRowDTO row, ExecutorService backgroundTasksPool) { - super(Children.LEAF, ContentNodeUtil.getLookup(row.getContent()), results, row, backgroundTasksPool); - setName(ContentNodeUtil.getContentName(row.getContent().getId())); - setDisplayName(row.getContent().getName()); - setShortDescription(row.getContent().getName()); - setIconBaseWithExtension(NodeIconUtil.IMAGE.getPath()); //NON-NLS - } - - @NbBundle.Messages({ - "ImageNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files" - }) - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionsFactory.ActionGroup group = new ActionsFactory.ActionGroup(); - group.add(new ExtractUnallocAction( - Bundle.ImageNode_ExtractUnallocAction_text(), getRowDTO().getContent())); - return Optional.of(group); - } - - @Override - public Optional<Content> getDataSourceForActions() { - return Optional.of(getRowDTO().getContent()); - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Content> getContent() { - return Optional.ofNullable(getRowDTO().getContent()); - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/NodeIconUtil.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/NodeIconUtil.java deleted file mode 100644 index 059db7b50c9391f17ef3e47e51181a2498170b5d..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/NodeIconUtil.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.mainui.nodes; - -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LocalFilesDataSource; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.Volume; - -/** - * Consolidates node paths shared between the result view table and the tree. - */ -class NodeIconUtil { - - final static NodeIconUtil FOLDER = new NodeIconUtil("org/sleuthkit/autopsy/images/Folder-icon.png"); - final static NodeIconUtil DELETED_FOLDER = new NodeIconUtil("org/sleuthkit/autopsy/images/folder-icon-deleted.png"); - final static NodeIconUtil VIRTUAL_DIRECTORY = new NodeIconUtil("org/sleuthkit/autopsy/images/folder-icon-virtual.png"); - final static NodeIconUtil CARVED_FILE = new NodeIconUtil("org/sleuthkit/autopsy/images/carved-file-x-icon-16.png"); - final static NodeIconUtil DELETED_FILE = new NodeIconUtil("org/sleuthkit/autopsy/images/file-icon-deleted.png"); - final static NodeIconUtil IMAGE = new NodeIconUtil("org/sleuthkit/autopsy/images/hard-drive-icon.jpg"); - final static NodeIconUtil VOLUME = new NodeIconUtil("org/sleuthkit/autopsy/images/vol-icon.png"); - final static NodeIconUtil POOL = new NodeIconUtil("org/sleuthkit/autopsy/images/pool-icon.png"); - final static NodeIconUtil FILE = new NodeIconUtil("org/sleuthkit/autopsy/images/file-icon.png"); - final static NodeIconUtil LOCAL_FILES_DATA_SOURCE = new NodeIconUtil("org/sleuthkit/autopsy/images/fileset-icon-16.png"); - //final static NodeIconUtil = new NodeIconUtil(""); - - private final String iconPath; - - private NodeIconUtil(String path) { - this.iconPath = path; - } - - String getPath() { - return iconPath; - } - - public static String getPathForContent(Content c) { - if (c instanceof Image) { - return IMAGE.getPath(); - } else if (c instanceof LocalFilesDataSource) { - return LOCAL_FILES_DATA_SOURCE.getPath(); - } else if (c instanceof Volume) { - return VOLUME.getPath(); - } else if (c instanceof Pool) { - return POOL.getPath(); - } else if (c instanceof AbstractFile) { - AbstractFile file = (AbstractFile) c; - if (((AbstractFile) c).isDir()) { - if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - return DELETED_FOLDER.getPath(); - } else { - return FOLDER.getPath(); - } - } else { - if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - if (file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.CARVED)) { - return CARVED_FILE.getPath(); - } else { - return DELETED_FILE.getPath(); - } - } else { - return FILE.getPath(); - } - } - } - return FILE.getPath(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/OsAccountNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/OsAccountNode.java deleted file mode 100755 index fe40d799c669f37c65fce7c47e8cbcd125f33638..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/OsAccountNode.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.stream.Collectors; -import java.util.concurrent.FutureTask; -import java.util.logging.Level; -import javax.swing.SwingUtilities; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.datamodel.TskContentItem; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.OsAccountRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.OsAccountsDAO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.autopsy.mainui.sco.SCOUtils; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.OsAccountRealm; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * A node representing a row for an OsAccount in the results table. - */ -public class OsAccountNode extends BaseNode<SearchResultsDTO, OsAccountRowDTO> implements SCOSupporter { - - private static final Logger logger = Logger.getLogger(OsAccountNode.class.getName()); - private static final String ICON_PATH = "org/sleuthkit/autopsy/images/os-account.png"; - - private FutureTask<String> realmFutureTask = null; - - public OsAccountNode(SearchResultsDTO results, OsAccountRowDTO rowData, ExecutorService backgroundTasksPool) { - super(Children.LEAF, - Lookups.fixed(rowData.getContent(), new TskContentItem<>(rowData.getContent())), - results, - rowData, - backgroundTasksPool); - String name = rowData.getContent().getName(); - setName(ContentNodeUtil.getContentName(rowData.getContent().getId())); - setDisplayName(name); - setShortDescription(name); - setIconBaseWithExtension(ICON_PATH); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsTableExtractActions() { - return true; - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - updateRealmColumns(); - return sheet; - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } - - /** - * Start a background task that will update the host name, realm name, and realm scope columns when complete. - */ - private void updateRealmColumns() { - if (realmFutureTask != null && !realmFutureTask.isDone()) { - realmFutureTask.cancel(true); - realmFutureTask = null; - } - - ExecutorService threadPool = getTaskPool(); - if ((threadPool != null && !threadPool.isShutdown() && !threadPool.isTerminated()) && (realmFutureTask == null || realmFutureTask.isDone())) { - realmFutureTask = new FutureTask<>(new RealmFetcher<>(new WeakReference<>(this)), ""); - threadPool.submit(realmFutureTask); - } - } - @Override - public Optional<Content> getContent() { - return Optional.ofNullable(getRowDTO().getContent()); - } - - @Override - public Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute, String defaultDescription) { - return SCOUtils.getCountPropertyAndDescription(attribute, defaultDescription); - } - - @Override - public DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) { - return SCOUtils.getCommentProperty(tags, attributes); - } - - static class RealmFetcher<T extends Content> implements Runnable { - - private final WeakReference<OsAccountNode> weakSupporterRef; - private static final Logger logger = Logger.getLogger(RealmFetcher.class.getName()); - - /** - * Construct a new RealmFetcher. - * - * @param weakSupporterRef A weak reference to a SCOSupporter. - */ - RealmFetcher(WeakReference<OsAccountNode> weakSupporterRef) { - this.weakSupporterRef = weakSupporterRef; - } - - @Override - public void run() { - try { - RealmData data = doInBackground(); - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - RealmFetcher.done(data, weakSupporterRef.get()); - } - }); - - } catch (Exception ex) { - logger.log(Level.SEVERE, "An exception occurred while trying to update the the SCO data", ex); - } - } - - private RealmData doInBackground() throws Exception { - OsAccountNode osAccountNode = weakSupporterRef.get(); - Optional<Content> content = osAccountNode.getContent(); - if (! content.isPresent() || ! (content.get() instanceof OsAccount)) { - return null; - } - OsAccount osAcct = (OsAccount) content.get(); - - try { - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - long realmId = osAcct.getRealmId(); - OsAccountRealm realm = skCase.getOsAccountRealmManager().getRealmByRealmId(realmId); - List<Host> hosts = skCase.getOsAccountManager().getHosts(osAcct); - return new RealmData(hosts, realm); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error loading host/realm information for OsAccount " + osAcct.getName() + "(ID: " + osAcct.getId() + ")", ex); - return null; - } catch (NoCurrentCaseException ex) { - // Case is closing - return null; - } - } - - @NbBundle.Messages({ - "RealmFetcher_nodescription_text=No description" - }) - private static void done(RealmData data, OsAccountNode osAccountNode) { - if (data == null || osAccountNode == null) { - return; - } - - List<NodeProperty<?>> props = new ArrayList<>(); - - List<Host> hosts = data.getHosts(); - if (!hosts.isEmpty()) { - String hostsString = hosts.stream() - .map(h -> h.getName().trim()) - .distinct() - .sorted((a, b) -> a.compareToIgnoreCase(b)) - .collect(Collectors.joining(", ")); - - props.add(new NodeProperty<>( - OsAccountsDAO.HOST_COLUMN_NAME, - OsAccountsDAO.HOST_COLUMN_NAME, - Bundle.RealmFetcher_nodescription_text(), - hostsString)); - } - - String scopeName = data.getRealm().getScope().getName(); - if (StringUtils.isNotBlank(scopeName)) { - props.add(new NodeProperty<>( - OsAccountsDAO.SCOPE_COLUMN_NAME, - OsAccountsDAO.SCOPE_COLUMN_NAME, - Bundle.RealmFetcher_nodescription_text(), - scopeName)); - } - - List<String> realmNames = data.getRealm().getRealmNames(); - if (!realmNames.isEmpty()) { - String realmNamesStr = realmNames.stream() - .map(String::trim) - .distinct() - .sorted((a, b) -> a.compareToIgnoreCase(b)) - .collect(Collectors.joining(", ")); - - props.add(new NodeProperty<>( - OsAccountsDAO.REALM_COLUMN_NAME, - OsAccountsDAO.REALM_COLUMN_NAME, - Bundle.RealmFetcher_nodescription_text(), - realmNamesStr)); - } - - if (!props.isEmpty()) { - osAccountNode.updateSheet(props); - } - } - } - - /** - * Class for passing the realm data. - */ - private static class RealmData { - - private final List<Host> hosts; - private final OsAccountRealm realm; - - /** - * Construct a new RealmData object. - * - * @param scoreAndDescription - * @param comment - * @param countAndDescription - */ - RealmData(List<Host> hosts, OsAccountRealm realm) { - this.hosts = hosts; - this.realm = realm; - } - - List<Host> getHosts() { - return hosts; - } - - OsAccountRealm getRealm() { - return realm; - } - } -} - diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/PoolNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/PoolNode.java deleted file mode 100755 index 45f2915d473ef6bfb3b541c1942219f3c3547322..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/PoolNode.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import org.openide.nodes.Children; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.datamodel.TskContentItem; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.PoolRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.datamodel.Content; - -/** - * A node representing a Pool. - */ -public class PoolNode extends BaseNode<SearchResultsDTO, PoolRowDTO> implements SCOSupporter { - - /** - * Pool node constructor. - * - * @param results Search Result DTO. - * @param row Pool table row DTO. - */ - public PoolNode(SearchResultsDTO results, PoolRowDTO row, ExecutorService backgroundTasksPool) { - super(Children.LEAF, - Lookups.fixed(row.getContent(), new TskContentItem<>(row.getContent())), - results, row, backgroundTasksPool); - - String name = row.getContent().getType().getName(); - setName(ContentNodeUtil.getContentName(row.getContent().getId())); - setDisplayName(name); - setShortDescription(name); - setIconBaseWithExtension(NodeIconUtil.POOL.getPath()); - } - - @Override - public Optional<Content> getContent() { - return Optional.ofNullable(getRowDTO().getContent()); - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ReportNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ReportNode.java deleted file mode 100644 index 649e1446edd7d9e7fe62177eaa84359a2dc0964c..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ReportNode.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import org.openide.nodes.Children; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.mainui.datamodel.ReportsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.datamodel.Report; - -/** - * A node representing a single row when viewing credit cards by file. - */ -public class ReportNode extends BaseNode<SearchResultsDTO, ReportsRowDTO> { - public ReportNode(SearchResultsDTO results, ReportsRowDTO rowData, ExecutorService backgroundTasksPool) { - super(Children.LEAF, - Lookups.fixed(rowData.getReport()), - results, - rowData, - backgroundTasksPool); - - setName(rowData.getReportName() + rowData.getId()); - setDisplayName(rowData.getSourceModuleName()); - setIconBaseWithExtension("org/sleuthkit/autopsy/images/report_16.png"); - } - - @Override - public Optional<Report> getReport() { - return Optional.ofNullable(this.getRowDTO().getReport()); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/RootFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/RootFactory.java deleted file mode 100644 index 1b923c0d3593177b241e14eac62144870f964223..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/RootFactory.java +++ /dev/null @@ -1,759 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.stream.Collectors; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.util.NbBundle.Messages; -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.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.FileSystemContentSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.HostPersonDAO; -import org.sleuthkit.autopsy.mainui.datamodel.HostSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.OsAccountsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.PersonSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.ReportsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.HostPersonEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.ScoreTypeFactory.ScoreParentNode; -import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskDataException; - -/** - * - * Root tree view factories. - */ -public class RootFactory { - - /** - * @return The root children to be displayed in the tree. - */ - public static Children getRootChildren() { - if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { - return Children.create(new HostPersonRootFactory(), true); - } else { - return new DefaultViewRootChildren(); - } - } - - /** - * Returns a string of the safely converted long to be used in a name id. - * - * @param l The number or null. - * - * @return The safely stringified number. - */ - private static String getLongString(Long l) { - return l == null ? "" : l.toString(); - } - - /** - * Factory for populating child nodes in a tree based on TreeResultsDTO - */ - static class HostPersonRootFactory extends TreeChildFactory<Object> { - - private static final Logger logger = Logger.getLogger(HostPersonRootFactory.class.getName()); - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof HostPersonEvent) { - super.update(); - return; - } - } - } - - @SuppressWarnings("unchecked") - private TreeNode<Object> downCastNode(TreeNode<? extends Object> orig) { - return (TreeNode<Object>) orig; - } - - @Override - @SuppressWarnings("unchecked") - protected TreeNode<Object> createNewNode(TreeItemDTO<? extends Object> rowData) { - if (rowData.getSearchParams() instanceof HostSearchParams) { - return downCastNode(HostNode.getPersonHostViewNode((TreeItemDTO<? extends HostSearchParams>) rowData)); - } else if (rowData.getSearchParams() instanceof PersonSearchParams) { - return downCastNode(new PersonNode((TreeItemDTO<? extends PersonSearchParams>) rowData)); - } else if (rowData.getSearchParams() instanceof ReportsSearchParams) { - return downCastNode(new ReportsRootNode()); - } else { - return null; - } - } - - /** - * This creates a modified tree item dto where the id has a prefix (to differentiate switches between person and host). - * @param orig The original tree item. - * @return The modified tree item - */ - private TreeItemDTO<Object> getModifiedIdTreeItem(TreeItemDTO<? extends Object> orig) { - return new TreeItemDTO<Object>( - orig.getTypeId(), - orig.getSearchParams(), - orig.getTypeId() + "_" + orig.getId(), - orig.getDisplayName(), - orig.getDisplayCount() - ); - } - - @Override - protected TreeResultsDTO<? extends Object> getChildResults() throws IllegalArgumentException, ExecutionException { - List<TreeItemDTO<Object>> toReturn = new ArrayList<>(); - try { - TreeResultsDTO<? extends PersonSearchParams> persons = MainDAO.getInstance().getHostPersonDAO().getAllPersons(); - if (persons.getItems().isEmpty() || (persons.getItems().size() == 1 && persons.getItems().get(0).getSearchParams().getPerson() == null)) { - toReturn.addAll(MainDAO.getInstance().getHostPersonDAO().getAllHosts().getItems().stream() - .map(this::getModifiedIdTreeItem) - .collect(Collectors.toList())); - } else { - toReturn.addAll(persons.getItems().stream() - .map(this::getModifiedIdTreeItem) - .collect(Collectors.toList())); - } - } catch (ExecutionException | IllegalArgumentException ex) { - logger.log(Level.WARNING, "Error acquiring top-level host/person data", ex); - } - - toReturn.add(new TreeItemDTO<Object>( - ReportsSearchParams.getTypeId(), - ReportsSearchParams.getInstance(), - ReportsSearchParams.class.getSimpleName(), - Bundle.RootFactory_ReportsRootNode_displayName(), - TreeDisplayCount.NOT_SHOWN)); - - return new TreeResultsDTO<Object>(toReturn); - } - - @Override - protected TreeItemDTO<? extends Object> getOrCreateRelevantChild(TreeEvent treeEvt) { - // all events will be handled with a full refresh - return null; - } - - @Override - public int compare(TreeItemDTO<? extends Object> o1, TreeItemDTO<? extends Object> o2) { - // all events will be handled with a full refresh - return 0; - } - } - - /** - * The root children for the default view preference. - */ - public static class DefaultViewRootChildren extends Children.Array { - - /** - * Main constructor. - */ - public DefaultViewRootChildren() { - super(Arrays.asList( - new AllDataSourcesNode(), - new ViewsRootNode(null), - new DataArtifactsRootNode(null), - new AnalysisResultsRootNode(null), - new OsAccountsRootNode(null), - new TagsRootNode(null), - new ScoreParentNode(null), - new ReportsRootNode() - )); - } - } - - /** - * Node in default view displaying all hosts/data sources. - */ - @Messages({"RootFactory_AllDataSourcesNode_displayName=Data Sources"}) - public static class AllDataSourcesNode extends StaticTreeNode { - - private static final String NAME_ID = "ALL_DATA_SOURCES"; - - /** - * Returns the name identifier of this node. - * - * @return The name identifier. - */ - public static final String getNameIdentifier() { - return NAME_ID; - } - - /** - * Main constructor. - */ - public AllDataSourcesNode() { - super(NAME_ID, - Bundle.RootFactory_AllDataSourcesNode_displayName(), - "org/sleuthkit/autopsy/images/image.png", - new AllHostsFactory()); - } - } - - /** - * A person node. - */ - @Messages(value = {"PersonNode_unknownPersonNode_title=Unknown Persons"}) - public static class PersonNode extends TreeNode<PersonSearchParams> { - - /** - * Returns the name prefix of this node type. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return PersonSearchParams.getTypeId(); - } - - /** - * Returns the id of an unknown persons node. This can be used with a - * node lookup. - * - * @return The id of an unknown persons node. - */ - public static String getUnknownPersonId() { - return Bundle.PersonNode_unknownPersonNode_title(); - } - - /** - * Main constructor. - * - * @param itemData The row data for the person. - */ - public PersonNode(TreeResultsDTO.TreeItemDTO<? extends PersonSearchParams> itemData) { - super(PersonSearchParams.getTypeId() + getLongString( - itemData.getSearchParams().getPerson() == null - ? 0 - : itemData.getSearchParams().getPerson().getPersonId()), - "org/sleuthkit/autopsy/images/person.png", - itemData, - Children.create(new HostFactory(itemData.getSearchParams().getPerson()), true), - itemData.getSearchParams().getPerson() != null - ? Lookups.fixed(itemData.getSearchParams(), itemData.getSearchParams().getPerson()) - : Lookups.fixed(itemData.getSearchParams(), HostPersonDAO.getUnknownPersonsName())); - } - - @Override - public Optional<Person> getPerson() { - return Optional.ofNullable(getItemData().getSearchParams().getPerson()); - } - } - - /** - * Factory displaying all hosts in default view. - */ - public static class AllHostsFactory extends BaseHostFactory { - - @Override - protected TreeResultsDTO<? extends HostSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getHostPersonDAO().getAllHosts(); - } - - @Override - protected TreeNode<HostSearchParams> createNewNode(TreeItemDTO<? extends HostSearchParams> rowData) { - return HostNode.getDefaultViewNode(rowData); - } - } - - /** - * Factory displaying hosts belonging to a person (or null). - */ - public static class HostFactory extends BaseHostFactory { - - private final Person parentPerson; - - /** - * Main constructor. - * - * @param parentPerson The person whose hosts will be shown. Null - * indicates showing any host with no person - * associated. - */ - public HostFactory(Person parentPerson) { - this.parentPerson = parentPerson; - } - - @Override - protected TreeResultsDTO<? extends HostSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getHostPersonDAO().getHosts(parentPerson); - } - - @Override - protected TreeNode<HostSearchParams> createNewNode(TreeItemDTO<? extends HostSearchParams> rowData) { - return HostNode.getPersonHostViewNode(rowData); - } - } - - /** - * Base factory for displaying hosts. - */ - public abstract static class BaseHostFactory extends TreeChildFactory<HostSearchParams> { - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof HostPersonEvent) { - super.update(); - return; - } - } - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends HostSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - return null; - } - - @Override - public int compare(TreeResultsDTO.TreeItemDTO<? extends HostSearchParams> o1, TreeResultsDTO.TreeItemDTO<? extends HostSearchParams> o2) { - return Comparator.comparing((TreeResultsDTO.TreeItemDTO<? extends HostSearchParams> h) -> h.getSearchParams().getHost().getName()).compare(o1, o2); - } - } - - /** - * Node for a host. - */ - public static class HostNode extends TreeNode<HostSearchParams> { - - /** - * Returns the name prefix of this node. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return HostSearchParams.getTypeId(); - } - - /** - * Returns a host node whose children will be used in the default view. - * - * @param itemData The data associated with the host. - * - * @return A host node. - */ - public static HostNode getDefaultViewNode(TreeResultsDTO.TreeItemDTO<? extends HostSearchParams> itemData) { - return new HostNode(itemData, Children.create(new FileSystemFactory(itemData.getSearchParams().getHost()), true)); - } - - /** - * Returns a host node whose children will be used in the person/host - * view. - * - * @param itemData The data associated with the host. - * - * @return A host node. - */ - public static HostNode getPersonHostViewNode(TreeResultsDTO.TreeItemDTO<? extends HostSearchParams> itemData) { - return new HostNode(itemData, Children.create(new DataSourceGroupedFactory(itemData.getSearchParams().getHost()), true)); - } - - /** - * Private constructor. - * - * @param itemData The data for the host. - * @param children The children to use with this host. - */ - private HostNode(TreeResultsDTO.TreeItemDTO<? extends HostSearchParams> itemData, Children children) { - super(HostSearchParams.getTypeId() + "_" + getLongString(itemData.getSearchParams().getHost().getHostId()), - "org/sleuthkit/autopsy/images/host.png", - itemData, - children, - Lookups.fixed(itemData.getSearchParams(), itemData.getSearchParams().getHost())); - } - - @Override - public Optional<Host> getHost() { - return Optional.of(getItemData().getSearchParams().getHost()); - } - } - - /** - * The factory to use to create data source grouping nodes to display in the - * host/person view. - */ - public static class DataSourceGroupedFactory extends FileSystemFactory { - - /** - * Main constructor. - * - * @param host The parent host. - */ - public DataSourceGroupedFactory(Host host) { - super(host); - } - - @Override - protected TreeNode<FileSystemContentSearchParam> createNewNode(TreeItemDTO<? extends FileSystemContentSearchParam> rowData) { - return DataSourceGroupedNode.getInstance(rowData); - } - - } - - /** - * A data source grouping node to display in host/person view. - */ - public static class DataSourceGroupedNode extends TreeNode<FileSystemContentSearchParam> { - - private static final Logger logger = Logger.getLogger(DataSourceGroupedNode.class.getName()); - - private static final String NAME_PREFIX = "DATA_SOURCE_GROUPED"; - - /** - * Returns the name prefix of this node. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return NAME_PREFIX; - } - - /** - * Returns an instance of the data source grouping node. - * - * @param itemData The row data. - * - * @return The node. - */ - public static DataSourceGroupedNode getInstance(TreeItemDTO<? extends FileSystemContentSearchParam> itemData) { - long dataSourceId = itemData.getSearchParams().getContentObjectId(); - try { - - DataSource ds = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSourceId); - return (ds == null) ? null : new DataSourceGroupedNode(itemData, ds); - } catch (NoCurrentCaseException ex) { - // Case is likely closing - return null; - } catch (TskCoreException | TskDataException ex) { - logger.log(Level.SEVERE, "Error creating node from data source with ID: " + dataSourceId, ex); - return null; - } - } - - /** - * Private constructor. - * - * @param itemData The row data. - * @param dataSource The relevant data source instance. - */ - private DataSourceGroupedNode(TreeItemDTO<? extends FileSystemContentSearchParam> itemData, DataSource dataSource) { - super(NAME_PREFIX + "_" + getLongString(dataSource.getId()), - dataSource instanceof Image - ? "org/sleuthkit/autopsy/images/image.png" - : "org/sleuthkit/autopsy/images/fileset-icon-16.png", - itemData, - new DataSourceGroupedChildren(itemData, dataSource), - Lookups.singleton(dataSource)); - } - } - - /** - * Shows all content related to a data source in host/person view. - */ - public static class DataSourceGroupedChildren extends Children.Array { - - /** - * Main constructor. - * - * @param itemData The row data. - * @param dataSource The data source. - */ - public DataSourceGroupedChildren(TreeItemDTO<? extends FileSystemContentSearchParam> itemData, DataSource dataSource) { - this(itemData, dataSource, dataSource.getId()); - } - - private DataSourceGroupedChildren(TreeItemDTO<? extends FileSystemContentSearchParam> itemData, DataSource dataSource, long dataSourceObjId) { - super(Arrays.asList( - new DataSourceFilesNode(itemData, dataSource), - new ViewsRootNode(dataSourceObjId), - new DataArtifactsRootNode(dataSourceObjId), - new AnalysisResultsRootNode(dataSourceObjId), - new OsAccountsRootNode(dataSourceObjId), - new ScoreParentNode(dataSourceObjId), - new TagsRootNode(dataSourceObjId) - )); - } - } - - /** - * Node for showing data source files in person/host view. - */ - @Messages({"RootFactory_DataSourceFilesNode_displayName=Data Source Files"}) - public static class DataSourceFilesNode extends StaticTreeNode { - - private static final String NAME_PREFIX = "DATA_SOURCE_FILES"; - - /** - * Returns the name prefix of this node. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return NAME_PREFIX; - } - - /** - * Main constructor. - * - * @param itemData The row data. - * @param dataSource The data source. - */ - public DataSourceFilesNode(TreeItemDTO<? extends FileSystemContentSearchParam> itemData, DataSource dataSource) { - super(NAME_PREFIX + "_" + getLongString(dataSource.getId()), - Bundle.RootFactory_DataSourceFilesNode_displayName(), - "org/sleuthkit/autopsy/images/image.png", - new DataSourcesChildren(FileSystemFactory.getContentNode(itemData, dataSource)) - ); - } - } - - /** - * A children object with just the specified node. - */ - public static class DataSourcesChildren extends Children.Array { - - /** - * Main constructor. - * - * @param node The node of this children object. - */ - public DataSourcesChildren(Node node) { - super(Arrays.asList(node)); - } - - } - - /** - * Root node for displaying "View" for file types. - */ - @Messages({"RootFactory_ViewsRootNode_displayName=Views"}) - public static class ViewsRootNode extends StaticTreeNode { - - private static final String NAME_PREFIX = "VIEWS"; - - /** - * Returns the name prefix of this node. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return NAME_PREFIX; - } - - /** - * Main constructor. - * - * @param dataSourceObjId The data source object id or null for no - * filter. - */ - public ViewsRootNode(Long dataSourceObjId) { - super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), - Bundle.RootFactory_ViewsRootNode_displayName(), - "org/sleuthkit/autopsy/images/views.png", - new ViewsTypeFactory.ViewsChildren(dataSourceObjId)); - } - } - - /** - * Root node for "Data Artifacts" in the tree. - */ - @Messages({"RootFactory_DataArtifactsRootNode_displayName=Data Artifacts"}) - public static class DataArtifactsRootNode extends StaticTreeNode { - - private static final String NAME_PREFIX = "DATA_ARTIFACT"; - - /** - * Returns the name prefix of this node. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return NAME_PREFIX; - } - - /** - * Main constructor. - * - * @param dataSourceObjId The data source object id or null for no - * filter. - */ - public DataArtifactsRootNode(Long dataSourceObjId) { - super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), - Bundle.RootFactory_DataArtifactsRootNode_displayName(), - "org/sleuthkit/autopsy/images/extracted_content.png", - new DataArtifactTypeFactory(dataSourceObjId)); - } - } - - /** - * Root node for "Analysis Results" in the tree. - */ - @Messages({"RootFactory_AnalysisResultsRootNode_displayName=Analysis Results"}) - public static class AnalysisResultsRootNode extends StaticTreeNode { - - private static final String NAME_PREFIX = "DATA_SOURCE_BY_TYPE"; - - /** - * Returns the name identifier of this node. - * - * @return The name identifier. - */ - public static final String getNamePrefix() { - return NAME_PREFIX; - } - - /** - * Main constructor. - * - * @param dataSourceObjId The data source object id or null for no - * filter. - */ - public AnalysisResultsRootNode(Long dataSourceObjId) { - super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), - Bundle.RootFactory_AnalysisResultsRootNode_displayName(), - "org/sleuthkit/autopsy/images/analysis_result.png", - new AnalysisResultTypeFactory(dataSourceObjId)); - } - } - - /** - * Root node for OS accounts in the tree. - */ - @Messages({"RootFactory_OsAccountsRootNode_displayName=OS Accounts"}) - public static class OsAccountsRootNode extends StaticTreeNode { - - private static final String NAME_PREFIX = "OS_ACCOUNTS"; - - /** - * Returns the name prefix of this node. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return NAME_PREFIX; - } - - private final Long dataSourceObjId; - - /** - * Main constructor. - * - * @param dataSourceObjId The data source object id or null for no - * filter. - */ - public OsAccountsRootNode(Long dataSourceObjId) { - super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), - Bundle.RootFactory_OsAccountsRootNode_displayName(), - "org/sleuthkit/autopsy/images/os-account.png"); - - this.dataSourceObjId = dataSourceObjId; - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayOsAccounts(new OsAccountsSearchParams(dataSourceObjId), getNodeSelectionInfo()); - } - - } - - /** - * Root node for tags in the tree. - */ - @Messages({"RootFactory_TagsRootNode_displayName=Tags"}) - public static class TagsRootNode extends StaticTreeNode { - - private static final String NAME_PREFIX = "DATA_SOURCE_BY_TYPE"; - - /** - * Returns the name prefix of this node. - * - * @return The name prefix. - */ - public static final String getNamePrefix() { - return NAME_PREFIX; - } - - /** - * Main constructor. - * - * @param dataSourceObjId The data source object id or null for no - * filter. - */ - public TagsRootNode(Long dataSourceObjId) { - super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), - Bundle.RootFactory_TagsRootNode_displayName(), - "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png", - new TagNameFactory(dataSourceObjId)); - } - } - - /** - * Root node for reports in the tree. - */ - @Messages({"RootFactory_ReportsRootNode_displayName=Reports"}) - public static class ReportsRootNode extends StaticTreeNode { - - private static final String NAME_ID = "REPORTS"; - - /** - * Returns the name identifier of this node. - * - * @return The name identifier. - */ - public static final String getNameIdentifier() { - return NAME_ID; - } - - /** - * Main constructor. - */ - public ReportsRootNode() { - super(NAME_ID, - Bundle.RootFactory_ReportsRootNode_displayName(), - "org/sleuthkit/autopsy/images/report_16.png"); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayReports(ReportsSearchParams.getInstance()); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ScoreTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ScoreTypeFactory.java deleted file mode 100644 index 997c5820e1092d0cda441467c2a536888048ac54..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ScoreTypeFactory.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2023 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import com.google.common.collect.ImmutableList; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import org.openide.nodes.Children; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewFilter; -import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode; - -/** - * - * Factories for displaying views. - */ -public class ScoreTypeFactory { - private static final String SCORE_ICON = "org/sleuthkit/autopsy/images/red-circle-exclamation.png"; - /** - * Children of 'Score' in the tree. - */ - public static class ScoreChildren extends Children.Array { - - public ScoreChildren(Long dataSourceId) { - super(ImmutableList.of( - new ScoreParentNode(dataSourceId) - )); - } - } - - /** - * Parent of score nodes in the tree. - */ - @Messages({"ScoreTypeFactory_ScoreParentNode_displayName=Score"}) - public static class ScoreParentNode extends StaticTreeNode { - - ScoreParentNode(Long dataSourceId) { - super( - "FILE_VIEW_SCORE_PARENT", - Bundle.ScoreTypeFactory_ScoreParentNode_displayName(), - SCORE_ICON, - new ScoreContentFactory(dataSourceId) - ); - } - } - - /** - * The factory for creating deleted content tree nodes. - */ - public static class ScoreContentFactory extends TreeChildFactory<ScoreViewSearchParams> { - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source to filter files to or null. - */ - public ScoreContentFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeNode<ScoreViewSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> rowData) { - return new ScoreContentTypeNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends ScoreViewSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getScoreDAO().getScoreContentCounts(dataSourceId); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof TreeEvent) { - TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, ScoreViewSearchParams.class); - // if search params has null filter, trigger full refresh - if (treeItem != null && treeItem.getSearchParams().getFilter() == null) { - super.update(); - return; - } - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, ScoreViewSearchParams.class); - - if (originalTreeItem != null - // only create child if size filter is present (if null, update should be triggered separately) - && originalTreeItem.getSearchParams().getFilter() != null - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - ScoreViewSearchParams searchParam = originalTreeItem.getSearchParams(); - return new TreeResultsDTO.TreeItemDTO<>( - ScoreViewSearchParams.getTypeId(), - new ScoreViewSearchParams(searchParam.getFilter(), this.dataSourceId), - searchParam.getFilter(), - searchParam.getFilter().getDisplayName(), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends ScoreViewSearchParams> o1, TreeItemDTO<? extends ScoreViewSearchParams> o2) { - return Integer.compare(o1.getSearchParams().getFilter().getId(), o2.getSearchParams().getFilter().getId()); - } - - /** - * Shows a deleted content tree node. - */ - static class ScoreContentTypeNode extends TreeNode<ScoreViewSearchParams> { - - private static final String BAD_SCORE_ICON = SCORE_ICON; - private static final String SUS_SCORE_ICON = "org/sleuthkit/autopsy/images/yellow-circle-yield.png"; - - private static String getIcon(ScoreViewFilter filter) { - switch (filter) { - case SUSPICIOUS: - return SUS_SCORE_ICON; - case BAD: - default: - return BAD_SCORE_ICON; - } - } - - /** - * Main constructor. - * - * @param itemData The data for the node. - */ - ScoreContentTypeNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> itemData) { - super("SCORE_CONTENT_" + itemData.getSearchParams().getFilter().name(), getIcon(itemData.getSearchParams().getFilter()), itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayScoreContent(this.getItemData().getSearchParams()); - } - - } - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchManager.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchManager.java deleted file mode 100644 index c331eac5c7bb84ed7c878f63878259cdc344fa87..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchManager.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.text.MessageFormat; -import java.util.concurrent.ExecutionException; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; - -/** - * Provides functionality to handle paging and fetching of search result data. - */ -public class SearchManager { - - private final DAOFetcher<?> daoFetcher; - private final int pageSize; - - private SearchResultsDTO currentSearchResults = null; - private int pageIdx = 0; - - /** - * Main constructor. - * - * @param daoFetcher Means of fetching data from the DAO. - * @param pageSize The size of a page. - */ - public SearchManager(DAOFetcher<?> daoFetcher, int pageSize) { - this.daoFetcher = daoFetcher; - this.pageSize = pageSize; - } - - /** - * @return The dao fetcher responsible for gathering data. - */ - public DAOFetcher<?> getDaoFetcher() { - return daoFetcher; - } - - /** - * @return The page size when handing paging. - */ - public synchronized int getPageSize() { - return pageSize; - } - - /** - * @return The index of the page to be viewed. - */ - public synchronized int getPageIdx() { - return pageIdx; - } - - /** - * @return True if there is a previous page of results. - */ - public synchronized boolean hasPrevPage() { - return pageIdx > 0; - } - - /** - * @return True if there is another page. - */ - public synchronized boolean hasNextPage() { - if (this.currentSearchResults == null) { - return false; - } else { - return (this.pageIdx + 1) * this.pageSize < this.currentSearchResults.getTotalResultsCount(); - } - } - - /** - * @return The total number of pages based on the current search results. - */ - public synchronized int getTotalPages() { - if (this.currentSearchResults == null) { - return 0; - } else { - return (int) Math.ceil(((double) this.currentSearchResults.getTotalResultsCount()) / this.pageSize); - } - } - - /** - * @param pageIdx The index of the page to be viewed. - */ - private synchronized void setPageIdx(int pageIdx) throws IllegalArgumentException { - if (pageIdx < 0 || pageIdx >= getTotalPages()) { - throw new IllegalArgumentException(MessageFormat.format("Page idx must be >= 0 and less than {0} but was {1}", getTotalPages(), pageIdx)); - } - - this.pageIdx = pageIdx; - } - - /** - * @return The last accessed search results or null. - */ - public synchronized SearchResultsDTO getCurrentSearchResults() { - return currentSearchResults; - } - - /** - * @return The total results or 0 if no current search results. - */ - public synchronized long getTotalResults() { - return this.currentSearchResults == null ? 0 : this.currentSearchResults.getTotalResultsCount(); - } - - /** - * Updates the page index and returns the results after updating the page - * index. - * - * @param pageIdx The page index. - * - * @return The search results or null if no search parameters provided. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public synchronized SearchResultsDTO updatePageIdx(int pageIdx) throws IllegalArgumentException, ExecutionException { - setPageIdx(pageIdx); - return getResults(); - } - - /** - * Increments page index or throws an exception if not possible. - * - * @return The search results after incrementing. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public synchronized SearchResultsDTO incrementPageIdx() throws IllegalArgumentException, ExecutionException { - if (this.currentSearchResults == null) { - throw new IllegalArgumentException("No current results"); - } - - return updatePageIdx(this.pageIdx + 1); - } - - /** - * Decrements page index or throws an exception if not possible. - * - * @return The search results after decrementing. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public synchronized SearchResultsDTO decrementPageIdx() throws IllegalArgumentException, ExecutionException { - if (this.daoFetcher == null) { - throw new IllegalArgumentException("No current page fetcher"); - } - - return updatePageIdx(this.pageIdx - 1); - } - - /** - * Determines if a refresh is required for the currently selected item. - * - * @param evt The event. - * - * @return True if an update is required. - */ - public synchronized boolean isRefreshRequired(DAOEvent evt) { - return isRefreshRequired(this.daoFetcher, evt); - } - - /** - * Determines if a refresh is required for the currently selected item. - * - * @param dataFetcher The data fetcher. - * @param evt The event. - * - * @return True if an update is required. - */ - private synchronized <P> boolean isRefreshRequired(DAOFetcher<P> dataFetcher, DAOEvent evt) { - if (dataFetcher == null) { - return false; - } - - return dataFetcher.isRefreshRequired(evt); - } - - /** - * Queries the dao cache for results storing the result in the current - * search results. - * - * @return The current search results. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - public synchronized SearchResultsDTO getResults() throws IllegalArgumentException, ExecutionException { - return fetchResults(this.daoFetcher); - } - - private synchronized SearchResultsDTO fetchResults(DAOFetcher<?> dataFetcher) throws ExecutionException { - SearchResultsDTO newResults = null; - if (dataFetcher != null) { - newResults = dataFetcher.getSearchResults(this.pageSize, this.pageIdx); - } - - this.currentSearchResults = newResults; - return newResults; - - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchResultChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchResultChildFactory.java deleted file mode 100644 index 484c3152685b723d056c53c1b6bae4de0d0f2019..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchResultChildFactory.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.text.MessageFormat; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.logging.Level; -import java.util.stream.Collectors; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultTableSearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.BlackboardArtifactTagsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentTagsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.DirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.LayoutFileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.SlackFileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.ImageRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.LocalDirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.LocalFileDataSourceRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.OsAccountRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.PoolRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VirtualDirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.nodes.SearchResultChildFactory.ChildKey; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.RowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VolumeRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.CreditCardByFileRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ReportsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ScoreResultRowDTO; -import org.sleuthkit.autopsy.mainui.nodes.FileNode.LayoutFileNode; -import org.sleuthkit.autopsy.mainui.nodes.FileNode.SlackFileNode; -import org.sleuthkit.autopsy.mainui.nodes.SpecialDirectoryNode.LocalDirectoryNode; -import org.sleuthkit.autopsy.mainui.nodes.SpecialDirectoryNode.LocalFileDataSourceNode; -import org.sleuthkit.autopsy.mainui.nodes.SpecialDirectoryNode.VirtualDirectoryNode; - -/** - * Factory for populating results in a results viewer with a SearchResultsDTO. - */ -public class SearchResultChildFactory extends ChildFactory<ChildKey> { - - private static final Logger logger = Logger.getLogger(SearchResultChildFactory.class.getName()); - private SearchResultsDTO results; - - private final ExecutorService nodeThreadPool; - - public SearchResultChildFactory(SearchResultsDTO initialResults, ExecutorService nodeThreadPool) { - this.results = initialResults; - this.nodeThreadPool = nodeThreadPool; - } - - @Override - protected boolean createKeys(List<ChildKey> toPopulate) { - SearchResultsDTO results = this.results; - - if (results != null) { - List<ChildKey> childKeys = results.getItems().stream() - .map((item) -> new ChildKey(results, item)) - .collect(Collectors.toList()); - - toPopulate.addAll(childKeys); - } - - return true; - } - - @Override - protected Node createNodeForKey(ChildKey key) { - String typeId = key.getRow().getTypeId(); - try { - if (ScoreResultRowDTO.getTypeIdForClass().equals(typeId) && key.getRow() instanceof ScoreResultRowDTO scoreRow) { - if (scoreRow.getArtifactDTO() != null && scoreRow.getArtifactType() != null) { - String iconPath = IconsUtil.getIconFilePath(scoreRow.getArtifactType().getTypeID()); - return new DataArtifactNode(key.getSearchResults(), scoreRow.getArtifactDTO(), iconPath, nodeThreadPool); - } else if (scoreRow.getFileDTO() != null) { - return new FileNode(key.getSearchResults(), scoreRow.getFileDTO(), true, nodeThreadPool); - } - } else if (DataArtifactRowDTO.getTypeIdForClass().equals(typeId)) { - return new DataArtifactNode((DataArtifactTableSearchResultsDTO) key.getSearchResults(), (DataArtifactRowDTO) key.getRow(), nodeThreadPool); - } else if (FileRowDTO.getTypeIdForClass().equals(typeId)) { - return new FileNode(key.getSearchResults(), (FileRowDTO) key.getRow(), true, nodeThreadPool); - } else if (AnalysisResultRowDTO.getTypeIdForClass().equals(typeId)) { - return new AnalysisResultNode((AnalysisResultTableSearchResultsDTO) key.getSearchResults(), (AnalysisResultRowDTO) key.getRow(), nodeThreadPool); - } else if (ContentTagsRowDTO.getTypeIdForClass().equals(typeId)) { - return new ContentTagNode(key.getSearchResults(), (ContentTagsRowDTO) key.getRow(), nodeThreadPool); - } else if (BlackboardArtifactTagsRowDTO.getTypeIdForClass().equals(typeId)) { - return new BlackboardArtifactTagNode(key.getSearchResults(), (BlackboardArtifactTagsRowDTO) key.getRow(), nodeThreadPool); - } else if (ImageRowDTO.getTypeIdForClass().equals(typeId)) { - return new ImageNode(key.getSearchResults(), (ImageRowDTO) key.getRow(), nodeThreadPool); - } else if (LocalFileDataSourceRowDTO.getTypeIdForClass().equals(typeId)) { - return new LocalFileDataSourceNode(key.getSearchResults(), (LocalFileDataSourceRowDTO) key.getRow(), nodeThreadPool); - } else if (DirectoryRowDTO.getTypeIdForClass().equals(typeId)) { - return new DirectoryNode(key.getSearchResults(), (DirectoryRowDTO) key.getRow(), nodeThreadPool); - } else if (VolumeRowDTO.getTypeIdForClass().equals(typeId)) { - return new VolumeNode(key.getSearchResults(), (VolumeRowDTO) key.getRow(), nodeThreadPool); - } else if (LocalDirectoryRowDTO.getTypeIdForClass().equals(typeId)) { - return new LocalDirectoryNode(key.getSearchResults(), (LocalDirectoryRowDTO) key.getRow(), nodeThreadPool); - } else if (VirtualDirectoryRowDTO.getTypeIdForClass().equals(typeId)) { - return new VirtualDirectoryNode(key.getSearchResults(), (VirtualDirectoryRowDTO) key.getRow(), nodeThreadPool); - } else if (LayoutFileRowDTO.getTypeIdForClass().equals(typeId)) { - return new LayoutFileNode(key.getSearchResults(), (LayoutFileRowDTO) key.getRow(), nodeThreadPool); - } else if (PoolRowDTO.getTypeIdForClass().equals(typeId)) { - return new PoolNode(key.getSearchResults(), (PoolRowDTO) key.getRow(), nodeThreadPool); - } else if (SlackFileRowDTO.getTypeIdForClass().equals(typeId)) { - return new SlackFileNode(key.getSearchResults(), (SlackFileRowDTO) key.getRow(), nodeThreadPool); - } else if (OsAccountRowDTO.getTypeIdForClass().equals(typeId)) { - return new OsAccountNode(key.getSearchResults(), (OsAccountRowDTO) key.getRow(), nodeThreadPool); - } else if (CreditCardByFileRowDTO.getTypeIdForClass().equals(typeId)) { - return new CreditCardByFileNode(key.getSearchResults(), (CreditCardByFileRowDTO) key.getRow(), nodeThreadPool); - } else if (ReportsRowDTO.getTypeIdForClass().equals(typeId)) { - return new ReportNode(key.getSearchResults(), (ReportsRowDTO) key.getRow(), nodeThreadPool); - }else { - logger.log(Level.WARNING, MessageFormat.format("No known node for type id: {0} provided by row result: {1}", typeId, key.getRow())); - } - } catch (ClassCastException ex) { - logger.log(Level.WARNING, MessageFormat.format("Could not cast item with type id: {0} to valid type.", typeId), ex); - } - - return null; - } - - public void update(SearchResultsDTO newResults) { - this.results = newResults; - this.refresh(false); - } - - public long getResultCount() { - return results == null ? 0 : results.getTotalResultsCount(); - } - - static class ChildKey { - - private final SearchResultsDTO searchResults; - private final RowDTO row; - - ChildKey(SearchResultsDTO searchResults, RowDTO child) { - this.searchResults = searchResults; - this.row = child; - } - - SearchResultsDTO getSearchResults() { - return searchResults; - } - - RowDTO getRow() { - return row; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 97 * hash + (this.row == null ? 0 : Objects.hashCode(this.row.getId())); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ChildKey other = (ChildKey) obj; - return this.row.getId() == other.row.getId(); - } - - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchResultRootNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchResultRootNode.java deleted file mode 100644 index 2960be87deba8c7b1c72a3bcc2a25b97dc69efaf..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SearchResultRootNode.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.concurrent.ExecutorService; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.Children; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle.Messages; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; - -/** - * A node whose children will be displayed in the results view and determines - * children based on a SearchResultDTO. - */ -public class SearchResultRootNode extends AbstractNode { - - private final SearchResultChildFactory factory; - - // This param is can change, is not used as part of the search query and - // therefore is not included in the equals and hashcode methods. - private ChildNodeSelectionInfo childNodeSelectionInfo; - - public SearchResultRootNode(SearchResultsDTO initialResults, ExecutorService nodeThreadPool) { - this(initialResults, new SearchResultChildFactory(initialResults, nodeThreadPool)); - } - - private SearchResultRootNode(SearchResultsDTO initialResults, SearchResultChildFactory factory) { - super(Children.create(factory, true), initialResults.getDataSourceParent() != null ? Lookups.singleton(initialResults.getDataSourceParent()) : null); - this.factory = factory; - - setName(initialResults.getTypeId()); - setDisplayName(initialResults.getDisplayName()); - } - - public ChildNodeSelectionInfo getNodeSelectionInfo() { - return childNodeSelectionInfo; - } - - public void setNodeSelectionInfo(ChildNodeSelectionInfo info) { - childNodeSelectionInfo = info; - } - - @Messages({ - "SearchResultRootNode_noDesc=No Description", - "SearchResultRootNode_createSheet_type_name=Name", - "SearchResultRootNode_createSheet_type_displayName=Name", - "SearchResultRootNode_createSheet_childCount_name=Child Count", - "SearchResultRootNode_createSheet_childCount_displayName=Child Count" - }) - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - - sheetSet.put(new NodeProperty<>( - Bundle.SearchResultRootNode_createSheet_type_name(), - Bundle.SearchResultRootNode_createSheet_type_displayName(), - Bundle.SearchResultRootNode_noDesc(), - getDisplayName())); - - sheetSet.put(new NodeProperty<>( - Bundle.SearchResultRootNode_createSheet_childCount_name(), - Bundle.SearchResultRootNode_createSheet_childCount_displayName(), - Bundle.SearchResultRootNode_noDesc(), - this.factory.getResultCount())); - - return sheet; - } - - /** - * Updates the child factory with the backing search results data performing - * a refresh of data without entirely resetting the node. - * - * @param updatedResults The search results. - */ - public void updateChildren(SearchResultsDTO updatedResults) { - this.factory.update(updatedResults); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SpecialDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/SpecialDirectoryNode.java deleted file mode 100755 index d8f8ffdbddc597ccbd9f6cfab101e002e28f30a3..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SpecialDirectoryNode.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.LocalFileDataSourceRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.LocalDirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VirtualDirectoryRowDTO; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.SpecialDirectory; - -/** - * Abstract Node class for SpecialDirectory row results. - */ -abstract class SpecialDirectoryNode extends BaseNode<SearchResultsDTO, ContentRowDTO<? extends SpecialDirectory>> implements SCOSupporter { - - /** - * An abstract base class for FileSystem objects that are subclasses of - * SpecialDirectory. - * - * @param results The search result DTO. - * @param row The table row DTO. - */ - private SpecialDirectoryNode(SearchResultsDTO results, ContentRowDTO<? extends SpecialDirectory> row, ExecutorService backgroundTasksPool) { - super(Children.LEAF, ContentNodeUtil.getLookup(row.getContent()), results, row, backgroundTasksPool); - setName(ContentNodeUtil.getContentName(row.getContent().getId())); - setDisplayName(row.getContent().getName()); - setShortDescription(row.getContent().getName()); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsTableExtractActions() { - return true; - } - - @Override - public Optional<Content> getContentForRunIngestionModuleAction() { - return Optional.of(getRowDTO().getContent()); - } - - @Override - public Optional<Content> getDataSourceForActions() { - return getRowDTO().getContent().isDataSource() - ? Optional.of(getRowDTO().getContent()) - : Optional.empty(); - } - - @Override - public Optional<Content> getContent() { - return Optional.ofNullable(getRowDTO().getContent()); - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } - - @Override - public boolean supportsFileSearchAction() { - return true; - } - - /** - * A node representing a LocalDirectory. - */ - public static class LocalDirectoryNode extends SpecialDirectoryNode { - - /** - * Simple node constructor. - * - * @param results The search result DTO. - * @param row The table row DTO. - */ - public LocalDirectoryNode(SearchResultsDTO results, LocalDirectoryRowDTO row, ExecutorService backgroundTasksPool) { - super(results, row, backgroundTasksPool); - setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); - } - } - - /** - * A node representing a VirtualDirectoryNode. - */ - public static class VirtualDirectoryNode extends SpecialDirectoryNode { - - /** - * Simple node constructor. - * - * @param results The search result DTO. - * @param row The table row DTO. - */ - public VirtualDirectoryNode(SearchResultsDTO results, VirtualDirectoryRowDTO row, ExecutorService backgroundTasksPool) { - super(results, row, backgroundTasksPool); - setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-virtual.png"); - } - } - - /** - * Node representing a LocalFileDataSource. - * - * The previous class hierarchy is maintained, but probably not need. - */ - public static class LocalFileDataSourceNode extends VirtualDirectoryNode { - - /** - * Simple node constructor. - * - * @param results The search result DTO. - * @param row The table row DTO. - */ - public LocalFileDataSourceNode(SearchResultsDTO results, LocalFileDataSourceRowDTO row, ExecutorService backgroundTasksPool) { - super(results, row, backgroundTasksPool); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TagNameFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TagNameFactory.java deleted file mode 100644 index 0a7d99f6272f85040f7575874b9ba597f80b88d6..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TagNameFactory.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.Comparator; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import org.openide.nodes.Children; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.TagNameSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DeleteAnalysisResultEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; - -/** - * Factory for displaying analysis result types in the tree. - */ -public class TagNameFactory extends TreeChildFactory<TagNameSearchParams> { - - private static final String TAG_ICON = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source id to filter on or null if no filter. - */ - public TagNameFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeResultsDTO<? extends TagNameSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getTagsDAO().getNameCounts(dataSourceId); - } - - @Override - protected TreeNode<TagNameSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends TagNameSearchParams> rowData) { - return new TagNameNode(rowData); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends TagNameSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<TagNameSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, TagNameSearchParams.class); - - if (originalTreeItem != null - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - return MainDAO.getInstance().getTagsDAO().createTagNameTreeItem( - originalTreeItem.getSearchParams().getTagName(), - dataSourceId, - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends TagNameSearchParams> o1, TreeItemDTO<? extends TagNameSearchParams> o2) { - return Comparator.comparing((TreeItemDTO<? extends TagNameSearchParams> tagTreeItem) -> tagTreeItem.getDisplayName()) - .compare(o1, o2); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof DeleteAnalysisResultEvent && evt.getType() == DAOEvent.Type.TREE) { - super.update(); - return; - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - /** - * A node for a tag name. - */ - static class TagNameNode extends TreeNode<TagNameSearchParams> { - - public TagNameNode(TreeResultsDTO.TreeItemDTO<? extends TagNameSearchParams> rowData) { - super(TagNameSearchParams.getTypeId() + "_" + Objects.toString(rowData.getId()), - TAG_ICON, - rowData, - Children.create(new TagTypeFactory(rowData.getSearchParams()), false), - getDefaultLookup(rowData)); - } - - } - - /** - * Factory displaying file type or result type underneath a tag name node. - */ - static class TagTypeFactory extends TreeChildFactory<TagsSearchParams> { - - private final TagNameSearchParams searchParams; - - TagTypeFactory(TagNameSearchParams searchParams) { - this.searchParams = searchParams; - } - - @Override - protected TreeNode<TagsSearchParams> createNewNode(TreeItemDTO<? extends TagsSearchParams> rowData) { - return new TagsTypeNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends TagsSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getTagsDAO().getTypeCounts(searchParams); - } - - @Override - protected TreeItemDTO<? extends TagsSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<TagsSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, TagsSearchParams.class); - - if (originalTreeItem != null - && Objects.equals(this.searchParams.getTagName(), originalTreeItem.getSearchParams().getTagName()) - && (this.searchParams.getDataSourceId() == null || Objects.equals(this.searchParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) { - - return MainDAO.getInstance().getTagsDAO().createTagTypeTreeItem( - searchParams.getTagName(), - originalTreeItem.getSearchParams().getTagType(), - searchParams.getDataSourceId(), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends TagsSearchParams> o1, TreeItemDTO<? extends TagsSearchParams> o2) { - return Comparator.comparing((TreeItemDTO<? extends TagsSearchParams> rowData) -> rowData.getSearchParams().getTagType() == TagsSearchParams.TagType.FILE ? 0 : 1) - .compare(o1, o2); - } - - } - - /** - * A tag type (i.e. File/Result) tree node. Clicking on this will go to - * results. - */ - static class TagsTypeNode extends TreeNode<TagsSearchParams> { - - private TagsTypeNode(TreeItemDTO<? extends TagsSearchParams> rowData) { - super(TagsSearchParams.getTypeId() + "_" + Objects.toString(rowData.getId()), TAG_ICON, rowData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayTags(this.getItemData().getSearchParams()); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java deleted file mode 100644 index 1170def9cfdc5a28ffd2d8ccc54cb9863bc2f95d..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import com.google.common.collect.MapMaker; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Node; -import org.openide.util.WeakListeners; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; - -/** - * Factory for populating child nodes in a tree based on TreeResultsDTO - */ -public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object> implements Comparator<TreeItemDTO<? extends T>> { - - private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); - - private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { - if (evt.getNewValue() instanceof DAOAggregateEvent) { - handleDAOAggregateEvent((DAOAggregateEvent) evt.getNewValue()); - } - }; - - private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, MainDAO.getInstance().getTreeEventsManager()); - - // maps the Node keys to the child TreeNode. Used to update existing Node with new counts - private final Map<Object, TreeNode<T>> typeNodeMap = new MapMaker().weakValues().makeMap(); - private final Object resultsUpdateLock = new Object(); - - // Results of the last full load from the DAO. May not be complete because - // events will come in with more updated data. - private TreeResultsDTO<? extends T> curResults = null; - - // All current child items (sorted). May have more items than curResults does because - // this is updated based on events and new data. - private List<TreeItemDTO<? extends T>> curItemsList = new ArrayList<>(); - - // maps the Node key (ID) to its DTO - private Map<Object, TreeItemDTO<? extends T>> idMapping = new HashMap<>(); - - /** - * Handles processing and updating due to an aggregate event. This method - * can be overridden for custom behavior while handling DAO aggregate - * events. - * - * @param aggEvt The aggregate event. - */ - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent daoEvt : aggEvt.getEvents()) { - if (daoEvt instanceof TreeEvent) { - TreeEvent treeEvt = (TreeEvent) daoEvt; - TreeItemDTO<? extends T> item = getOrCreateRelevantChild(treeEvt); - if (item != null) { - if (treeEvt.isRefreshRequired()) { - update(); - break; - } else { - updateNodeData(item); - } - } - } - } - } - - @Override - protected boolean createKeys(List<Object> toPopulate) { - List<TreeItemDTO<? extends T>> itemsList; - - // Load data from DAO if we haven't already. - // We should have previous data every time except initial run. - if (curResults == null) { - try { - updateData(); - } catch (IllegalArgumentException | ExecutionException ex) { - logger.log(Level.WARNING, "An error occurred while fetching keys", ex); - return false; - } - } - // make copy to avoid concurrent modification - synchronized (resultsUpdateLock) { - itemsList = new ArrayList<>(curItemsList); - } - - // update existing cached nodes - List<Object> curResultIds = new ArrayList<>(); - for (TreeItemDTO<? extends T> dto : itemsList) { - TreeNode<T> currentlyCached = typeNodeMap.get(dto.getId()); - if (currentlyCached != null) { - currentlyCached.update(dto); - } - curResultIds.add(dto.getId()); - } - - toPopulate.addAll(curResultIds); - return true; - } - - @Override - protected Node createNodeForKey(Object treeItemId) { - return typeNodeMap.computeIfAbsent(treeItemId, (id) -> { - TreeItemDTO<? extends T> itemData = idMapping.get(id); - // create new node if data for node exists. otherwise, return null. - if (itemData == null) { - logger.log(Level.WARNING, "No item data matching key: " + treeItemId + " in factory: " + getClass()); - return null; - } else { - return createNewNode(itemData); - } - }); - } - - /** - * Finds and updates a node based on new/updated data. - * - * @param item The added/updated item. - */ - protected void updateNodeData(TreeItemDTO<? extends T> item) { - synchronized (resultsUpdateLock) { - TreeItemDTO<? extends T> cachedTreeItem = this.idMapping.get(item.getId()); - // add to id mapping - this.idMapping.put(item.getId(), item); - - if (cachedTreeItem == null) { - // insert in sorted position - int insertIndex = 0; - for (; insertIndex < this.curItemsList.size(); insertIndex++) { - TreeItemDTO<? extends T> curItem = this.curItemsList.get(insertIndex); - if (this.compare(item, curItem) < 0) { - break; - } - } - this.curItemsList.add(insertIndex, item); - } else { - for (int i = 0; i < this.curItemsList.size(); i++) { - TreeItemDTO<? extends T> listItem = this.curItemsList.get(i); - if (Objects.equals(listItem.getId(), item.getId())) { - this.curItemsList.set(i, item); - break; - } - } - } - } - this.refresh(false); - } - - /** - * Updates local data structures by fetching new data from the DAO's. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - protected void updateData() throws IllegalArgumentException, ExecutionException { - TreeResultsDTO<? extends T> newResults = getChildResults(); - synchronized (resultsUpdateLock) { - this.curResults = newResults; - Map<Object, TreeItemDTO<? extends T>> idMapping = new HashMap<>(); - List<TreeItemDTO<? extends T>> curItemsList = new ArrayList<>(); - for (TreeItemDTO<? extends T> item : this.curResults.getItems()) { - idMapping.put(item.getId(), item); - curItemsList.add(item); - } - - this.idMapping = idMapping; - this.curItemsList = curItemsList; - } - } - - /** - * Updates the tree using new data from the DAO. - */ - public void update() { - try { - updateData(); - } catch (IllegalArgumentException | ExecutionException ex) { - logger.log(Level.WARNING, "An error occurred while fetching keys", ex); - return; - } - this.refresh(false); - } - - /** - * Dispose resources associated with this factory. - */ - private void disposeResources() { - typeNodeMap.clear(); - - synchronized (resultsUpdateLock) { - curResults = null; - this.curItemsList.clear(); - idMapping.clear(); - } - } - - /** - * Register listeners for DAO events. - */ - private void registerListeners() { - MainDAO.getInstance().getTreeEventsManager().addPropertyChangeListener(weakPcl); - } - - /** - * Unregister listeners for DAO events. - */ - private void unregisterListeners() { - MainDAO.getInstance().getTreeEventsManager().removePropertyChangeListener(weakPcl); - } - - @Override - protected void removeNotify() { - unregisterListeners(); - disposeResources(); - super.removeNotify(); - } - - @Override - protected void finalize() throws Throwable { - unregisterListeners(); - disposeResources(); - super.finalize(); - } - - @Override - protected void addNotify() { - registerListeners(); - super.addNotify(); - } - - /** - * A utility method that creates a TreeItemDTO using the data in 'original' - * for all fields except 'typeData' where 'updatedData' is used instead. - * - * @param original The original tree item dto. - * @param updatedData The new type data to use. - * - * @return The created tree item dto. - */ - static <T> TreeItemDTO<T> createTreeItemDTO(TreeItemDTO<T> original, T updatedData) { - return new TreeItemDTO<>( - original.getTypeId(), - updatedData, - original.getId(), - original.getDisplayName(), - original.getDisplayCount()); - } - - /** - * Returns the underlying tree item dto in the tree event if the search - * params of the tree item dto are of the expected type. Otherwise, returns - * null. - * - * @param treeEvt The tree event. - * @param expectedSearchParamsType The expected type of the search params of - * the tree item dto in the tree event. - * - * @return The typed tree item dto in the tree event or null if no match - * found. - */ - protected <T> TreeItemDTO<T> getTypedTreeItem(TreeEvent treeEvt, Class<T> expectedSearchParamsType) { - if (treeEvt != null && treeEvt.getItemRecord() != null && treeEvt.getItemRecord().getSearchParams() != null - && expectedSearchParamsType.isAssignableFrom(treeEvt.getItemRecord().getSearchParams().getClass())) { - - @SuppressWarnings("unchecked") - TreeItemDTO<T> originalTreeItem = (TreeItemDTO<T>) treeEvt.getItemRecord(); - return originalTreeItem; - } - return null; - } - - /** - * Creates a TreeNode given the tree item data. - * - * @param rowData The tree item data. - * - * @return The generated tree node. - */ - protected abstract TreeNode<T> createNewNode(TreeItemDTO<? extends T> rowData); - - /** - * Fetches data from the database to populate this part of the tree. - * - * @return The data. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - protected abstract TreeResultsDTO<? extends T> getChildResults() throws IllegalArgumentException, ExecutionException; - - /** - * Creates a child tree item dto that can be used to find the affected child - * node that requires updates. - * - * @param treeEvt The tree event. - * - * @return The tree item dto that can be used to find the child node - * affected by the tree event. - */ - protected abstract TreeItemDTO<? extends T> getOrCreateRelevantChild(TreeEvent treeEvt); -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java deleted file mode 100644 index d9d726526cefebe2374fbb7846232a06aa4944d6..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Autopsy Forensic Bitemser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import org.sleuthkit.autopsy.corecomponents.SelectionResponder; -import java.text.MessageFormat; -import java.util.Objects; -import java.util.logging.Level; -import javax.swing.Action; -import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; -import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; - -/** - * A node to be displayed in the tree that shows the count. - */ -public abstract class TreeNode<T> extends AbstractNode implements SelectionResponder, ActionContext { - - private static final Logger logger = Logger.getLogger(TreeNode.class.getName()); - - private ChildNodeSelectionInfo childNodeSelectionInfo; - - /** - * Returns the default lookup based on the item dto. - * - * @param itemData The item dto data. - * - * @return The lookup to use in the node. - */ - protected static <T> Lookup getDefaultLookup(TreeItemDTO<? extends T> itemData) { - return Lookups.fixed(itemData, itemData.getSearchParams()); - } - - private TreeItemDTO<? extends T> itemData; - - /** - * Main constructor assuming a leaf node with default lookup. - * - * @param nodeName The name of the node. - * @param icon The path of the icon or null. - * @param itemData The data to back the node. - * @param dataObjType The type of the underlying data object within the - * counts item dto. - */ - protected TreeNode(String nodeName, String icon, TreeItemDTO<? extends T> itemData) { - this(nodeName, icon, itemData, Children.LEAF, getDefaultLookup(itemData)); - } - - /** - * Constructor. - * - * @param nodeName The name of the node. - * @param icon The path of the icon or null. - * @param itemData The data to back the node. Must be non-null. - * @param children The children of this node. - * @param lookup The lookup for this node. - * @param dataObjType The type of the underlying data object within the - * counts item dto. - */ - protected TreeNode(String nodeName, String icon, TreeItemDTO<? extends T> itemData, Children children, Lookup lookup) { - super(children, lookup); - setName(nodeName); - if (icon != null) { - setIconBaseWithExtension(icon); - } - update(itemData); - } - - /** - * @return The current backing item data. - */ - protected TreeItemDTO<? extends T> getItemData() { - return itemData; - } - - /** - * Sets the display name of the node to include the display name and count - * of the item. - * - * @param prevData The previous item data (may be null). - * @param curData The item data (must be non-null). - */ - protected void updateDisplayName(TreeItemDTO<? extends T> prevData, TreeItemDTO<? extends T> curData) { - // update display name only if there is a change. - if (prevData == null - || !prevData.getDisplayName().equals(curData.getDisplayName()) - || !Objects.equals(prevData.getDisplayCount(), curData.getDisplayCount())) { - - this.setDisplayName(getDisplayNameString(curData.getDisplayName(), curData.getDisplayCount())); - } - } - - /** - * Returns a string to be used as the node display name (including the count). - * @param displayName The display name taken from the current tree item dto. - * @param displayCount The display count taken from the current tree item dto. - * @return The display string. - */ - protected String getDisplayNameString(String displayName, TreeDisplayCount displayCount) { - String safeDisplayName = StringUtils.defaultString(displayName); - return displayCount == null - ? safeDisplayName - : safeDisplayName + displayCount.getDisplaySuffix(); - } - - /** - * Updates the backing data of this node. - * - * @param updatedData The updated data. Must be non-null. - * - * @thitems IllegalArgumentException - */ - public void update(TreeItemDTO<? extends T> updatedData) { - if (updatedData == null) { - logger.log(Level.WARNING, "Expected non-null updatedData"); - } else if (this.itemData != null && !Objects.equals(this.itemData.getId(), updatedData.getId())) { - logger.log(Level.WARNING, MessageFormat.format( - "Expected update data to have same id but received [id: {0}] replacing [id: {1}]", - updatedData.getId(), - this.itemData.getId())); - return; - } - - TreeItemDTO<? extends T> prevData = this.itemData; - this.itemData = updatedData; - updateDisplayName(prevData, updatedData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.setNode(this); - } - - @Override - public Action getPreferredAction() { - // TreeNodes are used for both the result viewer and the tree viewer. For the result viewer, - // we want to open the child of the double-clicked node. For the tree viewer, we want the default - // action (explanding/closing the node). If getOpenChildAction() returns null, we likely - // have a tree node and want to call the default preferred action. - Action openChildAction = DirectoryTreeTopComponent.getOpenChildAction(getName()); - if (openChildAction == null) { - return super.getPreferredAction(); - } - return openChildAction; - } - - public ChildNodeSelectionInfo getNodeSelectionInfo() { - return childNodeSelectionInfo; - } - - public void setNodeSelectionInfo(ChildNodeSelectionInfo info) { - childNodeSelectionInfo = info; - } - - @Override - public boolean supportsCollapseAll() { - return !isLeaf(); - } - - @Override - public Action[] getActions(boolean context) { - return ActionsFactory.getActions(this); - } - - /** - * Tree node for displaying static content in the tree. - */ - public static class StaticTreeNode extends TreeNode<String> { - - public StaticTreeNode(String nodeName, String displayName, String icon) { - this(nodeName, displayName, icon, Children.LEAF); - } - - public StaticTreeNode(String nodeName, String displayName, String icon, ChildFactory<?> childFactory) { - this(nodeName, displayName, icon, Children.create(childFactory, true), null); - } - - public StaticTreeNode(String nodeName, String displayName, String icon, Children children) { - this(nodeName, displayName, icon, children, null); - } - - public StaticTreeNode(String nodeName, String displayName, String icon, Children children, Lookup lookup) { - super(nodeName, icon, new TreeItemDTO<String>(nodeName, nodeName, nodeName, displayName, TreeDisplayCount.NOT_SHOWN), children, lookup); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ViewsTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/ViewsTypeFactory.java deleted file mode 100644 index 85fb8cdd3a3135cd9c45a26f9e12e44f780ba532..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/ViewsTypeFactory.java +++ /dev/null @@ -1,634 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import com.google.common.collect.ImmutableList; -import java.util.Collection; -import java.util.Comparator; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.openide.nodes.Children; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.mainui.datamodel.FileExtDocumentFilter; -import org.sleuthkit.autopsy.mainui.datamodel.FileExtExecutableFilter; -import org.sleuthkit.autopsy.mainui.datamodel.FileExtRootFilter; -import org.sleuthkit.autopsy.mainui.datamodel.FileExtSearchFilter; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeExtensionsSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; -import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; -import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode; - -/** - * - * Factories for displaying views. - */ -public class ViewsTypeFactory { - - private static final Comparator<String> STRING_COMPARATOR = Comparator.nullsFirst(Comparator.naturalOrder()); - - private static final String FILE_TYPES_ICON = "org/sleuthkit/autopsy/images/file_types.png"; - private static final String SIZE_ICON = "org/sleuthkit/autopsy/images/file-size-16.png"; - - /** - * Node for file extensions parent in the tree. - */ - @Messages({"ViewsTypeFactory_ExtensionParentNode_displayName=By Extension"}) - private static class ExtensionParentNode extends StaticTreeNode { - - ExtensionParentNode(Long dataSourceId) { - super( - "FILE_VIEW_EXTENSIONS_PARENT", - Bundle.ViewsTypeFactory_ExtensionParentNode_displayName(), - FILE_TYPES_ICON, - new FileExtFactory(dataSourceId) - ); - } - } - - /** - * Parent mime types node in the tree. - */ - @Messages({"ViewsTypeFactory_MimeParentNode_displayName=By MIME Type"}) - public static class MimeParentNode extends StaticTreeNode { - - MimeParentNode(Long dataSourceId) { - super( - "FILE_VIEW_MIME_TYPE_PARENT", - Bundle.ViewsTypeFactory_MimeParentNode_displayName(), - FILE_TYPES_ICON, - new FileMimePrefixFactory(dataSourceId) - ); - } - } - - /** - * Parent of deleted content nodes in the tree. - */ - @Messages({"ViewsTypeFactory_DeletedParentNode_displayName=Deleted Files"}) - private static class DeletedParentNode extends StaticTreeNode { - - DeletedParentNode(Long dataSourceId) { - super( - "FILE_VIEW_DELETED_PARENT", - Bundle.ViewsTypeFactory_DeletedParentNode_displayName(), - NodeIconUtil.DELETED_FILE.getPath(), - new DeletedContentFactory(dataSourceId) - ); - } - } - - /** - * Parent of file size nodes in the tree. - */ - @Messages({"ViewsTypeFactory_SizeParentNode_displayName=File Size"}) - private static class SizeParentNode extends StaticTreeNode { - - SizeParentNode(Long dataSourceId) { - super( - "FILE_VIEW_SIZE_PARENT", - Bundle.ViewsTypeFactory_SizeParentNode_displayName(), - SIZE_ICON, - new FileSizeTypeFactory(dataSourceId) - ); - } - } - - /** - * 'File Types' children in the tree. - */ - public static class FileTypesChildren extends Children.Array { - - FileTypesChildren(Long dataSourceId) { - super(ImmutableList.of( - new ExtensionParentNode(dataSourceId), - new MimeParentNode(dataSourceId) - )); - } - } - - /** - * 'File Types' parent node in the tree. - */ - @Messages({"ViewsTypeFactory_FileTypesParentNode_displayName=File Types"}) - private static class FileTypesParentNode extends StaticTreeNode { - - public FileTypesParentNode(Long dataSourceId) { - super( - "FILE_TYPES_PARENT", - Bundle.ViewsTypeFactory_FileTypesParentNode_displayName(), - FILE_TYPES_ICON, - new FileTypesChildren(dataSourceId) - ); - } - - } - - /** - * Children of 'File Views' in the tree. - */ - public static class ViewsChildren extends Children.Array { - - public ViewsChildren(Long dataSourceId) { - super(ImmutableList.of( - new FileTypesParentNode(dataSourceId), - new DeletedParentNode(dataSourceId), - new SizeParentNode(dataSourceId) - )); - } - } - - /** - * The factory for creating deleted content tree nodes. - */ - public static class DeletedContentFactory extends TreeChildFactory<DeletedContentSearchParams> { - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source to filter files to or null. - */ - public DeletedContentFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeNode<DeletedContentSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends DeletedContentSearchParams> rowData) { - return new DeletedContentTypeNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends DeletedContentSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getViewsDAO().getDeletedContentCounts(dataSourceId); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof TreeEvent) { - TreeResultsDTO.TreeItemDTO<DeletedContentSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, DeletedContentSearchParams.class); - // if search params has null filter, trigger full refresh - if (treeItem != null && treeItem.getSearchParams().getFilter() == null) { - super.update(); - return; - } - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends DeletedContentSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<DeletedContentSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, DeletedContentSearchParams.class); - - if (originalTreeItem != null - // only create child if size filter is present (if null, update should be triggered separately) - && originalTreeItem.getSearchParams().getFilter() != null - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - DeletedContentSearchParams searchParam = originalTreeItem.getSearchParams(); - return new TreeResultsDTO.TreeItemDTO<>( - DeletedContentSearchParams.getTypeId(), - new DeletedContentSearchParams(searchParam.getFilter(), this.dataSourceId), - searchParam.getFilter(), - searchParam.getFilter().getDisplayName(), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends DeletedContentSearchParams> o1, TreeItemDTO<? extends DeletedContentSearchParams> o2) { - return Integer.compare(o1.getSearchParams().getFilter().getId(), o2.getSearchParams().getFilter().getId()); - } - - /** - * Shows a deleted content tree node. - */ - static class DeletedContentTypeNode extends TreeNode<DeletedContentSearchParams> { - - /** - * Main constructor. - * - * @param itemData The data for the node. - */ - DeletedContentTypeNode(TreeResultsDTO.TreeItemDTO<? extends DeletedContentSearchParams> itemData) { - super("DELETED_CONTENT_" + itemData.getSearchParams().getFilter().getName(), NodeIconUtil.DELETED_FILE.getPath(), itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayDeletedContent(this.getItemData().getSearchParams()); - } - - } - } - - /** - * The factory for creating file size tree nodes. - */ - public static class FileSizeTypeFactory extends TreeChildFactory<FileTypeSizeSearchParams> { - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source to filter files to or null. - */ - public FileSizeTypeFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeNode<FileTypeSizeSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> rowData) { - return new FileSizeTypeNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends FileTypeSizeSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getViewsDAO().getFileSizeCounts(this.dataSourceId); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof TreeEvent) { - TreeResultsDTO.TreeItemDTO<FileTypeSizeSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, FileTypeSizeSearchParams.class); - // if file type size search params has null filter, trigger full refresh - if (treeItem != null && treeItem.getSearchParams().getSizeFilter() == null) { - super.update(); - return; - } - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<FileTypeSizeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeSizeSearchParams.class); - - if (originalTreeItem != null - // only create child if size filter is present (if null, update should be triggered separately) - && originalTreeItem.getSearchParams().getSizeFilter() != null - && (this.dataSourceId == null - || originalTreeItem.getSearchParams().getDataSourceId() == null - || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - FileTypeSizeSearchParams searchParam = originalTreeItem.getSearchParams(); - return new TreeResultsDTO.TreeItemDTO<>( - FileTypeSizeSearchParams.getTypeId(), - new FileTypeSizeSearchParams(searchParam.getSizeFilter(), this.dataSourceId), - searchParam.getSizeFilter(), - searchParam.getSizeFilter().getDisplayName(), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends FileTypeSizeSearchParams> o1, TreeItemDTO<? extends FileTypeSizeSearchParams> o2) { - return Integer.compare(o1.getSearchParams().getSizeFilter().getId(), o2.getSearchParams().getSizeFilter().getId()); - } - - /** - * Shows a file size tree node. - */ - static class FileSizeTypeNode extends TreeNode<FileTypeSizeSearchParams> { - - /** - * Main constructor. - * - * @param itemData The data for the node. - */ - FileSizeTypeNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> itemData) { - super("FILE_SIZE_" + itemData.getSearchParams().getSizeFilter().getName(), SIZE_ICON, itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayFileSizes(this.getItemData().getSearchParams()); - } - - } - } - - /** - * Factory to display mime type prefix tree nodes (i.e. audio, multipart). - */ - public static class FileMimePrefixFactory extends TreeChildFactory<FileTypeMimeSearchParams> { - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source to filter files to or null. - */ - public FileMimePrefixFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeNode<FileTypeMimeSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> rowData) { - return new FileMimePrefixNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends FileTypeMimeSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getViewsDAO().getFileMimeCounts(null, this.dataSourceId); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<FileTypeMimeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeMimeSearchParams.class); - - if (originalTreeItem != null - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - FileTypeMimeSearchParams searchParam = originalTreeItem.getSearchParams(); - String mimePrefix = searchParam.getMimeType() == null ? "" : searchParam.getMimeType(); - int indexOfSlash = mimePrefix.indexOf("/"); - if (indexOfSlash >= 0) { - mimePrefix = mimePrefix.substring(0, indexOfSlash); - } - - return new TreeResultsDTO.TreeItemDTO<>( - FileTypeMimeSearchParams.getTypeId(), - new FileTypeMimeSearchParams(mimePrefix, this.dataSourceId), - mimePrefix, - mimePrefix, - originalTreeItem.getDisplayCount()); - } - return null; - - } - - @Override - public int compare(TreeItemDTO<? extends FileTypeMimeSearchParams> o1, TreeItemDTO<? extends FileTypeMimeSearchParams> o2) { - return STRING_COMPARATOR.compare(o1.getSearchParams().getMimeType(), o2.getSearchParams().getMimeType()); - } - - static class FileMimePrefixNode extends TreeNode<FileTypeMimeSearchParams> { - - /** - * Main constructor. - * - * @param itemData The data for the node. - */ - public FileMimePrefixNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> itemData) { - super( - "FILE_MIME_" + itemData.getSearchParams().getMimeType(), - FILE_TYPES_ICON, - itemData, - Children.create(new FileMimeSuffixFactory(itemData.getSearchParams().getDataSourceId(), itemData.getSearchParams().getMimeType()), true), - getDefaultLookup(itemData)); - } - } - } - - /** - * Displays mime type suffixes of a prefix (i.e. for prefix 'audio', a - * suffix could be 'aac'). - */ - public static class FileMimeSuffixFactory extends TreeChildFactory<FileTypeMimeSearchParams> { - - private final String mimeTypePrefix; - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source to filter files to or null. - * @param mimeTypePrefix The mime type prefix (i.e. 'audio', - * 'multipart'). - */ - private FileMimeSuffixFactory(Long dataSourceId, String mimeTypePrefix) { - this.dataSourceId = dataSourceId; - this.mimeTypePrefix = mimeTypePrefix; - } - - @Override - protected TreeNode<FileTypeMimeSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> rowData) { - return new FileMimeSuffixNode(rowData); - } - - @Override - protected TreeResultsDTO<? extends FileTypeMimeSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getViewsDAO().getFileMimeCounts(this.mimeTypePrefix, this.dataSourceId); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<FileTypeMimeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeMimeSearchParams.class); - - String prefixWithSlash = this.mimeTypePrefix + "/"; - if (originalTreeItem != null - && (originalTreeItem.getSearchParams().getMimeType().startsWith(prefixWithSlash)) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - FileTypeMimeSearchParams searchParam = originalTreeItem.getSearchParams(); - String mimeSuffix = searchParam.getMimeType().substring(prefixWithSlash.length()); - return new TreeResultsDTO.TreeItemDTO<>( - FileTypeMimeSearchParams.getTypeId(), - new FileTypeMimeSearchParams(searchParam.getMimeType(), this.dataSourceId), - mimeSuffix, - mimeSuffix, - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends FileTypeMimeSearchParams> o1, TreeItemDTO<? extends FileTypeMimeSearchParams> o2) { - return STRING_COMPARATOR.compare(o1.getSearchParams().getMimeType(), o2.getSearchParams().getMimeType()); - } - - /** - * Displays an individual suffix node in the tree (i.e. 'aac' underneath - * 'audio'). - */ - static class FileMimeSuffixNode extends TreeNode<FileTypeMimeSearchParams> { - - /** - * Main constructor. - * - * @param itemData The data for the node. - */ - public FileMimeSuffixNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> itemData) { - super("FILE_MIME_" + itemData.getSearchParams().getMimeType(), - "org/sleuthkit/autopsy/images/file-filter-icon.png", - itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayFileMimes(this.getItemData().getSearchParams()); - } - - } - } - - /** - * Displays file extension tree nodes with possibly nested tree nodes (for - * documents and executables). - */ - public static class FileExtFactory extends TreeChildFactory<FileTypeExtensionsSearchParams> { - - private final Long dataSourceId; - private final Collection<FileExtSearchFilter> childFilters; - - /** - * Main constructor using root filters. - * - * @param dataSourceId The data source to filter files to or null. - */ - public FileExtFactory(Long dataSourceId) { - this(dataSourceId, Stream.of(FileExtRootFilter.values()).collect(Collectors.toList())); - } - - /** - * Main constructor. - * - * @param dataSourceId The data source to filter files to or null. - * @param childFilters The file extension filters that will each be a - * child tree node of this factory. - */ - private FileExtFactory(Long dataSourceId, Collection<FileExtSearchFilter> childFilters) { - this.childFilters = childFilters; - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeNode<FileTypeExtensionsSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> rowData) { - Collection<FileExtSearchFilter> childFilters; - if (rowData.getSearchParams().getFilter() == FileExtRootFilter.TSK_DOCUMENT_FILTER) { - childFilters = Stream.of(FileExtDocumentFilter.values()).collect(Collectors.toList()); - } else if (rowData.getSearchParams().getFilter() == FileExtRootFilter.TSK_EXECUTABLE_FILTER) { - childFilters = Stream.of(FileExtExecutableFilter.values()).collect(Collectors.toList()); - } else { - childFilters = null; - } - - return new FileExtNode(rowData, childFilters); - } - - @Override - protected TreeResultsDTO<? extends FileTypeExtensionsSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getViewsDAO().getFileExtCounts(this.childFilters, this.dataSourceId); - } - - @Override - protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { - for (DAOEvent evt : aggEvt.getEvents()) { - if (evt instanceof TreeEvent) { - TreeResultsDTO.TreeItemDTO<FileTypeExtensionsSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, FileTypeExtensionsSearchParams.class); - // if search params has null filter, trigger full refresh - if (treeItem != null && treeItem.getSearchParams().getFilter() == null) { - super.update(); - return; - } - } - } - - super.handleDAOAggregateEvent(aggEvt); - } - - @Override - protected TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) { - TreeResultsDTO.TreeItemDTO<FileTypeExtensionsSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeExtensionsSearchParams.class); - - if (originalTreeItem != null - // if filter is null, this should trigger a full refresh which should be handled in handleDAOAggregateEvent - && originalTreeItem.getSearchParams().getFilter() != null - && this.childFilters.contains(originalTreeItem.getSearchParams().getFilter()) - && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { - - // generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created. - FileTypeExtensionsSearchParams searchParam = originalTreeItem.getSearchParams(); - return new TreeResultsDTO.TreeItemDTO<>( - FileTypeExtensionsSearchParams.getTypeId(), - new FileTypeExtensionsSearchParams(searchParam.getFilter(), this.dataSourceId), - searchParam.getFilter(), - searchParam.getFilter().getDisplayName(), - originalTreeItem.getDisplayCount()); - } - return null; - } - - @Override - public int compare(TreeItemDTO<? extends FileTypeExtensionsSearchParams> o1, TreeItemDTO<? extends FileTypeExtensionsSearchParams> o2) { - return Integer.compare(o1.getSearchParams().getFilter().getId(), o1.getSearchParams().getFilter().getId()); - } - - /** - * Represents a file extension tree node that may or may not have child - * filters. - */ - static class FileExtNode extends TreeNode<FileTypeExtensionsSearchParams> { - - private final Collection<FileExtSearchFilter> childFilters; - - /** - * Main constructor. - * - * @param itemData The data for the node. - * @param childFilters The file filters that will be used to make - * children of this node. - */ - public FileExtNode(TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> itemData, Collection<FileExtSearchFilter> childFilters) { - super("FILE_EXT_" + itemData.getSearchParams().getFilter().getName(), - childFilters == null ? "org/sleuthkit/autopsy/images/file-filter-icon.png" : "org/sleuthkit/autopsy/images/file_types.png", - itemData, - childFilters == null ? Children.LEAF : Children.create(new FileExtFactory(itemData.getSearchParams().getDataSourceId(), childFilters), true), - getDefaultLookup(itemData)); - - this.childFilters = childFilters; - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - if (childFilters == null) { - dataResultPanel.displayFileExtensions(this.getItemData().getSearchParams()); - } else { - super.respondSelection(dataResultPanel); - } - } - - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/VolumeNode.java deleted file mode 100644 index 172516386b0633995ea2d3ff842dec710b97c1cd..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/VolumeNode.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction; -import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction; -import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VolumeRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory.ActionGroup; -import org.sleuthkit.autopsy.mainui.sco.SCOSupporter; -import org.sleuthkit.datamodel.Content; - -/** - * A node representing a Volume. - */ -public class VolumeNode extends BaseNode<SearchResultsDTO, VolumeRowDTO> implements SCOSupporter { - - /** - * Simple node constructor. - * - * @param results The search result DTO. - * @param row The table row DTO. - */ - public VolumeNode(SearchResultsDTO results, VolumeRowDTO row, ExecutorService backgroundTasksPool) { - super(Children.LEAF, ContentNodeUtil.getLookup(row.getContent()), results, row, backgroundTasksPool); - setIconBaseWithExtension(NodeIconUtil.VOLUME.getPath()); //NON-NLS - - // use first cell value for display name - String displayName = row.getCellValues().size() > 0 - ? row.getCellValues().get(0).toString() - : ""; - - setName(ContentNodeUtil.getContentName(row.getContent().getId())); - setDisplayName(displayName); - setShortDescription(displayName); - } - - @Messages({ - "VolumnNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files" - }) - - @Override - public Optional<ActionsFactory.ActionGroup> getNodeSpecificActions() { - ActionGroup group = new ActionGroup(); - group.add(new ExtractUnallocAction( - Bundle.VolumnNode_ExtractUnallocAction_text(), getRowDTO().getContent())); - group.add(new FileSystemDetailsAction(getRowDTO().getContent())); - return Optional.of(group); - } - - @Override - public Optional<Node> getNewWindowActionNode() { - return Optional.of(this); - } - - @Override - public boolean supportsSourceContentViewerActions() { - return true; - } - - @Override - public Optional<Content> getContent() { - return Optional.ofNullable(getRowDTO().getContent()); - } - - @Override - public void updateSheet(List<NodeProperty<?>> newProps) { - super.updateSheet(newProps); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/ActionContext.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/ActionContext.java deleted file mode 100755 index 0a4529d1de64255b0901bddfa7f69307455b1a85..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/ActionContext.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes.actions; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory.ActionGroup; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.Report; - -/** - * Interface for nodes that want to use the ActionFactory to build their popup - * menu; - * - */ -public interface ActionContext { - - /** - * Return the source content. - * - * @return The source content object. - */ - default Optional<Content> getSourceContent() { - return Optional.empty(); - } - - /** - * Returns an ActionGroup containing the Actions that are specific to the - * node. - * - * @return ActionGroup of actions. - */ - default Optional<ActionGroup> getNodeSpecificActions() { - return Optional.empty(); - } - - /** - * Return the linked/associated file for the context. This method must - * return a file if hasLinkedFile returns true. - * - * @return An AbstractFile. - */ - default Optional<AbstractFile> getLinkedFile() { - return Optional.empty(); - } - - /** - * Returns an instance of an BlackboardArtifact. - * - * @return An artifact or null if the ActionContext does not have an - * artifact. - */ - default Optional<BlackboardArtifact> getArtifact() { - return Optional.empty(); - } - - /** - * Returns true if this context supports showing an artifact or a file in - * the Timeline viewer. - * - * @return True if context supports this action. - */ - default boolean supportsViewInTimeline() { - return false; - } - - /** - * Returns the artifact that should appear for the node in the Timeline - * viewer. - * - * @return The artifact to show in the timeline window. - */ - default Optional<BlackboardArtifact> getArtifactForTimeline() { - return Optional.empty(); - } - - /** - * Returns the file that should appear for the node in the Timeline viewer. - * - * @return The file to show in the timeline window. - */ - default Optional<AbstractFile> getFileForViewInTimelineAction() { - return Optional.empty(); - } - - /** - * True if the context supports an action to navigate to source content in - * tree hierarchy. - * - * @return True if this action is supported. - */ - default boolean supportsSourceContentActions() { - return false; - } - - /** - * Returns the source AbstractFile for to be viewed in the Timeline window. - * - * @return The source file. - */ - default Optional<AbstractFile> getSourceFileForTimelineAction() { - return Optional.empty(); - } - - /** - * Returns true if the context supports the associated/link file actions. - * - * @return True if this action is supported. - */ - default boolean supportsAssociatedFileActions() { - return false; - } - - /** - * True if the ActionContext supports showing a node in a new content - * panel. - * - * @return True if this action is supported. - */ - default boolean supportsSourceContentViewerActions() { - return false; - } - - /** - * Returns the node to be display in a new content panel as launched by - * NewWindowAction. - * - * @return The node to display. - */ - default Optional<Node> getNewWindowActionNode() { - return Optional.empty(); - } - - /** - * Returns the node to be display in an external viewer. - * - * @return The node to be display. - */ - default Optional<Node> getExternalViewerActionNode() { - return Optional.empty(); - } - - /** - * Returns true if the context supported the extract actions - * for nodes in the table view. - * - * @return True if the action is supported. - */ - default boolean supportsTableExtractActions() { - return false; - } - - /** - * Returns true if the context supported the extract actions - * for nodes in the tree view. - * - * @return True if the action is supported. - */ - default boolean supportsTreeExtractActions() { - return false; - } - - /** - * Returns true if the context supports the context tag actions. - * - * @return True if the action is supported. - */ - default boolean supportsContentTagAction() { - return false; - } - - /** - * Returns true of the context supported the artifact tag actions. - * - * @return True if the action is supported. - */ - default boolean supportsArtifactTagAction() { - return false; - } - - default boolean supportsReplaceTagAction() { - return false; - } - - /** - * Returns the file to be extracted. - * - * @return True if the action is supported. - */ - default Optional<AbstractFile> getExtractArchiveWithPasswordActionFile() { - return Optional.empty(); - } - - /** - * Returns the content object to be passed into the - * RunIngestModelAction constructor. - * - * @return The content object for ingest. - */ - default Optional<Content> getContentForRunIngestionModuleAction() { - return Optional.empty(); - } - - default Optional<Content> getDataSourceForActions() { - return Optional.empty(); - } - - default Optional<Long> getDataSourceIdForActions() { - return Optional.empty(); - } - - default Optional<AbstractFile> getFileForDirectoryBrowseMode() { - return Optional.empty(); - } - - default boolean supportsResultArtifactAction() { - return false; - } - - default boolean supportsFileSearchAction() { - return false; - } - - default boolean supportsCollapseAll() { - return false; - } - - - default boolean hasAnalysisResultConfigurations() { - return false; - } - - /** - * @return Provides the node configurations if applicable or an empty list. - */ - default List<String> getAnalysisResultConfigurations() { - return Collections.emptyList(); - } - - /** - * @return The analysis result type if applicable or empty. - */ - default Optional<BlackboardArtifact.Type> getAnalysisResultType() { - return Optional.empty(); - } - - /** - * @return The report relevant to the node if present. - */ - default Optional<Report> getReport() { - return Optional.empty(); - } - - /** - * @return The person relevant to the node if present. - */ - default Optional<Person> getPerson() { - return Optional.empty(); - } - - /** - * @return The host relevant to the node if present. - */ - default Optional<Host> getHost() { - return Optional.empty(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/ActionsFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/ActionsFactory.java deleted file mode 100755 index 095d831c69cc3f2c1b16bfd7a3f64296d204e3a1..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/ActionsFactory.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes.actions; - -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.stream.Stream; -import javax.swing.Action; -import org.openide.actions.PropertiesAction; -import org.openide.nodes.Node; -import org.openide.util.Lookup; -import org.openide.util.NbBundle.Messages; -import org.openide.util.Utilities; -import org.openide.util.actions.SystemAction; -import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; -import org.sleuthkit.autopsy.actions.AddContentTagAction; -import org.sleuthkit.autopsy.actions.DeleteContentTagAction; -import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; -import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; -import org.sleuthkit.autopsy.actions.DeleteReportAction; -import org.sleuthkit.autopsy.actions.OpenReportAction; -import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; -import org.sleuthkit.autopsy.actions.ViewArtifactAction; -import org.sleuthkit.autopsy.actions.ViewOsAccountAction; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.DeleteDataSourceAction; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; -import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; -import org.sleuthkit.autopsy.datamodel.hosts.AssociatePersonsMenuAction; -import org.sleuthkit.autopsy.datamodel.hosts.MergeHostMenuAction; -import org.sleuthkit.autopsy.datamodel.hosts.RemoveParentPersonAction; -import org.sleuthkit.autopsy.datamodel.persons.DeletePersonAction; -import org.sleuthkit.autopsy.datamodel.persons.EditPersonAction; -import org.sleuthkit.autopsy.datasourcesummary.ui.ViewSummaryInformationAction; -import org.sleuthkit.autopsy.directorytree.CollapseAction; -import org.sleuthkit.autopsy.directorytree.ExportCSVAction; -import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; -import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; -import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.FileSearchTreeAction; -import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.ViewContextAction; -import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction; -import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; -import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; -import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.Report; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * An action factory for node classes that will have a popup menu. - * - * Nodes do not need to implement the full ActionContext interface. They should - * subclass AbstractAutopsyNode and implement only the ActionContext methods for - * their supported actions. - */ -public final class ActionsFactory { - - @Messages({ - "ActionsFactory_Collapse_All_Name=Collapse All" - }) - - private static final Logger logger = Logger.getLogger(ActionsFactory.class.getName()); - private static final Action collapseAllAction = new CollapseAction(Bundle.ActionsFactory_Collapse_All_Name()); - - // private constructor for utility class. - private ActionsFactory() {} - - /** - * Create the list of actions for given ActionContext. - - * @param actionContext The context for the actions. - * - * @return The list of Actions to display. - */ - public static Action[] getActions(ActionContext actionContext) { - List<ActionGroup> actionGroups = new ArrayList<>(); - - Optional<ActionGroup> nodeSpecificGroup = actionContext.getNodeSpecificActions(); - if (nodeSpecificGroup.isPresent()) { - actionGroups.add(nodeSpecificGroup.get()); - } - - ActionGroup group = new ActionGroup(); - Optional<Action> opAction = getBrowseModeAction(actionContext); - if(opAction.isPresent()) { - group.add(opAction.get()); - } - - if (actionContext.supportsViewInTimeline()) { - group.add(getViewInTimelineAction(actionContext)); - } - - actionGroups.add(group); - - group = new ActionGroup(); - if (actionContext.supportsAssociatedFileActions()) { - Optional<ActionGroup> subGroup = getAssociatedFileActions(actionContext); - if(subGroup.isPresent()) { - group.addAll(subGroup.get()); - } - } - - if (actionContext.getSourceContent().isPresent()) { - - Optional<ActionGroup> optionalGroup = getSourceContentActions(actionContext); - if (optionalGroup.isPresent()) { - group.addAll(optionalGroup.get()); - } - } - actionGroups.add(group); - - Optional<Content> optionalSourceContext = actionContext.getSourceContent(); - if (optionalSourceContext.isPresent() && optionalSourceContext.get() instanceof Report) { - actionGroups.add(new ActionGroup(DataModelActionsFactory.getActions(optionalSourceContext.get(), false))); - } - - if (actionContext.supportsSourceContentViewerActions()) { - Optional<ActionGroup> optionalGroup = getSourceContentViewerActions(actionContext); - if (optionalGroup.isPresent()) { - actionGroups.add(optionalGroup.get()); - } - } - - if (actionContext.supportsTableExtractActions()) { - actionGroups.add(getTableExtractActions()); - } else if (actionContext.supportsTreeExtractActions()) { - actionGroups.add(getTreeExtractActions()); - } - - group = new ActionGroup(); - Optional<ActionGroup> ingestGroup = getRunIngestAction(actionContext); - if(ingestGroup.isPresent()) { - group.addAll(ingestGroup.get()); - } - group.addAll(ContextMenuExtensionPoint.getActions()); - actionGroups.add(group); - - actionGroups.add(getTagActions(actionContext)); - - - Optional<AbstractFile> optionalFile = actionContext.getExtractArchiveWithPasswordActionFile(); - if (optionalFile.isPresent()) { - actionGroups.add(new ActionGroup(new ExtractArchiveWithPasswordAction(optionalFile.get()))); - } - - // handle report actions if report is present. - actionContext.getReport() - .map(report -> { - ActionGroup actions = new ActionGroup(); - actions.add(new OpenReportAction(report.getPath())); - actions.add(DeleteReportAction.getInstance()); - return actions; - }) - .ifPresent(ag -> actionGroups.add(ag)); - - - getHostActions(actionContext).ifPresent(ag -> actionGroups.add(ag)); - - getPersonActions(actionContext).ifPresent(ag -> actionGroups.add(ag)); - - List<Action> actionList = new ArrayList<>(); - for (ActionGroup aGroup : actionGroups) { - if (aGroup != null) { - actionList.addAll(aGroup); - actionList.add(null); - } - } - - // Add the properties menu item to the bottom. - actionList.add(SystemAction.get(PropertiesAction.class)); - - - if(actionContext.supportsCollapseAll()) { - actionList.add(null); - actionList.add(collapseAllAction); - } - - Action[] actions = new Action[actionList.size()]; - actionList.toArray(actions); - return actions; - } - - /** - * Returns the Extract actions for a table node. These actions are not specific to the - * ActionContext. - * - * @return The Extract ActionGroup. - */ - static ActionGroup getTableExtractActions() { - ActionGroup actionsGroup = new ActionGroup(); - - Lookup lookup = Utilities.actionsGlobalContext(); - Collection<? extends AbstractFile> selectedFiles =lookup.lookupAll(AbstractFile.class); - if(selectedFiles.size() > 0) { - actionsGroup.add(ExtractAction.getInstance()); - } - actionsGroup.add(ExportCSVAction.getInstance()); - - return actionsGroup; - } - - /** - * Returns the Extract actions for a tree node. These actions are not specific to the - * ActionContext. - * - * @return The Extract ActionGroup. - */ - static ActionGroup getTreeExtractActions() { - ActionGroup actionsGroup = new ActionGroup(); - actionsGroup.add(ExtractAction.getInstance()); - - return actionsGroup; - } - - /** - * Returns the ActionGroup for the source content viewer actions . - * - * @param actionContext - * - * @return The action group with the actions, or null if these actions are - * not supported by the ActionContext. - */ - @Messages({ - "ActionsFactory_getSrcContentViewerActions_viewInNewWin=View Item in New Window", - "ActionsFactory_getSrcContentViewerActions_openInExtViewer=Open in External Viewer Ctrl+E" - }) - private static Optional<ActionGroup> getSourceContentViewerActions(ActionContext actionContext) { - ActionGroup actionGroup = new ActionGroup(); - Optional<Node> nodeOptional = actionContext.getNewWindowActionNode(); - - if (nodeOptional.isPresent()) { - actionGroup.add(new NewWindowViewAction(Bundle.ActionsFactory_getSrcContentViewerActions_viewInNewWin(), nodeOptional.get())); - } - - nodeOptional = actionContext.getExternalViewerActionNode(); - if (nodeOptional.isPresent()) { - int selectedFileCount = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size(); - if (selectedFileCount == 1) { - actionGroup.add(new ExternalViewerAction(Bundle.ActionsFactory_getSrcContentViewerActions_openInExtViewer(), nodeOptional.get())); - } else if(selectedFileCount > 1) { - actionGroup.add(ExternalViewerShortcutAction.getInstance()); - } - } - return actionGroup.isEmpty() ? Optional.empty() : Optional.of(actionGroup); - } - - /** - * Creates the ActionGroup for the source content actions. - * - * @param actionContext The context for these actions. - * - * @return An ActionGroup if one of the actions is supported. - */ - @Messages({ - "# {0} - contentType", - "ActionsFactory_getTimelineSrcContentAction_actionDisplayName=View Source {0} in Timeline... " - }) - private static Optional<ActionGroup> getSourceContentActions(ActionContext actionContext) { - ActionGroup group = new ActionGroup(); - - Optional<Action> optionalAction = getViewSrcContentAction(actionContext); - if (optionalAction.isPresent()) { - group.add(optionalAction.get()); - } - - Optional<AbstractFile> srcContentOptional = actionContext.getSourceFileForTimelineAction(); - if (srcContentOptional.isPresent()) { - group.add(new ViewFileInTimelineAction(srcContentOptional.get(), - Bundle.ActionsFactory_getTimelineSrcContentAction_actionDisplayName( - getContentTypeStr(srcContentOptional.get())))); - } - - Optional<ActionGroup> optionalGroup = getViewResultArtifactActions(actionContext); - if(optionalGroup.isPresent()) { - group.addAll(optionalGroup.get()); - } - - return group.isEmpty() ? Optional.empty() : Optional.of(group); - } - - @Messages({ - "ActionFactory_getViewResultArtifactActions_result_text=View Source Result", - "ActionFactory_getViewResultArtifactActions_timeline_text=View Source Result in Timeline..." - }) - private static Optional<ActionGroup> getViewResultArtifactActions(ActionContext actionContext) { - ActionGroup group = new ActionGroup(); - - if(actionContext.supportsResultArtifactAction()) { - Optional<BlackboardArtifact> optional = actionContext.getArtifact(); - if(optional.isPresent()) { - group.add(new ViewArtifactAction( - optional.get(), - Bundle.ActionFactory_getViewResultArtifactActions_result_text())); - - group.add(new ViewArtifactInTimelineAction(optional.get(), Bundle.ActionFactory_getViewResultArtifactActions_timeline_text())); - } - } - - return group.isEmpty() ? Optional.empty() : Optional.of(group); - } - - /** - * - * @param context - * - * @return - */ - @Messages({ - "# {0} - type", - "ActionsFactory_getAssociatedFileActions_viewAssociatedFileAction=View {0} in Directory", - "# {0} - type", - "ActionsFactory_getAssociatedFileActions_viewAssociatedFileInTimelineAction=View {0} in Timeline..." - }) - private static Optional<ActionGroup> getAssociatedFileActions(ActionContext context) { - Optional<AbstractFile> associatedFileOptional = context.getLinkedFile(); - Optional<BlackboardArtifact> artifactOptional = context.getArtifact(); - - if (!associatedFileOptional.isPresent() || !artifactOptional.isPresent()) { - return Optional.empty(); - } - - BlackboardArtifact.Type artifactType; - try { - artifactType = artifactOptional.get().getType(); - } catch (TskCoreException ex) { - - return Optional.empty(); - } - - ActionGroup group = new ActionGroup(Arrays.asList( - new ViewContextAction( - Bundle.ActionsFactory_getAssociatedFileActions_viewAssociatedFileAction( - getAssociatedTypeStr(artifactType)), - associatedFileOptional.get()), - new ViewFileInTimelineAction(associatedFileOptional.get(), - Bundle.ActionsFactory_getAssociatedFileActions_viewAssociatedFileInTimelineAction( - getAssociatedTypeStr(artifactType))) - )); - - return Optional.of(group); - } - - /** - * Returns the tag actions for the given context. - * - * @param context The action context. - * - * @return Tag ActionGroup. - */ - private static ActionGroup getTagActions(ActionContext context) { - int selectedFileCount = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size(); - int selectedArtifactCount = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactItem.class).size(); - - ActionGroup actionGroup = new ActionGroup(); - - if (context.supportsContentTagAction()) { - actionGroup.add(AddContentTagAction.getInstance()); - } - - if (context.supportsArtifactTagAction()) { - actionGroup.add(AddBlackboardArtifactTagAction.getInstance()); - } - - if (context.supportsContentTagAction() && (selectedFileCount == 1)) { - actionGroup.add(DeleteFileContentTagAction.getInstance()); - } - - if (context.supportsArtifactTagAction() && selectedArtifactCount == 1) { - actionGroup.add(DeleteFileBlackboardArtifactTagAction.getInstance()); - } - - if((context.supportsArtifactTagAction() || context.supportsContentTagAction()) && context.supportsReplaceTagAction()) { - actionGroup.add(DeleteContentTagAction.getInstance()); - actionGroup.add(ReplaceContentTagAction.getInstance()); - } - - return actionGroup; - } - - @Messages({ - "# {0} - contentType", - "ArtifactFactory_getViewSrcContentAction_displayName=View Source {0} in Directory", - "# {0} - contentType", - "ArtifactFactory_getViewSrcContentAction_displayName2=View Source {0}" - }) - /** - * Create an action to navigate to source content in tree hierarchy. - * - * @param context - * - * @return The action for the given context. - */ - private static Optional<Action> getViewSrcContentAction(ActionContext context) { - Optional<Content> sourceContent = context.getSourceContent(); - Optional<BlackboardArtifact> artifact = context.getArtifact(); - - if (sourceContent.isPresent()) { - if (sourceContent.get() instanceof DataArtifact) { - return Optional.of(new ViewArtifactAction( - (BlackboardArtifact) sourceContent.get(), - Bundle.ArtifactFactory_getViewSrcContentAction_displayName2( - getContentTypeStr(sourceContent.get())))); - } else if (sourceContent.get() instanceof OsAccount) { - return Optional.of(new ViewOsAccountAction( - (OsAccount) sourceContent.get(), - Bundle.ArtifactFactory_getViewSrcContentAction_displayName2( - getContentTypeStr(sourceContent.get())))); - } else if (sourceContent.get() instanceof AbstractFile || (artifact.isPresent() && artifact.get() instanceof DataArtifact)) { - return Optional.of(new ViewContextAction( - Bundle.ArtifactFactory_getViewSrcContentAction_displayName( - getContentTypeStr(sourceContent.get())), - sourceContent.get())); - } - } - - return Optional.empty(); - } - - /** - * Returns the name to represent the type of the content (file, data - * artifact, os account, item). - * - * @param content The content. - * - * @return The name of the type of content. - */ - @Messages({ - "ActionFactory_getViewSrcContentAction_type_File=File", - "ActionFactory_getViewSrcContentAction_type_DataArtifact=Data Artifact", - "ActionFactory_getViewSrcContentAction_type_OSAccount=OS Account", - "ActionFactory_getViewSrcContentAction_type_unknown=Item" - }) - private static String getContentTypeStr(Content content) { - if (content instanceof AbstractFile) { - return Bundle.ActionFactory_getViewSrcContentAction_type_File(); - } else if (content instanceof DataArtifact) { - return Bundle.ActionFactory_getViewSrcContentAction_type_DataArtifact(); - } else if (content instanceof OsAccount) { - return Bundle.ActionFactory_getViewSrcContentAction_type_OSAccount(); - } else { - return Bundle.ActionFactory_getViewSrcContentAction_type_unknown(); - } - } - - /** - * If the artifact represented by this node has a timestamp, an action to - * view it in the timeline. - * - * @param context The action context. - * - * @return The action or null if no action should exist. - */ - @Messages({ - "ActionsFactory_getTimelineArtifactAction_displayName=View Selected Item in Timeline... " - }) - private static Action getViewInTimelineAction(ActionContext context) { - Optional<BlackboardArtifact> optionalArtifact = context.getArtifact(); - Optional<AbstractFile> optionalFile = context.getFileForViewInTimelineAction(); - if (optionalArtifact.isPresent()) { - return new ViewArtifactInTimelineAction(optionalArtifact.get(), Bundle.ActionsFactory_getTimelineArtifactAction_displayName()); - } else if (optionalFile.isPresent()) { - return ViewFileInTimelineAction.createViewFileAction(optionalFile.get()); - } - return null; - } - - /** - * - * @param context - * @return - */ - @Messages({ - "ActionFactory_openFileSearchByAttr_text=Open File Search by Attributes" - }) - private static Optional<ActionGroup> getRunIngestAction(ActionContext context) { - ActionGroup group = new ActionGroup(); - Optional<Content> optional = context.getDataSourceForActions(); - if(optional.isPresent()) { - group.add(new FileSearchTreeAction(Bundle.ActionFactory_openFileSearchByAttr_text(), optional.get().getId())); - group.add(new ViewSummaryInformationAction(optional.get().getId())); - group.add(new RunIngestModulesAction(Collections.<Content>singletonList(optional.get()))); - group.add(new DeleteDataSourceAction(optional.get().getId())); - } - else { - optional = context.getContentForRunIngestionModuleAction(); - - if(optional.isPresent()) { - if (optional.get() instanceof AbstractFile) { - if(context.supportsFileSearchAction()) { - group.add(new FileSearchTreeAction(Bundle.ActionFactory_openFileSearchByAttr_text(), optional.get().getId())); - } - - group.add(new RunIngestModulesAction((AbstractFile)optional.get())); - } else { - logger.log(Level.WARNING, "Can not create RunIngestModulesAction on non-AbstractFile content with ID " + optional.get().getId()); - } - } - } - - return group.isEmpty() ? Optional.empty() : Optional.of(group); - } - - @Messages({ - "ActionsFactory_viewFileInDir_text=View File in Directory" - }) - private static Optional<Action> getBrowseModeAction(ActionContext actionContext) { - Optional<AbstractFile> optional = actionContext.getFileForDirectoryBrowseMode(); - if(optional.isPresent()) { - return Optional.of(new ViewContextAction(Bundle.ActionsFactory_viewFileInDir_text(), optional.get())); - } - - return Optional.empty(); - } - - /** - * Returns the name of the artifact based on the artifact type to be used - * with the associated file string in a right click menu. - * - * @param artifactType The artifact type. - * - * @return The artifact type name. - */ - @Messages({ - "ActionsFactory_getAssociatedTypeStr_webCache=Cached File", - "ActionsFactory_getAssociatedTypeStr_webDownload=Downloaded File", - "ActionsFactory_getAssociatedTypeStr_associated=Associated File",}) - private static String getAssociatedTypeStr(BlackboardArtifact.Type artifactType) { - if (BlackboardArtifact.Type.TSK_WEB_CACHE.equals(artifactType)) { - return Bundle.ActionsFactory_getAssociatedTypeStr_webCache(); - } else if (BlackboardArtifact.Type.TSK_WEB_DOWNLOAD.equals(artifactType)) { - return Bundle.ActionsFactory_getAssociatedTypeStr_webDownload(); - } else { - return Bundle.ActionsFactory_getAssociatedTypeStr_associated(); - } - } - - /** - * Returns an action group of host actions if host is present in action - * context. Otherwise, returns empty. - * - * @param actionContext The action context. - * - * @return The action group or empty. - */ - private static Optional<ActionGroup> getHostActions(ActionContext actionContext) { - return actionContext.getHost() - .flatMap(host -> { - // if there is a host, then provide actions - if (host != null) { - List<Action> actionsList = new ArrayList<>(); - - // Add the appropriate Person action - Optional<Person> parent; - try { - parent = Case.getCurrentCaseThrows().getSleuthkitCase().getPersonManager().getPerson(host); - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.WARNING, String.format("Error fetching parent person of host: %s", host.getName() == null ? "<null>" : host.getName()), ex); - return Optional.empty(); - } - - // if there is a parent, only give option to remove parent person. - if (parent.isPresent()) { - actionsList.add(new RemoveParentPersonAction(host, parent.get())); - } else { - actionsList.add(new AssociatePersonsMenuAction(host)); - } - - // Add option to merge hosts - actionsList.add(new MergeHostMenuAction(host)); - - return Optional.of(new ActionGroup(actionsList)); - } else { - return Optional.empty(); - } - }); - } - - /** - * Returns an action group of person actions if person is present in action - * context. Otherwise, returns empty. - * - * @param actionContext The action context. - * - * @return The action group or empty. - */ - private static Optional<ActionGroup> getPersonActions(ActionContext actionContext) { - return actionContext.getPerson() - .flatMap(person -> { - return Optional.of(new ActionGroup(Arrays.asList( - new EditPersonAction(person), - new DeletePersonAction(person) - ))); - }); - } - - - /** - * Represents a group of related actions. - */ - public static class ActionGroup extends AbstractCollection<Action> { - - private final List<Action> actionList; - - /** - * Construct a new ActionGroup instance with an empty list. - */ - public ActionGroup() { - this.actionList = new ArrayList<>(); - } - - /** - * Construct a new ActionGroup instance with the given list of actions. - * - * @param actionList List of actions to add to the group. - */ - public ActionGroup(List<Action> actionList) { - this(); - this.actionList.addAll(actionList); - } - - public ActionGroup(Action action) { - this(); - actionList.add(action); - } - - @Override - public boolean isEmpty() { - return actionList.isEmpty(); - } - - @Override - public boolean add(Action action) { - return actionList.add(action); - } - - @Override - public Iterator<Action> iterator() { - return actionList.iterator(); - } - - @Override - public void forEach(Consumer<? super Action> action) { - actionList.forEach(action); - } - - @Override - public int size() { - return actionList.size(); - } - - @Override - public Stream<Action> stream() { - return actionList.stream(); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/Bundle.properties-MERGED deleted file mode 100755 index 73e116239b9d97167db5b286b500348cc4f02712..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/Bundle.properties-MERGED +++ /dev/null @@ -1,38 +0,0 @@ -ActionFactory_getViewResultArtifactActions_result_text=View Source Result -ActionFactory_getViewResultArtifactActions_timeline_text=View Source Result in Timeline... -ActionFactory_getViewSrcContentAction_type_DataArtifact=Data Artifact -ActionFactory_getViewSrcContentAction_type_File=File -ActionFactory_getViewSrcContentAction_type_OSAccount=OS Account -ActionFactory_getViewSrcContentAction_type_unknown=Item -ActionFactory_openFileSearchByAttr_text=Open File Search by Attributes -ActionsFactory_Collapse_All_Name=Collapse All -# {0} - type -ActionsFactory_getAssociatedFileActions_viewAssociatedFileAction=View {0} in Directory -# {0} - type -ActionsFactory_getAssociatedFileActions_viewAssociatedFileInTimelineAction=View {0} in Timeline... -ActionsFactory_getAssociatedTypeStr_associated=Associated File -ActionsFactory_getAssociatedTypeStr_webCache=Cached File -ActionsFactory_getAssociatedTypeStr_webDownload=Downloaded File -ActionsFactory_getSrcContentViewerActions_openInExtViewer=Open in External Viewer Ctrl+E -ActionsFactory_getSrcContentViewerActions_viewInNewWin=View Item in New Window -ActionsFactory_getTimelineArtifactAction_displayName=View Selected Item in Timeline... -# {0} - contentType -ActionsFactory_getTimelineSrcContentAction_actionDisplayName=View Source {0} in Timeline... -ActionsFactory_viewFileInDir_text=View File in Directory -# {0} - contentType -ArtifactFactory_getViewSrcContentAction_displayName=View Source {0} in Directory -# {0} - contentType -ArtifactFactory_getViewSrcContentAction_displayName2=View Source {0} -DeleteAnalysisResultAction_label=Delete Analysis Result -DeleteAnalysisResultsAction.label=Delete Analysis Results -# {0} - result type -DeleteAnalysisResultsAction.progress.allResults=Deleting Analysis Results type {0} -# {0} - result type -# {1} - configuration -DeleteAnalysisResultsAction.progress.allResultsWithConfiguration=Deleting Analysis Results type {0} and configuration {1} -DeleteAnalysisResultsAction.title=Deleting Analysis Results -# {0} - result type -DeleteAnalysisResultsAction.warning.allResults=Are you sure you want to delete all Analysis Results of type {0}? -# {0} - result type -# {1} - configuration -DeleteAnalysisResultsAction.warning.allResultsWithConfiguration=Are you sure you want to delete all Analysis Results of type {0} and configuration {1}? diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/DeleteAnalysisResultAction.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/DeleteAnalysisResultAction.java deleted file mode 100755 index 6bcffd97d8328b78adfa94cb11f7babd04b947a8..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/DeleteAnalysisResultAction.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes.actions; - -import java.awt.event.ActionEvent; -import java.util.Collection; -import java.util.logging.Level; -import javax.swing.AbstractAction; -import javax.swing.SwingWorker; -import org.openide.util.NbBundle.Messages; -import org.openide.util.Utilities; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AnalysisResult; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Action class for Deleting Analysis Result objects. - */ -public class DeleteAnalysisResultAction extends AbstractAction { - - @Messages({ - "DeleteAnalysisResultAction_label=Delete Analysis Result" - }) - - private static final Logger logger = Logger.getLogger(DeleteAnalysisResultAction.class.getName()); - - private static final long serialVersionUID = 1L; - - public DeleteAnalysisResultAction() { - super(Bundle.DeleteAnalysisResultAction_label()); - } - - @Override - public void actionPerformed(ActionEvent e) { - Collection<? extends AnalysisResult> selectedResult = Utilities.actionsGlobalContext().lookupAll(AnalysisResult.class); - - SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { - @Override - protected Void doInBackground() throws Exception { - for (AnalysisResult result : selectedResult) { - if (!isCancelled()) { - try { - Case.getCurrentCase().getSleuthkitCase().getBlackboard().deleteAnalysisResult(result); - logger.log(Level.INFO, "Deleted Analysis Result id = " + result.getId()); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to delete analysis result id = " + result.getId(), ex); - } - } - } - return null; - } - }; - - worker.execute(); - - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/DeleteAnalysisResultSetAction.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/DeleteAnalysisResultSetAction.java deleted file mode 100755 index 6a0596331967c47769f6043d91b30949ec982dd9..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/actions/DeleteAnalysisResultSetAction.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.nodes.actions; - -import java.awt.event.ActionEvent; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import java.util.logging.Level; -import javax.swing.AbstractAction; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; -import org.openide.util.NbBundle.Messages; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.progress.AppFrameProgressBar; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Action class for Deleting Analysis Result objects. - */ -public class DeleteAnalysisResultSetAction extends AbstractAction { - - @Messages({ - "DeleteAnalysisResultsAction.label=Delete Analysis Results", - "DeleteAnalysisResultsAction.title=Deleting Analysis Results", - "# {0} - result type", - "DeleteAnalysisResultsAction.progress.allResults=Deleting Analysis Results type {0}", - "# {0} - result type", "# {1} - configuration", - "DeleteAnalysisResultsAction.progress.allResultsWithConfiguration=Deleting Analysis Results type {0} and configuration {1}", - "# {0} - result type", - "DeleteAnalysisResultsAction.warning.allResults=Are you sure you want to delete all Analysis Results of type {0}?", - "# {0} - result type", "# {1} - configuration", - "DeleteAnalysisResultsAction.warning.allResultsWithConfiguration=Are you sure you want to delete all Analysis Results of type {0} and configuration {1}?" - }) - - private static final Logger logger = Logger.getLogger(DeleteAnalysisResultSetAction.class.getName()); - private static final long serialVersionUID = 1L; - - private final BlackboardArtifact.Type type; - private final Supplier<List<String>> configurationsFetcher; - private final Long dsID; - - public DeleteAnalysisResultSetAction(BlackboardArtifact.Type type, Supplier<List<String>> configurationsFetcher, Long dsID) { - super(Bundle.DeleteAnalysisResultsAction_label()); - this.type = type; - this.configurationsFetcher = configurationsFetcher; - this.dsID = dsID; - } - - @Override - public void actionPerformed(ActionEvent e) { - - AppFrameProgressBar progress = new AppFrameProgressBar(Bundle.DeleteAnalysisResultsAction_title()); - - SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { - @Override - protected Void doInBackground() throws Exception { - // fetch configurations possibly from the database - List<String> configurations = (DeleteAnalysisResultSetAction.this.configurationsFetcher == null) - ? null - : DeleteAnalysisResultSetAction.this.configurationsFetcher.get(); - - String warningMessage; - if (configurations == null || configurations.isEmpty() || configurations.size() > 1 - || type == BlackboardArtifact.Type.TSK_KEYWORD_HIT) { - // either no configuration or multiple configurations. - // do not display configuration for KWS hits as it contains (KW term, search type, KW list name). - warningMessage = Bundle.DeleteAnalysisResultsAction_warning_allResults(type.getDisplayName()); - } else { - warningMessage = Bundle.DeleteAnalysisResultsAction_warning_allResultsWithConfiguration(type.getDisplayName(), configurations.get(0)); - } - - AtomicReference<Integer> confirmResponse = new AtomicReference<Integer>(JOptionPane.NO_OPTION); - SwingUtilities.invokeAndWait(() -> { - int response = JOptionPane.showConfirmDialog( - WindowManager.getDefault().getMainWindow(), - warningMessage, - Bundle.DeleteAnalysisResultsAction_title(), - JOptionPane.YES_NO_OPTION); - - confirmResponse.set(response); - }); - - if (confirmResponse.get() != JOptionPane.YES_OPTION) { - return null; - } - - progress.start(Bundle.DeleteAnalysisResultsAction_title()); - try { - if (configurations == null || configurations.isEmpty()) { - progress.switchToIndeterminate(Bundle.DeleteAnalysisResultsAction_progress_allResults(type.getDisplayName())); - if (!isCancelled()) { - delete(type, "", dsID); - } - } else { - for (String configuration : configurations) { - progress.switchToIndeterminate(Bundle.DeleteAnalysisResultsAction_progress_allResultsWithConfiguration(type.getDisplayName(), configuration)); - if (!isCancelled()) { - delete(type, configuration, dsID); - } - } - } - return null; - } finally { - progress.finish(); - } - } - }; - - worker.execute(); - } - - private static void delete(BlackboardArtifact.Type type, String configuration, Long dsID) { - try { - logger.log(Level.INFO, "Deleting Analysis Results type = {0}, data source ID = {1}, configuration = {2}", new Object[]{type, dsID, configuration}); - Case.getCurrentCase().getSleuthkitCase().getBlackboard().deleteAnalysisResults(type, dsID, configuration); - logger.log(Level.INFO, "Deleted Analysis Results type = {0}, data source ID = {1}, configuration = {2}", new Object[]{type, dsID, configuration}); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to delete analysis results of type = " + type + ", data source ID = " + dsID + ", configuration = " + configuration, ex); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/sco/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/mainui/sco/Bundle.properties-MERGED deleted file mode 100755 index 29a3f19a2a968ab160b56fefe28db3cd67daecc7..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/sco/Bundle.properties-MERGED +++ /dev/null @@ -1,14 +0,0 @@ -SCOFetcher_nodescription_text=No description -SCOFetcher_occurrences_defaultDescription=No correlation properties found -SCOFetcher_occurrences_multipleProperties=Multiple different correlation properties exist for this result -SCOSupporter.valueLoading=value loading -# {0} - significanceDisplayName -SCOSupporter_getScorePropertyAndDescription_description=Has an {0} analysis result score -SCOSupporter_nodescription_text=no description -SCOUtils_columnKeys_comment_name=C -SCOUtils_columnKeys_occurrences_name=O -SCOUtils_columnKeys_score_name=S -# {0} - occurrenceCount -# {1} - attributeType -SCOUtils_createSheet_count_description=There were {0} datasource(s) found with occurrences of the correlation value of type {1} -SCOUtils_createSheet_count_noCorrelationValues_description=Unable to find other occurrences because no value exists for the available correlation property diff --git a/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOFetcher.java b/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOFetcher.java deleted file mode 100755 index 6263500b2d4e0a728989c1c0de02ce17203c10e9..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOFetcher.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.sco; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.RunnableFuture; -import java.util.logging.Level; -import javax.swing.SwingUtilities; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.mainui.sco.SCOFetcher.SCOData; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.AnalysisResult; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.OsAccountInstance; -import org.sleuthkit.datamodel.Score; -import org.sleuthkit.datamodel.Tag; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * A Swingworker for fetching the SCO data for the given supporter. - * - * The SwingWorkers can be executed as a normal SwingWorkers or passed a - * separate ExecutorService. Nodes should use the ExecutorService in BaseNode to - * avoid interrupting other SwingWorkers. - */ -public class SCOFetcher<T extends Content> implements Runnable { - - private final WeakReference<SCOSupporter> weakSupporterRef; - private static final Logger logger = Logger.getLogger(SCOFetcher.class.getName()); - - /** - * Construct a new SCOFetcher. - * - * @param weakSupporterRef A weak reference to a SCOSupporter. - */ - public SCOFetcher(WeakReference<SCOSupporter> weakSupporterRef) { - this.weakSupporterRef = weakSupporterRef; - } - - @Override - public void run() { - try { - SCOData data = doInBackground(); - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - SCOFetcher.done(data, weakSupporterRef.get()); - } - }); - - } catch (Exception ex) { - logger.log(Level.SEVERE, "An exception occurred while trying to update the the SCO data", ex); - } - } - - @NbBundle.Messages({"SCOFetcher_occurrences_defaultDescription=No correlation properties found", - "SCOFetcher_occurrences_multipleProperties=Multiple different correlation properties exist for this result" - }) - private SCOData doInBackground() throws Exception { - SCOSupporter scoSupporter = weakSupporterRef.get(); - Content content = scoSupporter.getContent().get(); - //Check for stale reference or if columns are disabled - if (content == null || UserPreferences.getHideSCOColumns()) { - return null; - } - // get the SCO column values - Pair<Score, String> scoreAndDescription; - Pair<Long, String> countAndDescription = null; - scoreAndDescription = scoSupporter.getScorePropertyAndDescription(); - - String description = Bundle.SCOFetcher_occurrences_defaultDescription(); - List<CorrelationAttributeInstance> listOfPossibleAttributes = new ArrayList<>(); - //the lists returned will be empty if the CR is not enabled - if (content instanceof AbstractFile) { - listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) content)); - } else if (content instanceof AnalysisResult) { - listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) content)); - } else if (content instanceof DataArtifact) { - listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) content)); - } else if (content instanceof OsAccount) { - try { - List<OsAccountInstance> osAccountInstances = ((OsAccount) content).getOsAccountInstances(); - - /* - * In the most common use cases it will not matter which - * OsAccountInstance is selected, so choosing the first one is - * the most efficient solution. - */ - OsAccountInstance osAccountInstance = osAccountInstances.isEmpty() ? null : osAccountInstances.get(0); - /* - * If we have a Case whith both data sources in the CR and data - * sources not in the CR, some of the OsAccountInstances for - * this OsAccount have not been processed into the CR. In this - * situation the counts may not always be accurate or - * consistent. - * - * In order to ensure conistency in all use cases we would need - * to ensure we always had an OsAccountInstance whose data - * source was in the CR when such an OsAccountInstance was - * available. - * - * The following block of code has been commented out because it - * reduces efficiency in what are believed to be the most common - * use cases. It would serve the purpose of providing - * consistency in edge cases where users are putting some but - * not all the data concerning OS Accounts, which is present in - * a single Case, into the CR. See TODO-JIRA-8031 for a similar - * issue in the OO viewer. - */ - -// if (CentralRepository.isEnabled() && !osAccountInstances.isEmpty()) { -// try { -// CentralRepository centralRepo = CentralRepository.getInstance(); -// //Correlation Cases are cached when we get them so this shouldn't involve a round trip for every node. -// CorrelationCase crCase = centralRepo.getCase(Case.getCurrentCaseThrows()); -// for (OsAccountInstance caseOsAccountInstance : osAccountInstances) { -// //correlation data sources are also cached so once should not involve round trips every time. -// CorrelationDataSource correlationDataSource = centralRepo.getDataSource(crCase, caseOsAccountInstance.getDataSource().getId()); -// if (correlationDataSource != null) { -// //we have found a data source which exists in the CR we will use it instead of the arbitrary first instance -// osAccountInstance = caseOsAccountInstance; -// break; -// } -// } -// } catch (CentralRepoException ex) { -// logger.log(Level.SEVERE, "Error checking CR for data sources which exist in it", ex); -// } catch (NoCurrentCaseException ex) { -// logger.log(Level.WARNING, "The current case was closed while attempting to find a data source in the central repository", ex); -// } -// } - listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(osAccountInstance)); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Unable to get the DataSource or OsAccountInstances from an OsAccount with ID: " + content.getId(), ex); - } - } - - Optional<List<Tag>> optionalList = scoSupporter.getAllTagsFromDatabase(); - - DataResultViewerTable.HasCommentStatus commentStatus = scoSupporter.getCommentProperty(optionalList.isPresent()? optionalList.get() : Collections.emptyList(), listOfPossibleAttributes); - - CorrelationAttributeInstance corInstance = null; - if (CentralRepository.isEnabled()) { - if (listOfPossibleAttributes.size() > 1) { - //Don't display anything if there is more than 1 correlation property for an artifact but let the user know - description = Bundle.SCOFetcher_occurrences_multipleProperties(); - } else if (!listOfPossibleAttributes.isEmpty()) { - //there should only be one item in the list - corInstance = listOfPossibleAttributes.get(0); - } - countAndDescription = scoSupporter.getCountPropertyAndDescription(corInstance, description); - } - - return new SCOData(scoreAndDescription, commentStatus, countAndDescription, content.getId()); - } - - @Messages({ - "SCOFetcher_nodescription_text=No description" - }) - private static void done(SCOData data, SCOSupporter scoSupporter) { - if (data == null || UserPreferences.getHideSCOColumns()) { - return; - } - - if (scoSupporter == null) { - return; - } - - List<NodeProperty<?>> props = new ArrayList<>(); - - if (data.getScoreAndDescription() != null) { - props.add(new NodeProperty<>( - SCOUtils.SCORE_COLUMN_NAME, - SCOUtils.SCORE_COLUMN_NAME, - data.getScoreAndDescription().getRight(), - data.getScoreAndDescription().getLeft())); - } - - if (data.getComment() != null) { - props.add(new NodeProperty<>( - SCOUtils.COMMENT_COLUMN_NAME, - SCOUtils.COMMENT_COLUMN_NAME, - Bundle.SCOFetcher_nodescription_text(), - data.getComment())); - } - - if (data.getCountAndDescription() != null) { - props.add(new NodeProperty<>( - SCOUtils.OCCURANCES_COLUMN_NAME, - SCOUtils.OCCURANCES_COLUMN_NAME, - data.getCountAndDescription().getRight(), - data.getCountAndDescription().getLeft())); - } - - if (!props.isEmpty()) { - scoSupporter.updateSheet(props); - } - } - - /** - * Class for passing the SCO data. - */ - public static class SCOData { - - private final Pair<Score, String> scoreAndDescription; - private final DataResultViewerTable.HasCommentStatus comment; - private final Pair<Long, String> countAndDescription; - private final Long contentId; - - /** - * Construct a new SCOData object. - * - * @param scoreAndDescription - * @param comment - * @param countAndDescription - */ - SCOData(Pair<Score, String> scoreAndDescription, DataResultViewerTable.HasCommentStatus comment, Pair<Long, String> countAndDescription, Long contentId) { - this.scoreAndDescription = scoreAndDescription; - this.comment = comment; - this.countAndDescription = countAndDescription; - this.contentId = contentId; - } - - Pair<Score, String> getScoreAndDescription() { - return scoreAndDescription; - } - - DataResultViewerTable.HasCommentStatus getComment() { - return comment; - } - - Pair<Long, String> getCountAndDescription() { - return countAndDescription; - } - - Long getContentId() { - return contentId; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOSupporter.java b/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOSupporter.java deleted file mode 100755 index e6513bfa4253ef60cb3c06bd4d984f3b8e1f6406..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOSupporter.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.sco; - -import java.util.List; -import java.util.Optional; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Score; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.Tag; - -/** - * An interface to be implemented by nodes that support the SCO columns. - */ -public interface SCOSupporter { - - @NbBundle.Messages({"SCOSupporter_nodescription_text=no description", - "SCOSupporter.valueLoading=value loading"}) - static final String NO_DESCR = Bundle.SCOSupporter_nodescription_text(); - - /** - * Return the content object for this SCOSupporter. - * - * @return A content object. - */ - default Optional<Content> getContent() { - return Optional.empty(); - } - - /** - * Returns a list of all Tags that are associated with the node content. - * - * @return A list of Tags. - */ - default Optional<List<Tag>> getAllTagsFromDatabase() { - return Optional.empty(); - } - - /** - * Update the sheet with the updated SCO columns. - * - * @param newProps - */ - void updateSheet(List<NodeProperty<?>> newProps); - - /** - * Returns Score property for the content. - * - * @return - */ - @NbBundle.Messages({ - "# {0} - significanceDisplayName", - "SCOSupporter_getScorePropertyAndDescription_description=Has an {0} analysis result score" - }) - default Pair<Score, String> getScorePropertyAndDescription() throws TskCoreException { - Score score = Score.SCORE_UNKNOWN; - Optional<Content> optional = getContent(); - if (optional.isPresent()) { - Content content = optional.get(); - score = content.getAggregateScore(); - } - - String significanceDisplay = score.getSignificance().getDisplayName(); - String description = Bundle.SCOSupporter_getScorePropertyAndDescription_description(significanceDisplay); - return Pair.of(score, description); - } - - /** - * Returns comment property for the node. - * - * Default implementation is a null implementation. - * - * @param tags The list of tags. - * @param attributes The list of correlation attribute instances. - * - * @return Comment property for the underlying content of the node. - */ - default DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) { - return DataResultViewerTable.HasCommentStatus.NO_COMMENT; - } - - /** - * Returns occurrences/count property for the node. - * - * Default implementation is a null implementation. - * - * @param attribute The correlation attribute for which data will - * be retrieved. - * @param defaultDescription A description to use when none is determined by - * the getCountPropertyAndDescription method. - * - * @return count property for the underlying content of the node. - */ - default Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute, String defaultDescription) { - return Pair.of(-1L, NO_DESCR); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOUtils.java b/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOUtils.java deleted file mode 100755 index 625a0bfc8a673d82254537c015b66437a975d8e6..0000000000000000000000000000000000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/sco/SCOUtils.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.sco; - -import java.util.List; -import java.util.logging.Level; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.Tag; - -/** - * - * A utility class to unify the SCO columns across the nodes\DAOs. - */ -public class SCOUtils { - - private static final Logger logger = Logger.getLogger(SCOUtils.class.getName()); - - @NbBundle.Messages({ - "SCOUtils_columnKeys_score_name=S", - "SCOUtils_columnKeys_comment_name=C", - "SCOUtils_columnKeys_occurrences_name=O", - "# {0} - occurrenceCount", - "# {1} - attributeType", - "SCOUtils_createSheet_count_description=There were {0} datasource(s) found with occurrences of the correlation value of type {1}", - "SCOUtils_createSheet_count_noCorrelationValues_description=Unable to find other occurrences because no value exists for the available correlation property" - }) - - public final static String SCORE_COLUMN_NAME = Bundle.SCOUtils_columnKeys_score_name(); - public final static String COMMENT_COLUMN_NAME = Bundle.SCOUtils_columnKeys_comment_name(); - public final static String OCCURANCES_COLUMN_NAME = Bundle.SCOUtils_columnKeys_occurrences_name(); - - /** - * Private constructor for utility class. - */ - private SCOUtils() { - } - - public static Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attributeInstance, String defaultDescription) { - Long count = -1L; - String description = defaultDescription; - try { - if (attributeInstance != null && StringUtils.isNotBlank(attributeInstance.getCorrelationValue())) { - count = CentralRepository.getInstance().getCountCasesWithOtherInstances(attributeInstance); - description = Bundle.SCOUtils_createSheet_count_description(count, attributeInstance.getCorrelationType().getDisplayName()); - } else if (attributeInstance != null) { - description = Bundle.SCOUtils_createSheet_count_noCorrelationValues_description(); - } - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, String.format("Error getting count of data sources with %s correlation attribute %s", attributeInstance.getCorrelationType().getDisplayName(), attributeInstance.getCorrelationValue()), ex); - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.WARNING, String.format("Unable to normalize %s correlation attribute %s", attributeInstance.getCorrelationType().getDisplayName(), attributeInstance.getCorrelationValue()), ex); - } - return Pair.of(count, description); - } - - /** - * Returns comment property for the node. - * - * @param tags The list of tags. - * @param attributes The list of correlation attribute instances. - * - * @return Comment property for the underlying content of the node. - */ - public static DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) { - /* - * Has a tag with a comment been applied to the artifact or its source - * content? - */ - DataResultViewerTable.HasCommentStatus status = tags.size() > 0 ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT; - for (Tag tag : tags) { - if (!StringUtils.isBlank(tag.getComment())) { - status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT; - break; - } - } - /* - * Is there a comment in the CR for anything that matches the value and - * type of the specified attributes. - */ - try { - if (CentralRepoDbUtil.commentExistsOnAttributes(attributes)) { - if (status == DataResultViewerTable.HasCommentStatus.TAG_COMMENT) { - status = DataResultViewerTable.HasCommentStatus.CR_AND_TAG_COMMENTS; - } else { - status = DataResultViewerTable.HasCommentStatus.CR_COMMENT; - } - } - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Attempted to Query CR for presence of comments in a Blackboard Artifact node and was unable to perform query, comment column will only reflect caseDB", ex); - } - return status; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index cb96c4793cf53213aadaaef4160c6ab26a5e4314..cf099075ed3e6480118a83c6ef1131eddba9320b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -1050,7 +1050,7 @@ public enum Event { * * @throws TskCoreException */ - public abstract boolean isValid() throws TskCoreException; + abstract boolean isValid() throws TskCoreException; public abstract String getIndexPath() throws TskCoreException; @@ -1301,7 +1301,7 @@ public HashHitInfo lookupMD5(Content content) throws TskCoreException { * @throws TskCoreException */ @Override - public boolean isValid() throws TskCoreException { + boolean isValid() throws TskCoreException { return hasIndex(); } @@ -1646,7 +1646,7 @@ public HashHitInfo lookupMD5(Content content) throws TskCoreException { * @return true if is valid, false otherwise */ @Override - public boolean isValid() { + boolean isValid() { if (!CentralRepository.isEnabled()) { return false; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index 704aef8612555ee0d7950f901c0aec02be9e9903..c9b3dd772ae07524379819f2b052401ea972f736 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -982,7 +982,7 @@ private void sendIngestMessagesCheckBoxActionPerformed(java.awt.event.ActionEven * @return true if running on windows, false otherwise */ private boolean isWindows() { - return PlatformUtil.getOSName().toLowerCase().startsWith("windows"); + return PlatformUtil.getOSName().toLowerCase().startsWith("Windows"); } @NbBundle.Messages({"HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, it will be removed from the list.\n", diff --git a/Core/src/org/sleuthkit/autopsy/progress/SilentProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/SilentProgressIndicator.java index 97692a8adb8add8f345e8ccd59fb7825861c4401..c7ed9c671c5c437c0914415a57e7885514146a8d 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/SilentProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/progress/SilentProgressIndicator.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.progress; +import org.sleuthkit.autopsy.progress.ProgressIndicator; + /** * A "silent" or "null" progress indicator. */ diff --git a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java index 0afd1c093ab07e31fcd790b63c57c0dea69a11bf..12dfe741d49e09d6ee1ac4dbc5a4b845d0fa377b 100644 --- a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java @@ -29,11 +29,9 @@ import org.sleuthkit.autopsy.datasourcesummary.ui.DataSourceSummaryTabbedPane; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponents.AbstractDataResultViewer; -import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; /** * A tabular result viewer that displays a summary of the selected Data Source. @@ -46,7 +44,6 @@ public class DataSourceSummaryResultViewer extends AbstractDataResultViewer { private static final Logger LOGGER = Logger.getLogger(DataSourceSummaryResultViewer.class.getName()); private final String title; - private DataResultPanel.PagingControls pagingControls = null; /** * Constructs a tabular result viewer that displays a summary of the @@ -95,11 +92,6 @@ public DataResultViewer createInstance() { public boolean isSupported(Node node) { return getDataSource(node) != null; } - - @Override - public void setPagingControls(DataResultPanel.PagingControls pagingControls) { - this.pagingControls = pagingControls; - } /** * Returns the datasource attached to the node or null if none can be found. @@ -111,11 +103,6 @@ public void setPagingControls(DataResultPanel.PagingControls pagingControls) { private DataSource getDataSource(Node node) { return node == null ? null : node.getLookup().lookup(DataSource.class); } - - @Override - public void setNode(Node node, SearchResultsDTO searchResults) { - setNode(node); - } @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @@ -124,11 +111,6 @@ public void setNode(Node node) { LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread."); return; } - - // disable paging controls - if (pagingControls != null) { - pagingControls.setPageControlsEnabled(false); - } DataSource dataSource = getDataSource(node); @@ -160,4 +142,5 @@ public void clearComponent() { } private DataSourceSummaryTabbedPane summaryPanel; + } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java deleted file mode 100644 index 3eb5f77b4f2cba2900212ece0af46150c2789f76..0000000000000000000000000000000000000000 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java +++ /dev/null @@ -1,1310 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.mainui.datamodel; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.concurrent.ExecutionException; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import junit.framework.Assert; -import junit.framework.Test; -import org.netbeans.junit.NbModuleSuite; -import org.netbeans.junit.NbTestCase; -import org.openide.util.Exceptions; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.services.TagsManager; -import org.sleuthkit.autopsy.testutils.CaseUtils; -import org.sleuthkit.autopsy.testutils.TestUtilsException; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.AccountFileInstance; -import org.sleuthkit.datamodel.AnalysisResult; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.Blackboard.BlackboardException; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.FileSystem; -import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.HostManager; -import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.Pool; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.OsAccountInstance; -import org.sleuthkit.datamodel.OsAccountManager; -import org.sleuthkit.datamodel.OsAccountRealm; -import org.sleuthkit.datamodel.Score; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TagName; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.Volume; -import org.sleuthkit.datamodel.VolumeSystem; - - -/** - * - */ -public class TableSearchTest extends NbTestCase { - - private static final String MODULE_NAME = "TableSearchTest"; - - // Custom artifact and attribute names and display names - private static final String CUSTOM_DA_TYPE_NAME = "SEARCH_TEST_CUSTOM_DA_TYPE"; - private static final String CUSTOM_DA_TYPE_DISPLAY_NAME = "Search test custom data artifact type"; - private static final String CUSTOM_AR_TYPE_NAME = "SEARCH_TEST_CUSTOM_AR_TYPE"; - private static final String CUSTOM_AR_TYPE_DISPLAY_NAME = "Search test custom analysis result type"; - private static final String CUSTOM_ATTR_TYPE_NAME = "SEARCH_TEST_CUSTOM_ATTRIBUTE_TYPE"; - private static final String CUSTOM_ATTR_TYPE_DISPLAY_NAME = "Search test custom attribute type"; - - // Values used for attributes in the artifact tests - private static final String ARTIFACT_COMMENT = "Artifact comment"; - private static final String ARTIFACT_CUSTOM_ATTR_STRING = "Custom attribute string"; - private static final int ARTIFACT_INT = 5; - private static final double ARTIFACT_DOUBLE = 7.89; - private static final String ARTIFACT_CONCLUSION = "Test conclusion"; - private static final String ARTIFACT_CONFIGURATION = "Test configuration"; - private static final String ARTIFACT_JUSTIFICATION = "Test justification"; - private static final Score ARTIFACT_SCORE = Score.SCORE_LIKELY_NOTABLE; - private static final long ARTIFACT_COUNT_WEB_BOOKMARK = 125; - private static final long ARTIFACT_COUNT_YARA = 150; - - // Values for the hash set hit tests - private static final String HASH_SET_1 = "Hash Set 1"; - private static final String HASH_SET_2 = "Hash Set 2"; - private static final String HASH_HIT_VALUE = "aefe58b6dc38bbd7f2b7861e7e8f7539"; - - // Values for the keyword hit tests - private static final String KEYWORD_SET_1 = "Keyword Set 1"; - private static final String KEYWORD_SET_2 = "Keyword Set 2"; - private static final String KEYWORD = "bomb"; - private static final String KEYWORD_REGEX = "bomb*"; - private static final String KEYWORD_PREVIEW = "There is a bomb."; - - // Extension and MIME type test - private static AbstractFile customFile; - private static final String CUSTOM_MIME_TYPE = "fake/type"; - private static final String CUSTOM_MIME_TYPE_FILE_NAME = "test.fake"; - private static final String CUSTOM_EXTENSION = "fake"; - private static final Set<String> CUSTOM_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("." + CUSTOM_EXTENSION))); //NON-NLS - private static final Set<String> EMPTY_RESULT_SET_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(".blah", ".blah2", ".crazy"))); //NON-NLS - - // Tag test - private static final String TAG_COMMENT = "Tag comment"; - private static final String TAG_DESCRIPTION = "Tag description"; - private static final String MD5_COLUMN = "MD5 Hash"; - private static final String FILE_PATH_COLUMN = "File Path"; - private static final String MODIFIED_TIME_COLUMN = "Modified Time"; - private static final String SOURCE_NAME_COLUMN = "Source Name"; - private static final String SOURCE_FILE_PATH_COLUMN = "Source File Path"; - - // File system test - private static final String PERSON_NAME = "Person1"; - private static final String PERSON_HOST_NAME1 = "Host for Person A"; - private static final String PERSON_HOST_NAME2 = "Host for Person B"; - - // OS Accounts test - private static final String REALM_NAME_COLUMN = "Realm Name"; - private static final String HOST_COLUMN = "Host"; - - // Communications accounts test - private static final String ACCOUNT_TYPE_COLUMN = "Account Type"; - private static final String ID_COLUMN = "ID"; - private static final String EMAIL_A = "aaa@yahoo.com"; - private static final String EMAIL_B = "bbb@gmail.com"; - private static final String EMAIL_C = "ccc@funmail.com"; - private static final String PHONENUM_1 = "1117771111"; - private static final String PHONENUM_2 = "2223337777"; - - ///////////////////////////////////////////////// - // Data to be used across the test methods. - // These are initialized in setUpCaseDatabase(). - ///////////////////////////////////////////////// - Case openCase = null; // The case for testing - SleuthkitCase db = null; // The case database - Blackboard blackboard = null; // The blackboard - TagsManager tagsManager = null;// Tags manager - OsAccountManager accountMgr = null; - - DataSource dataSource1 = null; // A local files data source - DataSource dataSource2 = null; // A local files data source - DataSource dataSource3 = null; // A local files data source - - BlackboardArtifact.Type customDataArtifactType = null; // A custom data artifact type - BlackboardArtifact.Type customAnalysisResultType = null; // A custom analysis result type - BlackboardAttribute.Type customAttributeType = null; // A custom attribute type - - // Data artifact test - DataArtifact customDataArtifact = null; // A custom data artifact in dataSource1 - Content customDataArtifactSourceFile = null; // The source of customDataArtifact - AbstractFile customDataArtifactLinkedFile = null; // The linked file of customDataArtifact - - // Analysis result test - AnalysisResult customAnalysisResult = null; // A custom analysis result in dataSource 1 - Content customAnalysisResultSource = null; // The source of customDataArtifact - - // Hash hits test - AnalysisResult hashHitAnalysisResult = null; // A hash hit - Content fileWithHashHit = null; // The file associated with the hash hit above - - // Keyword hits test - AnalysisResult keywordHitAnalysisResult = null; // A keyword hit - Content keywordHitSource = null; // The source of the keyword hit above - - // File system test - Host fsTestHostA = null; // A host - Image fsTestImageA = null; // An image - VolumeSystem fsTestVsA = null; // A volume system - Volume fsTestVolumeA1 = null; // A volume - Volume fsTestVolumeA2 = null; // Another volume - Volume fsTestVolumeA3 = null; // Another volume - FileSystem fsTestFsA = null; // A file system - AbstractFile fsTestRootDirA = null; // The root directory - Image fsTestImageB = null; // Another image - Volume fsTestVolumeB1 = null; // Another volume - Pool fsTestPoolB = null; // A pool - Person person1 = null; // A person - Host personHost1 = null; // A host belonging to the above person - - // Tags test - TagName knownTag1 = null; - TagName tag2 = null; - - // OS Accounts test - OsAccount osAccount1 = null; - - public static Test suite() { - NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(TableSearchTest.class). - clusters(".*"). - enableModules(".*"); - return conf.suite(); - } - - public TableSearchTest(String name) { - super(name); - } - - // Main search method - public void testTableSearches() { - // Set up the database - setUpCaseDatabase(); - - // Run tests - dataArtifactSearchTest(); - analysisResultSearchTest(); - hashHitSearchTest(); - keywordHitSearchTest(); - mimeSearchTest(); - extensionSearchTest(); - sizeSearchTest(); - fileSystemTest(); - tagsTest(); - OsAccountsTest(); - commAccountsSearchTest(); - } - - /** - * Create a case and add sample data. - */ - private void setUpCaseDatabase() { - SleuthkitCase.CaseDbTransaction trans = null; - try { - // Create a test case - openCase = CaseUtils.createAsCurrentCase("testTableSearchCase"); - db = openCase.getSleuthkitCase(); - blackboard = db.getBlackboard(); - tagsManager = openCase.getServices().getTagsManager(); - accountMgr = openCase.getSleuthkitCase().getOsAccountManager(); - - // Add two logical files data sources - trans = db.beginTransaction(); - dataSource1 = db.addLocalFilesDataSource("devId1", "C:\\Fake\\Path\\1", "EST", null, trans); - dataSource2 = db.addLocalFilesDataSource("devId2", "C:\\Fake\\Path\\2", "EST", null, trans); - dataSource3 = db.addLocalFilesDataSource("devId3", "C:\\Fake\\Path\\3", "EST", null, trans); - trans.commit(); - trans = null; - - // Add files - AbstractFile folderA1 = db.addLocalDirectory(dataSource1.getId(), "folder1"); - AbstractFile fileA1 = db.addLocalFile("file1.txt", "", 10, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderA1); - fileA1.setMIMEType("text/plain"); - fileA1.save(); - AbstractFile folderA2 = db.addLocalDirectory(dataSource1.getId(), "folder2"); - AbstractFile fileA2 = db.addLocalFile("file2.jpg", "", 60000000, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderA2); - fileA2.setMIMEType("image/jpeg"); - fileA2.save(); - AbstractFile folderA3 = db.addLocalDirectory(folderA2.getId(), "folder3"); - AbstractFile fileA3 = db.addLocalFile("file3.doc", "", 150000000, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderA3); - fileA3.setMIMEType("application/msword"); - fileA3.save(); - AbstractFile fileA4 = db.addLocalFile("file4.txt", "", 100, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderA3); - fileA4.setMIMEType("text/plain"); - fileA4.save(); - - AbstractFile folderB1 = db.addLocalDirectory(dataSource2.getId(), "folder1"); - AbstractFile fileB1 = db.addLocalFile("fileA.txt", "", 210000000, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderB1); - fileB1.setMIMEType("text/plain"); - fileB1.save(); - - customFile = db.addLocalFile(CUSTOM_MIME_TYPE_FILE_NAME, "", 67000000, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderB1); - customFile.setMIMEType(CUSTOM_MIME_TYPE); - customFile.save(); - - // Create a custom artifact and attribute types - customDataArtifactType = blackboard.getOrAddArtifactType(CUSTOM_DA_TYPE_NAME, CUSTOM_DA_TYPE_DISPLAY_NAME, BlackboardArtifact.Category.DATA_ARTIFACT); - - customAnalysisResultType = blackboard.getOrAddArtifactType(CUSTOM_AR_TYPE_NAME, CUSTOM_AR_TYPE_DISPLAY_NAME, BlackboardArtifact.Category.ANALYSIS_RESULT); - customAttributeType = blackboard.getOrAddAttributeType(CUSTOM_ATTR_TYPE_NAME, - BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, CUSTOM_ATTR_TYPE_DISPLAY_NAME); - - // Add data artifacts - // DataSource1: contact, bookmark, and custom type - // DataSource2: contact - List<BlackboardAttribute> attrs = new ArrayList<>(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, "Contact 1")); - fileA1.newDataArtifact(BlackboardArtifact.Type.TSK_CONTACT, attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, "Bookmark 1")); - fileA2.newDataArtifact(BlackboardArtifact.Type.TSK_GPS_BOOKMARK, attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, "Contact 2")); - fileB1.newDataArtifact(BlackboardArtifact.Type.TSK_CONTACT, attrs); - - // This is the main artifact for the DataArtifact test. Make attributes of several types. - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, ARTIFACT_COMMENT)); - attrs.add(new BlackboardAttribute(customAttributeType, MODULE_NAME, ARTIFACT_CUSTOM_ATTR_STRING)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COUNT, MODULE_NAME, ARTIFACT_INT)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_ENTROPY, MODULE_NAME, ARTIFACT_DOUBLE)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_PATH_ID, MODULE_NAME, fileA2.getId())); - customDataArtifact = fileA3.newDataArtifact(customDataArtifactType, attrs); - customDataArtifactSourceFile = fileA3; - customDataArtifactLinkedFile = fileA2; - - // Add a lot of web bookmark data artifacts - for (int i = 0;i < ARTIFACT_COUNT_WEB_BOOKMARK;i++) { - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, Integer.toString(i))); - fileA1.newDataArtifact(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, attrs); - } - - // Add analysis results - // Data source 1: Encryption detected (2), custom type - // Data source 2: Encryption detected - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, "Encryption detected 1")); - fileA1.newAnalysisResult(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, Score.SCORE_NONE, "conclusion", "configuration", "justification", attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, "Encryption detected 1")); - fileA2.newAnalysisResult(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, Score.SCORE_LIKELY_NOTABLE, "conclusion", "configuration", "justification", attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, "Encryption detected 2")); - fileB1.newAnalysisResult(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, Score.SCORE_NOTABLE, "conclusion", "configuration", "justification", attrs); - - // This is the main artifact for the AnalysisResult test. Make attributes of several types. - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, ARTIFACT_COMMENT)); - attrs.add(new BlackboardAttribute(customAttributeType, MODULE_NAME, ARTIFACT_CUSTOM_ATTR_STRING)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COUNT, MODULE_NAME, ARTIFACT_INT)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_ENTROPY, MODULE_NAME, ARTIFACT_DOUBLE)); - customAnalysisResult = customDataArtifact.newAnalysisResult(customAnalysisResultType, ARTIFACT_SCORE, ARTIFACT_CONCLUSION, ARTIFACT_CONFIGURATION, ARTIFACT_JUSTIFICATION, attrs).getAnalysisResult(); - customAnalysisResultSource = customDataArtifact; - - // Add a lot of YARA hit analysis results - for (int i = 0;i < ARTIFACT_COUNT_YARA;i++) { - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, Integer.toString(i))); - fileA1.newAnalysisResult(BlackboardArtifact.Type.TSK_YARA_HIT, Score.SCORE_NOTABLE, "conclusion", "configuration", "justification", attrs); - } - - // Add hash hits - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, HASH_SET_1)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_HASH_MD5, MODULE_NAME, "43fffda5c5edd8e9c647f1df476717de")); - fileA1.newAnalysisResult( - BlackboardArtifact.Type.TSK_HASHSET_HIT, Score.SCORE_NOTABLE, - null, HASH_SET_1, null, attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, HASH_SET_1)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_HASH_MD5, MODULE_NAME, "b7cde263cc1b5df5a13aeec742637a89")); - fileA2.newAnalysisResult( - BlackboardArtifact.Type.TSK_HASHSET_HIT, Score.SCORE_NOTABLE, - null, HASH_SET_1, null, attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, HASH_SET_2)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_HASH_MD5, MODULE_NAME, "333510c92f8cd755f163328c2bac81fe")); - fileA3.newAnalysisResult( - BlackboardArtifact.Type.TSK_HASHSET_HIT, Score.SCORE_NONE, - null, HASH_SET_2, null, attrs); - - // This is the artifact that will get most of the testing - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, HASH_SET_1)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_HASH_MD5, MODULE_NAME, HASH_HIT_VALUE)); - hashHitAnalysisResult = fileB1.newAnalysisResult( - BlackboardArtifact.Type.TSK_HASHSET_HIT, Score.SCORE_NOTABLE, - null, HASH_SET_1, null, attrs).getAnalysisResult(); - fileWithHashHit = fileB1; - - // Add keyword hits - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, KEYWORD_SET_1)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD, MODULE_NAME, "keyword1")); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.LITERAL.getType())); - fileA1.newAnalysisResult( - BlackboardArtifact.Type.TSK_KEYWORD_HIT, Score.SCORE_NOTABLE, - null, KEYWORD_SET_1, null, attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, KEYWORD_SET_2)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD, MODULE_NAME, "keyword2")); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.LITERAL.getType())); - fileA3.newAnalysisResult( - BlackboardArtifact.Type.TSK_KEYWORD_HIT, Score.SCORE_NOTABLE, - null, KEYWORD_SET_2, null, attrs); - - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, KEYWORD_SET_2)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD, MODULE_NAME, KEYWORD)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD_REGEXP, MODULE_NAME, KEYWORD_REGEX)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD_PREVIEW, MODULE_NAME, KEYWORD_PREVIEW)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.REGEX.getType())); - fileB1.newAnalysisResult( - BlackboardArtifact.Type.TSK_KEYWORD_HIT, Score.SCORE_NOTABLE, - null, KEYWORD_SET_2, null, attrs); - - // This is the artifact that will get most of the testing. It is in data source 2 and has the previous hash hit as source. - attrs.clear(); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, KEYWORD_SET_1)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD, MODULE_NAME, KEYWORD)); - attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_KEYWORD_PREVIEW, MODULE_NAME, KEYWORD_PREVIEW)); - keywordHitAnalysisResult = hashHitAnalysisResult.newAnalysisResult( - BlackboardArtifact.Type.TSK_KEYWORD_HIT, Score.SCORE_NOTABLE, - null, KEYWORD_SET_1, null, attrs).getAnalysisResult(); - keywordHitSource = hashHitAnalysisResult; - - // Create a normal image - // fsTestImageA (Host: fsTestHostA) - // - fsTestVsA - // -- fsTestVolumeA1 - // --- fsTestFsA - // ---- fsTestRootDirA - // ----- (3 files) - // -- fsTestVolumeA2 - // -- fsTestVolumeA3 - fsTestHostA = db.getHostManager().newHost("File system test host"); - trans = db.beginTransaction(); - fsTestImageA = db.addImage(TskData.TSK_IMG_TYPE_ENUM.TSK_IMG_TYPE_DETECT, 512, 1024, "image1", Arrays.asList("C:\\Fake\\Path\\4"), - "EST", null, null, null, "deviceID12345", fsTestHostA, trans); - fsTestVsA = db.addVolumeSystem(fsTestImageA.getId(), TskData.TSK_VS_TYPE_ENUM.TSK_VS_TYPE_DOS, 0, 1024, trans); - fsTestVolumeA1 = db.addVolume(fsTestVsA.getId(), 0, 0, 512, "Test vol A1", 0, trans); - fsTestVolumeA2 = db.addVolume(fsTestVsA.getId(), 1, 512, 512, "Test vol A2", 0, trans); - fsTestVolumeA3 = db.addVolume(fsTestVsA.getId(), 2, 1024, 512, "Test vol A3", 0, trans); - long rootInum = 1; - fsTestFsA = db.addFileSystem(fsTestVolumeA1.getId(), 0, TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_EXT2, 512, 1, - rootInum, rootInum, 10, "Test file system", trans); - trans.commit(); - trans = null; - fsTestRootDirA = db.addFileSystemFile(fsTestImageA.getId(), fsTestFsA.getId(), - "", rootInum, 0, - TskData.TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, - TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC, (short)0, 0, - 0, 0, 0, 0, false, fsTestFsA); - db.addFileSystemFile(fsTestImageA.getId(), fsTestFsA.getId(), - "Test file 1", 0, 0, - TskData.TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, - TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC, (short)0, 123, - 0, 0, 0, 0, true, fsTestRootDirA); - db.addFileSystemFile(fsTestImageA.getId(), fsTestFsA.getId(), - "Test file 2", 0, 0, - TskData.TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, - TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC, (short)0, 456, - 0, 0, 0, 0, true, fsTestRootDirA); - db.addFileSystemFile(fsTestImageA.getId(), fsTestFsA.getId(), - "Test file 3", 0, 0, - TskData.TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, - TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC, (short)0, 789, - 0, 0, 0, 0, true, fsTestRootDirA); - - // Create an image with some odd structures for testing - trans = db.beginTransaction(); - fsTestImageB = db.addImage(TskData.TSK_IMG_TYPE_ENUM.TSK_IMG_TYPE_DETECT, 512, 1024, "image2", Arrays.asList("C:\\Fake\\Path\\5"), - "EST", null, null, null, "deviceID678", fsTestHostA, trans); - - // Images can have VS, pool, FS, file, artifact, and report children. - // Add a VS, pool, and local file - VolumeSystem vsB = db.addVolumeSystem(fsTestImageB.getId(), TskData.TSK_VS_TYPE_ENUM.TSK_VS_TYPE_BSD, 0, 2048, trans); - db.addPool(fsTestImageB.getId(), TskData.TSK_POOL_TYPE_ENUM.TSK_POOL_TYPE_APFS, trans); - db.addLocalFile("Test local file B1", "C:\\Fake\\Path\\6", 6000, 0, 0, 0, 0, - true, TskData.EncodingType.NONE, fsTestImageB, trans); - - // Volumes can have pool, FS, file, and artifact children - fsTestVolumeB1 = db.addVolume(vsB.getId(), 0, 0, 512, "Test vol B1", 0, trans); - fsTestPoolB = db.addPool(fsTestVolumeB1.getId(), TskData.TSK_POOL_TYPE_ENUM.TSK_POOL_TYPE_APFS, trans); - db.addLocalFile("Test local file B2", "C:\\Fake\\Path\\7", 7000, 0, 0, 0, 0, - true, TskData.EncodingType.NONE, fsTestVolumeB1, trans); - - // Pools can have VS, file, and artifact children - VolumeSystem vsB2 = db.addVolumeSystem(fsTestPoolB.getId(), TskData.TSK_VS_TYPE_ENUM.TSK_VS_TYPE_GPT, 0, 2048, trans); - db.addVolume(vsB2.getId(), 0, 0, 512, "Test vol B2", 0, trans); - db.addLocalFile("Test local file B3", "C:\\Fake\\Path\\8", 8000, 0, 0, 0, 0, - true, TskData.EncodingType.NONE, fsTestPoolB, trans); - - trans.commit(); - trans = null; - - // Create a person associated with two hosts - person1 = db.getPersonManager().newPerson(PERSON_NAME); - personHost1 = db.getHostManager().newHost(PERSON_HOST_NAME1); - Host personHost2 = db.getHostManager().newHost(PERSON_HOST_NAME2); - db.getPersonManager().addHostsToPerson(person1, Arrays.asList(personHost1, personHost2)); - - // Add tags ---- - knownTag1 = tagsManager.addTagName("Tag 1", TAG_DESCRIPTION, TagName.HTML_COLOR.RED, TskData.FileKnown.KNOWN); - tag2 = tagsManager.addTagName("Tag 2", "Descrition"); - - // Tag the custom artifacts in data source 1 - openCase.getServices().getTagsManager().addBlackboardArtifactTag(customDataArtifact, knownTag1, TAG_COMMENT); - openCase.getServices().getTagsManager().addBlackboardArtifactTag(customAnalysisResult, tag2, "Comment 2"); - - // Tag file in data source 1 - openCase.getServices().getTagsManager().addContentTag(fileA2, tag2); - openCase.getServices().getTagsManager().addContentTag(fileA3, tag2); - - // Tag file in data source 2 - openCase.getServices().getTagsManager().addContentTag(fileB1, tag2); - - // Tag the custom file in data source 2 - openCase.getServices().getTagsManager().addContentTag(customFile, knownTag1); - - // Add OS Accounts --------------------- - HostManager hostMrg = openCase.getSleuthkitCase().getHostManager(); - Host host1 = hostMrg.getHostByDataSource(dataSource1); - OsAccount osAccount2 = accountMgr.newWindowsOsAccount("S-1-5-21-647283-46237-200", null, null, host1, OsAccountRealm.RealmScope.LOCAL); - accountMgr.newOsAccountInstance(osAccount2, dataSource1, OsAccountInstance.OsAccountInstanceType.ACCESSED); - OsAccount osAccount3 = accountMgr.newWindowsOsAccount("S-1-5-21-647283-46237-300", null, null, host1, OsAccountRealm.RealmScope.UNKNOWN); - accountMgr.newOsAccountInstance(osAccount3, dataSource1, OsAccountInstance.OsAccountInstanceType.REFERENCED); - - Host host2 = hostMrg.getHostByDataSource(dataSource2); - osAccount1 = accountMgr.newWindowsOsAccount("S-1-5-21-647283-46237-100", null, null, host2, OsAccountRealm.RealmScope.DOMAIN); - accountMgr.newOsAccountInstance(osAccount1, dataSource2, OsAccountInstance.OsAccountInstanceType.LAUNCHED); - - - openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, EMAIL_A, "Test Module", fileA1, Collections.emptyList(), null); - openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, EMAIL_B, "Test Module", fileA2, Collections.emptyList(), null); - openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, "devId1", "Test Module", fileA2, Collections.emptyList(), null); - openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, PHONENUM_1, "Test Module", fileA2, Collections.emptyList(), null); - - openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, EMAIL_C, "Test Module", customFile, Collections.emptyList(), null); - openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, PHONENUM_2, "Test Module", customFile, Collections.emptyList(), null); - - } catch (TestUtilsException | TskCoreException | BlackboardException | TagsManager.TagNameAlreadyExistsException | OsAccountManager.NotUserSIDException ex) { - if (trans != null) { - try { - trans.rollback(); - } catch (TskCoreException ex2) { - Exceptions.printStackTrace(ex2); - } - } - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void dataArtifactSearchTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - // Get all contacts - DataArtifactSearchParam param = new DataArtifactSearchParam(BlackboardArtifact.Type.TSK_CONTACT, null); - DataArtifactDAO dataArtifactDAO = MainDAO.getInstance().getDataArtifactsDAO(); - - DataArtifactTableSearchResultsDTO results = dataArtifactDAO.getDataArtifactsForTable(param, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_CONTACT, results.getArtifactType()); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // Get contacts from data source 2 - param = new DataArtifactSearchParam(BlackboardArtifact.Type.TSK_CONTACT, dataSource2.getId()); - results = dataArtifactDAO.getDataArtifactsForTable(param, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_CONTACT, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get bookmarks from data source 2 - param = new DataArtifactSearchParam(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, dataSource2.getId()); - results = dataArtifactDAO.getDataArtifactsForTable(param, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, results.getArtifactType()); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Get all custom artifacts - param = new DataArtifactSearchParam(customDataArtifactType, null); - results = dataArtifactDAO.getDataArtifactsForTable(param, 0, null); - assertEquals(customDataArtifactType, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Check that a few of the expected column names are present - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains(BlackboardAttribute.Type.TSK_COMMENT.getDisplayName())); - assertTrue(columnDisplayNames.contains(BlackboardAttribute.Type.TSK_COUNT.getDisplayName())); - assertTrue(columnDisplayNames.contains(customAttributeType.getDisplayName())); - - // Check that the analysis result columns are not present - assertFalse(columnDisplayNames.contains("Justification")); - assertFalse(columnDisplayNames.contains("Conclusion")); - - // Get one of the rows - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof DataArtifactRowDTO); - DataArtifactRowDTO dataArtifactRowDTO = (DataArtifactRowDTO) rowDTO; - - // Check that the artifact, source content and linked file are correct - assertEquals(customDataArtifact, dataArtifactRowDTO.getDataArtifact()); - assertEquals(customDataArtifactSourceFile, dataArtifactRowDTO.getSrcContent()); - //assertEquals(customDataArtifactLinkedFile, dataArtifactRowDTO.getLinkedFile()); I'm doing something wrong or this isn't working yet - - // Check that some of the expected column values are present - assertTrue(dataArtifactRowDTO.getCellValues().contains(ARTIFACT_CUSTOM_ATTR_STRING)); - assertTrue(dataArtifactRowDTO.getCellValues().contains(ARTIFACT_COMMENT)); - assertTrue(dataArtifactRowDTO.getCellValues().contains(ARTIFACT_INT)); - assertTrue(dataArtifactRowDTO.getCellValues().contains(ARTIFACT_DOUBLE)); - - // Test paging - Long pageSize = new Long(100); - assertTrue(ARTIFACT_COUNT_WEB_BOOKMARK > pageSize); - - // Get the first page - param = new DataArtifactSearchParam(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, null); - results = dataArtifactDAO.getDataArtifactsForTable(param, 0, pageSize); - assertEquals(ARTIFACT_COUNT_WEB_BOOKMARK, results.getTotalResultsCount()); - assertEquals(pageSize.longValue(), results.getItems().size()); - - // Save all artifact IDs from the first page - Set<Long> firstPageObjIds = new HashSet<>(); - for (RowDTO row : results.getItems()) { - assertTrue(row instanceof DataArtifactRowDTO); - DataArtifactRowDTO dataRow = (DataArtifactRowDTO) row; - assertTrue(dataRow.getDataArtifact() != null); - firstPageObjIds.add(dataRow.getDataArtifact().getId()); - } - assertEquals(pageSize.longValue(), firstPageObjIds.size()); - - // Get the second page - param = new DataArtifactSearchParam(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, null); - results = dataArtifactDAO.getDataArtifactsForTable(param, pageSize, pageSize); - assertEquals(ARTIFACT_COUNT_WEB_BOOKMARK, results.getTotalResultsCount()); - assertEquals(ARTIFACT_COUNT_WEB_BOOKMARK - pageSize, results.getItems().size()); - - // Make sure no artifacts from the second page appeared on the first - for (RowDTO row : results.getItems()) { - assertTrue(row instanceof DataArtifactRowDTO); - DataArtifactRowDTO dataRow = (DataArtifactRowDTO) row; - assertTrue(dataRow.getDataArtifact() != null); - assertFalse("Data artifact ID: " + dataRow.getDataArtifact().getId() + " appeared on both page 1 and page 2", - firstPageObjIds.contains(dataRow.getDataArtifact().getId())); - } - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void commAccountsSearchTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - CommAccountsDAO commAccountsDAO = MainDAO.getInstance().getCommAccountsDAO(); - - // Get emails from all data sources - CommAccountsSearchParams param = new CommAccountsSearchParams(Account.Type.EMAIL, null); - SearchResultsDTO results = commAccountsDAO.getCommAcounts(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // Get device accounts from data source 1 - param = new CommAccountsSearchParams(Account.Type.DEVICE, dataSource1.getId()); - results = commAccountsDAO.getCommAcounts(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get email accounts from data source 2 - param = new CommAccountsSearchParams(Account.Type.EMAIL, dataSource2.getId()); - results = commAccountsDAO.getCommAcounts(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Check that a few of the expected column names are present - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains(ACCOUNT_TYPE_COLUMN)); - assertTrue(columnDisplayNames.contains(ID_COLUMN)); - - // Get the row - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof DataArtifactRowDTO); - DataArtifactRowDTO accountResultRowDTO = (DataArtifactRowDTO) rowDTO; - - // Check that some of the expected result column values are present - assertTrue(accountResultRowDTO.getCellValues().contains(EMAIL_C)); - assertTrue(accountResultRowDTO.getCellValues().contains(customFile.getName())); - - // Get phone accounts from all data sources - param = new CommAccountsSearchParams(Account.Type.PHONE, null); - results = commAccountsDAO.getCommAcounts(param, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // Get phone accounts from data source 2 - param = new CommAccountsSearchParams(Account.Type.PHONE, dataSource2.getId()); - results = commAccountsDAO.getCommAcounts(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get the row - rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof DataArtifactRowDTO); - accountResultRowDTO = (DataArtifactRowDTO) rowDTO; - - // Check that some of the expected result column values are present - assertTrue(accountResultRowDTO.getCellValues().contains(PHONENUM_2)); - assertTrue(accountResultRowDTO.getCellValues().contains(customFile.getName())); - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void mimeSearchTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - ViewsDAO viewsDAO = MainDAO.getInstance().getViewsDAO(); - - // Get plain text files from data source 1 - FileTypeMimeSearchParams param = new FileTypeMimeSearchParams("text/plain", dataSource1.getId()); - SearchResultsDTO results = viewsDAO.getFilesByMime(param, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // Get jpeg files from data source 1 - param = new FileTypeMimeSearchParams("image/jpeg", dataSource1.getId()); - results = viewsDAO.getFilesByMime(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get jpeg files from data source 2 - param = new FileTypeMimeSearchParams("image/jpeg", dataSource2.getId()); - results = viewsDAO.getFilesByMime(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Search for mime type that should produce no results - param = new FileTypeMimeSearchParams("blah/blah", null); - results = viewsDAO.getFilesByMime(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Get plain text files from all data sources - param = new FileTypeMimeSearchParams("text/plain", null); - results = viewsDAO.getFilesByMime(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // Get the custom file by MIME type - param = new FileTypeMimeSearchParams(CUSTOM_MIME_TYPE, null); - results = viewsDAO.getFilesByMime(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof FileRowDTO); - FileRowDTO fileRowDTO = (FileRowDTO) rowDTO; - - assertEquals(CUSTOM_MIME_TYPE_FILE_NAME, fileRowDTO.getFileName()); - assertEquals(CUSTOM_EXTENSION, fileRowDTO.getExtension()); - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void sizeSearchTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - ViewsDAO viewsDAO = MainDAO.getInstance().getViewsDAO(); - - // Get "50 - 200MB" files from data source 1 - FileTypeSizeSearchParams param = new FileTypeSizeSearchParams(FileSizeFilter.SIZE_50_200, dataSource1.getId()); - SearchResultsDTO results = viewsDAO.getFilesBySize(param, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // Get "200MB - 1GB" files from data source 1 - param = new FileTypeSizeSearchParams(FileSizeFilter.SIZE_200_1000, dataSource1.getId()); - results = viewsDAO.getFilesBySize(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Get "200MB - 1GB" files from data source 2 - param = new FileTypeSizeSearchParams(FileSizeFilter.SIZE_200_1000, dataSource2.getId()); - results = viewsDAO.getFilesBySize(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get "1GB+" files from all data sources - param = new FileTypeSizeSearchParams(FileSizeFilter.SIZE_1000_, null); - results = viewsDAO.getFilesBySize(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Get "50 - 200MB" files from all data sources - param = new FileTypeSizeSearchParams(FileSizeFilter.SIZE_50_200, null); - results = viewsDAO.getFilesBySize(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void tagsTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - TagsDAO tagsDAO = MainDAO.getInstance().getTagsDAO(); - - // Get "Tag1" file tags from data source 1 - TagsSearchParams param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.FILE, dataSource1.getId()); - SearchResultsDTO results = tagsDAO.getTags(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Get "Tag2" file tags from data source 1 - param = new TagsSearchParams(tag2, TagsSearchParams.TagType.FILE, dataSource1.getId()); - results = tagsDAO.getTags(param, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // Get "Tag2" file tags from all data sources - param = new TagsSearchParams(tag2, TagsSearchParams.TagType.FILE, null); - results = tagsDAO.getTags(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // Get "Tag1" file tags from data source 2 - param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.FILE, dataSource2.getId()); - results = tagsDAO.getTags(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get the row - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof BaseRowDTO); - BaseRowDTO tagResultRowDTO = (BaseRowDTO) rowDTO; - - // Check that the file tag is for the custom file - assertTrue(tagResultRowDTO.getCellValues().contains(customFile.getName())); - - // Check that a few of the expected file tag column names are present - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains(MD5_COLUMN)); - assertTrue(columnDisplayNames.contains(FILE_PATH_COLUMN)); - assertTrue(columnDisplayNames.contains(MODIFIED_TIME_COLUMN)); - - // Check that the result tag columns are not present - assertFalse(columnDisplayNames.contains(SOURCE_NAME_COLUMN)); - assertFalse(columnDisplayNames.contains(SOURCE_FILE_PATH_COLUMN)); - - // Get "Tag1" result tags from data source 2 - param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.RESULT, dataSource2.getId()); - results = tagsDAO.getTags(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Get "Tag2" result tags from data source 1 - param = new TagsSearchParams(tag2, TagsSearchParams.TagType.RESULT, dataSource1.getId()); - results = tagsDAO.getTags(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get "Tag1" result tags from data source 1 - param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.RESULT, dataSource1.getId()); - results = tagsDAO.getTags(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get the row - rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof BaseRowDTO); - tagResultRowDTO = (BaseRowDTO) rowDTO; - - // Check that some of the expected result tag column values are present - assertTrue(tagResultRowDTO.getCellValues().contains(TAG_COMMENT)); - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void OsAccountsTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - OsAccountsDAO accountsDAO = MainDAO.getInstance().getOsAccountsDAO(); - - // Get OS Accounts from data source 1 - OsAccountsSearchParams param = new OsAccountsSearchParams(dataSource1.getId()); - SearchResultsDTO results = accountsDAO.getAccounts(param, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // Get OS Accounts from all data sources - param = new OsAccountsSearchParams(null); - results = accountsDAO.getAccounts(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // Get OS Accounts from data source 1 - param = new OsAccountsSearchParams(dataSource2.getId()); - results = accountsDAO.getAccounts(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get the row - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof BaseRowDTO); - BaseRowDTO osAccountRowDTO = (BaseRowDTO) rowDTO; - - // Check that the result is for the custom OS Account - Optional<String> addr = osAccount1.getAddr(); - assertTrue(osAccountRowDTO.getCellValues().contains(addr.get())); - - // Check that a few of the expected OS Account column names are present - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains(REALM_NAME_COLUMN)); - assertTrue(columnDisplayNames.contains(HOST_COLUMN)); - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void analysisResultSearchTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - // Get all encryption detected artifacts - AnalysisResultSearchParam param = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, null, null); - AnalysisResultDAO analysisResultDAO = MainDAO.getInstance().getAnalysisResultDAO(); - - AnalysisResultTableSearchResultsDTO results = analysisResultDAO.getAnalysisResultsForTable(param, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, results.getArtifactType()); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // Get encryption detected artifacts from data source 2 - param = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, null, dataSource2.getId()); - results = analysisResultDAO.getAnalysisResultsForTable(param, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get all custom artifacts - param = new AnalysisResultSearchParam(customAnalysisResultType, null, null); - results = analysisResultDAO.getAnalysisResultsForTable(param, 0, null); - assertEquals(customAnalysisResultType, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Check that a few of the expected column names are present - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains(BlackboardAttribute.Type.TSK_COMMENT.getDisplayName())); - assertTrue(columnDisplayNames.contains(BlackboardAttribute.Type.TSK_COUNT.getDisplayName())); - assertTrue(columnDisplayNames.contains(customAttributeType.getDisplayName())); - - // Get the row - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof AnalysisResultRowDTO); - AnalysisResultRowDTO analysisResultRowDTO = (AnalysisResultRowDTO) rowDTO; - - // Check that the artifact, source content and linked file are correct - assertEquals(customAnalysisResult, analysisResultRowDTO.getAnalysisResult()); - assertEquals(customAnalysisResultSource, analysisResultRowDTO.getSrcContent()); - - // Check that some of the expected column values are present - assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_CUSTOM_ATTR_STRING)); - assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_COMMENT)); - assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_INT)); - assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_DOUBLE)); - assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_JUSTIFICATION)); - assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_CONFIGURATION)); - assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_CONCLUSION)); - - // Test paging - Long pageSize = new Long(100); - assertTrue(ARTIFACT_COUNT_YARA > pageSize); - - // Get the first page - param = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_YARA_HIT, null, null); - results = analysisResultDAO.getAnalysisResultsForTable(param, 0, pageSize); - assertEquals(ARTIFACT_COUNT_YARA, results.getTotalResultsCount()); - assertEquals(pageSize.longValue(), results.getItems().size()); - - // Save all artifact IDs from the first page - Set<Long> firstPageObjIds = new HashSet<>(); - for (RowDTO row : results.getItems()) { - assertTrue(row instanceof AnalysisResultRowDTO); - AnalysisResultRowDTO analysisRow = (AnalysisResultRowDTO) row; - assertTrue(analysisRow.getAnalysisResult() != null); - firstPageObjIds.add(analysisRow.getAnalysisResult().getId()); - } - assertEquals(pageSize.longValue(), firstPageObjIds.size()); - - // Get the second page - param = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_YARA_HIT, null, null); - results = analysisResultDAO.getAnalysisResultsForTable(param, pageSize, pageSize); - assertEquals(ARTIFACT_COUNT_YARA, results.getTotalResultsCount()); - assertEquals(ARTIFACT_COUNT_YARA - pageSize, results.getItems().size()); - - // Make sure no artifacts from the second page appeared on the first - for (RowDTO row : results.getItems()) { - assertTrue(row instanceof AnalysisResultRowDTO); - AnalysisResultRowDTO analysisRow = (AnalysisResultRowDTO) row; - assertTrue(analysisRow.getAnalysisResult() != null); - assertFalse("Analysis result ID: " + analysisRow.getAnalysisResult().getId() + " appeared on both page 1 and page 2", - firstPageObjIds.contains(analysisRow.getAnalysisResult().getId())); - } - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - private void hashHitSearchTest() { - try { - // Test hash set hits - AnalysisResultDAO analysisResultDAO = MainDAO.getInstance().getAnalysisResultDAO(); - AnalysisResultSearchParam hashParam = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_HASHSET_HIT, HASH_SET_1, null); - AnalysisResultTableSearchResultsDTO results = analysisResultDAO.getAnalysisResultConfigResults(hashParam, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_HASHSET_HIT, results.getArtifactType()); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - hashParam = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_HASHSET_HIT, HASH_SET_1, dataSource2.getId()); - results = analysisResultDAO.getAnalysisResultConfigResults(hashParam, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_HASHSET_HIT, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Check that a few of the expected column names are present - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains("Justification")); - assertTrue(columnDisplayNames.contains(BlackboardAttribute.Type.TSK_HASH_MD5.getDisplayName())); - - // Get the row - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof AnalysisResultRowDTO); - AnalysisResultRowDTO analysisResultRowDTO = (AnalysisResultRowDTO) rowDTO; - - // Check that the artifact, source content and linked file are correct - assertEquals(hashHitAnalysisResult, analysisResultRowDTO.getAnalysisResult()); - assertEquals(fileWithHashHit, analysisResultRowDTO.getSrcContent()); - - // Check that the hash is present - assertTrue(analysisResultRowDTO.getCellValues().contains(HASH_HIT_VALUE)); - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - private void keywordHitSearchTest() { - try { - // Test keyword set hits - AnalysisResultDAO analysisResultDAO = MainDAO.getInstance().getAnalysisResultDAO(); - KeywordHitSearchParam kwParam = new KeywordHitSearchParam(null, KEYWORD_SET_1, "keyword1", "", TskData.KeywordSearchQueryType.LITERAL, ""); - AnalysisResultTableSearchResultsDTO results = analysisResultDAO.getKeywordHitsForTable(kwParam, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_KEYWORD_HIT, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - kwParam = new KeywordHitSearchParam(dataSource1.getId(), KEYWORD_SET_2, "keyword2", "", TskData.KeywordSearchQueryType.LITERAL, ""); - results = analysisResultDAO.getKeywordHitsForTable(kwParam, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_KEYWORD_HIT, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - kwParam = new KeywordHitSearchParam(dataSource2.getId(), KEYWORD_SET_2, KEYWORD, KEYWORD_REGEX, TskData.KeywordSearchQueryType.REGEX, ""); - results = analysisResultDAO.getKeywordHitsForTable(kwParam, 0, null); - assertEquals(BlackboardArtifact.Type.TSK_KEYWORD_HIT, results.getArtifactType()); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Check that a few of the expected column names are present - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains("Justification")); - assertTrue(columnDisplayNames.contains(BlackboardAttribute.Type.TSK_KEYWORD.getDisplayName())); - - // Get the row - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof AnalysisResultRowDTO); - AnalysisResultRowDTO analysisResultRowDTO = (AnalysisResultRowDTO) rowDTO; - - // Check that the keyword and preview are present - assertTrue(analysisResultRowDTO.getCellValues().contains(KEYWORD)); - assertTrue(analysisResultRowDTO.getCellValues().contains(KEYWORD_PREVIEW)); - assertTrue(analysisResultRowDTO.getCellValues().contains(KEYWORD_REGEX)); - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - public void extensionSearchTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - ViewsDAO viewsDAO = MainDAO.getInstance().getViewsDAO(); - - // Get all text documents from data source 1 - FileTypeExtensionsSearchParams param = new FileTypeExtensionsSearchParams(FileExtRootFilter.TSK_DOCUMENT_FILTER, dataSource1.getId()); - SearchResultsDTO results = viewsDAO.getFilesByExtension(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // Get Word documents from data source 1 - param = new FileTypeExtensionsSearchParams(FileExtDocumentFilter.AUT_DOC_OFFICE, dataSource1.getId()); - results = viewsDAO.getFilesByExtension(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get image/jpeg files from data source 1 - param = new FileTypeExtensionsSearchParams(FileExtRootFilter.TSK_IMAGE_FILTER, dataSource1.getId()); - results = viewsDAO.getFilesByExtension(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - // Get text documents from all data sources - param = new FileTypeExtensionsSearchParams(FileExtRootFilter.TSK_DOCUMENT_FILTER, null); - results = viewsDAO.getFilesByExtension(param, 0, null); - assertEquals(4, results.getTotalResultsCount()); - assertEquals(4, results.getItems().size()); - - // Get jpeg files from data source 2 - param = new FileTypeExtensionsSearchParams(FileExtRootFilter.TSK_IMAGE_FILTER, dataSource2.getId()); - results = viewsDAO.getFilesByExtension(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Search for file extensions that should produce no results - param = new FileTypeExtensionsSearchParams(CustomRootFilter.EMPTY_RESULT_SET_FILTER, null); - results = viewsDAO.getFilesByExtension(param, 0, null); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Get the custom file by extension - param = new FileTypeExtensionsSearchParams(CustomRootFilter.CUSTOM_FILTER, null); - results = viewsDAO.getFilesByExtension(param, 0, null); - assertEquals(1, results.getTotalResultsCount()); - assertEquals(1, results.getItems().size()); - - RowDTO rowDTO = results.getItems().get(0); - assertTrue(rowDTO instanceof FileRowDTO); - FileRowDTO fileRowDTO = (FileRowDTO) rowDTO; - - assertEquals(CUSTOM_MIME_TYPE_FILE_NAME, fileRowDTO.getFileName()); - assertEquals(CUSTOM_EXTENSION, fileRowDTO.getExtension()); - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - private void fileSystemTest() { - // Quick test that everything is initialized - assertTrue(db != null); - - try { - FileSystemDAO fileSystemDAO = MainDAO.getInstance().getFileSystemDAO(); - - // There are 4 hosts not associated with a person - FileSystemPersonSearchParam personParam = new FileSystemPersonSearchParam(null); - BaseSearchResultsDTO results = fileSystemDAO.getHostsForTable(personParam, 0, null); - assertEquals(4, results.getTotalResultsCount()); - assertEquals(4, results.getItems().size()); - - // Person1 is associated with two hosts - personParam = new FileSystemPersonSearchParam(person1.getPersonId()); - results = fileSystemDAO.getHostsForTable(personParam, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // Check that the name of the first host is present - RowDTO row = results.getItems().get(0); - assertTrue(row.getCellValues().contains(PERSON_HOST_NAME1)); - - // HostA is associated with two images - FileSystemHostSearchParam hostParam = new FileSystemHostSearchParam(fsTestHostA.getHostId()); - results = fileSystemDAO.getContentForTable(hostParam, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // ImageA has one volume system child, which has three volumes that will be displayed - FileSystemContentSearchParam param = new FileSystemContentSearchParam(fsTestImageA.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // VsA has three volume children (this should match the previous search) - param = new FileSystemContentSearchParam(fsTestVsA.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // VolumeA1 has a file system child, which in turn has a root directory child with three file children - param = new FileSystemContentSearchParam(fsTestVolumeA1.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // FsA has a root directory child with three file children (this should match the previous search) - param = new FileSystemContentSearchParam(fsTestFsA.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // The root dir contains three files - param = new FileSystemContentSearchParam(fsTestRootDirA.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // ImageB has VS (which will display one volume), pool, and one local file children - param = new FileSystemContentSearchParam(fsTestImageB.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(3, results.getTotalResultsCount()); - assertEquals(3, results.getItems().size()); - - // Check that we have the "Type" column from the Pool and the "Known" column from the file - List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); - assertTrue(columnDisplayNames.contains("Type")); - assertTrue(columnDisplayNames.contains("Known")); - - // fsTestVolumeB1 has pool and one local file children - param = new FileSystemContentSearchParam(fsTestVolumeB1.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - // fsTestPoolB has VS (which will display one volume) and local file children - param = new FileSystemContentSearchParam(fsTestPoolB.getId()); - results = fileSystemDAO.getContentForTable(param, 0, null); - assertEquals(2, results.getTotalResultsCount()); - assertEquals(2, results.getItems().size()); - - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - } - - private enum CustomRootFilter implements FileExtSearchFilter { - - CUSTOM_FILTER(0, "CUSTOM_FILTER", "Test", CUSTOM_EXTENSIONS), //NON-NLS - EMPTY_RESULT_SET_FILTER(1, "EMPTY_RESULT_SET_FILTER", "Test", EMPTY_RESULT_SET_EXTENSIONS); //NON-NLS - final int id; - final String name; - final String displayName; - final Set<String> filter; - - private CustomRootFilter(int id, String name, String displayName, Set<String> filter) { - this.id = id; - this.name = name; - this.displayName = displayName; - this.filter = filter; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public int getId() { - return this.id; - } - - @Override - public String getDisplayName() { - return this.displayName; - } - - @Override - public Set<String> getFilter() { - return this.filter; - } - } - - @Override - public void tearDown() { - try { - CaseUtils.closeCurrentCase(); - } catch (TestUtilsException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex.getMessage()); - } - openCase = null; - db = null; - blackboard = null; - tagsManager = null; - accountMgr = null; - } -} diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index d3787488e9788b4261a4c5fa9b01b5d82e3e9c8e..fe5e78acffee0357254968f4833ac23fcb76ee49 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -128,7 +128,7 @@ file.reference.log4j-core-2.17.2.jar=release/modules/ext/log4j-core-2.17.2.jar file.reference.opencensus-api-0.31.0.jar=release/modules/ext/opencensus-api-0.31.0.jar file.reference.opencensus-contrib-http-util-0.31.0.jar=release/modules/ext/opencensus-contrib-http-util-0.31.0.jar file.reference.opencensus-proto-0.2.0.jar=release/modules/ext/opencensus-proto-0.2.0.jar -file.reference.opencv-3416.jar=release/modules/ext/opencv-3416.jar +file.reference.opencv-2413.jar=release/modules/ext/opencv-2413.jar file.reference.perfmark-api-0.23.0.jar=release/modules/ext/perfmark-api-0.23.0.jar file.reference.proto-google-cloud-translate-v3-2.1.11.jar=release/modules/ext/proto-google-cloud-translate-v3-2.1.11.jar file.reference.proto-google-cloud-translate-v3beta1-0.83.11.jar=release/modules/ext/proto-google-cloud-translate-v3beta1-0.83.11.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 79480e8ddd22438c76d1aee5f4868d9a8bb123e8..f7b139f50237a2d456d2e0d6c74258f23638b9ee 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -392,17 +392,6 @@ <package>org.apache.commons.codec.language.bm</package> <package>org.apache.commons.codec.net</package> <package>org.apache.commons.collections</package> - <package>org.apache.commons.collections.bag</package> - <package>org.apache.commons.collections.bidimap</package> - <package>org.apache.commons.collections.buffer</package> - <package>org.apache.commons.collections.collection</package> - <package>org.apache.commons.collections.comparators</package> - <package>org.apache.commons.collections.functors</package> - <package>org.apache.commons.collections.iterators</package> - <package>org.apache.commons.collections.keyvalue</package> - <package>org.apache.commons.collections.list</package> - <package>org.apache.commons.collections.map</package> - <package>org.apache.commons.collections.set</package> <package>org.apache.commons.collections4</package> <package>org.apache.commons.collections4.bag</package> <package>org.apache.commons.collections4.bidimap</package> @@ -464,16 +453,21 @@ <package>org.apache.log.output.net</package> <package>org.apache.log.util</package> <package>org.apache.commons.text</package> - <package>org.apache.commons.validator</package> <package>org.apache.commons.validator.routines</package> <package>org.apache.commons.validator.routines.checkdigit</package> - <package>org.apache.commons.validator.util</package> <package>org.apache.log4j</package> <package>org.apache.log4j.chainsaw</package> <package>org.apache.log4j.config</package> <package>org.apache.log4j.helpers</package> <package>org.apache.log4j.jdbc</package> + <package>org.apache.log4j.jmx</package> + <package>org.apache.log4j.lf5</package> + <package>org.apache.log4j.lf5.util</package> + <package>org.apache.log4j.lf5.viewer</package> + <package>org.apache.log4j.lf5.viewer.categoryexplorer</package> + <package>org.apache.log4j.lf5.viewer.configure</package> <package>org.apache.log4j.net</package> + <package>org.apache.log4j.nt</package> <package>org.apache.log4j.or</package> <package>org.apache.log4j.or.jms</package> <package>org.apache.log4j.or.sax</package> @@ -580,14 +574,13 @@ <package>org.opencv.core</package> <package>org.opencv.features2d</package> <package>org.opencv.gpu</package> + <package>org.opencv.highgui</package> <package>org.opencv.imgproc</package> <package>org.opencv.ml</package> <package>org.opencv.objdetect</package> <package>org.opencv.photo</package> <package>org.opencv.utils</package> <package>org.opencv.video</package> - <package>org.opencv.videoio</package> - <package>org.opencv.imgcodecs</package> <package>org.sleuthkit.autopsy.corelibs</package> <package>org.slf4j</package> <package>org.slf4j.event</package> @@ -1115,8 +1108,8 @@ <binary-origin>release/modules/ext/opencensus-proto-0.2.0.jar</binary-origin> </class-path-extension> <class-path-extension> - <runtime-relative-path>ext/opencv-3416.jar</runtime-relative-path> - <binary-origin>release/modules/ext/opencv-3416.jar</binary-origin> + <runtime-relative-path>ext/opencv-2413.jar</runtime-relative-path> + <binary-origin>release/modules/ext/opencv-2413.jar</binary-origin> </class-path-extension> <class-path-extension> <runtime-relative-path>ext/perfmark-api-0.23.0.jar</runtime-relative-path> diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index ff4d698b371f710e1a8ef6737e154c12d69f4300..7760187c5e57748a20150874230f3779add50f50 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -168,7 +168,6 @@ </module-dependencies> <public-packages> <package>org.sleuthkit.autopsy.experimental.autoingest</package> - <package>org.sleuthkit.autopsy.experimental.cleanup</package> <package>org.sleuthkit.autopsy.experimental.configuration</package> </public-packages> <class-path-extension> diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index fa93389769b1126b8813a5a4a7c9b277e3c0b65d..787be9cc30d2032db1e016fc57a2bbc97c01c282 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2016-2022 Basis Technology Corp. + * Copyright 2016-2021 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,15 +37,12 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Observable; import java.util.Set; import java.util.UUID; @@ -76,7 +73,6 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; -import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.coreutils.ThreadUtils; @@ -100,7 +96,6 @@ import org.sleuthkit.autopsy.datasourceprocessors.DataSourceProcessorUtility; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.AutoIngestJobException; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType; -import org.sleuthkit.autopsy.experimental.cleanup.AutoIngestCleanup; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; import org.sleuthkit.autopsy.ingest.IngestJobSettings; @@ -115,7 +110,6 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.keywordsearch.KeywordSearchJobSettings; -import org.sleuthkit.autopsy.progress.ProgressIndicator; /** * An auto ingest manager is responsible for processing auto ingest jobs defined @@ -225,7 +219,6 @@ private AutoIngestManager() { inputScanExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(INPUT_SCAN_THREAD_NAME).build()); jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(AUTO_INGEST_THREAD_NAME).build()); jobStatusPublishingExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat(JOB_STATUS_PUBLISHING_THREAD_NAME).build()); - hostNamesToRunningJobs = new ConcurrentHashMap<>(); hostNamesToLastMsgTime = new ConcurrentHashMap<>(); jobsLock = new Object(); @@ -263,9 +256,8 @@ void startUp() throws AutoIngestManagerException { rootOutputDirectory = Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder()); inputScanSchedulingExecutor.scheduleWithFixedDelay(new InputDirScanSchedulingTask(), 0, AutoIngestUserPreferences.getMinutesOfInputScanInterval(), TimeUnit.MINUTES); jobProcessingTask = new JobProcessingTask(); - jobProcessingTaskFuture = jobProcessingExecutor.submit(jobProcessingTask); + jobProcessingTaskFuture = jobProcessingExecutor.submit(jobProcessingTask); jobStatusPublishingExecutor.scheduleWithFixedDelay(new PeriodicJobStatusEventTask(), JOB_STATUS_EVENT_INTERVAL_SECONDS, JOB_STATUS_EVENT_INTERVAL_SECONDS, TimeUnit.SECONDS); - eventPublisher.addSubscriber(EVENT_LIST, instance); state = State.RUNNING; @@ -1955,12 +1947,6 @@ private void processJobs() throws CoordinationServiceException, SharedConfigurat processJob(); } finally { manifestLock.release(); - - // force garbage collection to release file handles - System.gc(); - - // perform optional input and output directory cleanup - cleanup(); } if (jobProcessingTaskFuture.isCancelled()) { return; @@ -1973,110 +1959,6 @@ private void processJobs() throws CoordinationServiceException, SharedConfigurat } } - private void cleanup() { - try { - //discover the registered implementations of automated cleanup - Collection<? extends AutoIngestCleanup> cleanups - = Lookup.getDefault().lookupAll(AutoIngestCleanup.class); - - if (!cleanups.isEmpty()) { - AutoIngestCleanup cleanup = cleanups.iterator().next(); - - sysLogger.log(Level.INFO, "CleanupSchedulingTask - trying to get ingest job lock"); - // NOTE1: Make a copy of the completed jobs list. There is no need to hold the jobs - // lock during the entire very lengthy cleanup operation. Jobs lock is also used - // to process incoming messages from other nodes so we don't want to hold it for hours. - - // NOTE2: Create a map of cases and data sources, so that we only attempt to clean - // each case once. otherwise if there are many completed jobs for a case - // that has jobs being processed by other AINs, we will be stuck attemping to clean - // that case over and over again, and unable to get locks. - Map<Path, List<Path>> casesToJobsMap = new HashMap<>(); - synchronized (jobsLock) { - for (AutoIngestJob job : completedJobs) { - Path casePath = job.getCaseDirectoryPath(); - Path dsPath = job.getManifest().getDataSourcePath(); - - List<Path> list = casesToJobsMap.get(casePath); - if (list == null) { - list = new ArrayList<>(); - casesToJobsMap.put(casePath, list); - } - list.add(dsPath); - } - } - - sysLogger.log(Level.INFO, "CleanupSchedulingTask - got ingest job lock"); - String deletedCaseName = ""; - for (Map.Entry<Path, List<Path>> caseData : casesToJobsMap.entrySet()) { - // do cleanup for each case and data source of the case - Path casePath = caseData.getKey(); - boolean success = true; - if (casePath.toFile().exists()) { - sysLogger.log(Level.INFO, "Cleaning up case {0} ", casePath.toString()); - success = cleanup.runCleanupTask(casePath, AutoIngestCleanup.DeleteOptions.DELETE_INPUT_AND_OUTPUT, new DoNothingProgressIndicator()); - } else { - // case directory has been deleted. make sure data source is deleted as well - // because we will never be able to run automated cleanup on a case directory - // that has been deleted. - for (Path dsPath : caseData.getValue()) { - File dsFile = dsPath.toFile(); - if (dsFile.exists()) { - sysLogger.log(Level.INFO, "Cleaning up data source {0} for deleted case {1}", new Object[]{dsPath.toString(), casePath.toString()}); - if (!FileUtil.deleteFileDir(dsFile)) { - sysLogger.log(Level.SEVERE, String.format("Failed to delete data source file at %s ", dsPath.toString())); - } - } - } - } - - if (success) { - sysLogger.log(Level.INFO, "Cleanup task successfully completed for case: {0}", casePath.toString()); - } else { - sysLogger.log(Level.WARNING, "Cleanup task failed for case: {0}", casePath.toString()); - continue; - } - - // NOTE: the code below asumes that case directory and all data sources are being deleted - // during cleanup. This may not be the case in future implementations of AutoIngestCleanup - - // verify that the data source and case directory have indeed been deleted - for (Path dsPath : caseData.getValue()) { - if (dsPath.toFile().exists()) { - // data source have NOT ben deleted - sysLogger.log(Level.SEVERE, "Data source has not been deleted during cleanup: {0}", dsPath.toString()); - } - } - - if (casePath.toFile().exists()) { - // case output directory has NOT ben deleted, or at least some contents of the - // case directory remain - sysLogger.log(Level.SEVERE, "Case directory has not been deleted during cleanup: {0}", casePath.toString()); - } - - deletedCaseName = casePath.toString(); - } - - if (!deletedCaseName.isEmpty()) { - // send message that a at lease one case has been deleted. This message triggers input direcotry - // re-scan on other AINs so only send one message after all cleanup is complete. The actual - // case name is not relevant either and is not being tracked on the receiving side. - final String name = deletedCaseName; - new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(LOCAL_HOST_NAME, name, - getSystemUserNameProperty())); - }).start(); - - // trigger input scan which will update the ZK nodes and tables - scanInputDirsNow(); - } - } - - } catch (Exception ex) { - sysLogger.log(Level.SEVERE, "Unexpected exception in CleanupSchedulingTask", ex); //NON-NLS - } - } - /** * Inspects the pending jobs queue, looking for the next job that is * ready for processing. If such a job is found, it is removed from the @@ -3218,44 +3100,6 @@ private JobMetricsCollectionException(String message, Throwable cause) { } } - } - - /** - * A progress monitor that does nothing. - */ - private class DoNothingProgressIndicator implements ProgressIndicator { - - @Override - public void start(String message, int totalWorkUnits) { - } - - @Override - public void start(String message) { - } - - @Override - public void switchToIndeterminate(String message) { - } - - @Override - public void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits) { - } - - @Override - public void progress(String message) { - } - - @Override - public void progress(int workUnitsCompleted) { - } - - @Override - public void progress(String message, int workUnitsCompleted) { - } - - @Override - public void finish() { - } } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java index bc78ea337e65708882021d76d4fc2e6499f61622..d51e03d350833be3af4689df4aeb4b5f14468e2a 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseAction.java @@ -23,7 +23,6 @@ import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.progress.ProgressIndicator; -import org.sleuthkit.autopsy.experimental.cleanup.AutoIngestCleanup.DeleteOptions; /** * An action that completely deletes one or more multi-user cases. Only the @@ -70,6 +69,6 @@ public void actionPerformed(ActionEvent event) { @Override DeleteCaseTask getTask(CaseNodeData caseNodeData, ProgressIndicator progress) { - return new DeleteCaseTask(caseNodeData, DeleteOptions.DELETE_CASE, progress); + return new DeleteCaseTask(caseNodeData, DeleteCaseTask.DeleteOptions.DELETE_CASE, progress); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java index 7d09401af3120f16c33414e50d41431580e376f4..a3148fbf52fa728dd03ea8ea9d36df723bc01056 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAction.java @@ -22,7 +22,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.experimental.cleanup.AutoIngestCleanup.DeleteOptions; +import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java index d1053c0311baaeb55cfaf4bbf532084a879a8987..c773c465e018a60348a05c8beac287b7a38a68c4 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseInputAndOutputAction.java @@ -23,7 +23,7 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.experimental.cleanup.AutoIngestCleanup.DeleteOptions; +import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java index 7970edb93b562c974e30339d07452391b707a6ab..cd6f6f9418c0777755672edee1e954a1669ade18 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseOutputAction.java @@ -22,7 +22,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.experimental.cleanup.AutoIngestCleanup.DeleteOptions; +import org.sleuthkit.autopsy.experimental.autoingest.DeleteCaseTask.DeleteOptions; import org.sleuthkit.autopsy.progress.ProgressIndicator; /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java index db9271e02b8e5d2848943f8ad8e39f5b45ae1313..e96d2cc5fdcd34e08dc07d6d61988af608d38de9 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseTask.java @@ -49,7 +49,6 @@ import org.sleuthkit.autopsy.progress.ProgressIndicator; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.InvalidDataException; -import org.sleuthkit.autopsy.experimental.cleanup.AutoIngestCleanup.DeleteOptions; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.TskCoreException; @@ -59,65 +58,68 @@ * directed to the dedicated auto ingest dashboard log instead of to the general * application log. */ -public final class DeleteCaseTask implements Runnable { +final class DeleteCaseTask implements Runnable { - private static final int MANIFEST_FILE_LOCKING_TIMEOUT_MINS = 1; + private static final int MANIFEST_FILE_LOCKING_TIMEOUT_MINS = 5; private static final int MANIFEST_DELETE_TRIES = 3; private static final Logger logger = AutoIngestDashboardLogger.getLogger(); private final CaseNodeData caseNodeData; private final DeleteOptions deleteOption; private final ProgressIndicator progress; - private final boolean bestEffortDeletion; - private final boolean deleteDataSourceDirectories; - private final List<ManifestFileLock> manifestFileLocks = new ArrayList<>(); + private final List<ManifestFileLock> manifestFileLocks; private CoordinationService coordinationService; private CaseMetadata caseMetadata; /** - * Constructs a task that deletes part or all of a given case.Note that all - * logging is directed to the dedicated auto ingest dashboard log instead of - * to the general application log. - * - * @param caseNodeData The case directory coordination service node data for - * the case. - * @param deleteOption The deletion option for the task. - * @param progress A progress indicator. - * @param bestEffortDeletion A flag whether manifest/input deletion is "best - * effort". If the flag is set to true, then we will delete all manifest - * files for which we are able to get exclusive lock, and leave behind all - * manifest files and data sources for which we were unable to get exclusive - * lock. If the flag is set to false, then the algorithm will abort and exit - * unless it is able to get exclusive lock on all of the manifest files. - * @param deleteDataSourceDirectories A flag whether to delete the directory - * containing each data source. If the flag is false, only the data source - * file will be deleted. If the flag is true, The entire directory - * containing the data source will be deleted. + * Options to support implementing different case deletion use cases. */ - public DeleteCaseTask(CaseNodeData caseNodeData, DeleteOptions deleteOption, ProgressIndicator progress, - boolean bestEffortDeletion, boolean deleteDataSourceDirectories) { - this.caseNodeData = caseNodeData; - this.deleteOption = deleteOption; - this.progress = progress; - this.bestEffortDeletion = bestEffortDeletion; - this.deleteDataSourceDirectories = deleteDataSourceDirectories; + enum DeleteOptions { + /** + * Delete the auto ingest job manifests and corresponding data sources, + * while leaving the manifest file coordination service nodes and the + * rest of the case intact. The use case is freeing auto ingest input + * directory space while retaining the option to restore the data + * sources, effectively restoring the case. + */ + DELETE_INPUT, + /** + * Delete the manifest file coordination service nodes and the output + * for a case, while leaving the auto ingest job manifests and + * corresponding data sources intact. The use case is auto ingest + * reprocessing of a case with a clean slate without having to restore + * the manifests and data sources. + */ + DELETE_OUTPUT, + /** + * Delete everything. + */ + DELETE_INPUT_AND_OUTPUT, + /** + * Delete only the case components that the application created. This is + * DELETE_OUTPUT with the additional feature that manifest file + * coordination service nodes are marked as deleted, rather than + * actually deleted. This eliminates the requirement that manifests and + * data sources have to be deleted before deleting the case to avoid an + * unwanted, automatic reprocessing of the case. + */ + DELETE_CASE } - + /** - * Constructs a task that deletes part or all of a given case.Note that all + * Constructs a task that deletes part or all of a given case. Note that all * logging is directed to the dedicated auto ingest dashboard log instead of * to the general application log. * * @param caseNodeData The case directory coordination service node data for - * the case. + * the case. * @param deleteOption The deletion option for the task. - * @param progress A progress indicator. + * @param progress A progress indicator. */ - public DeleteCaseTask(CaseNodeData caseNodeData, DeleteOptions deleteOption, ProgressIndicator progress) { + DeleteCaseTask(CaseNodeData caseNodeData, DeleteOptions deleteOption, ProgressIndicator progress) { this.caseNodeData = caseNodeData; this.deleteOption = deleteOption; this.progress = progress; - this.bestEffortDeletion = false; //abort and exit unless we get exclusive lock on all of the manifest files. - this.deleteDataSourceDirectories = false; // only data source files will be deleted + manifestFileLocks = new ArrayList<>(); } @Override @@ -175,7 +177,7 @@ public void run() { }) private void deleteCase() throws CoordinationServiceException, IOException, InterruptedException { progress.progress(Bundle.DeleteCaseTask_progress_connectingToCoordSvc()); - //logger.log(Level.INFO, String.format("Connecting to the coordination service for deletion of %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Connecting to the coordination service for deletion of %s", caseNodeData.getDisplayName())); coordinationService = CoordinationService.getInstance(); checkForCancellation(); @@ -210,7 +212,7 @@ private void deleteCase() throws CoordinationServiceException, IOException, Inte * case while it is being deleted. */ progress.progress(Bundle.DeleteCaseTask_progress_acquiringCaseDirLock()); - //logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Acquiring an exclusive case directory lock for %s", caseNodeData.getDisplayName())); String caseDirLockName = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory()); try (CoordinationService.Lock caseDirLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, caseDirLockName)) { if (caseDirLock == null) { @@ -373,7 +375,7 @@ private boolean acquireManifestFileLocks() throws IOException, CoordinationServi logger.log(Level.INFO, String.format("Found %d manifest file path(s) for %s", manifestFilePaths.size(), caseNodeData.getDisplayName())); if (!manifestFilePaths.isEmpty()) { progress.progress(Bundle.DeleteCaseTask_progress_acquiringManifestLocks()); - //logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Acquiring exclusive manifest file locks for %s", caseNodeData.getDisplayName())); /* * When acquiring the locks, it is reasonable to block briefly, * since the auto ingest node (AIN) input directory scanning tasks @@ -388,17 +390,15 @@ private boolean acquireManifestFileLocks() throws IOException, CoordinationServi for (Path manifestPath : manifestFilePaths) { checkForCancellation(); progress.progress(Bundle.DeleteCaseTask_progress_lockingManifest(manifestPath.toString())); - //logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Exclusively locking the manifest %s for %s", manifestPath, caseNodeData.getDisplayName())); CoordinationService.Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), MANIFEST_FILE_LOCKING_TIMEOUT_MINS, TimeUnit.MINUTES); if (null != manifestLock) { manifestFileLocks.add(new ManifestFileLock(manifestPath, manifestLock)); } else { - logger.log(Level.WARNING, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName())); - if (!bestEffortDeletion) { - allLocksAcquired = false; - releaseManifestFileLocks(); - break; - } + logger.log(Level.INFO, String.format("Failed to exclusively lock the manifest %s because it was already held by another host", manifestPath, caseNodeData.getDisplayName())); + allLocksAcquired = false; + releaseManifestFileLocks(); + break; } } } catch (CoordinationServiceException | InterruptedException ex) { @@ -422,7 +422,7 @@ private void deleteCaseContents() throws InterruptedException { final File caseDirectory = caseNodeData.getDirectory().toFile(); if (caseDirectory.exists()) { progress.progress(Bundle.DeleteCaseTask_progress_openingCaseMetadataFile()); - //logger.log(Level.INFO, String.format("Opening case metadata file for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Opening case metadata file for %s", caseNodeData.getDisplayName())); Path caseMetadataPath = CaseMetadata.getCaseMetadataFilePath(caseNodeData.getDirectory()); if (caseMetadataPath != null) { try { @@ -470,7 +470,7 @@ private void deleteAutoIngestInput() throws InterruptedException { SleuthkitCase caseDb = null; try { progress.progress(Bundle.DeleteCaseTask_progress_openingCaseDatabase()); - //logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Opening the case database for %s", caseNodeData.getDisplayName())); caseDb = SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory()); List<DataSource> dataSources = caseDb.getDataSources(); checkForCancellation(); @@ -493,13 +493,6 @@ private void deleteAutoIngestInput() throws InterruptedException { } else { allInputDeleted = false; } - - if (deleteDataSourceDirectories) { - File parentDir = manifestFile.getParentFile(); - if (parentDir.exists() && !FileUtil.deleteFileDir(parentDir)) { - logger.log(Level.WARNING, String.format("Failed to delete data source directory %s for %s", parentDir.toString(), caseNodeData.getDisplayName())); - } - } } else { logger.log(Level.WARNING, String.format("Failed to parse manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); allInputDeleted = false; @@ -570,7 +563,7 @@ private boolean deleteManifestFile(File manifestFile) throws InterruptedExceptio */ Path manifestFilePath = manifestFile.toPath(); progress.progress(Bundle.DeleteCaseTask_progress_deletingManifest(manifestFilePath)); - //logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Deleting manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); int tries = 0; boolean deleted = false; while (!deleted && tries < MANIFEST_DELETE_TRIES) { @@ -704,7 +697,7 @@ private void markManifestFileNodesAsDeleted() throws InterruptedException { private void deleteCaseResourcesNode() throws InterruptedException { if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT || deleteOption == DeleteOptions.DELETE_CASE) { progress.progress(Bundle.DeleteCaseTask_progress_deletingResourcesLockNode()); - //logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Deleting case resources log znode for %s", caseNodeData.getDisplayName())); String resourcesNodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseNodeData.getDirectory()); try { coordinationService.deleteNode(CategoryNode.CASES, resourcesNodePath); @@ -726,7 +719,7 @@ private void deleteCaseResourcesNode() throws InterruptedException { private void deleteCaseAutoIngestLogNode() throws InterruptedException { if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT || deleteOption == DeleteOptions.DELETE_CASE) { progress.progress(Bundle.DeleteCaseTask_progress_deletingJobLogLockNode()); - //logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Deleting case auto ingest job log znode for %s", caseNodeData.getDisplayName())); String logFilePath = CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseNodeData.getDirectory()); try { coordinationService.deleteNode(CategoryNode.CASES, logFilePath); @@ -760,7 +753,7 @@ private void deleteCaseDirectoryNode() throws InterruptedException { && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.CASE_DIR) && caseNodeData.isDeletedFlagSet(CaseNodeData.DeletedFlags.MANIFEST_FILE_NODES))) { progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseDirCoordSvcNode()); - //logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Deleting case directory znode for %s", caseNodeData.getDisplayName())); String caseDirNodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseNodeData.getDirectory()); try { coordinationService.deleteNode(CategoryNode.CASES, caseDirNodePath); @@ -780,7 +773,7 @@ private void deleteCaseDirectoryNode() throws InterruptedException { private void deleteCaseNameNode() throws InterruptedException { if (deleteOption == DeleteOptions.DELETE_OUTPUT || deleteOption == DeleteOptions.DELETE_INPUT_AND_OUTPUT || deleteOption == DeleteOptions.DELETE_CASE) { progress.progress(Bundle.DeleteCaseTask_progress_deletingCaseNameCoordSvcNode()); - //logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Deleting case name znode for %s", caseNodeData.getDisplayName())); try { String caseNameLockNodeName = CoordinationServiceUtils.getCaseNameNodePath(caseNodeData.getDirectory()); coordinationService.deleteNode(CategoryNode.CASES, caseNameLockNodeName); @@ -834,11 +827,11 @@ private void deleteManifestFileNodes() throws InterruptedException { String manifestFilePath = manifestFileLock.getManifestFilePath().toString(); try { progress.progress(Bundle.DeleteCaseTask_progress_releasingManifestLock(manifestFilePath)); - //logger.log(Level.INFO, String.format("Releasing the lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Releasing the lock on the manifest file %s for %s", manifestFilePath, caseNodeData.getDisplayName())); manifestFileLock.release(); if (manifestFileLock.isInputDeleted()) { progress.progress(Bundle.DeleteCaseTask_progress_deletingManifestFileNode(manifestFilePath)); - //logger.log(Level.INFO, String.format("Deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName())); + logger.log(Level.INFO, String.format("Deleting the manifest file znode for %s for %s", manifestFilePath, caseNodeData.getDisplayName())); coordinationService.deleteNode(CoordinationService.CategoryNode.MANIFESTS, manifestFilePath); } else { allINodesDeleted = false; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cleanup/AutoIngestCleanup.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cleanup/AutoIngestCleanup.java deleted file mode 100755 index e6c734175ed8ed1405040a56a5e9eb04ee3c467d..0000000000000000000000000000000000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/cleanup/AutoIngestCleanup.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2022 Basis Technology Corp. - * Contact: carrier <at> sleuthkit <dot> 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.experimental.cleanup; - -import java.nio.file.Path; -import org.sleuthkit.autopsy.progress.ProgressIndicator; - -/** - * Interface to perform automated cleanup of auto ingest input and output directories, - * as well as ZK nodes. - */ -public interface AutoIngestCleanup { - - /** - * Options to support implementing different case deletion use cases. - */ - public enum DeleteOptions { - /** - * Delete the auto ingest job manifests and corresponding data sources, - * while leaving the manifest file coordination service nodes and the - * rest of the case intact. The use case is freeing auto ingest input - * directory space while retaining the option to restore the data - * sources, effectively restoring the case. - */ - DELETE_INPUT, - /** - * Delete the manifest file coordination service nodes and the output - * for a case, while leaving the auto ingest job manifests and - * corresponding data sources intact. The use case is auto ingest - * reprocessing of a case with a clean slate without having to restore - * the manifests and data sources. - */ - DELETE_OUTPUT, - /** - * Delete everything. - */ - DELETE_INPUT_AND_OUTPUT, - /** - * Delete only the case components that the application created. This is - * DELETE_OUTPUT with the additional feature that manifest file - * coordination service nodes are marked as deleted, rather than - * actually deleted. This eliminates the requirement that manifests and - * data sources have to be deleted before deleting the case to avoid an - * unwanted, automatic reprocessing of the case. - */ - DELETE_CASE - } - - /** - * Performs auto ingest cleanup. For example, deletes input data sources, - * output directory, manifest file, and ZK nodes. - * - * @param caseOutputDirectoryPath Path to case output directory. - * @param deleteOption Cleanup options. - * @param progress Progress indicator. - * @return True if cleanup completed successfully, false otherwise. - */ - boolean runCleanupTask(Path caseOutputDirectoryPath, DeleteOptions deleteOption, ProgressIndicator progress); -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java index d37c777abd6caa3435150d58a92aab085a9ec5af..eee83e16ef92dd2085248a02e335079ddb2ee57d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java @@ -29,7 +29,7 @@ import org.opencv.core.Mat; import org.opencv.core.MatOfByte; import org.opencv.core.MatOfRect; -import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.highgui.Highgui; import org.opencv.objdetect.CascadeClassifier; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -136,7 +136,7 @@ public ProcessResult process(AbstractFile file) { Mat originalImage; try { - originalImage = Imgcodecs.imdecode(new MatOfByte(imageInMemory), Imgcodecs.IMREAD_GRAYSCALE); + originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_GRAYSCALE); } catch (CvException ex) { //The image was something which could not be decoded by OpenCv, our isImageThumbnailSupported(file) check above failed us logger.log(Level.WARNING, "Unable to decode image from byte array to perform object detection on " + file.getParentPath() + file.getName() + " with object id of " + file.getId(), ex); //NON-NLS diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index 8b1dad287ccd1d6bd967aee45683de3659859ffb..c01a81ff66f6ec286c86176b72bff157dbeb6e35 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -193,15 +193,11 @@ private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValue> t try { content = tskCase.getContentById(hit.getContentID()); if (content == null) { - // it is possible that the previously indexed KW hit is in the content - // that has since been deleted (e.g. deleted analysis result) - logger.log(Level.WARNING, "There was an error getting content by id."); //NON-NLS + logger.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS return false; } } catch (TskCoreException ex) { - // it is possible that the previously indexed KW hit is in the content - // that has since been deleted (e.g. deleted analysis result) - logger.log(Level.WARNING, "There was an error getting content by id.", ex); //NON-NLS + logger.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS return false; } @@ -219,9 +215,7 @@ private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValue> t artifact = tskCase.getBlackboardArtifact(hit.getArtifactID().get()); hitName = artifact.getDisplayName() + " Artifact"; //NON-NLS } catch (TskCoreException ex) { - // it is possible that the previously indexed KW hit is in the content - // that has since been deleted (e.g. deleted analysis result) - logger.log(Level.WARNING, "Error getting blckboard artifact by id", ex); + logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex); return false; } } else { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index a8cf36155a26a2e208ba1ead219392084a45b9f4..43d6b5417c7349e52dce91766096a2ed631661da 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -47,7 +47,6 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; /** * Highlights hits for a given document. Knows about pages and such for the @@ -98,7 +97,7 @@ class HighlightedText implements ExtractedText { private QueryResults hits = null; //original hits that may get passed in private BlackboardArtifact artifact; - private TskData.KeywordSearchQueryType qt; + private KeywordSearch.QueryType qt; private boolean isLiteral; /** @@ -175,7 +174,7 @@ synchronized private void loadPageInfoFromArtifact() throws TskCoreException, Ke //get the QueryType (if available) final BlackboardAttribute queryTypeAttribute = artifact.getAttribute(TSK_KEYWORD_SEARCH_TYPE); qt = (queryTypeAttribute != null) - ? TskData.KeywordSearchQueryType.values()[queryTypeAttribute.getValueInt()] : null; + ? KeywordSearch.QueryType.values()[queryTypeAttribute.getValueInt()] : null; Keyword keywordQuery = null; switch (qt) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index d34123d1cc94a84d0b2a09270b4551155f36ec0e..6d9875d433671428e27badfe995cd15c566541fc 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -52,7 +52,8 @@ import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.SleuthkitItemVisitor; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; import org.sleuthkit.datamodel.TskCoreException; /** @@ -124,11 +125,11 @@ void indexMetaDataOnly(BlackboardArtifact artifact, String sourceName) throws In * Creates a field map from a SleuthkitVisitableItem, that is later sent to * Solr. * - * @param item Content to get fields from + * @param item SleuthkitVisitableItem to get fields from * * @return the map from field name to value (as a string) */ - private Map<String, String> getContentFields(Content item) { + private Map<String, String> getContentFields(SleuthkitVisitableItem item) { return item.accept(SOLR_FIELDS_VISITOR); } @@ -205,7 +206,7 @@ private Map<String, String> getContentFields(Content item) { * @throws org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException */ // TODO (JIRA-3118): Cancelled text indexing does not propagate cancellation to clients - < T extends Content> void search(Reader sourceReader, long sourceID, String sourceName, T source, IngestJobContext context, boolean doLanguageDetection, boolean indexIntoSolr, List<String> keywordListNames) throws Ingester.IngesterException, IOException, TskCoreException, Exception { + < T extends SleuthkitVisitableItem> void search(Reader sourceReader, long sourceID, String sourceName, T source, IngestJobContext context, boolean doLanguageDetection, boolean indexIntoSolr, List<String> keywordListNames) throws Ingester.IngesterException, IOException, TskCoreException, Exception { int numChunks = 0; //unknown until chunking is done Map<String, String> contentFields = Collections.unmodifiableMap(getContentFields(source)); Optional<Language> language = Optional.empty(); @@ -302,6 +303,78 @@ < T extends Content> void search(Reader sourceReader, long sourceID, String sour } } + < T extends SleuthkitVisitableItem> boolean indexFile(Reader sourceReader, long sourceID, String sourceName, T source, IngestJobContext context, boolean doLanguageDetection) throws Ingester.IngesterException { + int numChunks = 0; //unknown until chunking is done + Map<String, String> contentFields = Collections.unmodifiableMap(getContentFields(source)); + Optional<Language> language = Optional.empty(); + //Get a reader for the content of the given source + try (BufferedReader reader = new BufferedReader(sourceReader)) { + Chunker chunker = new Chunker(reader); + while (chunker.hasNext()) { + if ( context.fileIngestIsCancelled()) { + logger.log(Level.INFO, "File ingest cancelled. Cancelling keyword search indexing of {0}", sourceName); + return false; + } + + Chunk chunk = chunker.next(); + + if (doLanguageDetection) { + int size = Math.min(chunk.getBaseChunkLength(), LANGUAGE_DETECTION_STRING_SIZE); + language = languageSpecificContentIndexingHelper.detectLanguageIfNeeded(chunk.toString().substring(0, size)); + + // only do language detection on the first chunk of the document + doLanguageDetection = false; + } + + Map<String, Object> fields = new HashMap<>(contentFields); + String chunkId = Server.getChunkIdString(sourceID, numChunks + 1); + fields.put(Server.Schema.ID.toString(), chunkId); + fields.put(Server.Schema.CHUNK_SIZE.toString(), String.valueOf(chunk.getBaseChunkLength())); + + language.ifPresent(lang -> languageSpecificContentIndexingHelper.updateLanguageSpecificFields(fields, chunk, lang)); + try { + //add the chunk text to Solr index + indexChunk(chunk.toString(), chunk.getLowerCasedChunk(), sourceName, fields); + // add mini chunk when there's a language specific field + if (chunker.hasNext() && language.isPresent()) { + languageSpecificContentIndexingHelper.indexMiniChunk(chunk, sourceName, new HashMap<>(contentFields), chunkId, language.get()); + } + numChunks++; + + } catch (Ingester.IngesterException ingEx) { + logger.log(Level.WARNING, "Ingester had a problem with extracted string from file '" //NON-NLS + + sourceName + "' (id: " + sourceID + ").", ingEx);//NON-NLS + + throw ingEx; //need to rethrow to signal error and move on + } + } + if (chunker.hasException()) { + logger.log(Level.WARNING, "Error chunking content from " + sourceID + ": " + sourceName, chunker.getException()); + return false; + } + + } catch (Exception ex) { + logger.log(Level.WARNING, "Unexpected error, can't read content stream from " + sourceID + ": " + sourceName, ex);//NON-NLS + return false; + } finally { + if (context.fileIngestIsCancelled()) { + return false; + } else { + Map<String, Object> fields = new HashMap<>(contentFields); + //after all chunks, index just the meta data, including the numChunks, of the parent file + fields.put(Server.Schema.NUM_CHUNKS.toString(), Integer.toString(numChunks)); + //reset id field to base document id + fields.put(Server.Schema.ID.toString(), Long.toString(sourceID)); + //"parent" docs don't have chunk_size + fields.remove(Server.Schema.CHUNK_SIZE.toString()); + indexChunk(null, null, sourceName, fields); + } + } + + + return true; + } + private void indexChunk(Chunk chunk, long sourceID, String sourceName, Optional<Language> language, Map<String, String> contentFields, boolean hasNext) throws IngesterException { Map<String, Object> fields = new HashMap<>(contentFields); String chunkId = Server.getChunkIdString(sourceID, chunk.getChunkId()); @@ -406,10 +479,10 @@ void commit() { /** * Visitor used to create fields to send to SOLR index. */ - static private class SolrFieldsVisitor extends ContentVisitor.Default<Map<String, String>> { + static private class SolrFieldsVisitor extends SleuthkitItemVisitor.Default<Map<String, String>> { @Override - protected Map<String, String> defaultVisit(Content svi) { + protected Map<String, String> defaultVisit(SleuthkitVisitableItem svi) { return new HashMap<>(); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/InlineSearcher.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/InlineSearcher.java index 373647faec6e2a3a877502a99752ea8db95ccdad..30365c304aa1f014e5482b56fa4c96a71c5ef534 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/InlineSearcher.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/InlineSearcher.java @@ -399,7 +399,6 @@ static void makeArtifacts(IngestJobContext context) throws TskException { } else { artifact = RegexQuery.createKeywordHitArtifact(content, originalKeyword, hitKeyword, hit, hit.getSnippet(), hitKeyword.getListName(), sourceId); } - // createKeywordHitArtifact has the potential to return null // when a CCN account is created. if (artifact != null) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java index 60ee843344d938daf9c017aa04822e8267465944..b988dd4e49dedf00b923f154c8379083d358deb8 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java @@ -42,6 +42,11 @@ public class KeywordSearch { private static final java.util.logging.Logger tikaLogger = java.util.logging.Logger.getLogger("Tika"); //NON-NLS private static final Logger logger = Logger.getLogger(Case.class.getName()); + // @@@ We should move this into TskData (or somewhere) because we are using + // this value in the results tree to display substring differently from regexp (KeywordHit.java) + public enum QueryType { + LITERAL, SUBSTRING, REGEX + }; public static final String NUM_FILES_CHANGE_EVT = "NUM_FILES_CHANGE_EVT"; //NON-NLS private static PropertyChangeSupport changeSupport = new PropertyChangeSupport(KeywordSearch.class); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index d243f09d92576fb70de7d85f6ac270fa81abb4cf..144704a27e64335f2064056c8eab4dd25d772b08 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -36,12 +36,12 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Score; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskException; /** @@ -234,42 +234,34 @@ public boolean validate() { */ @Override public BlackboardArtifact createKeywordHitArtifact(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName, Long ingestJobId) { - return createKeywordHitArtifact(content, originalKeyword, foundKeyword, hit, snippet, listName, ingestJobId); + return createKeywordHitArtifact(content, originalKeyword, foundKeyword, hit, snippet, listName, ingestJobId); } - + public static BlackboardArtifact createKeywordHitArtifact(Content content, Keyword originalKW, Keyword foundKeyword, KeywordHit hit, String snippet, String listName, Long ingestJobId) { final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); - String configuration; Collection<BlackboardAttribute> attributes = new ArrayList<>(); if (snippet != null) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); } attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm().toLowerCase())); + if (StringUtils.isNotBlank(listName)) { + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); + } if (originalKW != null) { - configuration = originalKW.getOriginalTerm(); BlackboardAttribute.ATTRIBUTE_TYPE selType = originalKW.getArtifactAttributeType(); if (selType != null) { attributes.add(new BlackboardAttribute(selType, MODULE_NAME, foundKeyword.getSearchTerm())); } if (originalKW.searchTermIsWholeWord()) { - configuration += " (" + TskData.KeywordSearchQueryType.LITERAL.name() + ")"; - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.LITERAL.getType())); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.LITERAL.ordinal())); } else { - configuration += " (" + TskData.KeywordSearchQueryType.SUBSTRING.name() + ")"; - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.SUBSTRING.getType())); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal())); } - } else { - configuration = foundKeyword.getOriginalTerm(); } - if (StringUtils.isNotBlank(listName)) { - configuration += " - " + listName; - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); - } - hit.getArtifactID().ifPresent(artifactID -> attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) ); @@ -277,7 +269,7 @@ public static BlackboardArtifact createKeywordHitArtifact(Content content, Keyw try { return content.newAnalysisResult( BlackboardArtifact.Type.TSK_KEYWORD_HIT, Score.SCORE_LIKELY_NOTABLE, - null, configuration, null, + null, listName, null, attributes) .getAnalysisResult(); } catch (TskCoreException e) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 044ac7872e34297e50cc15871dd25f7decd71e2f..051b323343334b8b05b3c98cb2212627dfff1dd0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -573,13 +573,15 @@ public static BlackboardArtifact createKeywordHitArtifact(Content content, Keyw * attributes */ Collection<BlackboardAttribute> attributes = new ArrayList<>(); - String configuration = originalKW.getOriginalTerm(); attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm().toLowerCase())); if(!originalKW.searchTermIsWholeWord() || !originalKW.searchTermIsLiteral()) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKW.getSearchTerm())); } + if (StringUtils.isNotBlank(listName)) { + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); + } if (snippet != null) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); } @@ -589,22 +591,15 @@ public static BlackboardArtifact createKeywordHitArtifact(Content content, Keyw ); if (originalKW.searchTermIsLiteral()) { - configuration += " (" + TskData.KeywordSearchQueryType.SUBSTRING.name() + ")"; - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.SUBSTRING.ordinal())); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal())); } else { - configuration += " (" + TskData.KeywordSearchQueryType.REGEX.name() + ")"; - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.REGEX.ordinal())); - } - - if (StringUtils.isNotBlank(listName)) { - configuration += " - " + listName; - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal())); } try { return content.newAnalysisResult( BlackboardArtifact.Type.TSK_KEYWORD_HIT, Score.SCORE_LIKELY_NOTABLE, - null, configuration, null, attributes) + null, listName, null, attributes) .getAnalysisResult(); } catch (TskCoreException e) { LOGGER.log(Level.SEVERE, "Error adding bb attributes for terms search artifact", e); //NON-NLS @@ -701,7 +696,7 @@ private static void createCCNAccount(Content content, Keyword originalKW, Keywor -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) ); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, TskData.KeywordSearchQueryType.REGEX.getType())); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal())); /* * Create an account instance. 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 c8661efb169e00ad56d0ceb3f48c1512fec2030f..6d7443a4a6fb434154bef3db464823bda5cd8e91 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 -#Tue, 14 Mar 2023 11:56:07 -0400 +#Wed, 28 Sep 2022 13:57:05 -0400 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 4.20.0 +currentVersion=Autopsy 4.19.3 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 9a651620b0efeab14649f81bbc5eb8eb15bfba52..f28a6b96b3a4c077acea5ede72be916cea81c779 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,6 +1,4 @@ #Updated by build script -#Tue, 14 Mar 2023 11:56:07 -0400 - #Wed, 28 Sep 2022 13:57:05 -0400 -CTL_MainWindow_Title=Autopsy 4.20.0 -CTL_MainWindow_Title_No_Project=Autopsy 4.20.0 +CTL_MainWindow_Title=Autopsy 4.19.3 +CTL_MainWindow_Title_No_Project=Autopsy 4.19.3 diff --git a/build.xml b/build.xml index fde76f6981a9f34699bb393ea705f2db2b99387c..ec5c30369df336a2a247c6a01d47a9c8ac59245f 100644 --- a/build.xml +++ b/build.xml @@ -244,47 +244,14 @@ </condition> </target> - <target name="-build-minimal-platform" depends="-init, clean, doxygen" description="Builds a minimal autopsy platform (i.e. just Core, CoreLibs, and Tika)"> - - <!-- step (1) use the general ZIP target --> + <target name="-build-minimal-platform" depends="-init,clean" description="Builds a minimal autopsy platform (i.e. just Core, CoreLibs, and Tika)"> <antcall inheritAll="false" target="suite.build-zip"> - <param name="modules" value="CoreLibs:Tika:Core:RecentActivity" /> + <param name="modules" value="CoreLibs:Tika:Core" /> </antcall> - <!-- step (2) unzip the result --> - <property name="zip-tmp" value="${nbdist.dir}/tmp"/> - <delete dir="${zip-tmp}"/> - <mkdir dir="${zip-tmp}"/> - <unzip src="${nbdist.dir}/${app.name}.zip" dest="${zip-tmp}"/> - - <!-- step (3) add things to the packaged archive --> - - <!-- Copy the Autopsy documentation to the docs folder --> - <copy flatten="true" todir="${zip-tmp}/${app.name}/docs"> - <fileset dir="${basedir}/docs/doxygen-user/user-docs"/> - </copy> - - <!-- Copy Linux/Mac related stuff --> - <copy file="${nbplatform.active.dir}/platform/modules/ext/junit-4.10.jar" - tofile="${zip-tmp}/${app.name}/platform/modules/ext/junit-4.10.jar"/> - <copy file="${basedir}/README.txt" tofile="${zip-tmp}/${app.name}/README.txt"/> - <copy file="${basedir}/LICENSE-2.0.txt" tofile="${zip-tmp}/${app.name}/LICENSE-2.0.txt"/> - <copy file="${basedir}/NEWS.txt" tofile="${zip-tmp}/${app.name}/NEWS.txt"/> - <copy file="${basedir}/Running_Linux_OSX.md" tofile="${zip-tmp}/${app.name}/Running_Linux_OSX.md"/> - <copy file="${basedir}/unix_setup.sh" tofile="${zip-tmp}/${app.name}/unix_setup.sh"/> - <copy flatten="false" todir="${zip-tmp}/${app.name}/linux_macos_install_scripts"> - <fileset dir="${basedir}/linux_macos_install_scripts"/> - </copy> - - <!-- step (4) zip again, but with the version numbers in the name & root folder --> - <zip destfile="${dist.dir}/${app.name}-${app.version}-minimal.zip"> - <zipfileset dir="${zip-tmp}/${app.name}" prefix="${app.name}-${app.version}-minimal"/> - </zip> - <property name="build-minimal-platform-renamed" location="${dist.dir}/${app.name}-${app.version}-minimal.zip" /> - - <!-- step (5) delete temp folder folder --> - <delete dir="${zip-tmp}"/> - <delete file="${nbdist.dir}/${app.name}.zip" quiet="true"/> + <move file="${dist.dir}/${app.name}.zip" tofile="${dist.dir}/${app.name}-minimal.zip"/> + <property name="build-minimal-platform-renamed" location="${dist.dir}/${app.name}-minimal.zip" /> + <echo>File moved to ${build-minimal-platform-renamed}</echo> </target> <target name="build-core-test-libs"> diff --git a/docs/doxygen-user/images/view_options_options_panel.png b/docs/doxygen-user/images/view_options_options_panel.png index 9c83a68e34c625cc77fc36a122d9d550305ae2ad..168ce70cc4d0bd4432f96974aab50da023d4aa1a 100644 Binary files a/docs/doxygen-user/images/view_options_options_panel.png and b/docs/doxygen-user/images/view_options_options_panel.png differ diff --git a/docs/doxygen-user/view_options.dox b/docs/doxygen-user/view_options.dox index 9ad94c915933b26e89517bbdacd4a5e71c846131..270d91950799a2b4eae84a2946e69f7ab939ff0b 100644 --- a/docs/doxygen-user/view_options.dox +++ b/docs/doxygen-user/view_options.dox @@ -76,4 +76,16 @@ The second option ("Group by Person/Host") separates the results for each data s \image html views_grouped_tree.png +\section view_options_session Current Session Settings + +The settings for the current session will be in effect until you close the application. + +\subsection view_options_rejected Hide rejected results + +Accounts can be approved or rejected by the user, as shown in the screenshot below. + +\image html view_options_reject_account.png + +Rejected accounts will not be included in the report, and by default will be hidden in the UI. If you accidentally reject an account and need to change its status, or simply want to view the the rejected accounts, you can uncheck the "hide rejected results" option. + */ \ No newline at end of file diff --git a/thirdparty/ffmpeg/avcodec-59.dll b/thirdparty/ffmpeg/avcodec-59.dll deleted file mode 100755 index 4241f36f291479f6f8917fca031f08e9e16c01ef..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/avcodec-59.dll and /dev/null differ diff --git a/thirdparty/ffmpeg/avdevice-59.dll b/thirdparty/ffmpeg/avdevice-59.dll deleted file mode 100755 index 07f8b8226e5256c5bcaf0e6e3d4f00c498fbc911..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/avdevice-59.dll and /dev/null differ diff --git a/thirdparty/ffmpeg/avfilter-8.dll b/thirdparty/ffmpeg/avfilter-8.dll deleted file mode 100755 index 7aa9015c2067df40295bdfa1d813546acb9e10ae..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/avfilter-8.dll and /dev/null differ diff --git a/thirdparty/ffmpeg/avformat-59.dll b/thirdparty/ffmpeg/avformat-59.dll deleted file mode 100755 index c579d74098c0e63c6cc6b48298ac1ddd946b00b2..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/avformat-59.dll and /dev/null differ diff --git a/thirdparty/ffmpeg/avutil-57.dll b/thirdparty/ffmpeg/avutil-57.dll deleted file mode 100755 index 95fd4168d8a8ec51faa7c0ef3296e276e838f658..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/avutil-57.dll and /dev/null differ diff --git a/thirdparty/ffmpeg/ffmpeg.exe b/thirdparty/ffmpeg/ffmpeg.exe deleted file mode 100755 index 7112dfddde9d696d1343c7f8f1eeb17e04c2b85f..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/ffmpeg.exe and /dev/null differ diff --git a/thirdparty/ffmpeg/postproc-56.dll b/thirdparty/ffmpeg/postproc-56.dll deleted file mode 100755 index 323746c09928bf6248c165c307f7de81e1e44969..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/postproc-56.dll and /dev/null differ diff --git a/thirdparty/ffmpeg/swresample-4.dll b/thirdparty/ffmpeg/swresample-4.dll deleted file mode 100755 index d78700ed59794cdf641c61106d7e6dc61308ea0a..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/swresample-4.dll and /dev/null differ diff --git a/thirdparty/ffmpeg/swscale-6.dll b/thirdparty/ffmpeg/swscale-6.dll deleted file mode 100755 index 71b4fd0522441fadd193cd686ea15b18f00c0470..0000000000000000000000000000000000000000 Binary files a/thirdparty/ffmpeg/swscale-6.dll and /dev/null differ diff --git a/thirdparty/opencv/ext/opencv-3416.jar b/thirdparty/opencv/ext/opencv-3416.jar deleted file mode 100755 index 41b864171c0ab457ebf41830e376640adf416daa..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/ext/opencv-3416.jar and /dev/null differ diff --git a/thirdparty/opencv/lib/amd64/opencv_ffmpeg3416_64.dll b/thirdparty/opencv/lib/amd64/opencv_ffmpeg3416_64.dll deleted file mode 100755 index ed3b7a0f0e2bca311a9655965bead1fcf7db7835..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/amd64/opencv_ffmpeg3416_64.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/amd64/opencv_java3416.dll b/thirdparty/opencv/lib/amd64/opencv_java3416.dll deleted file mode 100755 index 23384ae2d9893d4daa0957112eda94804b1b2513..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/amd64/opencv_java3416.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/i386/opencv_java3416.dll b/thirdparty/opencv/lib/i386/opencv_java3416.dll deleted file mode 100755 index 3fd35050da59fd6d7a676531bc5da642be0d9a62..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/i386/opencv_java3416.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/i586/opencv_java3416.dll b/thirdparty/opencv/lib/i586/opencv_java3416.dll deleted file mode 100755 index 3fd35050da59fd6d7a676531bc5da642be0d9a62..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/i586/opencv_java3416.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/i686/opencv_java3416.dll b/thirdparty/opencv/lib/i686/opencv_java3416.dll deleted file mode 100755 index 3fd35050da59fd6d7a676531bc5da642be0d9a62..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/i686/opencv_java3416.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/x86/opencv_java3416.dll b/thirdparty/opencv/lib/x86/opencv_java3416.dll deleted file mode 100755 index 3fd35050da59fd6d7a676531bc5da642be0d9a62..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/x86/opencv_java3416.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_ffmpeg3416_64.dll b/thirdparty/opencv/lib/x86_64/opencv_ffmpeg3416_64.dll deleted file mode 100755 index ed3b7a0f0e2bca311a9655965bead1fcf7db7835..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/x86_64/opencv_ffmpeg3416_64.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_java3416.dll b/thirdparty/opencv/lib/x86_64/opencv_java3416.dll deleted file mode 100755 index 23384ae2d9893d4daa0957112eda94804b1b2513..0000000000000000000000000000000000000000 Binary files a/thirdparty/opencv/lib/x86_64/opencv_java3416.dll and /dev/null differ diff --git a/thunderbirdparser/nbproject/project.properties b/thunderbirdparser/nbproject/project.properties index c349696cd41aa6ba16d82bcdd31442645f150899..8d13059ed61c4f5369e87cf29387a1e7fd20aa08 100644 --- a/thunderbirdparser/nbproject/project.properties +++ b/thunderbirdparser/nbproject/project.properties @@ -5,7 +5,6 @@ file.reference.jackson-core-2.15.2.jar=release/modules/ext/jackson-core-2.15.2.j file.reference.java-libpst-0.9.5-SNAPSHOT.jar=release/modules/ext/java-libpst-0.9.5-SNAPSHOT.jar file.reference.jsoup-1.16.1.jar=release/modules/ext/jsoup-1.16.1.jar file.reference.vinnie-2.0.2.jar=release/modules/ext/vinnie-2.0.2.jar -file.reference.commons-io-2.5.jar=release/modules/ext/commons-io-2.5.jar javac.source=17 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java index d9e7274b9ea95eb3f831cab36b6f97b24f616f9b..ba5b24842fd2cf8332aae91ff9d77775d8ab9681 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.UUID; import java.util.logging.Level; -import org.apache.commons.io.FilenameUtils; import org.apache.james.mime4j.dom.Body; import org.apache.james.mime4j.dom.Entity; import org.apache.james.mime4j.dom.Message; @@ -137,7 +136,7 @@ EmailMessage extractEmail(Message msg, String localPath, long sourceFileID) { email.setCc(getAddresses(msg.getCc())); email.setSubject(msg.getSubject()); email.setSentDate(msg.getDate()); - email.setLocalPath("\\" + FilenameUtils.getBaseName(localPath).replaceAll("\\\\", "/")); + email.setLocalPath(localPath); email.setMessageID(msg.getMessageId()); Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java index c8b34b18be9dbc63f7aa5921b46e52e8bcec603c..203bbeb9301087363a7b0742ed85aa42adf6f218 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java @@ -35,7 +35,6 @@ import java.util.List; import java.util.Scanner; import java.util.logging.Level; -import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -152,7 +151,7 @@ Iterator<EmailMessage> getEmailMessageIterator() { Iterable<EmailMessage> iterable = null; try { - iterable = getEmailMessageIterator(pstFile.getRootFolder(), "", fileID, true); + iterable = getEmailMessageIterator(pstFile.getRootFolder(), "\\", fileID, true); } catch (PSTException | IOException ex) { logger.log(Level.WARNING, String.format("Exception thrown while parsing fileID: %d", fileID), ex); } @@ -255,7 +254,7 @@ private Iterable<EmailMessage> getEmailMessageIterator(PSTFolder folder, String if (folder.hasSubfolders()) { List<PSTFolder> subFolders = folder.getSubFolders(); for (PSTFolder subFolder : subFolders) { - String newpath = path + "\\" + subFolder.getDisplayName().replaceAll("\\\\", "/"); + String newpath = path + "\\" + subFolder.getDisplayName(); Iterable<EmailMessage> subIterable = getEmailMessageIterator(subFolder, newpath, fileID, wholeMsg); if (subIterable == null) { continue;