Merge pull request #7820 from gdicristofaro/0718_ntl

7/18/23 ntl merge
This commit is contained in:
eugene7646 2023-07-18 22:14:12 -04:00 committed by GitHub
commit 07b9635081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
314 changed files with 30138 additions and 17161 deletions

View File

@ -116,6 +116,10 @@
<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>

View File

@ -356,9 +356,11 @@
<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>

View File

@ -32,6 +32,15 @@ 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
@ -84,6 +93,7 @@ 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}.

View File

@ -0,0 +1,102 @@
/*
* 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());
}
}
}
}

View File

@ -0,0 +1,62 @@
/*
* 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));
}
}
}

View File

@ -35,6 +35,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
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;
/**
@ -50,12 +51,15 @@ public class ViewArtifactAction extends AbstractAction {
@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 {
DirectoryTreeTopComponent.findInstance().viewArtifact(artifact);
comp.viewArtifact(artifact);
return null;
}

View File

@ -52,11 +52,12 @@ public class ViewOsAccountAction extends AbstractAction {
@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 {
DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount);
topComp.viewOsAccount(osAccount);
return null;
}

View File

@ -76,6 +76,7 @@ import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
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;
@ -507,7 +508,9 @@ public class Case {
/**
* One or more TagSets have been removed.
*/
TAG_SETS_DELETED;
TAG_SETS_DELETED,
ANALYSIS_RESULT_DELETED;
};
@ -628,17 +631,17 @@ public class Case {
}
@Subscribe
public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) {
public void publishHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) {
eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds()));
}
@Subscribe
public void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) {
public void publishTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) {
eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames()));
}
@Subscribe
public void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) {
public void publishTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) {
eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames()));
}
@ -648,14 +651,51 @@ public class Case {
}
@Subscribe
public void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) {
public void publishTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) {
eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets()));
}
@Subscribe
public void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) {
public void publishTagSetsDeleted(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));
}
}
}
/**
@ -1805,41 +1845,6 @@ public class Case {
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.
*
@ -1870,41 +1875,6 @@ public class Case {
}
}
/**
* 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.
*

View File

@ -0,0 +1,31 @@
/*
* 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);
}
}

View File

@ -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 class TagsManager implements Closeable {
}
}
};
private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null);
static {
@ -300,12 +300,12 @@ public class TagsManager implements Closeable {
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 class TagsManager implements Closeable {
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 class TagsManager implements Closeable {
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 class TagsManager implements Closeable {
* @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 class TagsManager implements Closeable {
* @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,14 +636,6 @@ public class TagsManager implements Closeable {
*/
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();
}
@ -657,11 +649,6 @@ public class TagsManager implements Closeable {
*/
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);
}
}
/**
@ -857,12 +844,6 @@ public class TagsManager implements Closeable {
*/
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();
}
@ -876,11 +857,6 @@ public class TagsManager implements Closeable {
*/
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);
}
}
/**

View File

@ -28,6 +28,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUti
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
@ -39,19 +40,23 @@ public class CentralRepoContextMenuActionsProvider implements ContextMenuActions
@Override
public List<Action> getActions() {
ArrayList<Action> actionsList = new ArrayList<>();
Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
Collection<? extends DataArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(DataArtifact.class);
if(artifacts.isEmpty()) {
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);
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);
}
actionsList.add(action);
}
}

View File

@ -30,11 +30,11 @@ import org.sleuthkit.autopsy.coreutils.Version;
public class CentralRepositoryNotificationDialog {
/**
* This dialog should display iff the mode is RELEASE and the
* application is running with a GUI.
* This dialog should display if 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();
}

View File

@ -178,7 +178,12 @@ public class Installer extends ModuleInstall {
return; // Nothing to do
}
if (CentralRepositoryNotificationDialog.shouldDisplay()) {
// 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()) {
CentralRepositoryNotificationDialog.display();
}

View File

@ -300,7 +300,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
}
Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata, correlationType);
DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this));
TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3);
CommonAttributeTableFilterNode tableFilterWithDescendantsNode = new CommonAttributeTableFilterNode(dataResultFilterNode, 3);
DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable();
Collection<DataResultViewer> viewers = new ArrayList<>(1);
viewers.add(table);
@ -402,7 +402,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
// -3969
Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata);
DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this));
TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3);
CommonAttributeTableFilterNode tableFilterWithDescendantsNode = new CommonAttributeTableFilterNode(dataResultFilterNode, 3);
DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable();
Collection<DataResultViewer> viewers = new ArrayList<>(1);
viewers.add(table);

View File

@ -0,0 +1,34 @@
/*
* 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);
}
}

View File

@ -25,7 +25,7 @@ import java.util.TimeZone;
import javax.swing.table.TableCellRenderer;
import org.netbeans.swing.outline.Outline;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
import org.sleuthkit.datamodel.Account;
/**
@ -48,7 +48,7 @@ public final class Utils {
* @return The path of the icon for the given Account Type.
*/
static public final String getIconFilePath(Account.Type type) {
return Accounts.getIconFilePath(type);
return IconsUtil.getIconFilePath(type);
}
static public void setColumnWidths(Outline outline) {

View File

@ -85,7 +85,7 @@ MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
HtmlPanel.showImagesToggleButton.text=Download Images
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
MediaViewImagePanel.tagsMenu.text_1=Image Tagging
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
MediaPlayerPanel.audioSlider.toolTipText=
MediaPlayerPanel.rewindButton.text=
@ -940,3 +940,5 @@ 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

View File

@ -60,8 +60,13 @@ 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
@ -151,7 +156,7 @@ MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
HtmlPanel.showImagesToggleButton.text=Download Images
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
MediaViewImagePanel.tagsMenu.text_1=Image Tagging
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
MediaPlayerPanel.audioSlider.toolTipText=
MediaPlayerPanel.rewindButton.text=
@ -953,3 +958,5 @@ 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

View File

@ -221,17 +221,14 @@
<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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View File

@ -121,6 +121,8 @@ 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.
@ -208,7 +210,13 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
"MediaViewImagePanel.createTagOption=Create",
"MediaViewImagePanel.deleteTagOption=Delete",
"MediaViewImagePanel.hideTagOption=Hide",
"MediaViewImagePanel.exportTagOption=Export"
"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() {
initComponents();
@ -222,6 +230,7 @@ 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());
@ -229,20 +238,23 @@ 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(300, 150);
imageTaggingOptions.setPopupSize(TAG_POPUP_WIDTH, 150);
//Disable image tagging for non-windows users or upon failure to load OpenCV.
if (!PlatformUtil.isWindowsOS() || !OpenCvLoader.openCvIsLoaded()) {
@ -457,10 +469,14 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
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.
@ -470,6 +486,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
}
readImageFileTask = ImageUtils.newReadImageTask(file);
readImageFileTask.setOnSucceeded(succeeded -> {
final double panelWidth = fxPanel.getWidth();
final double panelHeight = fxPanel.getHeight();
onReadImageTaskSucceeded(file, panelWidth, panelHeight);
});
readImageFileTask.setOnFailed(failed -> {
@ -536,6 +554,9 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
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
@ -790,11 +811,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
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) {
@ -1083,7 +1103,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
FilenameUtils.getBaseName(file.getName()) + "-with_tags.png"); //NON-NLS
ImageIO.write(taggedImage, "png", output.toFile());
JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport());
JOptionPane.showMessageDialog(MediaViewImagePanel.this, 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
@ -1099,7 +1119,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tagsMenuMousePressed
if (imageTaggingOptions.isEnabled()) {
imageTaggingOptions.show(tagsMenu, -300 + tagsMenu.getWidth(), tagsMenu.getHeight() + 3);
imageTaggingOptions.show(tagsMenu, -TAG_POPUP_WIDTH + tagsMenu.getWidth(), tagsMenu.getHeight() + 3);
}
}//GEN-LAST:event_tagsMenuMousePressed
@ -1107,8 +1127,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
* Display states for the show/hide tags button.
*/
private enum DisplayOptions {
HIDE_TAGS("Hide"),
SHOW_TAGS("Show");
HIDE_TAGS(Bundle.MediaViewImagePanel_hideTagOption()),
SHOW_TAGS(Bundle.MediaViewImagePanel_showTagOption());
private final String name;

View File

@ -29,13 +29,12 @@ import javafx.concurrent.Task;
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.highgui.Highgui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.datamodel.AbstractFile;
@ -124,7 +123,7 @@ public final class ImageTagsUtil {
byte[] imageBytes = outStream.toByteArray();
MatOfByte rawSourceBytes = new MatOfByte(imageBytes);
Mat sourceImage = Highgui.imdecode(rawSourceBytes, Highgui.IMREAD_COLOR);
Mat sourceImage = Imgcodecs.imdecode(rawSourceBytes, Imgcodecs.IMREAD_COLOR);
rawSourceBytes.release();
return sourceImage;
@ -150,12 +149,12 @@ public final class ImageTagsUtil {
int rectangleBorderWidth = (int) Math.rint(region.getStrokeThickness());
Core.rectangle(sourceImage, topLeft, bottomRight,
Imgproc.rectangle(sourceImage, topLeft, bottomRight,
rectangleBorderColor, rectangleBorderWidth);
}
MatOfByte taggedMatrix = new MatOfByte();
Highgui.imencode(OPENCV_PNG, sourceImage, taggedMatrix);
Imgcodecs.imencode(OPENCV_PNG, sourceImage, taggedMatrix);
return taggedMatrix;
}
@ -200,13 +199,13 @@ public final class ImageTagsUtil {
*/
private static MatOfByte getResizedMatrix(MatOfByte taggedMatrix, IconSize size) {
Size resizeDimensions = new Size(size.getSize(), size.getSize());
Mat taggedImage = Highgui.imdecode(taggedMatrix, Highgui.IMREAD_COLOR);
Mat taggedImage = Imgcodecs.imdecode(taggedMatrix, Imgcodecs.IMREAD_COLOR);
Mat thumbnailImage = new Mat();
Imgproc.resize(taggedImage, thumbnailImage, resizeDimensions);
MatOfByte thumbnailMatrix = new MatOfByte();
Highgui.imencode(OPENCV_PNG, thumbnailImage, thumbnailMatrix);
Imgcodecs.imencode(OPENCV_PNG, thumbnailImage, thumbnailMatrix);
thumbnailImage.release();
taggedImage.release();

View File

@ -4,6 +4,7 @@ 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

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2017 Basis Technology Corp.
* Copyright 2013-2022 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -49,7 +49,7 @@ public class RuntimeProperties {
/**
* 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 innvocation
* The flag can only be set once per application invocation
*
* @param runningInTarget
*

View File

@ -764,7 +764,7 @@ public final class UserPreferences {
* @return Saved value or default (10,000).
*/
public static int getResultsTablePageSize() {
return viewPreferences.getInt(RESULTS_TABLE_PAGE_SIZE, 10_000);
return viewPreferences.getInt(RESULTS_TABLE_PAGE_SIZE, 1000);
}
/**

View File

@ -18,8 +18,11 @@
*/
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
@ -75,6 +78,18 @@ 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
@ -113,6 +128,16 @@ public interface DataResultViewer {
*/
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

View File

@ -34,16 +34,9 @@ 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
@ -133,8 +126,6 @@ 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
@ -166,14 +157,7 @@ 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
@ -255,3 +239,18 @@ 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=

View File

@ -35,6 +35,9 @@ 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)
@ -45,12 +48,6 @@ 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
@ -98,16 +95,9 @@ 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
@ -199,8 +189,6 @@ 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
@ -232,14 +220,7 @@ 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
@ -321,3 +302,18 @@ 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=

View File

@ -274,7 +274,6 @@ 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\:
@ -283,7 +282,6 @@ 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
@ -302,3 +300,10 @@ 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=

View File

@ -19,47 +19,31 @@
<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>
<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>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<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, &quot;{key}&quot;)"/>
</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="[5, 14]"/>
<Dimension value="[50, 14]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 16]"/>
</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>
@ -67,6 +51,11 @@
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.numberOfChildNodesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</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>
@ -74,6 +63,186 @@
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.matchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>
@ -81,8 +250,48 @@
<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>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2019 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");
@ -38,7 +38,26 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
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
@ -360,6 +379,184 @@ public final class DataResultTopComponent extends TopComponent implements DataRe
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);
@ -463,5 +660,4 @@ public final class DataResultTopComponent extends TopComponent implements DataRe
public void resetTabs(Node node) {
dataResultPanel.setNode(node);
}
}

View File

@ -17,21 +17,7 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="outlineView" pref="904" max="32767" 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"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="32767" attributes="0"/>
<Component id="exportCSVButton" min="-2" max="-2" attributes="0"/>
</Group>
@ -41,16 +27,7 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="-2" pref="3" 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>
<Component id="exportCSVButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
<Component id="outlineView" pref="321" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
@ -59,86 +36,6 @@
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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);"/>
@ -146,26 +43,6 @@
<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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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">

View File

@ -18,14 +18,11 @@
*/
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;
@ -37,12 +34,10 @@ import java.util.Map;
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;
@ -67,25 +62,18 @@ import org.openide.nodes.AbstractNode;
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.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.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo;
import org.sleuthkit.autopsy.mainui.nodes.SearchResultRootNode;
import org.sleuthkit.datamodel.Score.Significance;
/**
@ -141,18 +129,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
private final TableListener outlineViewListener;
private final IconRendererTableListener iconRendererListener;
private Node rootNode;
/**
* 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;
private SearchResultsDTO searchResults;
private DataResultPanel.PagingControls pagingControls = null;
/**
* Constructs a tabular result viewer that displays the children of the
@ -199,8 +177,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
initComponents();
initializePagingSupport();
/*
* Disable the CSV export button for the common properties results
*/
@ -236,32 +212,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
outline.getTableHeader().addMouseListener(outlineViewListener);
}
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);
}
});
@Override
public void setPagingControls(DataResultPanel.PagingControls pagingControls) {
this.pagingControls = pagingControls;
}
/**
@ -302,6 +255,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
return true;
}
@Override
public void setNode(Node rootNode) {
setNode(rootNode, null);
}
/**
* Sets the current root node of this tabular result viewer.
*
@ -309,7 +267,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
@Override
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void setNode(Node rootNode) {
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);
}
if (!SwingUtilities.isEventDispatchThread()) {
LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread");
return;
@ -324,69 +289,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
outline.unsetQuickFilter();
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
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
}
});
}
}
this.rootNode = rootNode;
/*
* 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 (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
if ((searchResults != null && searchResults.getTotalResultsCount() > 0)
|| (rootNode != null && rootNode.getChildren().getNodesCount() > 0)) {
this.getExplorerManager().setRootContext(this.rootNode);
outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
setupTable();
@ -446,6 +358,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
}
/*
* Load column sorting information from preferences file and apply it to
* columns.
@ -484,8 +397,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* If one of the child nodes of the root node is to be selected, select
* it.
*/
if (rootNode instanceof TableFilterNode) {
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
if (rootNode instanceof SearchResultRootNode) {
ChildNodeSelectionInfo selectedChildInfo = ((SearchResultRootNode)rootNode).getNodeSelectionInfo();
if (null != selectedChildInfo) {
Node[] childNodes = rootNode.getChildren().getNodes(true);
for (int i = 0; i < childNodes.length; ++i) {
@ -502,7 +415,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
break;
}
}
((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
// Once it is selected clear the id.
((SearchResultRootNode) rootNode).setNodeSelectionInfo(null);
}
}
@ -624,13 +538,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) rootNode;
if (rootNode instanceof TableFilterNode || searchResults != null) {
TableFilterNode tfn = searchResults == null ? (TableFilterNode) rootNode : null;
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 = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
final String columnHiddenKey =
tfn != null ? ResultViewerPersistence.getColumnHiddenKey(tfn, columnName) :
ResultViewerPersistence.getColumnHiddenKey(searchResults, columnName);
final TableColumn column = entry.getValue();
boolean columnHidden = columnModel.isColumnHidden(column);
if (columnHidden) {
@ -650,12 +566,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) rootNode;
if (rootNode instanceof TableFilterNode || searchResults != null) {
TableFilterNode tfn = searchResults == null ? (TableFilterNode) rootNode : null;
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(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
preferences.putInt(tfn != null ?
ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()) :
ResultViewerPersistence.getColumnPositionKey(searchResults, entry.getValue().getName()), entry.getKey());
}
}
}
@ -667,16 +585,20 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (rootNode instanceof TableFilterNode) {
final TableFilterNode tfn = ((TableFilterNode) rootNode);
if (rootNode instanceof TableFilterNode || searchResults != null) {
final TableFilterNode tfn = searchResults == null ? ((TableFilterNode) rootNode) : null;
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 = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
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);
if (etc.isSorted() && (columnModel.isColumnHidden(etc) == false)) {
preferences.putBoolean(columnSortOrderKey, etc.isAscending());
preferences.putInt(columnSortRankKey, etc.getSortRank());
@ -686,7 +608,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
preferences.remove(columnSortRankKey);
}
}
}
}
}
/**
@ -699,17 +621,23 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (rootNode instanceof TableFilterNode) {
final TableFilterNode tfn = (TableFilterNode) rootNode;
if (rootNode instanceof TableFilterNode || searchResults != null) {
final TableFilterNode tfn = (searchResults == null ? (TableFilterNode) rootNode : null);
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(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
Integer sortRank = preferences.getInt(
tfn != null ?
ResultViewerPersistence.getColumnSortRankKey(tfn, propName) :
ResultViewerPersistence.getColumnSortRankKey(searchResults, propName), 0);
//default to true => ascending
Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
Boolean sortOrder = preferences.getBoolean(
tfn != null ?
ResultViewerPersistence.getColumnSortOrderKey(tfn, propName) :
ResultViewerPersistence.getColumnSortOrderKey(searchResults, propName), true);
sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
});
//apply sort information in rank order.
@ -725,13 +653,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
if (rootNode == null || propertiesMap.isEmpty()) {
return;
}
if (rootNode instanceof TableFilterNode) {
if (rootNode instanceof TableFilterNode || searchResults != null) {
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final TableFilterNode tfn = ((TableFilterNode) rootNode);
final TableFilterNode tfn = (searchResults == null ? ((TableFilterNode) rootNode) : null);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
final String propName = entry.getValue().getName();
boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false);
boolean hidden = preferences.getBoolean(
tfn != null ?
ResultViewerPersistence.getColumnHiddenKey(tfn, propName) :
ResultViewerPersistence.getColumnHiddenKey(searchResults, propName), false);
final TableColumn column = columnMap.get(propName);
columnModel.setColumnHidden(column, hidden);
}
@ -748,6 +679,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
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
@ -788,6 +723,51 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
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
@ -851,155 +831,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
/**
* 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
@ -1383,62 +1214,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
// <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) {
@ -1452,61 +1230,20 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.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)
.addContainerGap(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)
.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))
.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
@ -1520,14 +1257,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
// 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
}

View File

@ -21,12 +21,10 @@ package org.sleuthkit.autopsy.corecomponents;
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;
@ -34,7 +32,7 @@ import java.util.stream.Collectors;
import javax.swing.JOptionPane;
import javax.swing.ListSelectionModel;
import javax.swing.SortOrder;
import javax.swing.SwingWorker;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.StringUtils;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.DialogDescriptor;
@ -56,6 +54,7 @@ import static org.sleuthkit.autopsy.corecomponents.Bundle.*;
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;
@ -85,6 +84,7 @@ 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,6 +129,11 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
// 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.
@ -450,8 +455,19 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
return (selectedNode != null);
}
@Override
@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);
}
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (selectionListener == null) {
this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener());
@ -467,7 +483,11 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
// case where the DataResultViewerThumbnail stands along from the
// DataResultViewer. See DataResultViewer setNode for more information.
if (givenNode != null && givenNode.getChildren().getNodesCount() > 0) {
rootNode = (TableFilterNode) givenNode;
rootNode = (givenNode instanceof TableFilterNode)
? (TableFilterNode) givenNode
: new TableFilterNode(givenNode, true);
/*
* Wrap the given node in a ThumbnailViewChildren that will
* produce ThumbnailPageNodes with ThumbnailViewNode children
@ -521,6 +541,16 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
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();
}
}
}
@ -552,56 +582,39 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
switchPage();
}
private void switchPage() {
EventQueue.invokeLater(() -> {
private void switchPage() {
SwingUtilities.invokeLater(() -> {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
});
//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 {
try {
pagePrevButton.setEnabled(false);
pageNextButton.setEnabled(false);
goToPageField.setEnabled(false);
progress = ProgressHandle.createHandle(
ProgressHandle 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);
explorerManager.setExploredContext(pageNode);
currentPageImages = pageNode.getChildren().getNodesCount();
return null;
}
@Override
protected void done() {
if (pageNode != null) {
explorerManager.setExploredContext(pageNode);
currentPageImages = pageNode.getChildren().getNodesCount();
}
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({
@ -624,8 +637,10 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
imagesRangeLabel.setText(imagesFrom + "-" + imagesTo);
pageNextButton.setEnabled(!(currentPage == totalPages));
// enable "next page" button if there is either next thumbnail subpage or next top level page
pageNextButton.setEnabled(!(currentPage == totalPages && pagingControls.getCurrentPage() == pagingControls.getTotalPages()));
pagePrevButton.setEnabled(!(currentPage == 1));
goToPageField.setEnabled(totalPages > 1);
sortButton.setEnabled(true);

View File

@ -28,6 +28,7 @@ import javax.swing.SortOrder;
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 {
@ -46,6 +47,10 @@ final class 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
@ -59,6 +64,10 @@ final class ResultViewerPersistence {
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
@ -72,6 +81,10 @@ final class ResultViewerPersistence {
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
@ -85,10 +98,18 @@ final class ResultViewerPersistence {
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_]", "");

View File

@ -1,37 +1,33 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
*
* 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;
;
package org.sleuthkit.autopsy.corecomponents;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
/**
* AutopsyVisitableItems are the nodes in the directory tree that are for
* structure only. They are not associated with content objects.
* Interface for Nodes that can respond to a tree selection event.
*/
public interface AutopsyVisitableItem {
public interface SelectionResponder {
/**
* visitor pattern support
*
* @param visitor visitor
*
* @return visitor return value
* Method to be called on tree nodes that can handle selection.
*
* @param dataResultPanel
*/
public <T> T accept(AutopsyItemVisitor<T> visitor);
void respondSelection(DataResultTopComponent dataResultPanel);
}

View File

@ -66,7 +66,6 @@
<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>
@ -80,9 +79,7 @@
<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 type="unrelated" max="-2" attributes="0"/>
<Component id="currentSessionSettingsPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -148,7 +145,7 @@
</Group>
</Group>
</Group>
<EmptySpace pref="94" max="32767" attributes="0"/>
<EmptySpace pref="107" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -425,8 +422,8 @@
</Component>
<Component class="javax.swing.JSpinner" name="maxResultsSpinner">
<Properties>
<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 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>
</Properties>
<Events>
@ -499,49 +496,6 @@
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View File

@ -38,6 +38,9 @@ import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
@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;
/**
@ -104,9 +107,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
radioGroupByDataType.setEnabled(false);
radioGroupByPersonHost.setEnabled(false);
}
// Current Session Settings
hideRejectedResultsCheckbox.setSelected(DirectoryTreeTopComponent.getDefault().getShowRejectedResults() == false);
}
@Override
@ -126,8 +126,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
UserPreferences.setResultsTablePageSize((int) maxResultsSpinner.getValue());
storeGroupItemsInTreeByDataSource();
DirectoryTreeTopComponent.getDefault().setShowRejectedResults(hideRejectedResultsCheckbox.isSelected() == false);
}
/**
@ -184,8 +182,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
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));
@ -306,7 +302,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
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, 50000, 10000));
maxResultsSpinner.setModel(new javax.swing.SpinnerNumberModel(0, 0, PAGE_SIZE_MAX, PAGE_SIZE_INTERVAL));
maxResultsSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
maxResultsSpinnerStateChanged(evt);
@ -354,7 +350,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.addComponent(displayTimeLabel)
.addComponent(selectFileLabel)
.addComponent(translateTextLabel))))
.addContainerGap(94, Short.MAX_VALUE))
.addContainerGap(107, Short.MAX_VALUE))
);
globalSettingsPanelLayout.setVerticalGroup(
globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -446,31 +442,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.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(
@ -478,7 +449,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.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())
@ -489,9 +459,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.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)
.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))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
viewPreferencesScrollPane.setViewportView(viewPreferencesPanel);
@ -508,14 +476,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
);
}// </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());
@ -650,7 +610,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
// 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;
@ -659,7 +618,6 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
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;

View File

@ -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_ffmpeg2413_64"); //NON-NLS
System.loadLibrary("opencv_ffmpeg3416_64"); //NON-NLS
} else {
System.loadLibrary("opencv_ffmpeg2413"); //NON-NLS
System.loadLibrary("opencv_ffmpeg3416"); //NON-NLS
}
tempFfmpegLoaded = true;
} catch (UnsatisfiedLinkError e) {

View File

@ -20,8 +20,14 @@ package org.sleuthkit.autopsy.coreutils;
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;
@ -31,7 +37,13 @@ import java.util.TreeSet;
import java.util.logging.Level;
import org.netbeans.api.progress.ProgressHandle;
import org.opencv.core.Mat;
import org.opencv.highgui.VideoCapture;
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.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -45,6 +57,9 @@ import org.sleuthkit.datamodel.AbstractFile;
*/
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
@ -83,8 +98,8 @@ public class VideoUtils {
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());
@ -111,10 +126,10 @@ public class VideoUtils {
/**
* 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.
@ -161,7 +176,7 @@ public class VideoUtils {
if (Thread.interrupted()) {
return null;
}
double duration = 1000 * (totalFrames / fps); //total milliseconds
/*
@ -172,19 +187,18 @@ public class VideoUtils {
* 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.
@ -196,15 +210,15 @@ public class VideoUtils {
// 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();
@ -235,6 +249,280 @@ public class VideoUtils {
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.
@ -254,5 +542,5 @@ public class VideoUtils {
throw new IllegalStateException(ex);
}
}
}

View File

@ -50,8 +50,6 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStat
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;
@ -65,6 +63,8 @@ import org.sleuthkit.datamodel.Tag;
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;

View File

@ -20,26 +20,19 @@ package org.sleuthkit.autopsy.datamodel;
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) {
if (key instanceof SleuthkitVisitableItem) {
return ((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor);
} else {
return null;
}
return RootContentChildren.createNode(key);
}
}

View File

@ -36,8 +36,11 @@ import org.openide.util.NbBundle.Messages;
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;
@ -53,7 +56,7 @@ import org.sleuthkit.datamodel.TskException;
*
* @param <T> type of wrapped Content
*/
public abstract class AbstractContentNode<T extends Content> extends ContentNode {
public abstract class AbstractContentNode<T extends Content> extends ContentNode implements SelectionResponder {
/**
* Underlying Sleuth Kit Content object
@ -115,6 +118,11 @@ public abstract class AbstractContentNode<T extends Content> extends ContentNode
//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

View File

@ -36,7 +36,7 @@ public class AnalysisResultItem extends BlackboardArtifactItem<AnalysisResult> {
* @param sourceContent The source content of the AnalysisResult.
*/
@Beta
AnalysisResultItem(AnalysisResult analysisResult, Content sourceContent) {
public AnalysisResultItem(AnalysisResult analysisResult, Content sourceContent) {
super(analysisResult, sourceContent);
}

View File

@ -1,95 +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.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;
}
}

View File

@ -1,56 +0,0 @@
/*
* 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;
}
}

View File

@ -1,204 +0,0 @@
/*
* 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(" ", "&nbsp;"); //NON-NLS
str = str.replaceAll("<", "&lt;"); //NON-NLS
str = str.replaceAll(">", "&gt;"); //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;
}
}

View File

@ -1,738 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,252 +0,0 @@
/*
* 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);
}
}
}

View File

@ -1,200 +0,0 @@
/*
* 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);
}
}

View File

@ -23,6 +23,7 @@ import com.google.common.eventbus.EventBus;
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;
@ -33,6 +34,7 @@ import java.util.prefs.PreferenceChangeEvent;
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;
@ -57,6 +59,14 @@ 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}"

View File

@ -1391,11 +1391,6 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
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",

View File

@ -1,189 +0,0 @@
/*
* 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();
}
}

View File

@ -35,12 +35,6 @@ 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
@ -102,55 +96,12 @@ 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
@ -170,30 +121,6 @@ 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
@ -346,38 +273,6 @@ 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
@ -412,13 +307,6 @@ 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
@ -428,8 +316,6 @@ 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

View File

@ -36,13 +36,4 @@ abstract class ContentNode extends DisplayableItemNode {
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);
}

View File

@ -1,56 +0,0 @@
/*
* 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);
}
}

View File

@ -1,148 +0,0 @@
/*
* 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);
}
}
}

View File

@ -1,160 +0,0 @@
/*
* 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();
}
}

View File

@ -1,120 +0,0 @@
/*
* 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);
}
}

View File

@ -36,7 +36,7 @@ public class DataArtifactItem extends BlackboardArtifactItem<DataArtifact> {
* @param sourceContent The source content of the DataArtifact.
*/
@Beta
DataArtifactItem(DataArtifact dataArtifact, Content sourceContent) {
public DataArtifactItem(DataArtifact dataArtifact, Content sourceContent) {
super(dataArtifact, sourceContent);
}

View File

@ -1,95 +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.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;
}
}

View File

@ -25,19 +25,12 @@ import java.util.List;
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;
@ -46,9 +39,7 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
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;
@ -356,112 +347,6 @@ public class DataModelActionsFactory {
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);

View File

@ -1,182 +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.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;
}
}

View File

@ -1,104 +0,0 @@
/*
* 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();
}
}

View File

@ -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.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;
}
}

View File

@ -1,519 +0,0 @@
/*
* 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());
}
});
}
}
}
}

View File

@ -1,108 +0,0 @@
/*
* 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);
}
}

View File

@ -105,11 +105,6 @@ public class DirectoryNode extends AbstractFsContentNode<AbstractFile> {
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);

View File

@ -25,15 +25,7 @@ import org.sleuthkit.autopsy.commonpropertiessearch.InstanceCaseNode;
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
@ -44,8 +36,6 @@ public interface DisplayableItemNodeVisitor<T> {
/*
* Data Sources Area
*/
T visit(DataSourceFilesNode in);
T visit(LayoutFileNode lfn);
T visit(LocalFileNode dfn);
@ -70,58 +60,8 @@ 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);
@ -143,48 +83,10 @@ 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);
/*
@ -192,22 +94,13 @@ 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
@ -301,161 +194,11 @@ public interface DisplayableItemNodeVisitor<T> {
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);
@ -481,106 +224,11 @@ public interface DisplayableItemNodeVisitor<T> {
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);

View File

@ -1,576 +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.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
}
}
}

View File

@ -198,18 +198,6 @@ public class FileNode extends AbstractFsContentNode<AbstractFile> {
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.

View File

@ -1,533 +0,0 @@
/*
* 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()));
}
});
}
}
}
}

View File

@ -1,500 +0,0 @@
/*
* 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();
}
}
}

View File

@ -1,678 +0,0 @@
/*
* 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();
}
}

View File

@ -1,515 +0,0 @@
/*
* 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
}
}
}

View File

@ -1,440 +0,0 @@
/*
* 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();
}
}
}

View File

@ -1,95 +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.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);
}
}

View File

@ -1,95 +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.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);
}
}

View File

@ -1,327 +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.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()]);
}
}

View File

@ -172,11 +172,6 @@ public class ImageNode extends AbstractContentNode<Image> {
return sheet;
}
@Override
public <T> T accept(ContentNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isLeafTypeNode() {
return false;

View File

@ -1,477 +0,0 @@
/*
* 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
}
}
}

View File

@ -1,976 +0,0 @@
/*
* 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);
}
}
}

View File

@ -49,7 +49,7 @@ import org.sleuthkit.datamodel.TskData;
* Node for layout file
*/
public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
private static final Logger logger = Logger.getLogger(LayoutFileNode.class.getName());
@Deprecated
@ -85,10 +85,6 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
}
}
public <T> T accept(ContentNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isLeafTypeNode() {
return false;
@ -106,7 +102,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
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

View File

@ -37,9 +37,6 @@ public class LocalDirectoryNode extends SpecialDirectoryNode {
}
public <T> T accept(ContentNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {

View File

@ -100,16 +100,11 @@ public class LocalFileNode extends AbstractAbstractFileNode<AbstractFile> {
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);
return actionsList.toArray(new Action[actionsList.size()]);
}
@Override

View File

@ -87,11 +87,6 @@ public class LocalFilesDataSourceNode extends VirtualDirectoryNode {
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);

View File

@ -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;

View File

@ -1,628 +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.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;
}
}
}
}

View File

@ -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.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);
}
}

View File

@ -1,267 +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.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
};
}
}
}

View File

@ -106,11 +106,6 @@ public class PoolNode extends AbstractContentNode<Pool> {
return sheet;
}
@Override
public <T> T accept(ContentNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isLeafTypeNode() {
return false;

View File

@ -1,99 +0,0 @@
/*
* 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;
}
}

View File

@ -1,97 +0,0 @@
/*
* 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;
}
}

View File

@ -1,145 +0,0 @@
/*
* 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()));
}
});
}
}

View File

@ -1,94 +0,0 @@
/*
* 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();
}
}
}

View File

@ -1,71 +0,0 @@
/*
* 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();
}
}

View File

@ -1,306 +0,0 @@
/*
* 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));
}
}
}
}
}

View File

@ -18,200 +18,79 @@
*/
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.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;
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;
/**
* Children implementation for the root node of a ContentNode tree. Accepts a
* list of root Content objects for the tree.
* Produces legacy version of content nodes.
*/
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();
public class RootContentChildren {
/**
* @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.
* Creates a node for one of the known object keys that is not a sleuthkit
* item.
*
* TODO ideally, nodes would respond to event from wrapped content object
* but we are not ready for this.
* @param key The node key.
*
* @return The generated node or null if no match found.
*/
public void refreshContentKeys() {
contentKeys.forEach(this::refreshKey);
}
@Override
protected Node[] createNodes(Object key) {
if (key instanceof AutopsyVisitableItem) {
return new Node[]{((AutopsyVisitableItem) key).accept(createAutopsyNodeVisitor)};
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);
} else {
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());
return null;
}
}
}

View File

@ -1,812 +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.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();
}
}
}
}

View File

@ -100,11 +100,6 @@ public class SlackFileNode extends AbstractFsContentNode<AbstractFile> {
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);

Some files were not shown because too many files have changed in this diff Show More