Merge branch 'develop' of github.com:sleuthkit/autopsy into 6827-excelTranslations

This commit is contained in:
Greg DiCristofaro 2020-09-21 17:00:58 -04:00
commit 9d2cb73c4a
169 changed files with 11251 additions and 4868 deletions

View File

@ -46,6 +46,9 @@
<!-- map support for geolocation -->
<dependency conf="core->default" org="org.jxmapviewer" name="jxmapviewer2" rev="2.4"/>
<!-- For Discovery testing -->
<dependency conf="core->default" org="org.mockito" name="mockito-core" rev="3.5.7"/>
<!-- https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api -->
<dependency conf="core->default" org="javax.ws.rs" name="javax.ws.rs-api" rev="2.0"/>
<override org="jakarta.ws.rs" module="jakarta.ws.rs-api" rev="2.1.5"/>

View File

@ -11,6 +11,8 @@ file.reference.bcpkix-jdk15on-1.54.jar=release\\modules\\ext\\bcpkix-jdk15on-1.5
file.reference.bcprov-ext-jdk15on-1.54.jar=release\\modules\\ext\\bcprov-ext-jdk15on-1.54.jar
file.reference.bcprov-jdk15on-1.52.jar=release\\modules\\ext\\bcprov-jdk15on-1.52.jar
file.reference.bcprov-jdk15on-1.54.jar=release\\modules\\ext\\bcprov-jdk15on-1.54.jar
file.reference.byte-buddy-1.10.13.jar=release\\modules\\ext\\byte-buddy-1.10.13.jar
file.reference.byte-buddy-agent-1.10.13.jar=release\\modules\\ext\\byte-buddy-agent-1.10.13.jar
file.reference.c3p0-0.9.5.jar=release\\modules\\ext\\c3p0-0.9.5.jar
file.reference.checker-compat-qual-2.5.3.jar=release\\modules\\ext\\checker-compat-qual-2.5.3.jar
file.reference.commons-beanutils-1.9.2.jar=release\\modules\\ext\\commons-beanutils-1.9.2.jar
@ -70,9 +72,11 @@ file.reference.jai_core-1.1.3.jar=release\\modules\\ext\\jai_core-1.1.3.jar
file.reference.jai_imageio-1.1.jar=release\\modules\\ext\\jai_imageio-1.1.jar
file.reference.javax.annotation-api-1.3.2.jar=release\\modules\\ext\\javax.annotation-api-1.3.2.jar
file.reference.javax.ws.rs-api-2.0.jar=release\\modules\\ext\\javax.ws.rs-api-2.0.jar
file.reference.jcommon-1.0.23.jar=release/modules/ext/jcommon-1.0.23.jar
file.reference.jdom-2.0.5-contrib.jar=release\\modules\\ext\\jdom-2.0.5-contrib.jar
file.reference.jdom-2.0.5.jar=release\\modules\\ext\\jdom-2.0.5.jar
file.reference.jericho-html-3.3.jar=release\\modules\\ext\\jericho-html-3.3.jar
file.reference.jfreechart-1.0.19.jar=release/modules/ext/jfreechart-1.0.19.jar
file.reference.jgraphx-4.1.0.jar=release\\modules\\ext\\jgraphx-4.1.0.jar
file.reference.jline-0.9.94.jar=release\\modules\\ext\\jline-0.9.94.jar
file.reference.jsoup-1.10.3.jar=release\\modules\\ext\\jsoup-1.10.3.jar
@ -86,7 +90,9 @@ file.reference.listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar=re
file.reference.log4j-1.2.16.jar=release\\modules\\ext\\log4j-1.2.16.jar
file.reference.mchange-commons-java-0.2.9.jar=release\\modules\\ext\\mchange-commons-java-0.2.9.jar
file.reference.metadata-extractor-2.11.0.jar=release\\modules\\ext\\metadata-extractor-2.11.0.jar
file.reference.mockito-core-3.5.7.jar=release\\modules\\ext\\mockito-core-3.5.7.jar
file.reference.netty-3.7.0.Final.jar=release\\modules\\ext\\netty-3.7.0.Final.jar
file.reference.objenesis-3.1.jar=release\\modules\\ext\\objenesis-3.1.jar
file.reference.okhttp-2.7.5.jar=release\\modules\\ext\\okhttp-2.7.5.jar
file.reference.okio-1.6.0.jar=release\\modules\\ext\\okio-1.6.0.jar
file.reference.opencensus-api-0.19.2.jar=release\\modules\\ext\\opencensus-api-0.19.2.jar
@ -102,8 +108,8 @@ file.reference.protobuf-java-util-3.7.0.jar=release\\modules\\ext\\protobuf-java
file.reference.Rejistry-1.1-SNAPSHOT.jar=release\\modules\\ext\\Rejistry-1.1-SNAPSHOT.jar
file.reference.sevenzipjbinding-AllPlatforms.jar=release\\modules\\ext\\sevenzipjbinding-AllPlatforms.jar
file.reference.sevenzipjbinding.jar=release\\modules\\ext\\sevenzipjbinding.jar
file.reference.sleuthkit-4.10.0.jar=release\\modules\\ext\\sleuthkit-4.10.0.jar
file.reference.sleuthkit-caseuco-4.10.0.jar=release\\modules\\ext\\sleuthkit-caseuco-4.10.0.jar
file.reference.sleuthkit-4.10.0.jar=release/modules/ext/sleuthkit-4.10.0.jar
file.reference.sleuthkit-caseuco-4.10.0.jar=release/modules/ext/sleuthkit-caseuco-4.10.0.jar
file.reference.slf4j-api-1.7.6.jar=release\\modules\\ext\\slf4j-api-1.7.6.jar
file.reference.slf4j-log4j12-1.7.6.jar=release\\modules\\ext\\slf4j-log4j12-1.7.6.jar
file.reference.SparseBitSet-1.1.jar=release\\modules\\ext\\SparseBitSet-1.1.jar
@ -121,4 +127,3 @@ nbm.module.author=Brian Carrier
nbm.needs.restart=true
source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar
spec.version.base=10.21

View File

@ -399,6 +399,10 @@
<runtime-relative-path>ext/proto-google-cloud-translate-v3beta1-0.53.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\proto-google-cloud-translate-v3beta1-0.53.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/byte-buddy-1.10.13.jar</runtime-relative-path>
<binary-origin>release\modules\ext\byte-buddy-1.10.13.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/error_prone_annotations-2.3.2.jar</runtime-relative-path>
<binary-origin>release\modules\ext\error_prone_annotations-2.3.2.jar</binary-origin>
@ -439,14 +443,6 @@
<runtime-relative-path>ext/jxmapviewer2-2.4.jar</runtime-relative-path>
<binary-origin>release\modules\ext\jxmapviewer2-2.4.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jfreechart-1.0.19.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jfreechart-1.0.19.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jcommon-1.0.23.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jcommon-1.0.23.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jdom-2.0.5-contrib.jar</runtime-relative-path>
<binary-origin>release\modules\ext\jdom-2.0.5-contrib.jar</binary-origin>
@ -553,20 +549,12 @@
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-4.10.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\sleuthkit-4.10.0.jar</binary-origin>
<binary-origin>release/modules/ext/sleuthkit-4.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/animal-sniffer-annotations-1.17.jar</runtime-relative-path>
<binary-origin>release\modules\ext\animal-sniffer-annotations-1.17.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-caseuco-4.10.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\sleuthkit-caseuco-4.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-4.10.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-4.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-caseuco-4.10.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-caseuco-4.10.0.jar</binary-origin>
@ -611,6 +599,10 @@
<runtime-relative-path>ext/decodetect-core-0.3.jar</runtime-relative-path>
<binary-origin>release\modules\ext\decodetect-core-0.3.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/mockito-core-3.5.7.jar</runtime-relative-path>
<binary-origin>release\modules\ext\mockito-core-3.5.7.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/httpclient-4.5.5.jar</runtime-relative-path>
<binary-origin>release\modules\ext\httpclient-4.5.5.jar</binary-origin>
@ -623,6 +615,10 @@
<runtime-relative-path>ext/jackson-annotations-2.9.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\jackson-annotations-2.9.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/objenesis-3.1.jar</runtime-relative-path>
<binary-origin>release\modules\ext\objenesis-3.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jackson-core-2.9.7.jar</runtime-relative-path>
<binary-origin>release\modules\ext\jackson-core-2.9.7.jar</binary-origin>
@ -715,6 +711,10 @@
<runtime-relative-path>ext/netty-3.7.0.Final.jar</runtime-relative-path>
<binary-origin>release\modules\ext\netty-3.7.0.Final.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jfreechart-1.0.19.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jfreechart-1.0.19.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/opencensus-contrib-grpc-metrics-0.19.2.jar</runtime-relative-path>
<binary-origin>release\modules\ext\opencensus-contrib-grpc-metrics-0.19.2.jar</binary-origin>
@ -743,6 +743,10 @@
<runtime-relative-path>ext/javax.ws.rs-api-2.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\javax.ws.rs-api-2.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jcommon-1.0.23.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jcommon-1.0.23.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/icepdf-core-6.2.2.jar</runtime-relative-path>
<binary-origin>release\modules\ext\icepdf-core-6.2.2.jar</binary-origin>
@ -795,6 +799,10 @@
<runtime-relative-path>ext/jutf7-1.0.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\jutf7-1.0.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/byte-buddy-agent-1.10.13.jar</runtime-relative-path>
<binary-origin>release\modules\ext\byte-buddy-agent-1.10.13.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/batik-awt-util-1.6.jar</runtime-relative-path>
<binary-origin>release\modules\ext\batik-awt-util-1.6.jar</binary-origin>

View File

@ -14,6 +14,7 @@ AddContentTagAction.taggingErr=Tagging Error
AddContentTagAction.unableToTag.msg=Unable to tag {0}, not a regular file.
# {0} - fileName
AddContentTagAction.unableToTag.msg2=Unable to tag {0}.
CTL_DumpThreadAction=Thread Dump
CTL_ShowIngestProgressSnapshotAction=Ingest Status Details
DeleteBlackboardArtifactTagAction.deleteTag=Remove Selected Tag(s)
DeleteBlackboardArtifactTagAction.tagDelErr=Tag Deletion Error

View File

@ -44,7 +44,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
* This action should only be invoked in the event dispatch thread (EDT).
*/
@ActionRegistration(displayName = "#CTL_OpenLogFolder", iconInMenu = true)
@ActionReference(path = "Menu/Help", position = 1750)
@ActionReference(path = "Menu/Help", position = 2000)
@ActionID(id = "org.sleuthkit.autopsy.actions.OpenLogFolderAction", category = "Help")
public final class OpenLogFolderAction implements ActionListener {

View File

@ -0,0 +1,156 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.actions;
import java.awt.Desktop;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.SwingWorker;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
/**
* Action class for the Thread Dump help menu item. If there is no case open the
* dump file will be created in PlatformUtil.getLogDirectory() otherwise the
* file will be created in Case.getCurrentCase().getLogDirectoryPath()
*/
@ActionID(category = "Help", id = "org.sleuthkit.autopsy.actions.ThreadDumpAction")
@ActionRegistration(displayName = "#CTL_DumpThreadAction", lazy = false)
@ActionReference(path = "Menu/Help", position = 1750)
@Messages({
"CTL_DumpThreadAction=Thread Dump"
})
public final class ThreadDumpAction extends CallableSystemAction implements ActionListener {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(ThreadDumpAction.class.getName());
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS");
@Override
public void performAction() {
(new ThreadDumper()).run();
}
@Override
public String getName() {
return Bundle.CTL_DumpThreadAction();
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
/**
* SwingWorker to that will create the thread dump file. Once the file is
* created it will be opened in an external viewer.
*/
private final class ThreadDumper extends SwingWorker<File, Void> {
@Override
protected File doInBackground() throws Exception {
return createThreadDump();
}
@Override
protected void done() {
File dumpFile = null;
try {
dumpFile = get();
Desktop.getDesktop().open(dumpFile);
} catch (ExecutionException | InterruptedException ex) {
logger.log(Level.SEVERE, "Failure occurred while creating thread dump file", ex);
} catch (IOException ex) {
if (dumpFile != null) {
logger.log(Level.WARNING, "Failed to open thread dump file in external viewer: " + dumpFile.getAbsolutePath(), ex);
} else {
logger.log(Level.SEVERE, "Failed to create thread dump file.", ex);
}
}
}
/**
* Create the thread dump file.
*
* @throws IOException
*/
private File createThreadDump() throws IOException {
File dumpFile = createFilePath().toFile();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(dumpFile, true))) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100);
for (ThreadInfo threadInfo : threadInfos) {
writer.write(threadInfo.toString());
writer.write("\n");
}
long[] deadlockThreadIds = threadMXBean.findDeadlockedThreads();
if (deadlockThreadIds != null) {
writer.write("-------------------List of Deadlocked Thread IDs ---------------------");
String idsList = (Arrays
.stream(deadlockThreadIds)
.boxed()
.collect(Collectors.toList()))
.stream().map(n -> String.valueOf(n))
.collect(Collectors.joining("-", "{", "}"));
writer.write(idsList);
}
}
return dumpFile;
}
/**
* Create the dump file path.
*
* @return Path for dump file.
*/
private Path createFilePath() {
String fileName = "ThreadDump_" + DATE_FORMAT.format(new Date()) + ".txt";
if (Case.isCaseOpen()) {
return Paths.get(Case.getCurrentCase().getLogDirectoryPath(), fileName);
}
return Paths.get(PlatformUtil.getLogDirectory(), fileName);
}
}
}

View File

@ -107,7 +107,7 @@ import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.events.AutopsyEventException;
import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
import org.sleuthkit.autopsy.discovery.OpenDiscoveryAction;
import org.sleuthkit.autopsy.discovery.ui.OpenDiscoveryAction;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestServices;

View File

@ -49,8 +49,7 @@ public class CommandLineOptionProcessor extends OptionProcessor {
private final Option dataSourceObjectIdOption = Option.requiredArgument('i', "dataSourceObjectId");
private final Option addDataSourceCommandOption = Option.withoutArgument('a', "addDataSource");
private final Option caseDirOption = Option.requiredArgument('d', "caseDir");
private final Option runIngestCommandOption = Option.withoutArgument('r', "runIngest");
private final Option ingestProfileOption = Option.requiredArgument('p', "ingestProfile");
private final Option runIngestCommandOption = Option.optionalArgument('r', "runIngest");
private final Option listAllDataSourcesCommandOption = Option.withoutArgument('l', "listAllDataSources");
private final Option generateReportsOption = Option.optionalArgument('g', "generateReports");
private final Option defaultArgument = Option.defaultArguments();
@ -76,7 +75,6 @@ public class CommandLineOptionProcessor extends OptionProcessor {
set.add(dataSourceObjectIdOption);
set.add(caseDirOption);
set.add(runIngestCommandOption);
set.add(ingestProfileOption);
set.add(listAllDataSourcesCommandOption);
set.add(generateReportsOption);
set.add(defaultArgument);
@ -205,21 +203,6 @@ public class CommandLineOptionProcessor extends OptionProcessor {
}
}
String ingestProfile = "";
if (values.containsKey(ingestProfileOption)) {
argDirs = values.get(ingestProfileOption);
if (argDirs.length < 1) {
handleError("Argument missing from 'ingestProfile' option");
}
ingestProfile = argDirs[0];
// verify inputs
if (ingestProfile == null || ingestProfile.isEmpty()) {
handleError("Missing argument 'ingestProfile'");
}
}
// Create commands in order in which they should be executed:
// First create the "CREATE_CASE" command, if present
if (values.containsKey(createCaseCommandOption)) {
@ -263,9 +246,15 @@ public class CommandLineOptionProcessor extends OptionProcessor {
runFromCommandLine = true;
}
String ingestProfile = "";
// Add RUN_INGEST command, if present
if (values.containsKey(runIngestCommandOption)) {
argDirs = values.get(runIngestCommandOption);
if(argDirs != null && argDirs.length > 0) {
ingestProfile = argDirs[0];
}
// 'caseDir' must only be specified if the case is not being created during the current run
if (!values.containsKey(createCaseCommandOption) && caseDir.isEmpty()) {
// new case is not being created during this run, so 'caseDir' should have been specified

View File

@ -33,7 +33,7 @@ import org.openide.util.NbBundle;
*/
@ActionID(id = "org.sleuthkit.autopsy.corecomponents.AboutWindowAction", category = "Help")
@ActionRegistration(displayName = "#CTL_CustomAboutAction", iconInMenu = true, lazy = false)
@ActionReference(path = "Menu/Help", position = 3000)
@ActionReference(path = "Menu/Help", position = 3000, separatorBefore = 2999)
public class AboutWindowAction extends AboutAction {
@Override

View File

@ -117,6 +117,8 @@ public final class IconsUtil {
imageFile = "devices.png"; //NON-NLS
} else if (typeID == ARTIFACT_TYPE.TSK_VERIFICATION_FAILED.getTypeID()) {
imageFile = "validationFailed.png"; //NON-NLS
} else if (typeID == ARTIFACT_TYPE.TSK_WEB_ACCOUNT_TYPE.getTypeID()) {
imageFile = "web-account-type.png"; //NON-NLS
} else {
imageFile = "artifact-icon.png"; //NON-NLS
}

View File

@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -146,6 +147,11 @@ public class AnalysisSummary implements DefaultArtifactUpdateGovernor {
*/
private List<Pair<String, Long>> getCountsData(DataSource dataSource, BlackboardAttribute.Type keyType, ARTIFACT_TYPE... artifactTypes)
throws SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
return Collections.emptyList();
}
List<BlackboardArtifact> artifacts = new ArrayList<>();
SleuthkitCase skCase = provider.get();

View File

@ -1,2 +1,3 @@
DataSourceUserActivitySummary_getRecentAccounts_calllogMessage=Call Log
DataSourceUserActivitySummary_getRecentAccounts_emailMessage=Email Message
IngestModuleCheckUtil_recentActivityModuleName=Recent Activity

View File

@ -0,0 +1,146 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.IngestModuleInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utilities for checking if an ingest module has been run on a datasource.
*/
@Messages({
"IngestModuleCheckUtil_recentActivityModuleName=Recent Activity",
})
public class IngestModuleCheckUtil {
public static final String RECENT_ACTIVITY_FACTORY = "org.sleuthkit.autopsy.recentactivity.RecentActivityExtracterModuleFactory";
public static final String RECENT_ACTIVITY_MODULE_NAME = Bundle.IngestModuleCheckUtil_recentActivityModuleName();
// IngestModuleInfo separator for unique_name
private static final String UNIQUE_NAME_SEPARATOR = "-";
private final SleuthkitCaseProvider caseProvider;
/**
* Main constructor.
*/
public IngestModuleCheckUtil() {
this(SleuthkitCaseProvider.DEFAULT);
}
/**
* Main constructor with external dependencies specified. This constructor
* is designed with unit testing in mind since mocked dependencies can be
* utilized.
*
* @param provider The object providing the current SleuthkitCase.
* @param logger The logger to use.
*/
public IngestModuleCheckUtil(SleuthkitCaseProvider provider) {
this.caseProvider = provider;
}
/**
* Gets the fully qualified factory from the IngestModuleInfo.
* @param info The IngestJobInfo.
* @return The fully qualified factory.
*/
private static String getFullyQualifiedFactory(IngestModuleInfo info) {
if (info == null) {
return null;
}
String qualifiedName = info.getUniqueName();
if (StringUtils.isBlank(qualifiedName)) {
return null;
}
return qualifiedName.split(UNIQUE_NAME_SEPARATOR)[0];
}
/**
* Whether or not the ingest job info contains the ingest modulename.
* @param info The IngestJobInfo.
* @param fullyQualifiedFactory The fully qualified classname of the relevant factory.
* @return True if the ingest module name is contained in the data.
*/
private static boolean hasIngestModule(IngestJobInfo info, String fullyQualifiedFactory) {
if (info == null || info.getIngestModuleInfo() == null || StringUtils.isBlank(fullyQualifiedFactory)) {
return false;
}
return info.getIngestModuleInfo().stream()
.anyMatch((moduleInfo) -> {
String thisQualifiedFactory = getFullyQualifiedFactory(moduleInfo);
return fullyQualifiedFactory.equalsIgnoreCase(thisQualifiedFactory);
});
}
/**
* Whether or not a data source has been ingested with a particular ingest module.
* @param dataSource The datasource.
* @param fullyQualifiedFactory The fully qualified classname of the relevant factory.
* @return Whether or not a data source has been ingested with a particular ingest module.
* @throws TskCoreException
* @throws SleuthkitCaseProviderException
*/
public boolean isModuleIngested(DataSource dataSource, String fullyQualifiedFactory)
throws TskCoreException, SleuthkitCaseProviderException {
if (dataSource == null) {
return false;
}
long dataSourceId = dataSource.getId();
return caseProvider.get().getIngestJobs().stream()
.anyMatch((ingestJob) -> {
return ingestJob != null
&& ingestJob.getObjectId() == dataSourceId
&& hasIngestModule(ingestJob, fullyQualifiedFactory);
});
}
/**
* Get a mapping of fully qualified factory name to display name.
* @param skCase The SleuthkitCase.
* @return The mapping of fully qualified factory name to display name.
* @throws TskCoreException
*/
public static Map<String, String> getFactoryDisplayNames(SleuthkitCase skCase) throws TskCoreException {
return skCase.getIngestJobs().stream()
.flatMap(ingestJob -> ingestJob.getIngestModuleInfo().stream())
.collect(Collectors.toMap(
(moduleInfo) -> getFullyQualifiedFactory(moduleInfo),
(moduleInfo) -> moduleInfo.getDisplayName(),
(a,b) -> a));
}
}

View File

@ -28,7 +28,6 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
@ -38,7 +37,6 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@ -66,47 +64,6 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
public class PastCasesSummary implements DefaultArtifactUpdateGovernor {
/**
* Exception that is thrown in the event that a data source has not been
* ingested with a particular ingest module.
*/
public static class NotIngestedWithModuleException extends Exception {
private static final long serialVersionUID = 1L;
private final String moduleDisplayName;
/**
* Constructor.
*
* @param moduleName The module name.
* @param message The message for the exception.
*/
public NotIngestedWithModuleException(String moduleName, String message) {
super(message);
this.moduleDisplayName = moduleName;
}
/**
* Constructor.
*
* @param moduleName The module name.
* @param message The message for the exception.
* @param thrwbl Inner exception if applicable.
*/
public NotIngestedWithModuleException(String moduleName, String message, Throwable thrwbl) {
super(message, thrwbl);
this.moduleDisplayName = moduleName;
}
/**
* @return The module display name.
*/
public String getModuleDisplayName() {
return moduleDisplayName;
}
}
/**
* Return type for results items in the past cases tab.
*/
@ -335,16 +292,17 @@ public class PastCasesSummary implements DefaultArtifactUpdateGovernor {
*
* @param dataSource The data source.
*
* @return The retrieved data.
* @return The retrieved data or null if null dataSource.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
* @throws NotIngestedWithModuleException
*/
public PastCasesResult getPastCasesData(DataSource dataSource)
throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException {
throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException {
throwOnNotCentralRepoIngested(dataSource);
if (dataSource == null) {
return null;
}
SleuthkitCase skCase = caseProvider.get();
@ -372,75 +330,4 @@ public class PastCasesSummary implements DefaultArtifactUpdateGovernor {
getCaseCounts(Stream.concat(filesCases, nonDeviceArtifactCases.stream()))
);
}
/**
* Returns true if the ingest job info contains an ingest module that
* matches the Central Repo Module ingest display name.
*
* @param info The info.
*
* @return True if there is a central repo ingest match.
*/
private boolean hasCentralRepoIngest(IngestJobInfo info) {
if (info == null || info.getIngestModuleInfo() == null) {
return false;
}
return info.getIngestModuleInfo().stream()
.anyMatch((moduleInfo) -> {
return StringUtils.isNotBlank(moduleInfo.getDisplayName())
&& moduleInfo.getDisplayName().trim().equalsIgnoreCase(CENTRAL_REPO_INGEST_NAME);
});
}
/**
* Returns true if the central repository ingest module has been run on the
* datasource.
*
* @param dataSource The data source.
*
* @return True if there is an ingest job pertaining to the data source
* where an ingest module matches the central repo ingest module
* display name.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
public boolean isCentralRepoIngested(DataSource dataSource)
throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
return false;
}
long dataSourceId = dataSource.getId();
return this.caseProvider.get().getIngestJobs().stream()
.anyMatch((ingestJob) -> {
return ingestJob != null
&& ingestJob.getObjectId() == dataSourceId
&& hasCentralRepoIngest(ingestJob);
});
}
/**
* Throws an exception if the current data source has not been ingested with
* the Central Repository Ingest Module.
*
* @param dataSource The data source to check if it has been ingested with
* the Central Repository Ingest Module.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
* @throws NotIngestedWithModuleException
*/
private void throwOnNotCentralRepoIngested(DataSource dataSource)
throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException {
if (!isCentralRepoIngested(dataSource)) {
String objectId = (dataSource == null) ? "<null>" : String.valueOf(dataSource.getId());
String message = String.format("Data source: %s has not been ingested with the Central Repository Ingest Module.", objectId);
throw new NotIngestedWithModuleException(CENTRAL_REPO_INGEST_NAME, message);
}
}
}

View File

@ -24,6 +24,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@ -108,7 +109,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
*/
public List<RecentFileDetails> getRecentlyOpenedDocuments(DataSource dataSource, int maxCount) throws SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
throw new IllegalArgumentException("Failed to get recently opened documents given data source was null");
return Collections.emptyList();
}
List<BlackboardArtifact> artifactList
@ -159,6 +160,10 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
* @throws SleuthkitCaseProviderException
*/
public List<RecentDownloadDetails> getRecentDownloads(DataSource dataSource, int maxCount) throws TskCoreException, SleuthkitCaseProviderException {
if (dataSource == null) {
return Collections.emptyList();
}
List<BlackboardArtifact> artifactList
= DataSourceInfoUtilities.getArtifacts(provider.get(),
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD),
@ -206,6 +211,10 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
* @throws TskCoreException
*/
public List<RecentAttachmentDetails> getRecentAttachments(DataSource dataSource, int maxCount) throws SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
return Collections.emptyList();
}
return createListFromMap(buildAttachmentMap(dataSource), maxCount);
}

View File

@ -149,6 +149,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List<TopDomainsResult> getRecentDomains(DataSource dataSource, int count) throws TskCoreException, SleuthkitCaseProviderException {
assertValidCount(count);
if (dataSource == null) {
return Collections.emptyList();
}
Pair<Long, Map<String, List<Long>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
// if no recent domains, return accordingly
if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) {
@ -307,6 +311,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List<TopWebSearchResult> getMostRecentWebSearches(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
if (dataSource == null) {
return Collections.emptyList();
}
// get the artifacts
List<BlackboardArtifact> webSearchArtifacts = caseProvider.get().getBlackboard()
.getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId());
@ -391,6 +399,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List<TopDeviceAttachedResult> getRecentDevices(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
if (dataSource == null) {
return Collections.emptyList();
}
return DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED,
dataSource, TYPE_DATETIME, DataSourceInfoUtilities.SortOrder.DESCENDING, 0)
.stream()
@ -476,6 +488,10 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public List<TopAccountResult> getRecentAccounts(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
if (dataSource == null) {
return Collections.emptyList();
}
Stream<TopAccountResult> messageResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), dataSource.getId())
.stream()
.map((art) -> getMessageAccountResult(art));

View File

@ -57,6 +57,27 @@
<Property name="axis" type="int" value="3"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="ingestRunningPanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 25]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="ingestRunningLabel"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Component class="javax.swing.JLabel" name="hashsetHitsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">

View File

@ -26,8 +26,11 @@ import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.AnalysisSummary;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory;
import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory;
import org.sleuthkit.datamodel.DataSource;
/**
@ -36,12 +39,22 @@ import org.sleuthkit.datamodel.DataSource;
*/
@Messages({
"AnalysisPanel_keyColumn_title=Name",
"AnalysisPanel_countColumn_title=Count"
"AnalysisPanel_countColumn_title=Count",
"AnalysisPanel_keywordSearchModuleName=Keyword Search"
})
public class AnalysisPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final String KEYWORD_SEARCH_MODULE_NAME = Bundle.AnalysisPanel_keywordSearchModuleName();
private static final String KEYWORD_SEARCH_FACTORY = "org.sleuthkit.autopsy.keywordsearch.KeywordSearchModuleFactory";
private static final String INTERESTING_ITEM_MODULE_NAME = new InterestingItemsIngestModuleFactory().getModuleDisplayName();
private static final String INTERESTING_ITEM_FACTORY = InterestingItemsIngestModuleFactory.class.getCanonicalName();
private static final String HASHSET_MODULE_NAME = HashLookupModuleFactory.getModuleName();
private static final String HASHSET_FACTORY = HashLookupModuleFactory.class.getCanonicalName();
/**
* Default Column definitions for each table
*/
@ -77,6 +90,9 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
keywordHitsTable,
interestingItemsTable
);
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
/**
* All of the components necessary for data fetch swing workers to load data
@ -99,20 +115,28 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
// hashset hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getHashsetCounts(dataSource),
(result) -> hashsetHitsTable.showDataFetchResult(result)),
(result) -> showResultWithModuleCheck(hashsetHitsTable, result, HASHSET_FACTORY, HASHSET_MODULE_NAME)),
// keyword hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getKeywordCounts(dataSource),
(result) -> keywordHitsTable.showDataFetchResult(result)),
(result) -> showResultWithModuleCheck(keywordHitsTable, result, KEYWORD_SEARCH_FACTORY, KEYWORD_SEARCH_MODULE_NAME)),
// interesting item hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getInterestingItemCounts(dataSource),
(result) -> interestingItemsTable.showDataFetchResult(result))
(result) -> showResultWithModuleCheck(interestingItemsTable, result, INTERESTING_ITEM_FACTORY, INTERESTING_ITEM_MODULE_NAME))
);
initComponents();
}
@Override
public void close() {
ingestRunningLabel.unregister();
super.close();
}
@Override
protected void fetchInformation(DataSource dataSource) {
fetchInformation(dataFetchComponents, dataSource);
@ -134,6 +158,7 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel mainContentPanel = new javax.swing.JPanel();
javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JLabel hashsetHitsLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
javax.swing.JPanel hashSetHitsPanel = hashsetHitsTable;
@ -152,6 +177,12 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
mainContentPanel.setMinimumSize(new java.awt.Dimension(200, 452));
mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS));
ingestRunningPanel.setAlignmentX(0.0F);
ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
mainContentPanel.add(ingestRunningPanel);
org.openide.awt.Mnemonics.setLocalizedText(hashsetHitsLabel, org.openide.util.NbBundle.getMessage(AnalysisPanel.class, "AnalysisPanel.hashsetHitsLabel.text")); // NOI18N
mainContentPanel.add(hashsetHitsLabel);
mainContentPanel.add(filler1);

View File

@ -23,13 +23,18 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler;
@ -55,6 +60,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
private static final Logger logger = Logger.getLogger(BaseDataSourceSummaryPanel.class.getName());
private final SwingWorkerSequentialExecutor executor = new SwingWorkerSequentialExecutor();
private final IngestModuleCheckUtil ingestModuleCheck = new IngestModuleCheckUtil();
private final EventUpdateHandler updateHandler;
private final List<UpdateGovernor> governors;
@ -284,15 +290,20 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* @param dataSource The data source argument.
*/
protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) {
// create swing workers to run for each loadable item
List<DataFetchWorker<?, ?>> workers = dataFetchComponents
.stream()
.map((components) -> new DataFetchWorker<>(components, dataSource))
.collect(Collectors.toList());
if (dataSource == null || !Case.isCaseOpen()) {
dataFetchComponents.forEach((item) -> item.getResultHandler()
.accept(DataFetchResult.getSuccessResult(null)));
} else {
// create swing workers to run for each loadable item
List<DataFetchWorker<?, ?>> workers = dataFetchComponents
.stream()
.map((components) -> new DataFetchWorker<>(components, dataSource))
.collect(Collectors.toList());
// submit swing workers to run
if (!workers.isEmpty()) {
submit(workers);
// submit swing workers to run
if (!workers.isEmpty()) {
submit(workers);
}
}
}
@ -329,4 +340,78 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
fetchInformation(dataSource);
}
}
/**
* Get default message when there is a NotIngestedWithModuleException.
*
* @param exception The moduleName.
*
* @return Message specifying that the ingest module was not run.
*/
@Messages({
"# {0} - module name",
"BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source."
})
protected String getDefaultNoIngestMessage(String moduleName) {
return Bundle.BaseDataSourceSummaryPanel_defaultNotIngestMessage(moduleName);
}
/**
* Utility method to return the IngestModuleCheckUtil.
*
* @return The IngestModuleCheckUtil.
*/
protected IngestModuleCheckUtil getIngestModuleCheckUtil() {
return this.ingestModuleCheck;
}
/**
* Utility method that in the event of a) there are no results and b) a
* relevant ingest module has not been run on this datasource, then a
* message indicating the unrun ingest module will be shown. Otherwise, the
* default LoadableComponent.showDataFetchResult behavior will be used.
*
* @param component The component.
* @param result The data result.
* @param factoryClass The fully qualified class name of the relevant
* factory.
* @param moduleName The name of the ingest module (i.e. 'Keyword
* Search').
*/
protected <T> void showResultWithModuleCheck(LoadableComponent<List<T>> component, DataFetchResult<List<T>> result, String factoryClass, String moduleName) {
Predicate<List<T>> hasResults = (lst) -> lst != null && !lst.isEmpty();
showResultWithModuleCheck(component, result, hasResults, factoryClass, moduleName);
}
/**
* Utility method that in the event of a) there are no results and b) a
* relevant ingest module has not been run on this datasource, then a
* message indicating the unrun ingest module will be shown. Otherwise, the
* default LoadableComponent.showDataFetchResult behavior will be used.
*
* @param component The component.
* @param result The data result.
* @param hasResults Given the data type, will provide whether or not the
* data contains any actual results.
* @param factoryClass The fully qualified class name of the relevant
* factory.
* @param moduleName The name of the ingest module (i.e. 'Keyword
* Search').
*/
protected <T> void showResultWithModuleCheck(LoadableComponent<T> component, DataFetchResult<T> result,
Predicate<T> hasResults, String factoryClass, String moduleName) {
if (result != null && result.getResultType() == ResultType.SUCCESS && !hasResults.test(result.getData())) {
try {
if (!ingestModuleCheck.isModuleIngested(getDataSource(), factoryClass)) {
component.showMessage(getDefaultNoIngestMessage(moduleName));
return;
}
} catch (TskCoreException | SleuthkitCaseProviderException ex) {
logger.log(Level.WARNING, "There was an error while checking for ingest modules for datasource.", ex);
}
}
component.showDataFetchResult(result);
}
}

View File

@ -1,7 +1,8 @@
AnalysisPanel_countColumn_title=Count
AnalysisPanel_keyColumn_title=Name
ContainerPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.
ContainerPanel.getDataSources.error.title=Load Failure
AnalysisPanel_keywordSearchModuleName=Keyword Search
# {0} - module name
BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source.
CTL_DataSourceSummaryAction=Data Source Summary
DataSourceSummaryDialog.closeButton.text=Close
ContainerPanel.displayNameLabel.text=Display Name:
@ -54,6 +55,7 @@ PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central
RecentFilePanel_col_header_domain=Domain
RecentFilePanel_col_header_path=Path
RecentFilePanel_col_header_sender=Sender
RecentFilePanel_emailParserModuleName=Email Parser
RecentFilePanel_no_open_documents=No recently open documents found.
RecentFilesPanel_col_head_date=Date
SizeRepresentationUtil_units_bytes=\ bytes

View File

@ -26,7 +26,6 @@ import java.util.Set;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.table.DefaultTableModel;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
@ -49,23 +48,16 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
private final DataSource dataSource;
private final Long unallocatedFilesSize;
private final String operatingSystem;
private final String dataSourceType;
/**
* Main constructor.
*
* @param dataSource The original datasource.
* @param unallocatedFilesSize The unallocated file size.
* @param operatingSystem The string representing the operating
* system.
* @param dataSourceType The datasource type as a string.
*/
ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize, String operatingSystem, String dataSourceType) {
ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize) {
this.dataSource = dataSource;
this.unallocatedFilesSize = unallocatedFilesSize;
this.operatingSystem = operatingSystem;
this.dataSourceType = dataSourceType;
}
/**
@ -81,21 +73,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
Long getUnallocatedFilesSize() {
return unallocatedFilesSize;
}
/**
* @return The string representing the operating system.
*/
String getOperatingSystem() {
return operatingSystem;
}
/**
* @return The datasource type as a string.
*/
String getDataSourceType() {
return dataSourceType;
}
}
// set of case events for which to call update (if the name changes, that will impact data shown)
@ -134,8 +111,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
/**
* Creates new form ContainerPanel.
*/
@Messages({"ContainerPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.",
"ContainerPanel.getDataSources.error.title=Load Failure"})
ContainerPanel(ContainerSummary containerSummary) {
super(containerSummary, CONTAINER_UPDATES);
@ -144,20 +119,24 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
(dataSource) -> {
return new ContainerPanelData(
dataSource,
containerSummary.getSizeOfUnallocatedFiles(dataSource),
containerSummary.getOperatingSystems(dataSource),
containerSummary.getDataSourceType(dataSource)
containerSummary.getSizeOfUnallocatedFiles(dataSource)
);
},
(result) -> {
if (result.getResultType() == ResultType.SUCCESS) {
if (result != null && result.getResultType() == ResultType.SUCCESS) {
ContainerPanelData data = result.getData();
updateDetailsPanelData(
data.getDataSource(),
data.getUnallocatedFilesSize());
DataSource dataSource = (data == null) ? null : data.getDataSource();
Long unallocatedFileSize = (data == null) ? null : data.getUnallocatedFilesSize();
updateDetailsPanelData(dataSource, unallocatedFileSize);
} else {
logger.log(Level.WARNING, "An exception occurred while attempting to fetch data for the ContainerPanel.",
result.getException());
if (result == null) {
logger.log(Level.WARNING, "No data fetch result was provided to the ContainerPanel.");
} else {
logger.log(Level.WARNING, "An exception occurred while attempting to fetch data for the ContainerPanel.",
result.getException());
}
updateDetailsPanelData(null, null);
}
}

View File

@ -51,6 +51,27 @@
<Property name="axis" type="int" value="3"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="ingestRunningPanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 25]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="ingestRunningLabel"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Component class="javax.swing.JLabel" name="notableFileLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -70,7 +91,7 @@
<Component class="javax.swing.Box$Filler" name="filler1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 2]"/>
<Dimension value="[0, 2]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
@ -78,6 +99,7 @@
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="alignmentX" type="float" value="0.0"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
@ -109,7 +131,7 @@
<Component class="javax.swing.Box$Filler" name="filler2">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 20]"/>
<Dimension value="[0, 20]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
@ -117,6 +139,7 @@
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
</Property>
<Property name="alignmentX" type="float" value="0.0"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
@ -138,7 +161,7 @@
<Component class="javax.swing.Box$Filler" name="filler3">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 2]"/>
<Dimension value="[0, 2]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
@ -146,6 +169,7 @@
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="alignmentX" type="float" value="0.0"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
@ -179,6 +203,7 @@
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 32767]"/>
</Property>
<Property name="alignmentX" type="float" value="0.0"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>

View File

@ -21,18 +21,17 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.NotIngestedWithModuleException;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
@ -45,11 +44,12 @@ import org.sleuthkit.datamodel.DataSource;
"PastCasesPanel_caseColumn_title=Case",
"PastCasesPanel_countColumn_title=Count",
"PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run."
})
public class PastCasesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final String CR_FACTORY = CentralRepoIngestModuleFactory.class.getName();
private static final String CR_NAME = CentralRepoIngestModuleFactory.getModuleName();
private static final ColumnModel<Pair<String, Long>> CASE_COL = new ColumnModel<>(
Bundle.PastCasesPanel_caseColumn_title(),
@ -76,6 +76,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
public PastCasesPanel() {
this(new PastCasesSummary());
}
@ -95,21 +97,14 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
}
/**
* handles displaying the result for the table. If a
* NotCentralRepoIngestedException is thrown, then an appropriate message is
* shown. Otherwise, this method uses the tables default showDataFetchResult
* method.
* Handles displaying the result for each table by breaking apart subdata
* items into seperate results for each table.
*
* @param result The result.
*/
private void handleResult(DataFetchResult<PastCasesResult> result) {
if (result.getResultType() == ResultType.ERROR && result.getException() instanceof NotIngestedWithModuleException) {
notableFileTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message());
sameIdTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message());
} else {
notableFileTable.showDataFetchResult(getSubResult(result, (res) -> (res == null) ? null : res.getTaggedNotable()));
sameIdTable.showDataFetchResult(getSubResult(result, (res) -> (res == null) ? null : res.getSameIdsResults()));
}
showResultWithModuleCheck(notableFileTable, getSubResult(result, (res) -> res.getTaggedNotable()), CR_FACTORY, CR_NAME);
showResultWithModuleCheck(sameIdTable, getSubResult(result, (res) -> res.getSameIdsResults()), CR_FACTORY, CR_NAME);
}
/**
@ -124,9 +119,12 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
* @return The new result with the error of the original or the processed
* data.
*/
private <I, O> DataFetchResult<O> getSubResult(DataFetchResult<I> inputResult, Function<I, O> getSubResult) {
if (inputResult.getResultType() == ResultType.SUCCESS) {
return DataFetchResult.getSuccessResult(getSubResult.apply(inputResult.getData()));
private <O> DataFetchResult<O> getSubResult(DataFetchResult<PastCasesResult> inputResult, Function<PastCasesResult, O> getSubResult) {
if (inputResult == null) {
return null;
} else if (inputResult.getResultType() == ResultType.SUCCESS) {
O innerData = (inputResult.getData() == null) ? null : getSubResult.apply(inputResult.getData());
return DataFetchResult.getSuccessResult(innerData);
} else {
return DataFetchResult.getErrorResult(inputResult.getException());
}
@ -142,6 +140,12 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tables, dataSource);
}
@Override
public void close() {
ingestRunningLabel.unregister();
super.close();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -153,22 +157,30 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel mainContentPanel = new javax.swing.JPanel();
javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JLabel notableFileLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel notableFilePanel = notableFileTable;
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(32767, 20));
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel sameIdLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel sameIdPanel = sameIdTable;
javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS));
ingestRunningPanel.setAlignmentX(0.0F);
ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
mainContentPanel.add(ingestRunningPanel);
org.openide.awt.Mnemonics.setLocalizedText(notableFileLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N
mainContentPanel.add(notableFileLabel);
notableFileLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N
filler1.setAlignmentX(0.0F);
mainContentPanel.add(filler1);
notableFilePanel.setAlignmentX(0.0F);
@ -176,10 +188,14 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
notableFilePanel.setMinimumSize(new java.awt.Dimension(100, 106));
notableFilePanel.setPreferredSize(new java.awt.Dimension(100, 106));
mainContentPanel.add(notableFilePanel);
filler2.setAlignmentX(0.0F);
mainContentPanel.add(filler2);
org.openide.awt.Mnemonics.setLocalizedText(sameIdLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.sameIdLabel.text")); // NOI18N
mainContentPanel.add(sameIdLabel);
filler3.setAlignmentX(0.0F);
mainContentPanel.add(filler3);
sameIdPanel.setAlignmentX(0.0F);
@ -187,6 +203,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
sameIdPanel.setMinimumSize(new java.awt.Dimension(100, 106));
sameIdPanel.setPreferredSize(new java.awt.Dimension(100, 106));
mainContentPanel.add(sameIdPanel);
filler5.setAlignmentX(0.0F);
mainContentPanel.add(filler5);
mainScrollPane.setViewportView(mainContentPanel);

View File

@ -31,6 +31,11 @@
<SubComponents>
<Container class="javax.swing.JPanel" name="tablePanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
<EmptyBorder bottom="10" left="10" right="10" top="10"/>
</Border>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[400, 400]"/>
</Property>
@ -45,13 +50,39 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="ingestRunningPanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 25]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="ingestRunningLabel"/>
<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="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Container class="javax.swing.JPanel" name="openedDocPane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new JTablePanel&lt;RecentFileDetails&gt;()"/>
</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="1" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="0" insetsRight="5" anchor="11" weightX="1.0" weightY="1.0"/>
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
@ -63,7 +94,7 @@
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="0" insetsRight="5" anchor="11" weightX="1.0" weightY="1.0"/>
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
@ -75,7 +106,7 @@
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="5" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="10" insetsRight="5" anchor="11" weightX="1.0" weightY="1.0"/>
<GridBagConstraints gridX="0" gridY="6" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
@ -93,7 +124,7 @@
</AuxValues>
<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="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="10" insetsLeft="5" insetsBottom="0" insetsRight="5" anchor="11" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@ -109,7 +140,7 @@
</AuxValues>
<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="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="5" insetsBottom="0" insetsRight="5" anchor="10" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@ -125,7 +156,7 @@
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="5" insetsBottom="0" insetsRight="5" anchor="11" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="0" gridY="5" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>

View File

@ -22,12 +22,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ListTableModel;
@ -39,17 +41,22 @@ import org.sleuthkit.datamodel.DataSource;
public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final String EMAIL_PARSER_FACTORY = "org.sleuthkit.autopsy.thunderbirdparser.EmailParserModuleFactory";
private static final String EMAIL_PARSER_MODULE_NAME = Bundle.RecentFilePanel_emailParserModuleName();
private final List<JTablePanel<?>> tablePanelList = new ArrayList<>();
private final List<DataFetchWorker.DataFetchComponents<DataSource, ?>> dataFetchComponents = new ArrayList<>();
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
private final RecentFilesSummary dataHandler;
@Messages({
"RecentFilesPanel_col_head_date=Date",
"RecentFilePanel_col_header_domain=Domain",
"RecentFilePanel_col_header_path=Path",
"RecentFilePanel_col_header_sender=Sender"
"RecentFilePanel_col_header_sender=Sender",
"RecentFilePanel_emailParserModuleName=Email Parser"
})
/**
@ -80,6 +87,12 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tablePanelList, dataSource);
}
@Override
public void close() {
ingestRunningLabel.unregister();
super.close();
}
/**
* Setup the data model and columns for the panel tables.
*/
@ -118,8 +131,11 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentlyOpenedDocuments(dataSource, 10),
(result) -> pane.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.RecentFilePanel_no_open_documents()));
(result) -> {
showResultWithModuleCheck(pane, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
});
dataFetchComponents.add(worker);
}
@ -154,8 +170,11 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentDownloads(dataSource, 10),
(result) -> pane.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.RecentFilePanel_no_open_documents()));
(result) -> {
showResultWithModuleCheck(pane, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
});
dataFetchComponents.add(worker);
}
@ -190,8 +209,8 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
DataFetchWorker.DataFetchComponents<DataSource, List<RecentAttachmentDetails>> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentAttachments(dataSource, 10),
(result) -> pane.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.RecentFilePanel_no_open_documents()));
(result) -> showResultWithModuleCheck(pane, result, EMAIL_PARSER_FACTORY, EMAIL_PARSER_MODULE_NAME)
);
dataFetchComponents.add(worker);
}
@ -208,6 +227,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel tablePanel = new javax.swing.JPanel();
javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
openedDocPane = new JTablePanel<RecentFileDetails>();
downloadsPane = new JTablePanel<RecentDownloadDetails>();
attachmentsPane = new JTablePanel<RecentAttachmentDetails>();
@ -217,61 +237,72 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
setLayout(new java.awt.BorderLayout());
tablePanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
tablePanel.setMinimumSize(new java.awt.Dimension(400, 400));
tablePanel.setPreferredSize(new java.awt.Dimension(600, 400));
tablePanel.setLayout(new java.awt.GridBagLayout());
ingestRunningPanel.setAlignmentX(0.0F);
ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
tablePanel.add(ingestRunningPanel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 5);
gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
tablePanel.add(openedDocPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 5);
gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
tablePanel.add(downloadsPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 5;
gridBagConstraints.gridy = 6;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 10, 5);
gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
tablePanel.add(attachmentsPane, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(openDocsLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.openDocsLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.insets = new java.awt.Insets(10, 5, 0, 5);
tablePanel.add(openDocsLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(downloadLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.downloadLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(15, 5, 0, 5);
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(downloadLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(attachmentLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.attachmentLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4;
gridBagConstraints.gridy = 5;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.insets = new java.awt.Insets(15, 5, 0, 5);
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(attachmentLabel, gridBagConstraints);
scrollPane.setViewportView(tablePanel);

View File

@ -52,6 +52,27 @@
<Property name="axis" type="int" value="3"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="ingestRunningPanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 25]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="ingestRunningLabel"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Container class="javax.swing.JPanel" name="usagePanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>

View File

@ -26,22 +26,29 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JLabel;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TypesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.MimeTypeSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.AbstractLoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel.PieChartItem;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
@ -93,27 +100,64 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
this.showResults(null);
}
private void setValue(String value, boolean italicize) {
private void setValue(String value) {
String formattedKey = StringUtils.isBlank(key) ? "" : key;
String formattedValue = StringUtils.isBlank(value) ? "" : value;
String htmlFormattedValue = (italicize) ? String.format("<i>%s</i>", formattedValue) : formattedValue;
label.setText(String.format("<html><div style='text-align: center;'>%s: %s</div></html>", formattedKey, htmlFormattedValue));
label.setText(String.format("%s: %s", formattedKey, formattedValue));
}
@Override
protected void setMessage(boolean visible, String message) {
setValue(message, true);
setValue(message);
}
@Override
protected void setResults(String data) {
setValue(data, false);
setValue(data);
}
}
/**
* Data for types pie chart.
*/
private static class TypesPieChartData {
private final List<PieChartItem> pieSlices;
private final boolean usefulContent;
/**
* Main constructor.
*
* @param pieSlices The pie slices.
* @param usefulContent True if this is useful content; false if there
* is 0 mime type information.
*/
public TypesPieChartData(List<PieChartItem> pieSlices, boolean usefulContent) {
this.pieSlices = pieSlices;
this.usefulContent = usefulContent;
}
/**
* @return The pie chart data.
*/
public List<PieChartItem> getPieSlices() {
return pieSlices;
}
/**
* @return Whether or not the data is usefulContent.
*/
public boolean isUsefulContent() {
return usefulContent;
}
}
private static final long serialVersionUID = 1L;
private static final DecimalFormat INTEGER_SIZE_FORMAT = new DecimalFormat("#");
private static final DecimalFormat COMMA_FORMATTER = new DecimalFormat("#,###");
private static final String FILE_TYPE_FACTORY = FileTypeIdModuleFactory.class.getCanonicalName();
private static final String FILE_TYPE_MODULE_NAME = FileTypeIdModuleFactory.getModuleName();
private static final Logger logger = Logger.getLogger(TypesPanel.class.getName());
// All file type categories.
private static final List<Pair<String, Set<String>>> FILE_MIME_TYPE_CATEGORIES = Arrays.asList(
@ -148,6 +192,8 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
directoriesLabel
);
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
// all of the means for obtaining data for the gui components.
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
@ -158,6 +204,12 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
this(new MimeTypeSummary(), new TypesSummary(), new ContainerSummary());
}
@Override
public void close() {
ingestRunningLabel.unregister();
super.close();
}
/**
* Creates a new TypesPanel.
*
@ -176,11 +228,25 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
// usage label worker
new DataFetchWorker.DataFetchComponents<>(
containerData::getDataSourceType,
usageLabel::showDataFetchResult),
(result) -> {
showResultWithModuleCheck(
usageLabel,
result,
StringUtils::isNotBlank,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
}),
// os label worker
new DataFetchWorker.DataFetchComponents<>(
containerData::getOperatingSystems,
osLabel::showDataFetchResult),
(result) -> {
showResultWithModuleCheck(
osLabel,
result,
StringUtils::isNotBlank,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
}),
// size label worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> {
@ -191,7 +257,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
// file types worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getMimeTypeCategoriesModel(mimeTypeData, dataSource),
fileMimeTypesChart::showDataFetchResult),
this::showMimeTypeCategories),
// allocated files worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getStringOrZero(typeData.getCountOfAllocatedFiles(dataSource)),
@ -230,7 +296,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
*
* @return The pie chart items.
*/
private List<PieChartItem> getMimeTypeCategoriesModel(MimeTypeSummary mimeTypeData, DataSource dataSource)
private TypesPieChartData getMimeTypeCategoriesModel(MimeTypeSummary mimeTypeData, DataSource dataSource)
throws SQLException, SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
@ -259,15 +325,76 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
fileCategoryItems.add(Pair.of(Bundle.TypesPanel_fileMimeTypesChart_other_title(),
allRegularFiles - (categoryTotalCount + noMimeTypeCount)));
// check at this point to see if these are all 0; if so, we don't have useful content.
boolean usefulContent = fileCategoryItems.stream().anyMatch((pair) -> pair.getValue() != null && pair.getValue() > 0);
// create entry for not analyzed mime types category
fileCategoryItems.add(Pair.of(Bundle.TypesPanel_fileMimeTypesChart_notAnalyzed_title(),
noMimeTypeCount));
// create pie chart items to provide to pie chart
return fileCategoryItems.stream()
List<PieChartItem> items = fileCategoryItems.stream()
.filter(keyCount -> keyCount.getRight() != null && keyCount.getRight() > 0)
.map(keyCount -> new PieChartItem(keyCount.getLeft(), keyCount.getRight()))
.collect(Collectors.toList());
return new TypesPieChartData(items, usefulContent);
}
/**
* Handles properly showing data for the mime type categories pie chart
* accounting for whether there are any files with mime types specified and
* whether or not the current data source has been ingested with the file
* type ingest module.
*
* @param result The result to be shown.
*/
private void showMimeTypeCategories(DataFetchResult<TypesPieChartData> result) {
// if result is null check for ingest module and show empty results.
if (result == null) {
showPieResultWithModuleCheck(null);
return;
}
// if error, show error
if (result.getResultType() == ResultType.ERROR) {
this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getErrorResult(result.getException()));
return;
}
TypesPieChartData data = result.getData();
if (data == null) {
// if no data, do an ingest module check with empty results
showPieResultWithModuleCheck(null);
} else if (!data.isUsefulContent()) {
// if no useful data, do an ingest module check and show data
showPieResultWithModuleCheck(data.getPieSlices());
} else {
// otherwise, show the data
this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(data.getPieSlices()));
}
}
/**
* Shows a message in the fileMimeTypesChart about the data source not being
* ingested with the file type ingest module if the data source has not been
* ingested with that module. Also shows data if present.
*
* @param items The list of items to show.
*/
private void showPieResultWithModuleCheck(List<PieChartItem> items) {
boolean hasBeenIngested = false;
try {
hasBeenIngested = this.getIngestModuleCheckUtil().isModuleIngested(getDataSource(), FILE_TYPE_FACTORY);
} catch (TskCoreException | SleuthkitCaseProviderException ex) {
logger.log(Level.WARNING, "There was an error fetching whether or not the current data source has been ingested with the file type ingest module.", ex);
}
if (hasBeenIngested) {
this.fileMimeTypesChart.showDataFetchResult(DataFetchResult.getSuccessResult(items));
} else {
this.fileMimeTypesChart.showDataWithMessage(items, getDefaultNoIngestMessage(FILE_TYPE_MODULE_NAME));
}
}
/**
@ -304,6 +431,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane scrollParent = new javax.swing.JScrollPane();
javax.swing.JPanel contentParent = new javax.swing.JPanel();
javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JPanel usagePanel = usageLabel;
javax.swing.JPanel osPanel = osLabel;
javax.swing.JPanel sizePanel = sizeLabel;
@ -322,6 +450,12 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
contentParent.setMinimumSize(new java.awt.Dimension(400, 490));
contentParent.setLayout(new javax.swing.BoxLayout(contentParent, javax.swing.BoxLayout.PAGE_AXIS));
ingestRunningPanel.setAlignmentX(0.0F);
ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
contentParent.add(ingestRunningPanel);
usagePanel.setAlignmentX(0.0F);
usagePanel.setMaximumSize(new java.awt.Dimension(32767, 20));
usagePanel.setMinimumSize(new java.awt.Dimension(10, 20));

View File

@ -60,6 +60,27 @@
<Property name="axis" type="int" value="3"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="ingestRunningPanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 25]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 25]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="ingestRunningLabel"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Component class="javax.swing.JLabel" name="programsRunLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="2"/>

View File

@ -27,6 +27,7 @@ import java.util.List;
import java.util.Locale;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult;
@ -36,6 +37,7 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary.TopP
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
@ -70,6 +72,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
private static final int TOP_SEARCHES_COUNT = 10;
private static final int TOP_ACCOUNTS_COUNT = 5;
private static final int TOP_DEVICES_COUNT = 10;
private static final String ANDROID_FACTORY = "org.python.proxies.module$AndroidModuleFactory";
private static final String ANDROID_MODULE_NAME = "Android Analyzer";
/**
* Gets a string formatted date or returns empty string if the date is null.
@ -220,6 +224,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topAccountsTable
);
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private final TopProgramsSummary topProgramsData;
@ -250,28 +256,43 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// top programs query
new DataFetchComponents<DataSource, List<TopProgramsResult>>(
(dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
(result) -> topProgramsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.UserActivityPanel_noDataExists())),
(result) -> {
showResultWithModuleCheck(topProgramsTable, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
}),
// top domains query
new DataFetchComponents<DataSource, List<TopDomainsResult>>(
(dataSource) -> userActivityData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT),
(result) -> recentDomainsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.UserActivityPanel_noDataExists())),
(result) -> {
showResultWithModuleCheck(recentDomainsTable, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
}),
// top web searches query
new DataFetchComponents<DataSource, List<TopWebSearchResult>>(
(dataSource) -> userActivityData.getMostRecentWebSearches(dataSource, TOP_SEARCHES_COUNT),
(result) -> topWebSearchesTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.UserActivityPanel_noDataExists())),
(result) -> {
showResultWithModuleCheck(topWebSearchesTable, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
}),
// top devices query
new DataFetchComponents<DataSource, List<TopDeviceAttachedResult>>(
(dataSource) -> userActivityData.getRecentDevices(dataSource, TOP_DEVICES_COUNT),
(result) -> topDevicesAttachedTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.UserActivityPanel_noDataExists())),
(result) -> {
showResultWithModuleCheck(topDevicesAttachedTable, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
IngestModuleCheckUtil.RECENT_ACTIVITY_MODULE_NAME);
}),
// top accounts query
new DataFetchComponents<DataSource, List<TopAccountResult>>(
(dataSource) -> userActivityData.getRecentAccounts(dataSource, TOP_ACCOUNTS_COUNT),
(result) -> topAccountsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(),
Bundle.UserActivityPanel_noDataExists()))
(result) -> {
showResultWithModuleCheck(topAccountsTable, result,
ANDROID_FACTORY,
ANDROID_MODULE_NAME);
})
);
initComponents();
@ -299,6 +320,12 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tables, dataSource);
}
@Override
public void close() {
ingestRunningLabel.unregister();
super.close();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -310,6 +337,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
javax.swing.JScrollPane contentScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel contentPanel = new javax.swing.JPanel();
javax.swing.JPanel ingestRunningPanel = ingestRunningLabel;
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel topProgramsTablePanel = topProgramsTable;
@ -340,6 +368,12 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
contentPanel.setMinimumSize(new java.awt.Dimension(10, 450));
contentPanel.setLayout(new javax.swing.BoxLayout(contentPanel, javax.swing.BoxLayout.PAGE_AXIS));
ingestRunningPanel.setAlignmentX(0.0F);
ingestRunningPanel.setMaximumSize(new java.awt.Dimension(32767, 25));
ingestRunningPanel.setMinimumSize(new java.awt.Dimension(10, 25));
ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25));
contentPanel.add(ingestRunningPanel);
programsRunLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.programsRunLabel.text")); // NOI18N
programsRunLabel.setAlignmentX(Component.LEFT_ALIGNMENT);

View File

@ -1,3 +1,5 @@
AbstractLoadableComponent_errorMessage_defaultText=There was an error loading results.
AbstractLoadableComponent_loadingMessage_defaultText=Loading results...
AbstractLoadableComponent_noDataExists_defaultText=No data exists.
IngestRunningLabel_defaultMessage=Ingest is currently running.
PieChartPanel_noDataLabel=No Data

View File

@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -140,9 +139,6 @@ public class DataFetchWorker<A, R> extends SwingWorker<R, Void> {
}
}
// otherwise, there is an error to log
logger.log(Level.WARNING, "There was an error while fetching results.", ex);
// and pass the result to the client
resultHandler.accept(DataFetchResult.getErrorResult(inner));
return;

View File

@ -36,6 +36,10 @@ public interface DefaultArtifactUpdateGovernor extends DefaultUpdateGovernor {
@Override
default boolean isRefreshRequired(ModuleDataEvent evt) {
if (evt == null || evt.getBlackboardArtifactType() == null) {
return false;
}
return getArtifactTypeIdsForRefresh().contains(evt.getBlackboardArtifactType().getTypeID());
}

View File

@ -0,0 +1,167 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.BorderLayout;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.ingest.IngestManager;
/**
* JLabel that shows ingest is running.
*/
@Messages({
"IngestRunningLabel_defaultMessage=Ingest is currently running."
})
public class IngestRunningLabel extends JPanel {
private static final long serialVersionUID = 1L;
public static final String DEFAULT_MESSAGE = Bundle.IngestRunningLabel_defaultMessage();
private static final URL DEFAULT_ICON = IngestRunningLabel.class.getResource("/org/sleuthkit/autopsy/modules/filetypeid/warning16.png");
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(
IngestManager.IngestJobEvent.STARTED,
IngestManager.IngestJobEvent.CANCELLED,
IngestManager.IngestJobEvent.COMPLETED
);
private static Set<IngestRunningLabel> activeLabels = new HashSet<>();
private static PropertyChangeListener classListener = null;
private static Object lockObject = new Object();
/**
* Setup ingest event listener for the current label.
*
* @param label The label.
*/
private static void setupListener(IngestRunningLabel label) {
synchronized (lockObject) {
// if listener is not initialized, initialize it.
if (classListener == null) {
classListener = (evt) -> {
if (evt == null) {
return;
}
if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.STARTED.toString())) {
// ingest started
notifyListeners(true);
} else if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.CANCELLED.toString())
|| evt.getPropertyName().equals(IngestManager.IngestJobEvent.COMPLETED.toString())) {
// ingest cancelled or finished
notifyListeners(false);
}
};
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, classListener);
}
// add the item to the set
activeLabels.add(label);
}
}
/**
* Notifies all listening instances of an update in ingest state.
*
* @param ingestIsRunning Whether or not ingest is running currently.
*/
private static void notifyListeners(boolean ingestIsRunning) {
synchronized (lockObject) {
for (IngestRunningLabel label : activeLabels) {
label.refreshState(ingestIsRunning);
}
}
}
/**
* Removes a label from listening events.
*
* @param label The label to remove from listening events.
*/
private static void removeListener(IngestRunningLabel label) {
synchronized (lockObject) {
activeLabels.remove(label);
if (activeLabels.isEmpty() && classListener != null) {
IngestManager.getInstance().removeIngestJobEventListener(classListener);
classListener = null;
}
}
}
/**
* Main constructor with default message and showing icon.
*/
public IngestRunningLabel() {
this(DEFAULT_MESSAGE, true);
}
/**
* Constructor.
*
* @param message The message to be shown.
* @param showWarningIcon Whether or not to show warning icon.
*/
public IngestRunningLabel(String message, boolean showWarningIcon) {
JLabel jlabel = new JLabel();
jlabel.setText(message);
if (showWarningIcon) {
jlabel.setIcon(new ImageIcon(DEFAULT_ICON));
}
setLayout(new BorderLayout());
add(jlabel, BorderLayout.NORTH);
setupListener(this);
refreshState();
}
/**
* Refresh state of this label based on ingest status.
*/
protected final void refreshState() {
refreshState(IngestManager.getInstance().isIngestRunning());
}
/**
* Refresh state of this label based on ingest status.
*
* @param ingestIsRunning True if ingest is running.
*/
protected final void refreshState(boolean ingestIsRunning) {
setVisible(ingestIsRunning);
}
/**
* Unregister this instance from listening for ingest status changes.
*/
public void unregister() {
removeListener(this);
}
}

View File

@ -33,10 +33,14 @@ import org.jfree.chart.panel.AbstractOverlay;
import org.jfree.chart.panel.Overlay;
import org.jfree.chart.plot.PiePlot;
import org.jfree.data.general.DefaultPieDataset;
import org.openide.util.NbBundle.Messages;
/**
* A pie chart panel.
*/
@Messages({
"PieChartPanel_noDataLabel=No Data"
})
public class PieChartPanel extends AbstractLoadableComponent<List<PieChartPanel.PieChartItem>> {
/**
@ -111,6 +115,13 @@ public class PieChartPanel extends AbstractLoadableComponent<List<PieChartPanel.
private static final long serialVersionUID = 1L;
private static final Font DEFAULT_FONT = new JLabel().getFont();
/**
* It appears that JFreeChart will show nothing if all values are zero. So
* this is a value close to zero but not to be displayed.
*/
private static final double NEAR_ZERO = Math.ulp(1d);
private static final Font DEFAULT_HEADER_FONT = new Font(DEFAULT_FONT.getName(), DEFAULT_FONT.getStyle(), (int) (DEFAULT_FONT.getSize() * 1.5));
private static final PieSectionLabelGenerator DEFAULT_LABEL_GENERATOR
= new StandardPieSectionLabelGenerator(
@ -192,10 +203,27 @@ public class PieChartPanel extends AbstractLoadableComponent<List<PieChartPanel.
@Override
protected void setResults(List<PieChartPanel.PieChartItem> data) {
this.dataset.clear();
if (data != null) {
if (data != null && !data.isEmpty()) {
for (PieChartPanel.PieChartItem slice : data) {
this.dataset.setValue(slice.getLabel(), slice.getValue());
}
} else {
// show a no data label if no data.
// this in fact shows a very small number for the value
// that should be way below rounding error for formatters
this.dataset.setValue(Bundle.PieChartPanel_noDataLabel(), NEAR_ZERO);
}
}
/**
* Shows a message on top of data.
*
* @param data The data.
* @param message The message.
*/
public synchronized void showDataWithMessage(List<PieChartPanel.PieChartItem> data, String message) {
setResults(data);
setMessage(true, message);
repaint();
}
}

View File

@ -1,106 +0,0 @@
FileSearchDialog.jLabel1.text=File Type
FileSearchDialog.dsCheckBox.text=Data source
FileSearchDialog.cancelButton.text=Cancel
FileSearchDialog.freqCheckBox.text=CR Frequency
FileSearchDialog.sizeCheckBox.text=Size
FileSearchDialog.kwCheckBox.text=Keyword
FileSearchDialog.addParentButton.text=Add
FileSearchDialog.deleteParentButton.text=Delete
FileSearchDialog.parentFullRadioButton.text=Full
FileSearchDialog.parentSubstringRadioButton.text=Substring
FileSearchDialog.jLabel2.text=(All will be used)
FileSearchDialog.jLabel3.text=Group by attribute:
FileSearchDialog.jLabel4.text=Order groups by:
FileSearchDialog.orderAttrRadioButton.text=Attribute
FileSearchDialog.orderSizeRadioButton.text=Group Size
FileSearchDialog.jLabel5.text=Order files by:
FileSearchDialog.parentCheckBox.text=Parent
FileSearchPanel.sortingPanel.border.title=Grouping
FileSearchPanel.addButton.text=Add
FileSearchPanel.substringRadioButton.text=Substring
FileSearchPanel.fullRadioButton.text=Full
FileSearchPanel.parentCheckbox.text=Parent Folder:
FileSearchPanel.keywordCheckbox.text=Keyword:
FileSearchPanel.crFrequencyCheckbox.text=Past Occurrences:
FileSearchPanel.dataSourceCheckbox.text=Data Source:
FileSearchPanel.sizeCheckbox.text=File Size:
FileSearchPanel.orderGroupsByLabel.text=Order Groups By:
FileSearchPanel.filtersScrollPane.border.title=Filters
FileSearchPanel.parentLabel.text=(All will be used)
FileSearchPanel.deleteButton.text=Delete
FileSearchPanel.orderByLabel.text=Order Within Groups By:
FileSearchPanel.groupByLabel.text=Group By:
FileSearchDialog.searchButton.text=Search
FileSearchDialog.hashCheckBox.text=Hash Set
FileSearchDialog.intCheckBox.text=Interesting Items
FileSearchDialog.tagsCheckBox.text=Tags
FileSearchDialog.objCheckBox.text=Objects
FileSearchDialog.exifCheckBox.text=Must contain EXIF data
FileSearchDialog.notableCheckBox.text=Must have been tagged as notable
FileSearchDialog.scoreCheckBox.text=Has score
FileSearchPanel.hashSetCheckbox.text=Hash Set:
FileSearchPanel.tagsCheckbox.text=Tag:
FileSearchPanel.interestingItemsCheckbox.text=Interesting Item:
FileSearchPanel.scoreCheckbox.text=Has Score:
FileSearchPanel.notableCheckbox.text=Must have been tagged as notable
FileSearchPanel.objectsCheckbox.text=Object Detected:
ResultsPanel.currentPageLabel.text=Page: -
ResultsPanel.pageControlsLabel.text=Pages:
ResultsPanel.gotoPageLabel.text=Go to Page:
ResultsPanel.pageSizeLabel.text=Page Size:
DiscoveryExtractAction.title.extractFiles.text=Extract File
FileSearchPanel.includeRadioButton.text=Include
FileSearchPanel.excludeRadioButton.text=Exclude
FileSearchPanel.knownFilesCheckbox.toolTipText=
FileSearchPanel.knownFilesCheckbox.text=Hide known files
GroupListPanel.groupKeyList.border.title=Groups
FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings
DocumentPanel.fileSizeLabel.toolTipText=
DocumentPanel.isDeletedLabel.toolTipText=
ImageThumbnailPanel.isDeletedLabel.toolTipText=
FileSearchPanel.userCreatedCheckbox.text=Possibly User Created
DiscoveryDialog.documentsButton.text=Documents
DiscoveryDialog.videosButton.text=Videos
DiscoveryDialog.imagesButton.text=Images
DiscoveryDialog.searchButton.text=Search
DetailsPanel.instancesList.border.title=Instances
SizeFilterPanel.sizeCheckbox.text=File Size:
DataSourceFilterPanel.dataSourceCheckbox.text=Data Source:
UserCreatedFilterPanel.userCreatedCheckbox.text=Possibly User Created
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
InterestingItemFilterPanel.interestingItemsCheckbox.text=Interesting Item:
ParentFolderFilterPanel.parentCheckbox.text=Parent Folder:
ParentFolderFilterPanel.deleteButton.text=Delete
ParentFolderFilterPanel.excludeRadioButton.text=Exclude
ParentFolderFilterPanel.includeRadioButton.text=Include
ParentFolderFilterPanel.substringRadioButton.text=Substring
ParentFolderFilterPanel.fullRadioButton.text=Full
ParentFolderFilterPanel.parentLabel.text=(All will be used)
ParentFolderFilterPanel.addButton.text=Add
ParentFolderFilterPanel.parentCheckbox.text_1=Parent Folder:
ParentFolderFilterPanel.addButton.text_1=Add
ParentFolderFilterPanel.deleteButton.text_1=Delete
ParentFolderFilterPanel.excludeRadioButton.text_1=Exclude
ParentFolderFilterPanel.substringRadioButton.text_1=Substring
ParentFolderFilterPanel.includeRadioButton.text_1=Include
ParentFolderFilterPanel.fullRadioButton.text_1=Full
ParentFolderFilterPanel.parentLabel.text_1=(All will be used)
InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item:
UserCreatedFilterPanel.userCreatedCheckbox.text_1=Possibly User Created
PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
ObjectDetectedFilterPanel.text=Object Detected:
DiscoveryDialog.sortingPanel.border.title=Step 3: Choose display settings
DiscoveryDialog.groupByLabel.text=Group By:
DiscoveryDialog.orderByLabel.text=Order Within Groups By:
DiscoveryDialog.orderGroupsByLabel.text=Order Groups By:
ImageFilterPanel.imageFiltersSplitPane.toolTipText=
DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
DiscoveryDialog.step1Label.text=Step 1: Choose result type
ResultsSplitPaneDivider.hideButton.text=
ResultsSplitPaneDivider.showButton.text=
ResultsSplitPaneDivider.detailsLabel.text=Details Area

View File

@ -1,280 +0,0 @@
CTL_OpenDiscoveryAction=Discovery
# {0} - dataSourceName
DataSourceModuleWrapper.exifModule.text=Picture Analyzer module was not run on data source: {0}\n
# {0} - dataSourceName
DataSourceModuleWrapper.fileTypeModule.text=File Type Identification module was not run on data source: {0}\n
# {0} - dataSourceName
DataSourceModuleWrapper.hashModule.text=Hash Lookup module was not run on data source: {0}\n
DiscoveryDialog.name.text=Discovery
DiscoveryTopComponent.cancelButton.text=Cancel Search
DiscoveryTopComponent.name=\ Discovery
DiscoveryTopComponent.newSearch.text=New Search
DiscoveryTopComponent.searchCancelled.text=Search has been cancelled.
# {0} - search
DiscoveryTopComponent.searchComplete.text=Results with {0}
# {0} - searchType
DiscoveryTopComponent.searchInProgress.text=Performing search for results of type {0}. Please wait.
DiscoveryUiUtility.bytes.text=bytes
DiscoveryUiUtility.gigaBytes.text=GB
DiscoveryUiUtility.kiloBytes.text=KB
DiscoveryUiUtility.megaBytes.text=MB
# {0} - fileSize
# {1} - units
DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}
DiscoveryUiUtility.terraBytes.text=TB
# {0} - otherInstanceCount
DocumentPanel.nameLabel.more.text=\ and {0} more
DocumentPanel.noImageExtraction.text=0 of ? images
DocumentPanel.numberOfImages.noImages=No images
# {0} - numberOfImages
DocumentPanel.numberOfImages.text=1 of {0} images
DocumentWrapper.previewInitialValue=Preview not generated yet.
FileGroup.groupSortingAlgorithm.groupName.text=Group Name
FileGroup.groupSortingAlgorithm.groupSize.text=Group Size
# {0} - Data source name
# {1} - Data source ID
FileSearch.DataSourceGroupKey.datasourceAndID={0}(ID: {1})
# {0} - Data source ID
FileSearch.DataSourceGroupKey.idOnly=Data source (ID: {0})
FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview.
FileSearch.documentSummary.noPreview=No preview available.
FileSearch.FileTagGroupKey.noSets=None
# {0} - file name
FileSearch.genVideoThumb.progress.text=extracting temporary file {0}
FileSearch.GroupingAttributeType.datasource.displayName=Data Source
FileSearch.GroupingAttributeType.fileType.displayName=File Type
FileSearch.GroupingAttributeType.frequency.displayName=Past Occurrences
FileSearch.GroupingAttributeType.hash.displayName=Hash Set
FileSearch.GroupingAttributeType.interestingItem.displayName=Interesting Item
FileSearch.GroupingAttributeType.keywordList.displayName=Keyword
FileSearch.GroupingAttributeType.none.displayName=None
FileSearch.GroupingAttributeType.object.displayName=Object Detected
FileSearch.GroupingAttributeType.parent.displayName=Parent Folder
FileSearch.GroupingAttributeType.size.displayName=File Size
FileSearch.GroupingAttributeType.tag.displayName=Tag
FileSearch.HashHitsGroupKey.noHashHits=None
FileSearch.InterestingItemGroupKey.noSets=None
FileSearch.KeywordListGroupKey.noKeywords=None
FileSearch.NoGroupingGroupKey.allFiles=All Files
FileSearch.ObjectDetectedGroupKey.noSets=None
FileSearchData.FileSize.100kbto1mb=: 100KB-1MB
FileSearchData.FileSize.100mbto1gb=: 100MB-1GB
FileSearchData.FileSize.10PlusGb=: 10GB+
FileSearchData.FileSize.16kbto100kb=: 16-100KB
FileSearchData.FileSize.1gbto5gb=: 1-5GB
FileSearchData.FileSize.1mbto50mb=: 1-50MB
FileSearchData.FileSize.200PlusMb=: 200MB+
FileSearchData.FileSize.500kbto100mb=: 500KB-100MB
FileSearchData.FileSize.50mbto200mb=: 50-200MB
FileSearchData.FileSize.5gbto10gb=: 5-10GB
FileSearchData.FileSize.LARGE.displayName=Large
FileSearchData.FileSize.MEDIUM.displayName=Medium
FileSearchData.FileSize.SMALL.displayName=Small
FileSearchData.FileSize.upTo16kb=: 0-16KB
FileSearchData.FileSize.upTo500kb=: 0-500KB
FileSearchData.FileSize.XLARGE.displayName=XLarge
FileSearchData.FileSize.XSMALL.displayName=XSmall
FileSearchData.FileSize.XXLARGE.displayName=XXLarge
FileSearchData.FileType.Audio.displayName=Audio
FileSearchData.FileType.Documents.displayName=Documents
FileSearchData.FileType.Executables.displayName=Executables
FileSearchData.FileType.Image.displayName=Image
FileSearchData.FileType.Other.displayName=Other/Unknown
FileSearchData.FileType.Video.displayName=Video
FileSearchData.Frequency.common.displayName=Common (11 - 100)
FileSearchData.Frequency.known.displayName=Known (NSRL)
FileSearchData.Frequency.rare.displayName=Rare (2-10)
FileSearchData.Frequency.unique.displayName=Unique (1)
FileSearchData.Frequency.unknown.displayName=Unknown
FileSearchData.Frequency.verycommon.displayName=Very Common (100+)
FileSearchData.Score.interesting.displayName=Interesting
FileSearchData.Score.notable.displayName=Notable
FileSearchData.Score.unknown.displayName=Unknown
FileSearchDialog.jLabel1.text=File Type
FileSearchDialog.dsCheckBox.text=Data source
FileSearchDialog.cancelButton.text=Cancel
FileSearchDialog.freqCheckBox.text=CR Frequency
FileSearchDialog.sizeCheckBox.text=Size
FileSearchDialog.kwCheckBox.text=Keyword
FileSearchDialog.addParentButton.text=Add
FileSearchDialog.deleteParentButton.text=Delete
FileSearchDialog.parentFullRadioButton.text=Full
FileSearchDialog.parentSubstringRadioButton.text=Substring
FileSearchDialog.jLabel2.text=(All will be used)
FileSearchDialog.jLabel3.text=Group by attribute:
FileSearchDialog.jLabel4.text=Order groups by:
FileSearchDialog.orderAttrRadioButton.text=Attribute
FileSearchDialog.orderSizeRadioButton.text=Group Size
FileSearchDialog.jLabel5.text=Order files by:
FileSearchDialog.parentCheckBox.text=Parent
FileSearchFiltering.concatenateSetNamesForDisplay.comma=,
# {0} - Data source name
# {1} - Data source ID
FileSearchFiltering.DataSourceFilter.datasource={0}({1})
# {0} - filters
FileSearchFiltering.DataSourceFilter.desc=Data source(s): {0}
FileSearchFiltering.DataSourceFilter.or=,
# {0} - filters
FileSearchFiltering.FileTypeFilter.desc=Type: {0}
FileSearchFiltering.FileTypeFilter.or=,
# {0} - filters
FileSearchFiltering.FrequencyFilter.desc=Past occurrences: {0}
FileSearchFiltering.FrequencyFilter.or=,
# {0} - filters
FileSearchFiltering.HashSetFilter.desc=Hash set hits in set(s): {0}
# {0} - filters
FileSearchFiltering.InterestingItemSetFilter.desc=Interesting item hits in set(s): {0}
# {0} - filters
FileSearchFiltering.KeywordListFilter.desc=Keywords in list(s): {0}
FileSearchFiltering.KnownFilter.desc=which are not known
# {0} - filters
FileSearchFiltering.ObjectDetectionFilter.desc=Objects detected in set(s): {0}
# {0} - filters
FileSearchFiltering.ParentFilter.desc=Paths matching: {0}
FileSearchFiltering.ParentFilter.exact=(exact match)
FileSearchFiltering.ParentFilter.excluded=(excluded)
FileSearchFiltering.ParentFilter.included=(included)
FileSearchFiltering.ParentFilter.or=,
FileSearchFiltering.ParentFilter.substring=(substring)
FileSearchFiltering.ParentSearchTerm.excludeString=\ (exclude)
FileSearchFiltering.ParentSearchTerm.fullString=\ (exact)
FileSearchFiltering.ParentSearchTerm.includeString=\ (include)
FileSearchFiltering.ParentSearchTerm.subString=\ (substring)
FileSearchFiltering.PreviouslyNotableFilter.desc=that were previously marked as notable
# {0} - filters
FileSearchFiltering.ScoreFilter.desc=Score(s) of : {0}
# {0} - filters
FileSearchFiltering.SizeFilter.desc=Size(s): {0}
FileSearchFiltering.SizeFilter.or=,
# {0} - tag names
FileSearchFiltering.TagsFilter.desc=Tagged {0}
FileSearchFiltering.TagsFilter.or=,
FileSearchFiltering.UserCreatedFilter.desc=that contain EXIF data
FileSearchPanel.sortingPanel.border.title=Grouping
FileSearchPanel.addButton.text=Add
FileSearchPanel.substringRadioButton.text=Substring
FileSearchPanel.fullRadioButton.text=Full
FileSearchPanel.parentCheckbox.text=Parent Folder:
FileSearchPanel.keywordCheckbox.text=Keyword:
FileSearchPanel.crFrequencyCheckbox.text=Past Occurrences:
FileSearchPanel.dataSourceCheckbox.text=Data Source:
FileSearchPanel.sizeCheckbox.text=File Size:
FileSearchPanel.orderGroupsByLabel.text=Order Groups By:
FileSearchPanel.filtersScrollPane.border.title=Filters
FileSearchPanel.parentLabel.text=(All will be used)
FileSearchPanel.deleteButton.text=Delete
FileSearchPanel.orderByLabel.text=Order Within Groups By:
FileSearchPanel.groupByLabel.text=Group By:
FileSearchDialog.searchButton.text=Search
FileSearchDialog.hashCheckBox.text=Hash Set
FileSearchDialog.intCheckBox.text=Interesting Items
FileSearchDialog.tagsCheckBox.text=Tags
FileSearchDialog.objCheckBox.text=Objects
FileSearchDialog.exifCheckBox.text=Must contain EXIF data
FileSearchDialog.notableCheckBox.text=Must have been tagged as notable
FileSearchDialog.scoreCheckBox.text=Has score
FileSearchPanel.hashSetCheckbox.text=Hash Set:
FileSearchPanel.tagsCheckbox.text=Tag:
FileSearchPanel.interestingItemsCheckbox.text=Interesting Item:
FileSearchPanel.scoreCheckbox.text=Has Score:
FileSearchPanel.notableCheckbox.text=Must have been tagged as notable
FileSearchPanel.objectsCheckbox.text=Object Detected:
FileSorter.SortingMethod.datasource.displayName=Data Source
FileSorter.SortingMethod.filename.displayName=File Name
FileSorter.SortingMethod.filesize.displayName=File Size
FileSorter.SortingMethod.filetype.displayName=File Type
FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency
FileSorter.SortingMethod.fullPath.displayName=Full Path
FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names
GroupsListPanel.noResults.message.text=No results were found for the selected filters.\n\nReminder:\n -The File Type Identification module must be run on each data source you want to find results in.\n -The Hash Lookup module must be run on each data source if you want to filter by past occurrence.\n -The Exif module must be run on each data source if you are filtering by User Created content.
GroupsListPanel.noResults.title.text=No results found
ImageThumbnailPanel.isDeleted.text=All instances of file are deleted.
# {0} - otherInstanceCount
ImageThumbnailPanel.nameLabel.more.text=\ and {0} more
OpenDiscoveryAction.resultsIncomplete.text=Discovery results may be incomplete
ResultFile.score.interestingResult.description=At least one instance of the file has an interesting result associated with it.
ResultFile.score.notableFile.description=At least one instance of the file was recognized as notable.
ResultFile.score.notableTaggedFile.description=At least one instance of the file is tagged with a notable tag.
ResultFile.score.taggedFile.description=At least one instance of the file has been tagged.
# {0} - currentPage
# {1} - totalPages
ResultsPanel.currentPage.displayValue=Page: {0} of {1}
ResultsPanel.currentPageLabel.text=Page: -
ResultsPanel.documentPreview.text=Document preview creation cancelled.
# {0} - selectedPage
# {1} - maxPage
ResultsPanel.invalidPageNumber.message=The selected page number {0} does not exist. Please select a value from 1 to {1}.
ResultsPanel.invalidPageNumber.title=Invalid Page Number
ResultsPanel.openInExternalViewer.name=Open in External Viewer
ResultsPanel.pageControlsLabel.text=Pages:
ResultsPanel.gotoPageLabel.text=Go to Page:
ResultsPanel.pageSizeLabel.text=Page Size:
DiscoveryExtractAction.title.extractFiles.text=Extract File
FileSearchPanel.includeRadioButton.text=Include
FileSearchPanel.excludeRadioButton.text=Exclude
FileSearchPanel.knownFilesCheckbox.toolTipText=
FileSearchPanel.knownFilesCheckbox.text=Hide known files
GroupListPanel.groupKeyList.border.title=Groups
FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings
DocumentPanel.fileSizeLabel.toolTipText=
DocumentPanel.isDeletedLabel.toolTipText=
ImageThumbnailPanel.isDeletedLabel.toolTipText=
FileSearchPanel.userCreatedCheckbox.text=Possibly User Created
DiscoveryDialog.documentsButton.text=Documents
DiscoveryDialog.videosButton.text=Videos
DiscoveryDialog.imagesButton.text=Images
DiscoveryDialog.searchButton.text=Search
DetailsPanel.instancesList.border.title=Instances
ResultsPanel.unableToCreate.text=Unable to create summary.
ResultsPanel.viewFileInDir.name=View File in Directory
SizeFilterPanel.sizeCheckbox.text=File Size:
DataSourceFilterPanel.dataSourceCheckbox.text=Data Source:
UserCreatedFilterPanel.userCreatedCheckbox.text=Possibly User Created
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
InterestingItemFilterPanel.interestingItemsCheckbox.text=Interesting Item:
ParentFolderFilterPanel.parentCheckbox.text=Parent Folder:
ParentFolderFilterPanel.deleteButton.text=Delete
ParentFolderFilterPanel.excludeRadioButton.text=Exclude
ParentFolderFilterPanel.includeRadioButton.text=Include
ParentFolderFilterPanel.substringRadioButton.text=Substring
ParentFolderFilterPanel.fullRadioButton.text=Full
ParentFolderFilterPanel.parentLabel.text=(All will be used)
ParentFolderFilterPanel.addButton.text=Add
ParentFolderFilterPanel.parentCheckbox.text_1=Parent Folder:
ParentFolderFilterPanel.addButton.text_1=Add
ParentFolderFilterPanel.deleteButton.text_1=Delete
ParentFolderFilterPanel.excludeRadioButton.text_1=Exclude
ParentFolderFilterPanel.substringRadioButton.text_1=Substring
ParentFolderFilterPanel.includeRadioButton.text_1=Include
ParentFolderFilterPanel.fullRadioButton.text_1=Full
ParentFolderFilterPanel.parentLabel.text_1=(All will be used)
InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item:
UserCreatedFilterPanel.userCreatedCheckbox.text_1=Possibly User Created
PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
ObjectDetectedFilterPanel.text=Object Detected:
DiscoveryDialog.sortingPanel.border.title=Step 3: Choose display settings
DiscoveryDialog.groupByLabel.text=Group By:
DiscoveryDialog.orderByLabel.text=Order Within Groups By:
DiscoveryDialog.orderGroupsByLabel.text=Order Groups By:
ImageFilterPanel.imageFiltersSplitPane.toolTipText=
DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
DiscoveryDialog.step1Label.text=Step 1: Choose result type
ResultsSplitPaneDivider.hideButton.text=
ResultsSplitPaneDivider.showButton.text=
ResultsSplitPaneDivider.detailsLabel.text=Details Area
VideoThumbnailPanel.bytes.text=bytes
VideoThumbnailPanel.deleted.text=All instances of file are deleted.
VideoThumbnailPanel.gigaBytes.text=GB
VideoThumbnailPanel.kiloBytes.text=KB
VideoThumbnailPanel.megaBytes.text=MB
# {0} - otherInstanceCount
VideoThumbnailPanel.nameLabel.more.text=\ and {0} more
# {0} - fileSize
# {1} - units
VideoThumbnailPanel.sizeLabel.text=Size: {0} {1}
VideoThumbnailPanel.terraBytes.text=TB

View File

@ -1,253 +0,0 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utility class for the various user interface elements used by Discovery.
*/
final class DiscoveryUiUtils {
private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
private static final int BYTE_UNIT_CONVERSION = 1000;
private static final int ICON_SIZE = 16;
private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
private static final String DELETE_ICON_PATH = "org/sleuthkit/autopsy/images/file-icon-deleted.png";
private static final String UNSUPPORTED_DOC_PATH = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false));
private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, false));
@NbBundle.Messages({"# {0} - fileSize",
"# {1} - units",
"DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
"DiscoveryUiUtility.bytes.text=bytes",
"DiscoveryUiUtility.kiloBytes.text=KB",
"DiscoveryUiUtility.megaBytes.text=MB",
"DiscoveryUiUtility.gigaBytes.text=GB",
"DiscoveryUiUtility.terraBytes.text=TB"})
/**
* Convert a size in bytes to a string with representing the size in the
* largest units which represent the value as being greater than or equal to
* one. Result will be rounded down to the nearest whole number of those
* units.
*
* @param bytes Size in bytes.
*/
static String getFileSizeString(long bytes) {
long size = bytes;
int unitsSwitchValue = 0;
while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
size /= BYTE_UNIT_CONVERSION;
unitsSwitchValue++;
}
String units;
switch (unitsSwitchValue) {
case 1:
units = Bundle.DiscoveryUiUtility_kiloBytes_text();
break;
case 2:
units = Bundle.DiscoveryUiUtility_megaBytes_text();
break;
case 3:
units = Bundle.DiscoveryUiUtility_gigaBytes_text();
break;
case 4:
units = Bundle.DiscoveryUiUtility_terraBytes_text();
break;
default:
units = Bundle.DiscoveryUiUtility_bytes_text();
break;
}
return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
}
/**
* Get the image to use when the document type does not support image
* extraction.
*
* @return An image that indicates we don't know if there are images.
*/
static ImageIcon getUnsupportedImageThumbnail() {
return UNSUPPORTED_DOCUMENT_THUMBNAIL;
}
/**
* Get the names of the sets which exist in the case database for the
* specified artifact and attribute types.
*
* @param artifactType The artifact type to get the list of sets for.
* @param setNameAttribute The attribute type which contains the set names.
*
* @return A list of set names which exist in the case for the specified
* artifact and attribute types.
*
* @throws TskCoreException
*/
static List<String> getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
List<BlackboardArtifact> arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
List<String> setNames = new ArrayList<>();
for (BlackboardArtifact art : arts) {
for (BlackboardAttribute attr : art.getAttributes()) {
if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
String setName = attr.getValueString();
if (!setNames.contains(setName)) {
setNames.add(setName);
}
}
}
}
Collections.sort(setNames);
return setNames;
}
/**
* Helper method to see if point is on the icon.
*
* @param comp The component to check if the cursor is over the icon of
* @param point The point the cursor is at.
*
* @return True if the point is over the icon, false otherwise.
*/
static boolean isPointOnIcon(Component comp, Point point) {
return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
}
/**
* Method to set the icon and tool tip text for a label to show deleted
* status.
*
* @param isDeleted True if the label should reflect deleted status,
* false otherwise.
* @param isDeletedLabel The label to set the icon and tooltip for.
*/
static void setDeletedIcon(boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
if (isDeleted) {
isDeletedLabel.setIcon(DELETED_ICON);
isDeletedLabel.setToolTipText(Bundle.ImageThumbnailPanel_isDeleted_text());
} else {
isDeletedLabel.setIcon(null);
isDeletedLabel.setToolTipText(null);
}
}
/**
* Method to set the icon and tool tip text for a label to show the score.
*
* @param resultFile The result file which the label should reflect the
* score of.
* @param scoreLabel The label to set the icon and tooltip for.
*/
static void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
switch (resultFile.getScore()) {
case NOTABLE_SCORE:
scoreLabel.setIcon(NOTABLE_SCORE_ICON);
break;
case INTERESTING_SCORE:
scoreLabel.setIcon(INTERESTING_SCORE_ICON);
break;
case NO_SCORE: // empty case - this is interpreted as an intentional fall-through
default:
scoreLabel.setIcon(null);
break;
}
scoreLabel.setToolTipText(resultFile.getScoreDescription());
}
/**
* Get the size of the icons used by the UI.
*
* @return
*/
static int getIconSize() {
return ICON_SIZE;
}
/**
* Helper method to display an error message when the results of the
* Discovery Top component may be incomplete.
*/
static void displayErrorMessage(DiscoveryDialog dialog) {
//check if modules run and assemble message
try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
Map<Long, DataSourceModulesWrapper> dataSourceIngestModules = new HashMap<>();
for (DataSource dataSource : skCase.getDataSources()) {
dataSourceIngestModules.put(dataSource.getId(), new DataSourceModulesWrapper(dataSource.getName()));
}
for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
}
String message = "";
for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
message += dsmodulesWrapper.getMessage();
}
if (!message.isEmpty()) {
JScrollPane messageScrollPane = new JScrollPane();
JTextPane messageTextPane = new JTextPane();
messageTextPane.setText(message);
messageTextPane.setVisible(true);
messageTextPane.setEditable(false);
messageTextPane.setCaretPosition(0);
messageScrollPane.setMaximumSize(new Dimension(600, 100));
messageScrollPane.setPreferredSize(new Dimension(600, 100));
messageScrollPane.setViewportView(messageTextPane);
JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.OpenDiscoveryAction_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
}
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.WARNING, "Exception while determining which modules have been run for Discovery", ex);
}
dialog.validateDialog();
}
/**
* Private constructor for DiscoveryUiUtils utility class.
*/
private DiscoveryUiUtils() {
//private constructor in a utility class intentionally left blank
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,444 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils;
/**
* Utility enums for FileSearch.
*/
final class FileSearchData {
private final static long BYTES_PER_MB = 1000000;
/**
* Enum representing how often the file occurs in the Central Repository.
*/
@NbBundle.Messages({
"FileSearchData.Frequency.unique.displayName=Unique (1)",
"FileSearchData.Frequency.rare.displayName=Rare (2-10)",
"FileSearchData.Frequency.common.displayName=Common (11 - 100)",
"FileSearchData.Frequency.verycommon.displayName=Very Common (100+)",
"FileSearchData.Frequency.known.displayName=Known (NSRL)",
"FileSearchData.Frequency.unknown.displayName=Unknown",})
enum Frequency {
UNIQUE(0, 1, Bundle.FileSearchData_Frequency_unique_displayName()),
RARE(1, 10, Bundle.FileSearchData_Frequency_rare_displayName()),
COMMON(2, 100, Bundle.FileSearchData_Frequency_common_displayName()),
VERY_COMMON(3, 0, Bundle.FileSearchData_Frequency_verycommon_displayName()),
KNOWN(4, 0, Bundle.FileSearchData_Frequency_known_displayName()),
UNKNOWN(5, 0, Bundle.FileSearchData_Frequency_unknown_displayName());
private final int ranking;
private final String displayName;
private final int maxOccur;
Frequency(int ranking, int maxOccur, String displayName) {
this.ranking = ranking;
this.maxOccur = maxOccur;
this.displayName = displayName;
}
/**
* Get the rank for sorting.
*
* @return the rank (lower should be displayed first)
*/
int getRanking() {
return ranking;
}
/**
* Get the enum matching the given occurrence count.
*
* @param count Number of times a file is in the Central Repository.
*
* @return the corresponding enum
*/
static Frequency fromCount(long count) {
if (count <= UNIQUE.getMaxOccur()) {
return UNIQUE;
} else if (count <= RARE.getMaxOccur()) {
return RARE;
} else if (count <= COMMON.getMaxOccur()) {
return COMMON;
}
return VERY_COMMON;
}
/**
* Get the list of enums that are valid for filtering when a CR is
* enabled.
*
* @return enums that can be used to filter with a CR.
*/
static List<Frequency> getOptionsForFilteringWithCr() {
return Arrays.asList(UNIQUE, RARE, COMMON, VERY_COMMON, KNOWN);
}
/**
* Get the list of enums that are valid for filtering when no CR is
* enabled.
*
* @return enums that can be used to filter without a CR.
*/
static List<Frequency> getOptionsForFilteringWithoutCr() {
return Arrays.asList(KNOWN, UNKNOWN);
}
@Override
public String toString() {
return displayName;
}
/**
* @return the maxOccur
*/
int getMaxOccur() {
return maxOccur;
}
}
/**
* Enum representing the file size
*/
@NbBundle.Messages({
"FileSearchData.FileSize.XXLARGE.displayName=XXLarge",
"FileSearchData.FileSize.XLARGE.displayName=XLarge",
"FileSearchData.FileSize.LARGE.displayName=Large",
"FileSearchData.FileSize.MEDIUM.displayName=Medium",
"FileSearchData.FileSize.SMALL.displayName=Small",
"FileSearchData.FileSize.XSMALL.displayName=XSmall",
"FileSearchData.FileSize.10PlusGb=: 10GB+",
"FileSearchData.FileSize.5gbto10gb=: 5-10GB",
"FileSearchData.FileSize.1gbto5gb=: 1-5GB",
"FileSearchData.FileSize.100mbto1gb=: 100MB-1GB",
"FileSearchData.FileSize.200PlusMb=: 200MB+",
"FileSearchData.FileSize.50mbto200mb=: 50-200MB",
"FileSearchData.FileSize.500kbto100mb=: 500KB-100MB",
"FileSearchData.FileSize.1mbto50mb=: 1-50MB",
"FileSearchData.FileSize.100kbto1mb=: 100KB-1MB",
"FileSearchData.FileSize.16kbto100kb=: 16-100KB",
"FileSearchData.FileSize.upTo500kb=: 0-500KB",
"FileSearchData.FileSize.upTo16kb=: 0-16KB",})
enum FileSize {
XXLARGE_VIDEO(0, 10000 * BYTES_PER_MB, -1, Bundle.FileSearchData_FileSize_XXLARGE_displayName(), Bundle.FileSearchData_FileSize_10PlusGb()),
XLARGE_VIDEO(1, 5000 * BYTES_PER_MB, 10000 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_XLARGE_displayName(), Bundle.FileSearchData_FileSize_5gbto10gb()),
LARGE_VIDEO(2, 1000 * BYTES_PER_MB, 5000 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_LARGE_displayName(), Bundle.FileSearchData_FileSize_1gbto5gb()),
MEDIUM_VIDEO(3, 100 * BYTES_PER_MB, 1000 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_MEDIUM_displayName(), Bundle.FileSearchData_FileSize_100mbto1gb()),
SMALL_VIDEO(4, 500000, 100 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_SMALL_displayName(), Bundle.FileSearchData_FileSize_500kbto100mb()),
XSMALL_VIDEO(5, 0, 500000, Bundle.FileSearchData_FileSize_XSMALL_displayName(), Bundle.FileSearchData_FileSize_upTo500kb()),
XXLARGE_IMAGE(6, 200 * BYTES_PER_MB, -1, Bundle.FileSearchData_FileSize_XXLARGE_displayName(), Bundle.FileSearchData_FileSize_200PlusMb()),
XLARGE_IMAGE(7, 50 * BYTES_PER_MB, 200 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_XLARGE_displayName(), Bundle.FileSearchData_FileSize_50mbto200mb()),
LARGE_IMAGE(8, 1 * BYTES_PER_MB, 50 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_LARGE_displayName(), Bundle.FileSearchData_FileSize_1mbto50mb()),
MEDIUM_IMAGE(9, 100000, 1 * BYTES_PER_MB, Bundle.FileSearchData_FileSize_MEDIUM_displayName(), Bundle.FileSearchData_FileSize_100kbto1mb()),
SMALL_IMAGE(10, 16000, 100000, Bundle.FileSearchData_FileSize_SMALL_displayName(), Bundle.FileSearchData_FileSize_16kbto100kb()),
XSMALL_IMAGE(11, 0, 16000, Bundle.FileSearchData_FileSize_XSMALL_displayName(), Bundle.FileSearchData_FileSize_upTo16kb());
private final int ranking; // Must be unique for each value
private final long minBytes; // Note that the size must be strictly greater than this to match
private final long maxBytes;
private final String sizeGroup;
private final String displaySize;
final static long NO_MAXIMUM = -1;
FileSize(int ranking, long minB, long maxB, String displayName, String displaySize) {
this.ranking = ranking;
this.minBytes = minB;
if (maxB >= 0) {
this.maxBytes = maxB;
} else {
this.maxBytes = NO_MAXIMUM;
}
this.sizeGroup = displayName;
this.displaySize = displaySize;
}
/**
* Get the enum corresponding to the given file size for image files.
* The file size must be strictly greater than minBytes.
*
* @param size the file size
*
* @return the enum whose range contains the file size
*/
static FileSize fromImageSize(long size) {
if (size > XXLARGE_IMAGE.getMinBytes()) {
return XXLARGE_IMAGE;
} else if (size > XLARGE_IMAGE.getMinBytes()) {
return XLARGE_IMAGE;
} else if (size > LARGE_IMAGE.getMinBytes()) {
return LARGE_IMAGE;
} else if (size > MEDIUM_IMAGE.getMinBytes()) {
return MEDIUM_IMAGE;
} else if (size > SMALL_IMAGE.getMinBytes()) {
return SMALL_IMAGE;
} else {
return XSMALL_IMAGE;
}
}
/**
* Get the enum corresponding to the given file size for video files.
* The file size must be strictly greater than minBytes.
*
* @param size the file size
*
* @return the enum whose range contains the file size
*/
static FileSize fromVideoSize(long size) {
if (size > XXLARGE_VIDEO.getMinBytes()) {
return XXLARGE_VIDEO;
} else if (size > XLARGE_VIDEO.getMinBytes()) {
return XLARGE_VIDEO;
} else if (size > LARGE_VIDEO.getMinBytes()) {
return LARGE_VIDEO;
} else if (size > MEDIUM_VIDEO.getMinBytes()) {
return MEDIUM_VIDEO;
} else if (size > SMALL_VIDEO.getMinBytes()) {
return SMALL_VIDEO;
} else {
return XSMALL_VIDEO;
}
}
/**
* Get the upper limit of the range.
*
* @return the maximum file size that will fit in this range.
*/
long getMaxBytes() {
return maxBytes;
}
/**
* Get the lower limit of the range.
*
* @return the maximum file size that is not part of this range
*/
long getMinBytes() {
return minBytes;
}
/**
* Get the rank for sorting.
*
* @return the rank (lower should be displayed first)
*/
int getRanking() {
return ranking;
}
@Override
public String toString() {
return sizeGroup + displaySize;
}
String getSizeGroup(){
return sizeGroup;
}
/**
* Get the list of enums that are valid for most file sizes.
*
* @return Enums that can be used to filter most file including images
* by size.
*/
static List<FileSize> getDefaultSizeOptions() {
return Arrays.asList(XXLARGE_IMAGE, XLARGE_IMAGE, LARGE_IMAGE, MEDIUM_IMAGE, SMALL_IMAGE, XSMALL_IMAGE);
}
/**
* Get the list of enums that are valid for video sizes.
*
* @return enums that can be used to filter videos by size.
*/
static List<FileSize> getOptionsForVideos() {
return Arrays.asList(XXLARGE_VIDEO, XLARGE_VIDEO, LARGE_VIDEO, MEDIUM_VIDEO, SMALL_VIDEO, XSMALL_VIDEO);
}
}
//Discovery uses a different list of document mime types than FileTypeUtils.FileTypeCategory.DOCUMENTS
private static final ImmutableSet<String> DOCUMENT_MIME_TYPES
= new ImmutableSet.Builder<String>()
.add("text/html", //NON-NLS
"text/csv", //NON-NLS
"application/rtf", //NON-NLS
"application/pdf", //NON-NLS
"application/xhtml+xml", //NON-NLS
"application/x-msoffice", //NON-NLS
"application/msword", //NON-NLS
"application/msword2", //NON-NLS
"application/vnd.wordperfect", //NON-NLS
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
"application/vnd.ms-powerpoint", //NON-NLS
"application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
"application/vnd.ms-excel", //NON-NLS
"application/vnd.ms-excel.sheet.4", //NON-NLS
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
"application/vnd.oasis.opendocument.presentation", //NON-NLS
"application/vnd.oasis.opendocument.spreadsheet", //NON-NLS
"application/vnd.oasis.opendocument.text" //NON-NLS
).build();
private static final ImmutableSet<String> IMAGE_UNSUPPORTED_DOC_TYPES
= new ImmutableSet.Builder<String>()
.add("application/pdf", //NON-NLS
"application/xhtml+xml").build(); //NON-NLS
static Collection<String> getDocTypesWithoutImageExtraction() {
return Collections.unmodifiableCollection(IMAGE_UNSUPPORTED_DOC_TYPES);
}
/**
* Enum representing the file type. We don't simply use
* FileTypeUtils.FileTypeCategory because: - Some file types categories
* overlap - It is convenient to have the "OTHER" option for files that
* don't match the given types
*/
@NbBundle.Messages({
"FileSearchData.FileType.Audio.displayName=Audio",
"FileSearchData.FileType.Video.displayName=Video",
"FileSearchData.FileType.Image.displayName=Image",
"FileSearchData.FileType.Documents.displayName=Documents",
"FileSearchData.FileType.Executables.displayName=Executables",
"FileSearchData.FileType.Other.displayName=Other/Unknown"})
enum FileType {
IMAGE(0, Bundle.FileSearchData_FileType_Image_displayName(), FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes()),
AUDIO(1, Bundle.FileSearchData_FileType_Audio_displayName(), FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes()),
VIDEO(2, Bundle.FileSearchData_FileType_Video_displayName(), FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes()),
EXECUTABLE(3, Bundle.FileSearchData_FileType_Executables_displayName(), FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes()),
DOCUMENTS(4, Bundle.FileSearchData_FileType_Documents_displayName(), DOCUMENT_MIME_TYPES),
OTHER(5, Bundle.FileSearchData_FileType_Other_displayName(), new ArrayList<>());
private final int ranking; // For ordering in the UI
private final String displayName;
private final Collection<String> mediaTypes;
FileType(int value, String displayName, Collection<String> mediaTypes) {
this.ranking = value;
this.displayName = displayName;
this.mediaTypes = mediaTypes;
}
/**
* Get the MIME types matching this category.
*
* @return Collection of MIME type strings
*/
Collection<String> getMediaTypes() {
return Collections.unmodifiableCollection(mediaTypes);
}
@Override
public String toString() {
return displayName;
}
/**
* Get the rank for sorting.
*
* @return the rank (lower should be displayed first)
*/
int getRanking() {
return ranking;
}
/**
* Get the enum matching the given MIME type.
*
* @param mimeType The MIME type for the file
*
* @return the corresponding enum (will be OTHER if no types matched)
*/
static FileType fromMIMEtype(String mimeType) {
for (FileType type : FileType.values()) {
if (type.getMediaTypes().contains(mimeType)) {
return type;
}
}
return OTHER;
}
/**
* Get the list of enums that are valid for filtering.
*
* @return enums that can be used to filter
*/
static List<FileType> getOptionsForFiltering() {
return Arrays.asList(IMAGE, VIDEO);
}
}
/**
* Enum representing the score of the file.
*/
@NbBundle.Messages({
"FileSearchData.Score.notable.displayName=Notable",
"FileSearchData.Score.interesting.displayName=Interesting",
"FileSearchData.Score.unknown.displayName=Unknown",})
enum Score {
NOTABLE(0, Bundle.FileSearchData_Score_notable_displayName()),
INTERESTING(1, Bundle.FileSearchData_Score_interesting_displayName()),
UNKNOWN(2, Bundle.FileSearchData_Score_unknown_displayName());
private final int ranking;
private final String displayName;
Score(int ranking, String displayName) {
this.ranking = ranking;
this.displayName = displayName;
}
/**
* Get the rank for sorting.
*
* @return the rank (lower should be displayed first)
*/
int getRanking() {
return ranking;
}
/**
* Get the list of enums that are valid for filtering.
*
* @return enums that can be used to filter
*/
static List<Score> getOptionsForFiltering() {
return Arrays.asList(NOTABLE, INTERESTING);
}
@Override
public String toString() {
return displayName;
}
}
private FileSearchData() {
// Class should not be instantiated
}
}

View File

@ -1,292 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.openide.util.NbBundle;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Class used to sort ResultFiles using the supplied method.
*/
class FileSorter implements Comparator<ResultFile> {
private final List<Comparator<ResultFile>> comparators = new ArrayList<>();
/**
* Set up the sorter using the supplied sorting method. The sorting is
* defined by a list of ResultFile comparators. These comparators will be
* run in order until one returns a non-zero result.
*
* @param method The method that should be used to sort the files
*/
FileSorter(SortingMethod method) {
// Set up the primary comparators that should applied to the files
switch (method) {
case BY_DATA_SOURCE:
comparators.add(getDataSourceComparator());
break;
case BY_FILE_SIZE:
comparators.add(getFileSizeComparator());
break;
case BY_FILE_TYPE:
comparators.add(getFileTypeComparator());
comparators.add(getMIMETypeComparator());
break;
case BY_FREQUENCY:
comparators.add(getFrequencyComparator());
break;
case BY_KEYWORD_LIST_NAMES:
comparators.add(getKeywordListNameComparator());
break;
case BY_FULL_PATH:
comparators.add(getParentPathComparator());
break;
case BY_FILE_NAME:
comparators.add(getFileNameComparator());
break;
default:
// The default comparator will be added afterward
break;
}
// Add the default comparator to the end. This will ensure a consistent sort
// order regardless of the order the files were added to the list.
comparators.add(getDefaultComparator());
}
@Override
public int compare(ResultFile file1, ResultFile file2) {
int result = 0;
for (Comparator<ResultFile> comp : comparators) {
result = comp.compare(file1, file2);
if (result != 0) {
return result;
}
}
// The files are the same
return result;
}
/**
* Compare files using data source ID. Will order smallest to largest.
*
* @return -1 if file1 has the lower data source ID, 0 if equal, 1 otherwise
*/
private static Comparator<ResultFile> getDataSourceComparator() {
return (ResultFile file1, ResultFile file2) -> Long.compare(file1.getFirstInstance().getDataSourceObjectId(), file2.getFirstInstance().getDataSourceObjectId());
}
/**
* Compare files using their FileType enum. Orders based on the ranking in
* the FileType enum.
*
* @return -1 if file1 has the lower FileType value, 0 if equal, 1 otherwise
*/
private static Comparator<ResultFile> getFileTypeComparator() {
return (ResultFile file1, ResultFile file2) -> Integer.compare(file1.getFileType().getRanking(), file2.getFileType().getRanking());
}
/**
* Compare files using a concatenated version of keyword list names.
* Alphabetical by the list names with files with no keyword list hits going
* last.
*
* @return -1 if file1 has the earliest combined keyword list name, 0 if
* equal, 1 otherwise
*/
private static Comparator<ResultFile> getKeywordListNameComparator() {
return (ResultFile file1, ResultFile file2) -> {
// Put empty lists at the bottom
if (file1.getKeywordListNames().isEmpty()) {
if (file2.getKeywordListNames().isEmpty()) {
return 0;
}
return 1;
} else if (file2.getKeywordListNames().isEmpty()) {
return -1;
}
String list1 = String.join(",", file1.getKeywordListNames());
String list2 = String.join(",", file2.getKeywordListNames());
return compareStrings(list1, list2);
};
}
/**
* Compare files based on parent path. Order alphabetically.
*
* @return -1 if file1's path comes first alphabetically, 0 if equal, 1
* otherwise
*/
private static Comparator<ResultFile> getParentPathComparator() {
return new Comparator<ResultFile>() {
@Override
public int compare(ResultFile file1, ResultFile file2) {
String file1ParentPath;
try {
file1ParentPath = file1.getFirstInstance().getParent().getUniquePath();
} catch (TskCoreException ingored) {
file1ParentPath = file1.getFirstInstance().getParentPath();
}
String file2ParentPath;
try {
file2ParentPath = file2.getFirstInstance().getParent().getUniquePath();
} catch (TskCoreException ingored) {
file2ParentPath = file2.getFirstInstance().getParentPath();
}
return compareStrings(file1ParentPath.toLowerCase(), file2ParentPath.toLowerCase());
}
};
}
/**
* Compare files based on number of occurrences in the central repository.
* Order from most rare to least rare Frequency enum.
*
* @return -1 if file1's rarity is lower than file2, 0 if equal, 1 otherwise
*/
private static Comparator<ResultFile> getFrequencyComparator() {
return (ResultFile file1, ResultFile file2) -> Integer.compare(file1.getFrequency().getRanking(), file2.getFrequency().getRanking());
}
/**
* Compare files based on MIME type. Order is alphabetical.
*
* @return -1 if file1's MIME type comes before file2's, 0 if equal, 1
* otherwise
*/
private static Comparator<ResultFile> getMIMETypeComparator() {
return (ResultFile file1, ResultFile file2) -> compareStrings(file1.getFirstInstance().getMIMEType(), file2.getFirstInstance().getMIMEType());
}
/**
* Compare files based on size. Order large to small.
*
* @return -1 if file1 is larger than file2, 0 if equal, 1 otherwise
*/
private static Comparator<ResultFile> getFileSizeComparator() {
return (ResultFile file1, ResultFile file2) -> -1 * Long.compare(file1.getFirstInstance().getSize(), file2.getFirstInstance().getSize()) // Sort large to small
;
}
/**
* Compare files based on file name. Order alphabetically.
*
* @return -1 if file1 comes before file2, 0 if equal, 1 otherwise
*/
private static Comparator<ResultFile> getFileNameComparator() {
return (ResultFile file1, ResultFile file2) -> compareStrings(file1.getFirstInstance().getName().toLowerCase(), file2.getFirstInstance().getName().toLowerCase());
}
/**
* A final default comparison between two ResultFile objects. Currently this
* is on file name and then object ID. It can be changed but should always
* include something like the object ID to ensure a consistent sorting when
* the rest of the compared fields are the same.
*
* @return -1 if file1 comes before file2, 0 if equal, 1 otherwise
*/
private static Comparator<ResultFile> getDefaultComparator() {
return (ResultFile file1, ResultFile file2) -> {
// Compare file names and then object ID (to ensure a consistent sort)
int result = getFileNameComparator().compare(file1, file2);
if (result == 0) {
return Long.compare(file1.getFirstInstance().getId(), file2.getFirstInstance().getId());
}
return result;
};
}
/**
* Compare two strings alphabetically. Nulls are allowed.
*
* @param s1
* @param s2
*
* @return -1 if s1 comes before s2, 0 if equal, 1 otherwise
*/
private static int compareStrings(String s1, String s2) {
String string1 = s1 == null ? "" : s1;
String string2 = s2 == null ? "" : s2;
return string1.compareTo(string2);
}
/**
* Enum for selecting the primary method for sorting result files.
*/
@NbBundle.Messages({
"FileSorter.SortingMethod.datasource.displayName=Data Source",
"FileSorter.SortingMethod.filename.displayName=File Name",
"FileSorter.SortingMethod.filesize.displayName=File Size",
"FileSorter.SortingMethod.filetype.displayName=File Type",
"FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency",
"FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names",
"FileSorter.SortingMethod.fullPath.displayName=Full Path"})
enum SortingMethod {
BY_FILE_NAME(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_filename_displayName()), // Sort alphabetically by file name
BY_DATA_SOURCE(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_datasource_displayName()), // Sort in increasing order of data source ID
BY_FILE_SIZE(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_filesize_displayName()), // Sort in decreasing order of size
BY_FILE_TYPE(Arrays.asList(new FileSearch.FileTypeAttribute()),
Bundle.FileSorter_SortingMethod_filetype_displayName()), // Sort in order of file type (defined in FileType enum), with secondary sort on MIME type
BY_FREQUENCY(Arrays.asList(new FileSearch.FrequencyAttribute()),
Bundle.FileSorter_SortingMethod_frequency_displayName()), // Sort by decreasing rarity in the central repository
BY_KEYWORD_LIST_NAMES(Arrays.asList(new FileSearch.KeywordListAttribute()),
Bundle.FileSorter_SortingMethod_keywordlist_displayName()), // Sort alphabetically by list of keyword list names found
BY_FULL_PATH(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_fullPath_displayName()); // Sort alphabetically by path
private final String displayName;
private final List<FileSearch.AttributeType> requiredAttributes;
SortingMethod(List<FileSearch.AttributeType> attributes, String displayName) {
this.requiredAttributes = attributes;
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
List<FileSearch.AttributeType> getRequiredAttributes() {
return Collections.unmodifiableList(requiredAttributes);
}
/**
* Get the list of enums that are valid for ordering images.
*
* @return enums that can be used to ordering images.
*/
static List<SortingMethod> getOptionsForOrdering() {
return Arrays.asList(BY_FILE_SIZE, BY_FULL_PATH, BY_FILE_NAME, BY_DATA_SOURCE);
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import java.util.ArrayList;
import java.util.List;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.datamodel.SleuthkitCase;
/**
* Base class for the filters.
*/
public abstract class AbstractFilter {
/**
* Returns part of a query on the table that can be AND-ed with other pieces
*
* @return the SQL query or an empty string if there is no SQL query for
* this filter.
*/
public abstract String getWhereClause();
/**
* Indicates whether this filter needs to use the secondary, non-SQL method
* applyAlternateFilter().
*
* @return false by default
*/
public boolean useAlternateFilter() {
return false;
}
/**
* Run a secondary filter that does not operate on table.
*
* @param currentResults The current list of matching results; empty if no
* filters have yet been run.
* @param caseDb The case database
* @param centralRepoDb The central repo database. Can be null if the
* filter does not require it.
*
* @return The list of results that match this filter (and any that came
* before it)
*
* @throws DiscoveryException
*/
public List<Result> applyAlternateFilter(List<Result> currentResults, SleuthkitCase caseDb,
CentralRepository centralRepoDb) throws DiscoveryException {
return new ArrayList<>();
}
/**
* Get a description of the selected filter.
*
* @return A description of the filter
*/
public abstract String getDesc();
}

View File

@ -0,0 +1,132 @@
DiscoveryAttributes.GroupingAttributeType.datasource.displayName=Data Source
DiscoveryAttributes.GroupingAttributeType.fileType.displayName=File Type
DiscoveryAttributes.GroupingAttributeType.firstDate.displayName=First Activity Date
DiscoveryAttributes.GroupingAttributeType.frequency.displayName=Past Occurrences
DiscoveryAttributes.GroupingAttributeType.hash.displayName=Hash Set
DiscoveryAttributes.GroupingAttributeType.interestingItem.displayName=Interesting Item
DiscoveryAttributes.GroupingAttributeType.keywordList.displayName=Keyword
DiscoveryAttributes.GroupingAttributeType.mostRecentDate.displayName=Most Recent Activity Date
DiscoveryAttributes.GroupingAttributeType.none.displayName=None
DiscoveryAttributes.GroupingAttributeType.numberOfVisits.displayName=Number of Visits
DiscoveryAttributes.GroupingAttributeType.object.displayName=Object Detected
DiscoveryAttributes.GroupingAttributeType.parent.displayName=Parent Folder
DiscoveryAttributes.GroupingAttributeType.size.displayName=File Size
DiscoveryAttributes.GroupingAttributeType.tag.displayName=Tag
# {0} - Data source name
# {1} - Data source ID
DiscoveryKeyUtils.DataSourceGroupKey.datasourceAndID={0}(ID: {1})
# {0} - Data source ID
DiscoveryKeyUtils.DataSourceGroupKey.idOnly=Data source (ID: {0})
DiscoveryKeyUtils.FileTagGroupKey.noSets=None
DiscoveryKeyUtils.FirstActivityDateGroupKey.noDate=No Date Available
DiscoveryKeyUtils.HashHitsGroupKey.noHashHits=None
DiscoveryKeyUtils.InterestingItemGroupKey.noSets=None
DiscoveryKeyUtils.KeywordListGroupKey.noKeywords=None
DiscoveryKeyUtils.MostRecentActivityDateGroupKey.noDate=No Date Available
DiscoveryKeyUtils.NoGroupingGroupKey.allFiles=All Files
# {0} - totalVisits
DiscoveryKeyUtils.NumberOfVisitsGroupKey.displayName={0} visits
DiscoveryKeyUtils.NumberOfVisitsGroupKey.noVisits=No visits
DiscoveryKeyUtils.ObjectDetectedGroupKey.noSets=None
FileGroup.groupSortingAlgorithm.groupName.text=Group Name
FileGroup.groupSortingAlgorithm.groupSize.text=Group Size
FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview.
FileSearch.documentSummary.noPreview=No preview available.
FileSearchFiltering.concatenateSetNamesForDisplay.comma=,
# {0} - filters
FileSearchFiltering.HashSetFilter.desc=Hash set hits in set(s): {0}
FileSearchFiltering.KnownFilter.desc=which are not known
FileSearchFiltering.PreviouslyNotableFilter.desc=that were previously marked as notable
# {0} - tag names
FileSearchFiltering.TagsFilter.desc=Tagged {0}
FileSearchFiltering.TagsFilter.or=,
FileSearchFiltering.UserCreatedFilter.desc=that contain EXIF data
FileSorter.SortingMethod.datasource.displayName=Data Source
FileSorter.SortingMethod.domain.displayName=Domain
FileSorter.SortingMethod.filename.displayName=File Name
FileSorter.SortingMethod.filesize.displayName=File Size
FileSorter.SortingMethod.filetype.displayName=File Type
FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency
FileSorter.SortingMethod.fullPath.displayName=Full Path
FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names
ResultFile.score.interestingResult.description=At least one instance of the file has an interesting result associated with it.
ResultFile.score.notableFile.description=At least one instance of the file was recognized as notable.
ResultFile.score.notableTaggedFile.description=At least one instance of the file is tagged with a notable tag.
ResultFile.score.taggedFile.description=At least one instance of the file has been tagged.
SearchData.AttributeType.Domain.displayName=Domain
SearchData.FileSize.100kbto1mb=: 100KB-1MB
SearchData.FileSize.100mbto1gb=: 100MB-1GB
SearchData.FileSize.10PlusGb=: 10GB+
SearchData.FileSize.16kbto100kb=: 16-100KB
SearchData.FileSize.1gbto5gb=: 1-5GB
SearchData.FileSize.1mbto50mb=: 1-50MB
SearchData.FileSize.200PlusMb=: 200MB+
SearchData.FileSize.500kbto100mb=: 500KB-100MB
SearchData.FileSize.50mbto200mb=: 50-200MB
SearchData.FileSize.5gbto10gb=: 5-10GB
SearchData.FileSize.LARGE.displayName=Large
SearchData.FileSize.MEDIUM.displayName=Medium
SearchData.FileSize.SMALL.displayName=Small
SearchData.FileSize.upTo16kb=: 0-16KB
SearchData.FileSize.upTo500kb=: 0-500KB
SearchData.FileSize.XLARGE.displayName=XLarge
SearchData.FileSize.XSMALL.displayName=XSmall
SearchData.FileSize.XXLARGE.displayName=XXLarge
SearchData.FileType.Audio.displayName=Audio
SearchData.FileType.Documents.displayName=Document
SearchData.FileType.Executables.displayName=Executable
SearchData.FileType.Image.displayName=Image
SearchData.FileType.Other.displayName=Other/Unknown
SearchData.FileType.Video.displayName=Video
SearchData.Frequency.common.displayName=Common (11 - 100)
SearchData.Frequency.known.displayName=Known (NSRL)
SearchData.Frequency.rare.displayName=Rare (2-10)
SearchData.Frequency.unique.displayName=Unique (1)
SearchData.Frequency.unknown.displayName=Unknown
SearchData.Frequency.verycommon.displayName=Very Common (100+)
SearchData.Score.interesting.displayName=Interesting
SearchData.Score.notable.displayName=Notable
SearchData.Score.unknown.displayName=Unknown
# {0} - artifactTypes
SearchFiltering.artifactTypeFilter.desc=Result type(s): {0}
SearchFiltering.artifactTypeFilter.or=,
# {0} - Data source name
# {1} - Data source ID
SearchFiltering.DataSourceFilter.datasource={0}({1})
# {0} - filters
SearchFiltering.DataSourceFilter.desc=Data source(s): {0}
SearchFiltering.DataSourceFilter.or=,
# {0} - startDate
SearchFiltering.dateRangeFilter.after=after: {0}
SearchFiltering.dateRangeFilter.and=\ and
# {0} - endDate
SearchFiltering.dateRangeFilter.before=before: {0}
SearchFiltering.dateRangeFilter.lable=Activity date
# {0} - filters
SearchFiltering.FileTypeFilter.desc=Type: {0}
SearchFiltering.FileTypeFilter.or=,
# {0} - filters
SearchFiltering.FrequencyFilter.desc=Past occurrences: {0}
SearchFiltering.FrequencyFilter.or=,
# {0} - filters
SearchFiltering.InterestingItemSetFilter.desc=Interesting item hits in set(s): {0}
# {0} - filters
SearchFiltering.KeywordListFilter.desc=Keywords in list(s): {0}
# {0} - filters
SearchFiltering.ObjectDetectionFilter.desc=Objects detected in set(s): {0}
# {0} - filters
SearchFiltering.ParentFilter.desc=Paths matching: {0}
SearchFiltering.ParentFilter.exact=(exact match)
SearchFiltering.ParentFilter.excluded=(excluded)
SearchFiltering.ParentFilter.included=(included)
SearchFiltering.ParentFilter.or=,
SearchFiltering.ParentFilter.substring=(substring)
SearchFiltering.ParentSearchTerm.excludeString=\ (exclude)
SearchFiltering.ParentSearchTerm.fullString=\ (exact)
SearchFiltering.ParentSearchTerm.includeString=\ (include)
SearchFiltering.ParentSearchTerm.subString=\ (substring)
# {0} - filters
SearchFiltering.ScoreFilter.desc=Score(s) of : {0}
# {0} - filters
SearchFiltering.SizeFilter.desc=Size(s): {0}
SearchFiltering.SizeFilter.or=,

View File

@ -0,0 +1,896 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.InstanceTableCallback;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import java.util.StringJoiner;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizer;
/**
* Class which contains the search attributes which can be specified for
* Discovery.
*/
public class DiscoveryAttributes {
private final static Logger logger = Logger.getLogger(DiscoveryAttributes.class.getName());
/**
* Base class for the grouping attributes.
*/
public abstract static class AttributeType {
/**
* For a given Result, return the key for the group it belongs to for
* this attribute type.
*
* @param result The result to be grouped.
*
* @return The key for the group this result goes in.
*/
public abstract DiscoveryKeyUtils.GroupKey getGroupKey(Result result);
/**
* Add any extra data to the ResultFile object from this attribute.
*
* @param files The list of results to enhance.
* @param caseDb The case database.
* @param centralRepoDb The central repository database. Can be null if
* not needed.
*
* @throws DiscoveryException
*/
public void addAttributeToResults(List<Result> results, SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
// Default is to do nothing
}
}
/**
* Attribute for grouping/sorting by file size.
*/
public static class FileSizeAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
return new DiscoveryKeyUtils.FileSizeGroupKey(result);
}
}
/**
* Attribute for grouping/sorting by parent path.
*/
public static class ParentPathAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
return new DiscoveryKeyUtils.ParentPathGroupKey((ResultFile) file);
}
}
/**
* Default attribute used to make one group.
*/
static class NoGroupingAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
return new DiscoveryKeyUtils.NoGroupingGroupKey();
}
}
/**
* Attribute for grouping/sorting by data source.
*/
static class DataSourceAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
return new DiscoveryKeyUtils.DataSourceGroupKey(result);
}
}
/**
* Attribute for grouping/sorting by file type.
*/
static class FileTypeAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
return new DiscoveryKeyUtils.FileTypeGroupKey(file);
}
}
/**
* Attribute for grouping/sorting by keyword lists.
*/
static class KeywordListAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
return new DiscoveryKeyUtils.KeywordListGroupKey((ResultFile) file);
}
@Override
public void addAttributeToResults(List<Result> results, SleuthkitCase caseDb,
CentralRepository centralRepoDb) throws DiscoveryException {
// Get pairs of (object ID, keyword list name) for all files in the list of files that have
// keyword list hits.
String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
SetKeywordListNamesCallback callback = new SetKeywordListNamesCallback(results);
try {
caseDb.getCaseDbAccessManager().select(selectQuery, callback);
} catch (TskCoreException ex) {
throw new DiscoveryException("Error looking up keyword list attributes", ex); // NON-NLS
}
}
/**
* Callback to process the results of the CaseDbAccessManager select
* query. Will add the keyword list names to the list of ResultFile
* objects.
*/
private static class SetKeywordListNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
List<Result> resultFiles;
/**
* Create the callback.
*
* @param resultFiles List of files to add keyword list names to.
*/
SetKeywordListNamesCallback(List<Result> resultFiles) {
this.resultFiles = resultFiles;
}
@Override
public void process(ResultSet rs) {
try {
// Create a temporary map of object ID to ResultFile
Map<Long, ResultFile> tempMap = new HashMap<>();
for (Result result : resultFiles) {
if (result.getType() == SearchData.Type.DOMAIN) {
break;
}
ResultFile file = (ResultFile) result;
tempMap.put(file.getFirstInstance().getId(), file);
}
while (rs.next()) {
try {
Long objId = rs.getLong("object_id"); // NON-NLS
String keywordListName = rs.getString("set_name"); // NON-NLS
tempMap.get(objId).addKeywordListName(keywordListName);
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
}
}
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to get keyword list names", ex); // NON-NLS
}
}
}
}
/**
* Attribute for grouping/sorting by frequency in the central repository.
*/
static class FrequencyAttribute extends AttributeType {
static final int BATCH_SIZE = 50; // Number of hashes to look up at one time
static final int DOMAIN_BATCH_SIZE = 500; // Number of domains to look up at one time
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
return new DiscoveryKeyUtils.FrequencyGroupKey(file);
}
@Override
public void addAttributeToResults(List<Result> results, SleuthkitCase caseDb,
CentralRepository centralRepoDb) throws DiscoveryException {
if (centralRepoDb == null) {
for (Result result : results) {
if (result.getFrequency() == SearchData.Frequency.UNKNOWN && result.getKnown() == TskData.FileKnown.KNOWN) {
result.setFrequency(SearchData.Frequency.KNOWN);
}
}
} else {
processResultFilesForCR(results, centralRepoDb);
}
}
/**
* Private helper method for adding Frequency attribute when CR is
* enabled.
*
* @param files The list of ResultFiles to caluclate frequency
* for.
* @param centralRepoDb The central repository currently in use.
*/
private void processResultFilesForCR(List<Result> results,
CentralRepository centralRepoDb) throws DiscoveryException {
List<ResultFile> currentFiles = new ArrayList<>();
Set<String> hashesToLookUp = new HashSet<>();
List<ResultDomain> domainsToQuery = new ArrayList<>();
for (Result result : results) {
if (result.getKnown() == TskData.FileKnown.KNOWN) {
result.setFrequency(SearchData.Frequency.KNOWN);
}
if (result.getType() != SearchData.Type.DOMAIN) {
ResultFile file = (ResultFile) result;
if (file.getFrequency() == SearchData.Frequency.UNKNOWN
&& file.getFirstInstance().getMd5Hash() != null
&& !file.getFirstInstance().getMd5Hash().isEmpty()) {
hashesToLookUp.add(file.getFirstInstance().getMd5Hash());
currentFiles.add(file);
}
if (hashesToLookUp.size() >= BATCH_SIZE) {
computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
hashesToLookUp.clear();
currentFiles.clear();
}
} else {
ResultDomain domainInstance = (ResultDomain) result;
if (domainInstance.getFrequency() != SearchData.Frequency.UNKNOWN) {
// Frequency already calculated, skipping...
continue;
}
domainsToQuery.add(domainInstance);
if (domainsToQuery.size() == DOMAIN_BATCH_SIZE) {
queryDomainFrequency(domainsToQuery, centralRepoDb);
domainsToQuery.clear();
}
}
}
queryDomainFrequency(domainsToQuery, centralRepoDb);
computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
}
}
/**
* Query to get the frequency of a domain.
*
* @param domainsToQuery List of domains to check the frequency of.
* @param centralRepository The central repository to query.
*
* @throws DiscoveryException
*/
private static void queryDomainFrequency(List<ResultDomain> domainsToQuery, CentralRepository centralRepository) throws DiscoveryException {
if (domainsToQuery.isEmpty()) {
return;
}
try {
final Map<String, List<ResultDomain>> resultDomainTable = new HashMap<>();
final StringJoiner joiner = new StringJoiner(", ");
final CorrelationAttributeInstance.Type attributeType = centralRepository.getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID);
for (ResultDomain domainInstance : domainsToQuery) {
try {
final String domainValue = domainInstance.getDomain();
final String normalizedDomain = CorrelationAttributeNormalizer.normalize(attributeType, domainValue);
final List<ResultDomain> bucket = resultDomainTable.getOrDefault(normalizedDomain, new ArrayList<>());
bucket.add(domainInstance);
resultDomainTable.put(normalizedDomain, bucket);
joiner.add("'" + normalizedDomain + "'");
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.INFO, String.format("Domain [%s] failed normalization, skipping...", domainInstance.getDomain()));
}
}
final String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType);
final String domainFrequencyQuery = " value AS domain_name, COUNT(*) AS frequency "
+ "FROM " + tableName + " "
+ "WHERE value IN (" + joiner + ") "
+ "GROUP BY value";
final DomainFrequencyCallback frequencyCallback = new DomainFrequencyCallback(resultDomainTable);
centralRepository.processSelectClause(domainFrequencyQuery, frequencyCallback);
if (frequencyCallback.getCause() != null) {
throw frequencyCallback.getCause();
}
} catch (CentralRepoException | SQLException ex) {
throw new DiscoveryException("Fatal exception encountered querying the CR.", ex);
}
}
/**
* Callback to get the frequency of domain.
*/
private static class DomainFrequencyCallback implements InstanceTableCallback {
private final Map<String, List<ResultDomain>> domainLookup;
private SQLException sqlCause;
/**
* Construct a new DomainFrequencyCallback.
*
* @param domainLookup The map to get domain from.
*/
private DomainFrequencyCallback(Map<String, List<ResultDomain>> domainLookup) {
this.domainLookup = domainLookup;
}
@Override
public void process(ResultSet resultSet) {
try {
while (resultSet.next()) {
String domain = resultSet.getString("domain_name");
Long frequency = resultSet.getLong("frequency");
List<ResultDomain> domainInstances = domainLookup.get(domain);
for (ResultDomain domainInstance : domainInstances) {
domainInstance.setFrequency(SearchData.Frequency.fromCount(frequency));
}
}
} catch (SQLException ex) {
this.sqlCause = ex;
}
}
/**
* Get the SQL exception if one occurred during this callback.
*
* @return
*/
SQLException getCause() {
return this.sqlCause;
}
}
/**
* Callback to use with findInterCaseValuesByCount which generates a list of
* values for common property search
*/
private static class FrequencyCallback implements InstanceTableCallback {
private final List<ResultFile> files;
/**
* Construct a new FrequencyCallback.
*
* @param resultFiles List of files to add hash set names to.
*/
private FrequencyCallback(List<ResultFile> files) {
this.files = new ArrayList<>(files);
}
@Override
public void process(ResultSet resultSet) {
try {
while (resultSet.next()) {
String hash = resultSet.getString(1);
int count = resultSet.getInt(2);
for (Iterator<ResultFile> iterator = files.iterator(); iterator.hasNext();) {
ResultFile file = iterator.next();
if (file.getFirstInstance().getMd5Hash().equalsIgnoreCase(hash)) {
file.setFrequency(SearchData.Frequency.fromCount(count));
iterator.remove();
}
}
}
// The files left had no matching entries in the CR, so mark them as unique
for (ResultFile file : files) {
file.setFrequency(SearchData.Frequency.UNIQUE);
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
}
}
}
/**
* Attribute for grouping/sorting by hash set lists.
*/
static class HashHitsAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
if (result.getType() == SearchData.Type.DOMAIN) {
return null;
}
return new DiscoveryKeyUtils.HashHitsGroupKey((ResultFile) result);
}
@Override
public void addAttributeToResults(List<Result> results, SleuthkitCase caseDb,
CentralRepository centralRepoDb) throws DiscoveryException {
// Get pairs of (object ID, hash set name) for all files in the list of files that have
// hash set hits.
String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
HashSetNamesCallback callback = new HashSetNamesCallback(results);
try {
caseDb.getCaseDbAccessManager().select(selectQuery, callback);
} catch (TskCoreException ex) {
throw new DiscoveryException("Error looking up hash set attributes", ex); // NON-NLS
}
}
/**
* Callback to process the results of the CaseDbAccessManager select
* query. Will add the hash set names to the list of ResultFile objects.
*/
private static class HashSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
List<Result> results;
/**
* Create the callback.
*
* @param resultFiles List of files to add hash set names to.
*/
HashSetNamesCallback(List<Result> results) {
this.results = results;
}
@Override
public void process(ResultSet rs) {
try {
// Create a temporary map of object ID to ResultFile
Map<Long, ResultFile> tempMap = new HashMap<>();
for (Result result : results) {
if (result.getType() == SearchData.Type.DOMAIN) {
return;
}
ResultFile file = (ResultFile) result;
tempMap.put(file.getFirstInstance().getId(), file);
}
while (rs.next()) {
try {
Long objId = rs.getLong("object_id"); // NON-NLS
String hashSetName = rs.getString("set_name"); // NON-NLS
tempMap.get(objId).addHashSetName(hashSetName);
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
}
}
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to get hash set names", ex); // NON-NLS
}
}
}
}
/**
* Attribute for grouping/sorting by interesting item set lists.
*/
static class InterestingItemAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
return new DiscoveryKeyUtils.InterestingItemGroupKey((ResultFile) file);
}
@Override
public void addAttributeToResults(List<Result> results, SleuthkitCase caseDb,
CentralRepository centralRepoDb) throws DiscoveryException {
// Get pairs of (object ID, interesting item set name) for all files in the list of files that have
// interesting file set hits.
String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
InterestingFileSetNamesCallback callback = new InterestingFileSetNamesCallback(results);
try {
caseDb.getCaseDbAccessManager().select(selectQuery, callback);
} catch (TskCoreException ex) {
throw new DiscoveryException("Error looking up interesting file set attributes", ex); // NON-NLS
}
}
/**
* Callback to process the results of the CaseDbAccessManager select
* query. Will add the interesting file set names to the list of
* ResultFile objects.
*/
private static class InterestingFileSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
List<Result> results;
/**
* Create the callback.
*
* @param resultFiles List of files to add interesting file set
* names to.
*/
InterestingFileSetNamesCallback(List<Result> results) {
this.results = results;
}
@Override
public void process(ResultSet rs) {
try {
// Create a temporary map of object ID to ResultFile
Map<Long, ResultFile> tempMap = new HashMap<>();
for (Result result : results) {
if (result.getType() == SearchData.Type.DOMAIN) {
return;
}
ResultFile file = (ResultFile) result;
tempMap.put(file.getFirstInstance().getId(), file);
}
while (rs.next()) {
try {
Long objId = rs.getLong("object_id"); // NON-NLS
String setName = rs.getString("set_name"); // NON-NLS
tempMap.get(objId).addInterestingSetName(setName);
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
}
}
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to get interesting file set names", ex); // NON-NLS
}
}
}
}
/**
* Attribute for grouping/sorting by date of most recent activity.
*/
static class MostRecentActivityDateAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
return new DiscoveryKeyUtils.MostRecentActivityDateGroupKey(result);
}
}
/**
* Attribute for grouping/sorting by date of first activity.
*/
static class FirstActivityDateAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
return new DiscoveryKeyUtils.FirstActivityDateGroupKey(result);
}
}
/**
* Attribute for grouping/sorting by number of visits.
*/
static class NumberOfVisitsAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) {
return new DiscoveryKeyUtils.NumberOfVisitsGroupKey(result);
}
}
/**
* Attribute for grouping/sorting by objects detected.
*/
static class ObjectDetectedAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
return new DiscoveryKeyUtils.ObjectDetectedGroupKey((ResultFile) file);
}
@Override
public void addAttributeToResults(List<Result> results, SleuthkitCase caseDb,
CentralRepository centralRepoDb) throws DiscoveryException {
// Get pairs of (object ID, object type name) for all files in the list of files that have
// objects detected
String selectQuery = createSetNameClause(results, BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID());
ObjectDetectedNamesCallback callback = new ObjectDetectedNamesCallback(results);
try {
caseDb.getCaseDbAccessManager().select(selectQuery, callback);
} catch (TskCoreException ex) {
throw new DiscoveryException("Error looking up object detected attributes", ex); // NON-NLS
}
}
/**
* Callback to process the results of the CaseDbAccessManager select
* query. Will add the object type names to the list of ResultFile
* objects.
*/
private static class ObjectDetectedNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
List<Result> results;
/**
* Create the callback.
*
* @param resultFiles List of files to add object detected names to.
*/
ObjectDetectedNamesCallback(List<Result> results) {
this.results = results;
}
@Override
public void process(ResultSet rs) {
try {
// Create a temporary map of object ID to ResultFile
Map<Long, ResultFile> tempMap = new HashMap<>();
for (Result result : results) {
if (result.getType() == SearchData.Type.DOMAIN) {
return;
}
ResultFile file = (ResultFile) result;
tempMap.put(file.getFirstInstance().getId(), file);
}
while (rs.next()) {
try {
Long objId = rs.getLong("object_id"); // NON-NLS
String setName = rs.getString("set_name"); // NON-NLS
tempMap.get(objId).addObjectDetectedName(setName);
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Unable to get object_id or set_name from result set", ex); // NON-NLS
}
}
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to get object detected names", ex); // NON-NLS
}
}
}
}
/**
* Attribute for grouping/sorting by tag name.
*/
static class FileTagAttribute extends AttributeType {
@Override
public DiscoveryKeyUtils.GroupKey getGroupKey(Result file) {
return new DiscoveryKeyUtils.FileTagGroupKey((ResultFile) file);
}
@Override
public void addAttributeToResults(List<Result> results, SleuthkitCase caseDb,
CentralRepository centralRepoDb) throws DiscoveryException {
try {
for (Result result : results) {
if (result.getType() == SearchData.Type.DOMAIN) {
return;
}
ResultFile file = (ResultFile) result;
List<ContentTag> contentTags = caseDb.getContentTagsByContent(file.getFirstInstance());
for (ContentTag tag : contentTags) {
result.addTagName(tag.getName().getDisplayName());
}
}
} catch (TskCoreException ex) {
throw new DiscoveryException("Error looking up file tag attributes", ex); // NON-NLS
}
}
}
/**
* Enum for the attribute types that can be used for grouping.
*/
@NbBundle.Messages({
"DiscoveryAttributes.GroupingAttributeType.fileType.displayName=File Type",
"DiscoveryAttributes.GroupingAttributeType.frequency.displayName=Past Occurrences",
"DiscoveryAttributes.GroupingAttributeType.keywordList.displayName=Keyword",
"DiscoveryAttributes.GroupingAttributeType.size.displayName=File Size",
"DiscoveryAttributes.GroupingAttributeType.datasource.displayName=Data Source",
"DiscoveryAttributes.GroupingAttributeType.parent.displayName=Parent Folder",
"DiscoveryAttributes.GroupingAttributeType.hash.displayName=Hash Set",
"DiscoveryAttributes.GroupingAttributeType.interestingItem.displayName=Interesting Item",
"DiscoveryAttributes.GroupingAttributeType.tag.displayName=Tag",
"DiscoveryAttributes.GroupingAttributeType.object.displayName=Object Detected",
"DiscoveryAttributes.GroupingAttributeType.mostRecentDate.displayName=Most Recent Activity Date",
"DiscoveryAttributes.GroupingAttributeType.firstDate.displayName=First Activity Date",
"DiscoveryAttributes.GroupingAttributeType.numberOfVisits.displayName=Number of Visits",
"DiscoveryAttributes.GroupingAttributeType.none.displayName=None"})
public enum GroupingAttributeType {
FILE_SIZE(new FileSizeAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_size_displayName()),
FREQUENCY(new FrequencyAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_frequency_displayName()),
KEYWORD_LIST_NAME(new KeywordListAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_keywordList_displayName()),
DATA_SOURCE(new DataSourceAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_datasource_displayName()),
PARENT_PATH(new ParentPathAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_parent_displayName()),
HASH_LIST_NAME(new HashHitsAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_hash_displayName()),
INTERESTING_ITEM_SET(new InterestingItemAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_interestingItem_displayName()),
FILE_TAG(new FileTagAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_tag_displayName()),
OBJECT_DETECTED(new ObjectDetectedAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_object_displayName()),
MOST_RECENT_DATE(new MostRecentActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_mostRecentDate_displayName()),
FIRST_DATE(new FirstActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_firstDate_displayName()),
NUMBER_OF_VISITS(new NumberOfVisitsAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_numberOfVisits_displayName()),
NO_GROUPING(new NoGroupingAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_none_displayName());
private final AttributeType attributeType;
private final String displayName;
/**
* Construct a new GroupingAttributeType enum value.
*
* @param attributeType The type of attribute this enum value was
* constructed for.
* @param displayName The display name for this grouping attribute
* type.
*/
GroupingAttributeType(AttributeType attributeType, String displayName) {
this.attributeType = attributeType;
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
/**
* Get the type of attribute this enum value was constructed for.
*
* @return The type of attribute this enum value was constructed for.
*/
public AttributeType getAttributeType() {
return attributeType;
}
/**
* Get the list of enums that are valid for grouping files.
*
* @return Enums that can be used to group files.
*/
public static List<GroupingAttributeType> getOptionsForGroupingForFiles() {
return Arrays.asList(FILE_SIZE, FREQUENCY, PARENT_PATH, OBJECT_DETECTED, HASH_LIST_NAME, INTERESTING_ITEM_SET);
}
/**
* Get the list of enums that are valid for grouping files.
*
* @return Enums that can be used to group files.
*/
public static List<GroupingAttributeType> getOptionsForGroupingForDomains() {
return Arrays.asList(FREQUENCY, MOST_RECENT_DATE, FIRST_DATE, NUMBER_OF_VISITS);
}
}
/**
* Computes the CR frequency of all the given hashes and updates the list of
* files.
*
* @param hashesToLookUp Hashes to find the frequency of.
* @param currentFiles List of files to update with frequencies.
* @param centralRepoDb The central repository being used.
*/
private static void computeFrequency(Set<String> hashesToLookUp, List<ResultFile> currentFiles, CentralRepository centralRepoDb) {
if (hashesToLookUp.isEmpty()) {
return;
}
String hashes = String.join("','", hashesToLookUp);
hashes = "'" + hashes + "'";
try {
CorrelationAttributeInstance.Type attributeType = centralRepoDb.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType);
String selectClause = " value, COUNT(value) FROM "
+ "(SELECT DISTINCT case_id, value FROM " + tableName
+ " WHERE value IN ("
+ hashes
+ ")) AS foo GROUP BY value";
FrequencyCallback callback = new FrequencyCallback(currentFiles);
centralRepoDb.processSelectClause(selectClause, callback);
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Error getting frequency counts from Central Repository", ex); // NON-NLS
}
}
/**
* Private helper method to create a set name clause to be used in queries.
*
* @param results The list of results to create the set name clause
* for.
* @param artifactTypeID The Blackboard Artifact type ID for the artifact
* type.
* @param setNameAttrID The set name attribute id.
*
* @return The String to use as a set name clause in queries.
*
* @throws DiscoveryException
*/
private static String createSetNameClause(List<Result> results,
int artifactTypeID, int setNameAttrID) throws DiscoveryException {
// Concatenate the object IDs in the list of files
String objIdList = ""; // NON-NLS
for (Result result : results) {
if (result.getType() == SearchData.Type.DOMAIN) {
break;
}
ResultFile file = (ResultFile) result;
if (!objIdList.isEmpty()) {
objIdList += ","; // NON-NLS
}
objIdList += "\'" + file.getFirstInstance().getId() + "\'"; // NON-NLS
}
// Get pairs of (object ID, set name) for all files in the list of files that have
// the given artifact type.
return "blackboard_artifacts.obj_id AS object_id, blackboard_attributes.value_text AS set_name "
+ "FROM blackboard_artifacts "
+ "INNER JOIN blackboard_attributes ON blackboard_artifacts.artifact_id=blackboard_attributes.artifact_id "
+ "WHERE blackboard_attributes.artifact_type_id=\'" + artifactTypeID + "\' "
+ "AND blackboard_attributes.attribute_type_id=\'" + setNameAttrID + "\' "
+ "AND blackboard_artifacts.obj_id IN (" + objIdList
+ ") "; // NON-NLS
}
/**
* Private constructor for DiscoveryAttributes class.
*/
private DiscoveryAttributes() {
// Class should not be instantiated
}
}

View File

@ -16,20 +16,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.eventbus.EventBus;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.sleuthkit.autopsy.discovery.FileSearch.GroupKey;
import org.sleuthkit.autopsy.discovery.FileSearchData.FileType;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
import org.sleuthkit.autopsy.discovery.search.SearchData.Type;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Class to handle event bus and events for discovery tool.
*/
final class DiscoveryEventUtils {
public final class DiscoveryEventUtils {
private final static EventBus discoveryEventBus = new EventBus();
@ -38,7 +38,7 @@ final class DiscoveryEventUtils {
*
* @return The discovery event bus.
*/
static EventBus getDiscoveryEventBus() {
public static EventBus getDiscoveryEventBus() {
return discoveryEventBus;
}
@ -52,38 +52,39 @@ final class DiscoveryEventUtils {
/**
* Event to signal the start of a search being performed.
*/
static final class SearchStartedEvent {
public static final class SearchStartedEvent {
private final FileType fileType;
private final Type type;
/**
* Construct a new SearchStartedEvent
* Construct a new SearchStartedEvent.
*
* @param type The type of file the search event is for.
* @param type The type of result the search event is for.
*/
SearchStartedEvent(FileType type) {
this.fileType = type;
public SearchStartedEvent(Type type) {
this.type = type;
}
/**
* Get the type of file the search is being performed for.
* Get the type of result the search is being performed for.
*
* @return The type of files being searched for.
* @return The type of results being searched for.
*/
FileType getType() {
return fileType;
public Type getType() {
return type;
}
}
/**
* Event to signal that the Instances list should have selection cleared.
*/
static final class ClearInstanceSelectionEvent {
public static final class ClearInstanceSelectionEvent {
/**
* Construct a new ClearInstanceSelectionEvent.
*/
ClearInstanceSelectionEvent() {
public ClearInstanceSelectionEvent() {
//no arg constructor
}
}
@ -91,21 +92,23 @@ final class DiscoveryEventUtils {
/**
* Event to signal that the Instances list should be populated.
*/
static final class PopulateInstancesListEvent {
public static final class PopulateInstancesListEvent {
private final List<AbstractFile> instances;
/**
* Construct a new PopulateInstancesListEvent.
*/
PopulateInstancesListEvent(List<AbstractFile> files) {
public PopulateInstancesListEvent(List<AbstractFile> files) {
instances = files;
}
/**
* @return the instances
* Get the list of AbstractFiles for the instances list.
*
* @return The list of AbstractFiles for the instances list.
*/
List<AbstractFile> getInstances() {
public List<AbstractFile> getInstances() {
return Collections.unmodifiableList(instances);
}
}
@ -113,13 +116,13 @@ final class DiscoveryEventUtils {
/**
* Event to signal the completion of a search being performed.
*/
static final class SearchCompleteEvent {
public static final class SearchCompleteEvent {
private final Map<GroupKey, Integer> groupMap;
private final List<FileSearchFiltering.FileFilter> searchFilters;
private final FileSearch.AttributeType groupingAttribute;
private final FileGroup.GroupSortingAlgorithm groupSort;
private final FileSorter.SortingMethod fileSortMethod;
private final List<AbstractFilter> searchFilters;
private final DiscoveryAttributes.AttributeType groupingAttribute;
private final Group.GroupSortingAlgorithm groupSort;
private final ResultsSorter.SortingMethod sortMethod;
/**
* Construct a new SearchCompleteEvent,
@ -130,16 +133,16 @@ final class DiscoveryEventUtils {
* search.
* @param groupingAttribute The grouping attribute used by the search.
* @param groupSort The sorting algorithm used for groups.
* @param fileSortMethod The sorting method used for files.
* @param sortMethod The sorting method used for results.
*/
SearchCompleteEvent(Map<GroupKey, Integer> groupMap, List<FileSearchFiltering.FileFilter> searchfilters,
FileSearch.AttributeType groupingAttribute, FileGroup.GroupSortingAlgorithm groupSort,
FileSorter.SortingMethod fileSortMethod) {
public SearchCompleteEvent(Map<GroupKey, Integer> groupMap, List<AbstractFilter> searchfilters,
DiscoveryAttributes.AttributeType groupingAttribute, Group.GroupSortingAlgorithm groupSort,
ResultsSorter.SortingMethod sortMethod) {
this.groupMap = groupMap;
this.searchFilters = searchfilters;
this.groupingAttribute = groupingAttribute;
this.groupSort = groupSort;
this.fileSortMethod = fileSortMethod;
this.sortMethod = sortMethod;
}
/**
@ -147,16 +150,16 @@ final class DiscoveryEventUtils {
*
* @return The map of groups which were found by the search.
*/
Map<GroupKey, Integer> getGroupMap() {
public Map<GroupKey, Integer> getGroupMap() {
return Collections.unmodifiableMap(groupMap);
}
/**
* Get the file filters used by the search.
* Get the filters used by the search.
*
* @return The search filters which were used by the search.
*/
List<FileSearchFiltering.FileFilter> getFilters() {
public List<AbstractFilter> getFilters() {
return Collections.unmodifiableList(searchFilters);
}
@ -165,7 +168,7 @@ final class DiscoveryEventUtils {
*
* @return The grouping attribute used by the search.
*/
FileSearch.AttributeType getGroupingAttr() {
public DiscoveryAttributes.AttributeType getGroupingAttr() {
return groupingAttribute;
}
@ -174,17 +177,17 @@ final class DiscoveryEventUtils {
*
* @return The sorting algorithm used for groups.
*/
FileGroup.GroupSortingAlgorithm getGroupSort() {
public Group.GroupSortingAlgorithm getGroupSort() {
return groupSort;
}
/**
* Get the sorting method used for files.
* Get the sorting method used for results.
*
* @return The sorting method used for files.
* @return The sorting method used for results.
*/
FileSorter.SortingMethod getFileSort() {
return fileSortMethod;
public ResultsSorter.SortingMethod getResultSort() {
return sortMethod;
}
}
@ -193,31 +196,31 @@ final class DiscoveryEventUtils {
* Event to signal the completion of page retrieval and include the page
* contents.
*/
static final class PageRetrievedEvent {
public static final class PageRetrievedEvent {
private final List<ResultFile> results;
private final List<Result> results;
private final int page;
private final FileType resultType;
private final Type resultType;
/**
* Construct a new PageRetrievedEvent.
*
* @param resultType The type of files which exist in the page.
* @param resultType The type of results which exist in the page.
* @param page The number of the page which was retrieved.
* @param results The list of files in the page retrieved.
* @param results The list of results in the page retrieved.
*/
PageRetrievedEvent(FileType resultType, int page, List<ResultFile> results) {
public PageRetrievedEvent(Type resultType, int page, List<Result> results) {
this.results = results;
this.page = page;
this.resultType = resultType;
}
/**
* Get the list of files in the page retrieved.
* Get the list of results in the page retrieved.
*
* @return The list of files in the page retrieved.
* @return The list of results in the page retrieved.
*/
List<ResultFile> getSearchResults() {
public List<Result> getSearchResults() {
return Collections.unmodifiableList(results);
}
@ -226,16 +229,16 @@ final class DiscoveryEventUtils {
*
* @return The number of the page which was retrieved.
*/
int getPageNumber() {
public int getPageNumber() {
return page;
}
/**
* Get the type of files which exist in the page.
* Get the type of results which exist in the page.
*
* @return The type of files which exist in the page.
* @return The type of results which exist in the page.
*/
FileType getType() {
public Type getType() {
return resultType;
}
}
@ -243,25 +246,25 @@ final class DiscoveryEventUtils {
/**
* Event to signal that there were no results for the search.
*/
static final class NoResultsEvent {
public static final class NoResultsEvent {
/**
* Construct a new NoResultsEvent.
*/
NoResultsEvent() {
public NoResultsEvent() {
//no arg constructor
}
}
/**
* Event to signal that a search has been cancelled
* Event to signal that a search has been cancelled.
*/
static final class SearchCancelledEvent {
public static final class SearchCancelledEvent {
/**
* Construct a new SearchCancelledEvent.
*/
SearchCancelledEvent() {
public SearchCancelledEvent() {
//no arg constructor
}
@ -270,15 +273,15 @@ final class DiscoveryEventUtils {
/**
* Event to signal that a group has been selected.
*/
static final class GroupSelectedEvent {
public static final class GroupSelectedEvent {
private final FileType resultType;
private final Type resultType;
private final GroupKey groupKey;
private final int groupSize;
private final List<FileSearchFiltering.FileFilter> searchfilters;
private final FileSearch.AttributeType groupingAttribute;
private final FileGroup.GroupSortingAlgorithm groupSort;
private final FileSorter.SortingMethod fileSortMethod;
private final List<AbstractFilter> searchfilters;
private final DiscoveryAttributes.AttributeType groupingAttribute;
private final Group.GroupSortingAlgorithm groupSort;
private final ResultsSorter.SortingMethod sortMethod;
/**
* Construct a new GroupSelectedEvent.
@ -287,31 +290,32 @@ final class DiscoveryEventUtils {
* search.
* @param groupingAttribute The grouping attribute used by the search.
* @param groupSort The sorting algorithm used for groups.
* @param fileSortMethod The sorting method used for files.
* @param sortMethod The sorting method used for results.
* @param groupKey The key associated with the group which was
* selected.
* @param groupSize The number of files in the group which was
* @param groupSize The number of results in the group which was
* selected.
* @param resultType The type of files which exist in the group.
* @param resultType The type of results which exist in the
* group.
*/
GroupSelectedEvent(List<FileSearchFiltering.FileFilter> searchfilters,
FileSearch.AttributeType groupingAttribute, FileGroup.GroupSortingAlgorithm groupSort,
FileSorter.SortingMethod fileSortMethod, GroupKey groupKey, int groupSize, FileType resultType) {
public GroupSelectedEvent(List<AbstractFilter> searchfilters,
DiscoveryAttributes.AttributeType groupingAttribute, Group.GroupSortingAlgorithm groupSort,
ResultsSorter.SortingMethod sortMethod, GroupKey groupKey, int groupSize, Type resultType) {
this.searchfilters = searchfilters;
this.groupingAttribute = groupingAttribute;
this.groupSort = groupSort;
this.fileSortMethod = fileSortMethod;
this.sortMethod = sortMethod;
this.groupKey = groupKey;
this.groupSize = groupSize;
this.resultType = resultType;
}
/**
* Get the type of files which exist in the group.
* Get the type of results which exist in the group.
*
* @return The type of files which exist in the group.
* @return The type of results which exist in the group.
*/
FileType getResultType() {
public Type getResultType() {
return resultType;
}
@ -322,16 +326,16 @@ final class DiscoveryEventUtils {
* @return The group key which is used to uniquely identify the group
* selected.
*/
GroupKey getGroupKey() {
public GroupKey getGroupKey() {
return groupKey;
}
/**
* Get the number of files in the group which was selected.
* Get the number of results in the group which was selected.
*
* @return The number of files in the group which was selected.
* @return The number of results in the group which was selected.
*/
int getGroupSize() {
public int getGroupSize() {
return groupSize;
}
@ -340,25 +344,25 @@ final class DiscoveryEventUtils {
*
* @return The sorting algorithm used for groups.
*/
FileGroup.GroupSortingAlgorithm getGroupSort() {
public Group.GroupSortingAlgorithm getGroupSort() {
return groupSort;
}
/**
* Get the sorting method used for files in the group.
* Get the sorting method used for results in the group.
*
* @return The sorting method used for files.
* @return The sorting method used for results.
*/
FileSorter.SortingMethod getFileSort() {
return fileSortMethod;
public ResultsSorter.SortingMethod getResultSort() {
return sortMethod;
}
/**
* Get the file filters which were used by the search
* Get the result filters which were used by the search.
*
* @return The search filters which were used by the search.
*/
List<FileSearchFiltering.FileFilter> getFilters() {
public List<AbstractFilter> getFilters() {
return Collections.unmodifiableList(searchfilters);
}
@ -367,7 +371,7 @@ final class DiscoveryEventUtils {
*
* @return The grouping attribute used by the search.
*/
FileSearch.AttributeType getGroupingAttr() {
public DiscoveryAttributes.AttributeType getGroupingAttr() {
return groupingAttribute;
}
@ -376,7 +380,7 @@ final class DiscoveryEventUtils {
/**
* Event to signal that the visibility of the Details area should change.
*/
static class DetailsVisibleEvent {
public static class DetailsVisibleEvent {
private final boolean showDetailsArea;
@ -386,7 +390,7 @@ final class DiscoveryEventUtils {
* @param isVisible True if the details area should be visible, false
* otherwise.
*/
DetailsVisibleEvent(boolean isVisible) {
public DetailsVisibleEvent(boolean isVisible) {
showDetailsArea = isVisible;
}
@ -395,7 +399,7 @@ final class DiscoveryEventUtils {
*
* @return True if the details area should be visible, false otherwise.
*/
boolean isShowDetailsArea() {
public boolean isShowDetailsArea() {
return showDetailsArea;
}
}

View File

@ -16,12 +16,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.search;
/**
* Exception type used for FileSearch.
* Exception type used for Discovery search.
*/
final public class FileSearchException extends Exception {
final public class DiscoveryException extends Exception {
private static final long serialVersionUID = 1L;
@ -30,7 +30,7 @@ final public class FileSearchException extends Exception {
*
* @param message The message to associate with this exception.
*/
FileSearchException(String message) {
DiscoveryException(String message) {
super(message);
}
@ -40,7 +40,7 @@ final public class FileSearchException extends Exception {
* @param message The message to associate with this exception.
* @param cause The Throwable cause of the exception.
*/
FileSearchException(String message, Throwable cause) {
DiscoveryException(String message, Throwable cause) {
super(message, cause);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import java.awt.Image;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
import org.sleuthkit.datamodel.SleuthkitCase;
/**
* Main class to perform the domain search.
*/
public class DomainSearch {
private final DomainSearchCache searchCache;
private final DomainSearchThumbnailCache thumbnailCache;
/**
* Construct a new DomainSearch object.
*/
public DomainSearch() {
this(new DomainSearchCache(), new DomainSearchThumbnailCache());
}
/**
* Construct a new DomainSearch object with an existing DomainSearchCache
* and DomainSearchThumbnailCache.
*
* @param cache The DomainSearchCache to use for this DomainSearch.
* @param thumbnailCache The DomainSearchThumnailCache to use for this
* DomainSearch.
*/
DomainSearch(DomainSearchCache cache, DomainSearchThumbnailCache thumbnailCache) {
this.searchCache = cache;
this.thumbnailCache = thumbnailCache;
}
/**
* Run the domain search to get the group keys and sizes. Clears cache of
* search results, caching new results for access at later time.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply.
* @param groupAttributeType The attribute to use for grouping.
* @param groupSortingType The method to use to sort the groups.
* @param domainSortingMethod The method to use to sort the domains within
* the groups.
* @param caseDb The case database.
* @param centralRepoDb The central repository database. Can be null
* if not needed.
*
* @return A LinkedHashMap grouped and sorted according to the parameters.
*
* @throws DiscoveryException
*/
public Map<GroupKey, Integer> getGroupSizes(String userName,
List<AbstractFilter> filters,
DiscoveryAttributes.AttributeType groupAttributeType,
Group.GroupSortingAlgorithm groupSortingType,
ResultsSorter.SortingMethod domainSortingMethod,
SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
final Map<GroupKey, List<Result>> searchResults = searchCache.get(
userName, filters, groupAttributeType, groupSortingType,
domainSortingMethod, caseDb, centralRepoDb);
// Transform the cached results into a map of group key to group size.
final LinkedHashMap<GroupKey, Integer> groupSizes = new LinkedHashMap<>();
for (GroupKey groupKey : searchResults.keySet()) {
groupSizes.put(groupKey, searchResults.get(groupKey).size());
}
return groupSizes;
}
/**
* Get the domains from the specified group from the cache, if the the group
* was not cached perform a search caching the groups.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply.
* @param groupAttributeType The attribute to use for grouping.
* @param groupSortingType The method to use to sort the groups.
* @param domainSortingMethod The method to use to sort the Domains within
* the groups.
* @param groupKey The key which uniquely identifies the group to
* get entries from.
* @param startingEntry The first entry to return.
* @param numberOfEntries The number of entries to return.
* @param caseDb The case database.
* @param centralRepoDb The central repository database. Can be null
* if not needed.
*
* @return A LinkedHashMap grouped and sorted according to the parameters.
*
* @throws DiscoveryException
*/
public List<Result> getDomainsInGroup(String userName,
List<AbstractFilter> filters,
DiscoveryAttributes.AttributeType groupAttributeType,
Group.GroupSortingAlgorithm groupSortingType,
ResultsSorter.SortingMethod domainSortingMethod,
GroupKey groupKey, int startingEntry, int numberOfEntries,
SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
final Map<GroupKey, List<Result>> searchResults = searchCache.get(
userName, filters, groupAttributeType, groupSortingType,
domainSortingMethod, caseDb, centralRepoDb);
final List<Result> domainsInGroup = searchResults.get(groupKey);
final List<Result> page = new ArrayList<>();
for (int i = startingEntry; (i < startingEntry + numberOfEntries)
&& (i < domainsInGroup.size()); i++) {
page.add(domainsInGroup.get(i));
}
return page;
}
/**
* Get a thumbnail representation of a domain name. See
* DomainSearchThumbnailRequest for more details.
*
* @param thumbnailRequest Thumbnail request for domain.
*
* @return An Image instance or null if no thumbnail is available.
*
* @throws DiscoveryException If there is an error with Discovery related
* processing.
*/
public Image getThumbnail(DomainSearchThumbnailRequest thumbnailRequest) throws DiscoveryException {
return thumbnailCache.get(thumbnailRequest);
}
}

View File

@ -0,0 +1,56 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Caches artifact requests.
*/
public class DomainSearchArtifactsCache {
private static final int MAXIMUM_CACHE_SIZE = 500;
private static final LoadingCache<DomainSearchArtifactsRequest, List<BlackboardArtifact>> cache
= CacheBuilder.newBuilder()
.maximumSize(MAXIMUM_CACHE_SIZE)
.build(new DomainSearchArtifactsLoader());
/**
* Get artifact instances that match the requested criteria. If the request
* is new, the results will be automatically loaded.
*
* @param request Artifact request, specifies type, Case, and domain name.
*
* @return A list of matching artifacts.
*
* @throws DiscoveryException Any error that occurs during the loading
* process.
*/
public List<BlackboardArtifact> get(DomainSearchArtifactsRequest request) throws DiscoveryException {
try {
return cache.get(request);
} catch (ExecutionException ex) {
throw new DiscoveryException("Error fetching artifacts from cache", ex);
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.cache.CacheLoader;
import java.util.List;
import java.util.ArrayList;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute.Type;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Loads artifacts for the given request. Searches TSK_DOMAIN and TSK_URL
* attributes for the requested domain name. TSK_DOMAIN is exact match (ignoring
* case). TSK_URL is sub-string match (ignoring case).
*/
public class DomainSearchArtifactsLoader extends CacheLoader<DomainSearchArtifactsRequest, List<BlackboardArtifact>> {
private static final Type TSK_DOMAIN = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN);
private static final Type TSK_URL = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL);
@Override
public List<BlackboardArtifact> load(DomainSearchArtifactsRequest artifactsRequest) throws TskCoreException {
final SleuthkitCase caseDb = artifactsRequest.getSleuthkitCase();
final String normalizedDomain = artifactsRequest.getDomain().toLowerCase();
final List<BlackboardArtifact> artifacts = caseDb.getBlackboardArtifacts(artifactsRequest.getArtifactType());
final List<BlackboardArtifact> matchingDomainArtifacts = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
final BlackboardAttribute tskDomain = artifact.getAttribute(TSK_DOMAIN);
final BlackboardAttribute tskUrl = artifact.getAttribute(TSK_URL);
if (tskDomain != null && tskDomain.getValueString().equalsIgnoreCase(normalizedDomain)) {
matchingDomainArtifacts.add(artifact);
} else if (tskUrl != null && tskUrl.getValueString().toLowerCase().contains(normalizedDomain)) {
matchingDomainArtifacts.add(artifact);
}
}
return matchingDomainArtifacts;
}
}

View File

@ -0,0 +1,93 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import java.util.Objects;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
/**
* Requests artifacts of a specific type and domain from a given Case.
*/
public class DomainSearchArtifactsRequest {
private final SleuthkitCase sleuthkitCase;
private final String domain;
private final ARTIFACT_TYPE artifactType;
/**
* Construct a new DomainSearchArtifactsRequest object.
*
* @param sleuthkitCase The case database for the search.
* @param domain The domain that artifacts are being requested for.
* @param artifactType The type of artifact being requested.
*/
public DomainSearchArtifactsRequest(SleuthkitCase sleuthkitCase,
String domain, ARTIFACT_TYPE artifactType) {
this.sleuthkitCase = sleuthkitCase;
this.domain = domain;
this.artifactType = artifactType;
}
/**
* Get the case database for the search.
*
* @return The case database for the search.
*/
public SleuthkitCase getSleuthkitCase() {
return sleuthkitCase;
}
/**
* Get the domain that artifacts are being requested for.
*
* @return The domain that artifacts are being requested for.
*/
public String getDomain() {
return domain;
}
/**
* Get the type of artifact being requested.
*
* @return The type of artifact being requested.
*/
public ARTIFACT_TYPE getArtifactType() {
return artifactType;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof DomainSearchArtifactsRequest)) {
return false;
}
DomainSearchArtifactsRequest otherRequest = (DomainSearchArtifactsRequest) other;
return this.sleuthkitCase == otherRequest.getSleuthkitCase()
&& this.domain.equals(otherRequest.getDomain())
&& this.artifactType == otherRequest.getArtifactType();
}
@Override
public int hashCode() {
return 79 * 5 + Objects.hash(this.domain, this.artifactType);
}
}

View File

@ -0,0 +1,77 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.SearchKey;
import org.sleuthkit.datamodel.SleuthkitCase;
/**
* Caches results for domain searches initiated by the user in the Discovery
* panel. Uses a Guava Cache as a backing data structure. See
* DomainSearchCacheLoader for database querying in the event of a cache miss.
*/
class DomainSearchCache {
private static final int MAXIMUM_CACHE_SIZE = 10;
private static final LoadingCache<SearchKey, Map<GroupKey, List<Result>>> cache
= CacheBuilder.newBuilder()
.maximumSize(MAXIMUM_CACHE_SIZE)
.build(new DomainSearchCacheLoader());
/**
* Get domain search results matching the given parameters. If no results
* are found, the cache will automatically load them.
*
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply.
* @param groupAttributeType The attribute to use for grouping.
* @param groupSortingType The method to use to sort the groups.
* @param fileSortingMethod The method to use to sort the domains within
* the groups.
* @param caseDb The case database.
* @param centralRepoDb The central repository database. Can be null if
* not needed.
*
* @return Domain search results matching the given parameters.
*
* @throws DiscoveryException
*/
Map<GroupKey, List<Result>> get(String userName,
List<AbstractFilter> filters,
DiscoveryAttributes.AttributeType groupAttributeType,
Group.GroupSortingAlgorithm groupSortingType,
ResultsSorter.SortingMethod domainSortingMethod,
SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
try {
final SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType,
groupSortingType, domainSortingMethod, caseDb, centralRepoDb);
return cache.get(searchKey);
} catch (ExecutionException ex) {
throw new DiscoveryException("Error fetching results from cache", ex.getCause());
}
}
}

View File

@ -0,0 +1,341 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.cache.CacheLoader;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import org.apache.commons.lang3.tuple.Pair;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.AttributeType;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.DataSourceAttribute;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.SearchKey;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactDateRangeFilter;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering.DataSourceFilter;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN;
import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Loads domain search results for cache misses. This loader is a Guava cache
* loader, which will be used in tandem with the DomainSearchCache, which is
* backed by a Guava LoadingCache.
*/
class DomainSearchCacheLoader extends CacheLoader<SearchKey, Map<GroupKey, List<Result>>> {
@Override
public Map<GroupKey, List<Result>> load(SearchKey key) throws DiscoveryException, SQLException, TskCoreException {
List<Result> domainResults = getResultDomainsFromDatabase(key);
// Apply secondary in memory filters
for (AbstractFilter filter : key.getFilters()) {
if (filter.useAlternateFilter()) {
domainResults = filter.applyAlternateFilter(domainResults, key.getSleuthkitCase(), key.getCentralRepository());
}
}
// Grouping by CR Frequency, for example, will require further processing
// in order to make the correct decision. The attribute types that require
// more information implement their logic by overriding `addAttributeToResults`.
List<AttributeType> searchAttributes = new ArrayList<>();
searchAttributes.add(key.getGroupAttributeType());
searchAttributes.addAll(key.getFileSortingMethod().getRequiredAttributes());
for (AttributeType attr : searchAttributes) {
attr.addAttributeToResults(domainResults,
key.getSleuthkitCase(), key.getCentralRepository());
}
// Sort the ResultDomains by the requested criteria.
final SearchResults searchResults = new SearchResults(
key.getGroupSortingType(),
key.getGroupAttributeType(),
key.getFileSortingMethod());
searchResults.add(domainResults);
return searchResults.toLinkedHashMap();
}
/**
* Queries for domain names from the case database.
*
* @param key The SearchKey passed to the cache.
*
* @return A list of results corresponding to the domains found in the case
* database.
*/
List<Result> getResultDomainsFromDatabase(SearchKey key) throws TskCoreException, SQLException, DiscoveryException {
// Filters chosen in the UI are aggregated into SQL statements to be used in
// the queries that follow.
final Pair<String, String> filterClauses = createWhereAndHavingClause(key.getFilters());
final String whereClause = filterClauses.getLeft();
final String havingClause = filterClauses.getRight();
// You may think of each row of this result as a TSK_DOMAIN attribute, where the parent
// artifact type is within the (optional) filter and the parent artifact
// had a date time attribute that was within the (optional) filter. With this
// table in hand, we can simply group by domain and apply aggregate functions
// to get, for example, # of downloads, # of visits in last 60, etc.
final String domainsTable
= "SELECT LOWER(MAX(value_text)) AS domain,"
+ " MAX(value_int64) AS date,"
+ " artifact_id AS parent_artifact_id,"
+ " MAX(artifact_type_id) AS parent_artifact_type_id "
+ "FROM blackboard_attributes "
+ "WHERE " + whereClause + " "
+ "GROUP BY artifact_id "
+ "HAVING " + havingClause;
// Needed to populate the visitsInLast60 data.
final Instant currentTime = Instant.now();
final Instant sixtyDaysAgo = currentTime.minus(60, ChronoUnit.DAYS);
// Check the group attribute, if by data source then the GROUP BY clause
// should group by data source id before grouping by domain.
final AttributeType groupAttribute = key.getGroupAttributeType();
final String groupByClause = (groupAttribute instanceof DataSourceAttribute)
? "data_source_obj_id, domain" : "domain";
final Optional<AbstractFilter> dataSourceFilter = key.getFilters().stream()
.filter(filter -> filter instanceof DataSourceFilter)
.findFirst();
String dataSourceWhereClause = null;
if (dataSourceFilter.isPresent()) {
dataSourceWhereClause = dataSourceFilter.get().getWhereClause();
}
// This query just processes the domains table, performing additional
// groupings and applying aggregate functions to calculate discovery data.
final String domainsQuery
= /*
* SELECT
*/ " domain,"
+ " MIN(date) AS activity_start,"
+ " MAX(date) AS activity_end,"
+ " SUM(CASE "
+ " WHEN artifact_type_id = " + TSK_WEB_DOWNLOAD.getTypeID() + " THEN 1 "
+ " ELSE 0 "
+ " END) AS fileDownloads,"
+ " SUM(CASE "
+ " WHEN artifact_type_id = " + TSK_WEB_HISTORY.getTypeID() + " THEN 1 "
+ " ELSE 0 "
+ " END) AS totalVisits,"
+ " SUM(CASE "
+ " WHEN artifact_type_id = " + TSK_WEB_HISTORY.getTypeID() + " AND"
+ " date BETWEEN " + sixtyDaysAgo.getEpochSecond() + " AND " + currentTime.getEpochSecond() + " THEN 1 "
+ " ELSE 0 "
+ " END) AS last60,"
+ " MAX(data_source_obj_id) AS dataSource "
+ "FROM blackboard_artifacts"
+ " JOIN (" + domainsTable + ") AS domains_table"
+ " ON artifact_id = parent_artifact_id "
+ // Add the data source where clause here if present.
((dataSourceWhereClause != null) ? "WHERE " + dataSourceWhereClause + " " : "")
+ "GROUP BY " + groupByClause;
final SleuthkitCase caseDb = key.getSleuthkitCase();
final CaseDbAccessManager dbManager = caseDb.getCaseDbAccessManager();
final DomainCallback domainCallback = new DomainCallback(caseDb);
dbManager.select(domainsQuery, domainCallback);
if (domainCallback.getSQLException() != null) {
throw domainCallback.getSQLException();
}
if (domainCallback.getTskCoreException() != null) {
throw domainCallback.getTskCoreException();
}
return domainCallback.getResultDomains();
}
/**
* A utility method to transform filters into the necessary SQL statements
* for the domainsTable query. The complexity of that query requires this
* transformation process to be conditional. The date time filter is a good
* example of the type of conditional handling that follows in the method
* below. If no dateTime filter is supplied, then in order for the query to
* be correct, an additional clause needs to be added in.
*
* @param filters The list of filters to apply create the where clause from.
*
* @return The whereClause and havingClause as a pair. These methods are one
* to stress that these clauses are tightly coupled.
*/
Pair<String, String> createWhereAndHavingClause(List<AbstractFilter> filters) {
final StringJoiner whereClause = new StringJoiner(" OR ");
final StringJoiner havingClause = new StringJoiner(" AND ");
String artifactTypeFilter = null;
boolean hasDateTimeFilter = false;
for (AbstractFilter filter : filters) {
if (filter instanceof ArtifactTypeFilter) {
artifactTypeFilter = filter.getWhereClause();
} else if (!(filter instanceof DataSourceFilter) && !filter.useAlternateFilter()) {
if (filter instanceof ArtifactDateRangeFilter) {
hasDateTimeFilter = true;
}
whereClause.add("(" + filter.getWhereClause() + ")");
havingClause.add("SUM(CASE WHEN " + filter.getWhereClause() + " THEN 1 ELSE 0 END) > 0");
}
}
if (!hasDateTimeFilter) {
whereClause.add(ArtifactDateRangeFilter.createAttributeTypeClause());
}
String domainAttributeFilter = "attribute_type_id = " + TSK_DOMAIN.getTypeID()
+ " AND value_text <> ''";
whereClause.add("(" + domainAttributeFilter + ")");
havingClause.add("SUM(CASE WHEN " + domainAttributeFilter + " THEN 1 ELSE 0 END) > 0");
return Pair.of(
whereClause.toString() + ((artifactTypeFilter != null) ? " AND (" + artifactTypeFilter + ")" : ""),
havingClause.toString()
);
}
/**
* Callback to handle the result set of the domain query. This callback is
* responsible for mapping result set rows into ResultDomain objects for
* display.
*/
private class DomainCallback implements CaseDbAccessQueryCallback {
private final List<Result> resultDomains;
private final SleuthkitCase skc;
private SQLException sqlCause;
private TskCoreException coreCause;
private final Set<String> bannedDomains = new HashSet<String>() {{
add("localhost");
add("127.0.0.1");
}};
/**
* Construct a new DomainCallback object.
*
* @param skc The case database for the query being performed.
*/
private DomainCallback(SleuthkitCase skc) {
this.resultDomains = new ArrayList<>();
this.skc = skc;
}
@Override
public void process(ResultSet resultSet) {
try {
resultSet.setFetchSize(500);
while (resultSet.next()) {
String domain = resultSet.getString("domain");
if (bannedDomains.contains(domain)) {
// Skip banned domains
// Domain names are lowercased in the SQL query
continue;
}
Long activityStart = resultSet.getLong("activity_start");
if (resultSet.wasNull()) {
activityStart = null;
}
Long activityEnd = resultSet.getLong("activity_end");
if (resultSet.wasNull()) {
activityEnd = null;
}
Long filesDownloaded = resultSet.getLong("fileDownloads");
if (resultSet.wasNull()) {
filesDownloaded = null;
}
Long totalVisits = resultSet.getLong("totalVisits");
if (resultSet.wasNull()) {
totalVisits = null;
}
Long visitsInLast60 = resultSet.getLong("last60");
if (resultSet.wasNull()) {
visitsInLast60 = null;
}
Long dataSourceID = resultSet.getLong("dataSource");
Content dataSource = skc.getContentById(dataSourceID);
resultDomains.add(new ResultDomain(domain, activityStart,
activityEnd, totalVisits, visitsInLast60, filesDownloaded, dataSource));
}
} catch (SQLException ex) {
this.sqlCause = ex;
} catch (TskCoreException ex) {
this.coreCause = ex;
}
}
/**
* Get the list of Result object for the domains which were in the
* search results.
*
* @return The list of Result object for the domains which were in the
* search results.
*/
private List<Result> getResultDomains() {
return Collections.unmodifiableList(this.resultDomains);
}
/**
* Get the SQLEception in an exception occurred.
*
* @return The SQLEception in an exception occurred.
*/
private SQLException getSQLException() {
return this.sqlCause;
}
/**
* Get the TskCoreException if a SQL exception occurred.
*
* @return The TskCoreException if a tsk core exception occurred.
*/
private TskCoreException getTskCoreException() {
return this.coreCause;
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import java.awt.Image;
import java.util.concurrent.ExecutionException;
/**
* Caches thumbnail requests.
*/
public class DomainSearchThumbnailCache {
private static final int MAXIMUM_CACHE_SIZE = 500;
private static final LoadingCache<DomainSearchThumbnailRequest, Image> cache
= CacheBuilder.newBuilder()
.maximumSize(MAXIMUM_CACHE_SIZE)
.build(new DomainSearchThumbnailLoader());
/**
* Get a thumbnail for the requested domain. If the request is new, the
* thumbnail will be automatically loaded.
*
* @param request Requested domain to thumbnail.
*
* @return The thumbnail Image instance, or null if no thumbnail is
* available.
*
* @throws DiscoveryException If any error occurs during thumbnail
* generation.
*/
public Image get(DomainSearchThumbnailRequest request) throws DiscoveryException {
try {
return cache.get(request);
} catch (ExecutionException ex) {
throw new DiscoveryException("Error fetching artifacts from cache", ex);
}
}
}

View File

@ -0,0 +1,163 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.cache.CacheLoader;
import java.awt.Image;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.AbstractFile;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.openide.util.ImageUtilities;
/**
* Loads a thumbnail for the given request. Thumbnail candidates are JPEG files
* that are either TSK_WEB_DOWNLOAD or TSK_WEB_CACHE artifacts. JPEG files are
* sorted by most recent if sourced from TSK_WEB_DOWNLOADs. JPEG files are
* sorted by size if sourced from TSK_WEB_CACHE artifacts. Artifacts are first
* loaded from the DomainSearchArtifactsCache and then further analyzed.
*/
public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbnailRequest, Image> {
private static final String UNSUPPORTED_IMAGE = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
private static final String JPG_EXTENSION = "jpg";
private static final String JPG_MIME_TYPE = "image/jpeg";
private final DomainSearchArtifactsCache artifactsCache;
/**
* Construct a new DomainSearchThumbnailLoader.
*/
public DomainSearchThumbnailLoader() {
this(new DomainSearchArtifactsCache());
}
/**
* Construct a new DomainSearchThumbnailLoader with an existing
* DomainSearchArtifactsCache.
*
* @param artifactsCache The DomainSearchArtifactsCache to use for this
* DomainSearchThumnailLoader.
*/
DomainSearchThumbnailLoader(DomainSearchArtifactsCache artifactsCache) {
this.artifactsCache = artifactsCache;
}
@Override
public Image load(DomainSearchThumbnailRequest thumbnailRequest) throws TskCoreException, DiscoveryException {
final SleuthkitCase caseDb = thumbnailRequest.getSleuthkitCase();
final DomainSearchArtifactsRequest webDownloadsRequest = new DomainSearchArtifactsRequest(
caseDb, thumbnailRequest.getDomain(), TSK_WEB_DOWNLOAD);
final List<BlackboardArtifact> webDownloads = artifactsCache.get(webDownloadsRequest);
final List<AbstractFile> webDownloadPictures = getJpegsFromWebDownload(caseDb, webDownloads);
Collections.sort(webDownloadPictures, (file1, file2) -> Long.compare(file1.getCrtime(), file2.getCrtime()));
for (int i = webDownloadPictures.size() - 1; i >= 0; i--) {
// Get the most recent image, according to creation time.
final AbstractFile mostRecent = webDownloadPictures.get(i);
final Image candidateThumbnail = ImageUtils.getThumbnail(mostRecent, thumbnailRequest.getIconSize());
if (candidateThumbnail != ImageUtils.getDefaultThumbnail()) {
return candidateThumbnail;
}
}
final DomainSearchArtifactsRequest webCacheRequest = new DomainSearchArtifactsRequest(
caseDb, thumbnailRequest.getDomain(), TSK_WEB_CACHE);
final List<BlackboardArtifact> webCacheArtifacts = artifactsCache.get(webCacheRequest);
final List<AbstractFile> webCachePictures = getJpegsFromWebCache(caseDb, webCacheArtifacts);
Collections.sort(webCachePictures, (file1, file2) -> Long.compare(file1.getSize(), file2.getSize()));
for (int i = webCachePictures.size() - 1; i >= 0; i--) {
// Get the largest image, according to file size.
final AbstractFile largest = webCachePictures.get(i);
final Image candidateThumbnail = ImageUtils.getThumbnail(largest, thumbnailRequest.getIconSize());
if (candidateThumbnail != ImageUtils.getDefaultThumbnail()) {
return candidateThumbnail;
}
}
return ImageUtilities.loadImage(UNSUPPORTED_IMAGE, false);
}
/**
* Finds all JPEG source files from TSK_WEB_DOWNLOAD instances.
*
* @param caseDb The case database being searched.
* @param artifacts The list of artifacts to get jpegs from.
*
* @return The list of AbstractFiles representing jpegs which were
* associated with the artifacts.
*
* @throws TskCoreException
*/
private List<AbstractFile> getJpegsFromWebDownload(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException {
final List<AbstractFile> jpegs = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
final Content sourceContent = caseDb.getContentById(artifact.getObjectID());
addIfJpeg(jpegs, sourceContent);
}
return jpegs;
}
/**
* Finds all JPEG source files from TSK_WEB_CACHE instances.
*
* @param caseDb The case database being searched.
* @param artifacts The list of artifacts to get jpegs from.
*
* @return The list of AbstractFiles representing jpegs which were
* associated with the artifacts.
*/
private List<AbstractFile> getJpegsFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException {
final BlackboardAttribute.Type TSK_PATH_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID);
final List<AbstractFile> jpegs = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID);
if (tskPathId != null) {
final Content sourceContent = caseDb.getContentById(tskPathId.getValueLong());
addIfJpeg(jpegs, sourceContent);
}
}
return jpegs;
}
/**
* Checks if the candidate source content is indeed a JPEG file.
*
* @param files The list of source content files which are jpegs to
* add to.
* @param sourceContent The source content to check and possibly add.
*/
private void addIfJpeg(List<AbstractFile> files, Content sourceContent) {
if ((sourceContent instanceof AbstractFile) && !(sourceContent instanceof DataSource)) {
final AbstractFile file = (AbstractFile) sourceContent;
if (JPG_EXTENSION.equals(file.getNameExtension())
|| JPG_MIME_TYPE.equals(file.getMIMEType())) {
files.add(file);
}
}
}
}

View File

@ -0,0 +1,96 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import java.util.Objects;
import org.sleuthkit.datamodel.SleuthkitCase;
/**
* Requests a thumbnail to be generated for a given Case, domain and size.
* IconSize should be a value obtained from ImageUtils.
*/
public class DomainSearchThumbnailRequest {
private final SleuthkitCase sleuthkitCase;
private final String domain;
private final int iconSize;
/**
* Construct a new DomainSearchThumbnailRequest.
*
* @param sleuthkitCase The case database for this thumbnail request.
* @param domain The domain name for this thumbnail request.
* @param iconSize The size of icon that this thumbnail request should
* retrieve.
*/
public DomainSearchThumbnailRequest(SleuthkitCase sleuthkitCase,
String domain, int iconSize) {
this.sleuthkitCase = sleuthkitCase;
this.domain = domain;
this.iconSize = iconSize;
}
/**
* Get the case database for this thumbnail request.
*
* @return The case database for this thumbnail request.
*/
public SleuthkitCase getSleuthkitCase() {
return sleuthkitCase;
}
/**
* Get the domain name for this thumbnail request.
*
* @return The domain name for this thumbnail request.
*/
public String getDomain() {
return domain;
}
/**
* Get the size of icon that this thumbnail request should retrieve.
*
* @return The size of icon that this thumbnail request should retrieve.
*/
public int getIconSize() {
return iconSize;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof DomainSearchThumbnailRequest)) {
return false;
}
DomainSearchThumbnailRequest otherRequest = (DomainSearchThumbnailRequest) other;
return this.sleuthkitCase == otherRequest.getSleuthkitCase()
&& this.domain.equals(otherRequest.getDomain())
&& this.iconSize == otherRequest.getIconSize();
}
@Override
public int hashCode() {
return 79 * 5 + Objects.hash(this.domain, this.iconSize);
}
}

View File

@ -0,0 +1,312 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.AttributeType;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.SearchKey;
import org.sleuthkit.autopsy.textsummarizer.TextSummarizer;
import org.sleuthkit.autopsy.textsummarizer.TextSummary;
/**
* Main class to perform the file search.
*/
public class FileSearch {
private final static Logger logger = Logger.getLogger(FileSearch.class.getName());
private static final int MAXIMUM_CACHE_SIZE = 10;
private static final Cache<SearchKey, Map<GroupKey, List<Result>>> searchCache = CacheBuilder.newBuilder()
.maximumSize(MAXIMUM_CACHE_SIZE)
.build();
/**
* Run the file search and returns the SearchResults object for debugging.
* Caching new results for access at later time.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply
* @param groupAttributeType The attribute to use for grouping
* @param groupSortingType The method to use to sort the groups
* @param fileSortingMethod The method to use to sort the files within the
* groups
* @param caseDb The case database
* @param centralRepoDb The central repository database. Can be null if
* not needed.
*
* @return The raw search results
*
* @throws DiscoveryException
*/
static SearchResults runFileSearchDebug(String userName,
List<AbstractFilter> filters,
AttributeType groupAttributeType,
Group.GroupSortingAlgorithm groupSortingType,
ResultsSorter.SortingMethod fileSortingMethod,
SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
// Make a list of attributes that we want to add values for. This ensures the
// ResultFile objects will have all needed fields set when it's time to group
// and sort them. For example, if we're grouping by central repo frequency, we need
// to make sure we've loaded those values before grouping.
List<AttributeType> attributesNeededForGroupingOrSorting = new ArrayList<>();
attributesNeededForGroupingOrSorting.add(groupAttributeType);
attributesNeededForGroupingOrSorting.addAll(fileSortingMethod.getRequiredAttributes());
// Run the queries for each filter
List<Result> results = SearchFiltering.runQueries(filters, caseDb, centralRepoDb);
// Add the data to resultFiles for any attributes needed for sorting and grouping
addAttributes(attributesNeededForGroupingOrSorting, results, caseDb, centralRepoDb);
// Collect everything in the search results
SearchResults searchResults = new SearchResults(groupSortingType, groupAttributeType, fileSortingMethod);
searchResults.add(results);
// Sort and group the results
searchResults.sortGroupsAndFiles();
Map<GroupKey, List<Result>> resultHashMap = searchResults.toLinkedHashMap();
SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
synchronized (searchCache) {
searchCache.put(searchKey, resultHashMap);
}
return searchResults;
}
/**
* Run the file search to get the group keys and sizes. Clears cache of
* search results, caching new results for access at later time.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply
* @param groupAttributeType The attribute to use for grouping
* @param groupSortingType The method to use to sort the groups
* @param fileSortingMethod The method to use to sort the files within the
* groups
* @param caseDb The case database
* @param centralRepoDb The central repository database. Can be null if
* not needed.
*
* @return A LinkedHashMap grouped and sorted according to the parameters
*
* @throws DiscoveryException
*/
public static Map<GroupKey, Integer> getGroupSizes(String userName,
List<AbstractFilter> filters,
AttributeType groupAttributeType,
Group.GroupSortingAlgorithm groupSortingType,
ResultsSorter.SortingMethod fileSortingMethod,
SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
Map<GroupKey, List<Result>> searchResults = runFileSearch(userName, filters,
groupAttributeType, groupSortingType, fileSortingMethod, caseDb, centralRepoDb);
LinkedHashMap<GroupKey, Integer> groupSizes = new LinkedHashMap<>();
for (GroupKey groupKey : searchResults.keySet()) {
groupSizes.put(groupKey, searchResults.get(groupKey).size());
}
return groupSizes;
}
/**
* Get the files from the specified group from the cache, if the the group
* was not cached perform a search caching the groups.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply
* @param groupAttributeType The attribute to use for grouping
* @param groupSortingType The method to use to sort the groups
* @param fileSortingMethod The method to use to sort the files within the
* groups
* @param groupKey The key which uniquely identifies the group to
* get entries from
* @param startingEntry The first entry to return
* @param numberOfEntries The number of entries to return
* @param caseDb The case database
* @param centralRepoDb The central repository database. Can be null if
* not needed.
*
* @return A LinkedHashMap grouped and sorted according to the parameters
*
* @throws DiscoveryException
*/
public static List<Result> getFilesInGroup(String userName,
List<AbstractFilter> filters,
AttributeType groupAttributeType,
Group.GroupSortingAlgorithm groupSortingType,
ResultsSorter.SortingMethod fileSortingMethod,
GroupKey groupKey,
int startingEntry,
int numberOfEntries,
SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
//the group should be in the cache at this point
List<Result> filesInGroup = null;
SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
Map<GroupKey, List<Result>> resultsMap;
synchronized (searchCache) {
resultsMap = searchCache.getIfPresent(searchKey);
}
if (resultsMap != null) {
filesInGroup = resultsMap.get(groupKey);
}
List<Result> page = new ArrayList<>();
if (filesInGroup == null) {
logger.log(Level.INFO, "Group {0} was not cached, performing search to cache all groups again", groupKey);
runFileSearch(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod, caseDb, centralRepoDb);
synchronized (searchCache) {
resultsMap = searchCache.getIfPresent(searchKey.getKeyString());
}
if (resultsMap != null) {
filesInGroup = resultsMap.get(groupKey);
}
if (filesInGroup == null) {
logger.log(Level.WARNING, "Group {0} did not exist in cache or new search results", groupKey);
return page; //group does not exist
}
}
// Check that there is data after the starting point
if (filesInGroup.size() < startingEntry) {
logger.log(Level.WARNING, "Group only contains {0} files, starting entry of {1} is too large.", new Object[]{filesInGroup.size(), startingEntry});
return page;
}
// Add files to the page
for (int i = startingEntry; (i < startingEntry + numberOfEntries)
&& (i < filesInGroup.size()); i++) {
page.add(filesInGroup.get(i));
}
return page;
}
/**
* Get a summary for the specified AbstractFile. If no TextSummarizers exist
* get the beginning of the file.
*
* @param file The AbstractFile to summarize.
*
* @return The summary or beginning of the specified file as a String.
*/
@NbBundle.Messages({"FileSearch.documentSummary.noPreview=No preview available.",
"FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview."})
public static TextSummary summarize(AbstractFile file) {
TextSummary summary = null;
TextSummarizer localSummarizer;
synchronized (searchCache) {
localSummarizer = SummaryHelpers.getLocalSummarizer();
}
if (localSummarizer != null) {
try {
//a summary of length 40 seems to fit without vertical scroll bars
summary = localSummarizer.summarize(file, 40);
} catch (IOException ex) {
return new TextSummary(Bundle.FileSearch_documentSummary_noPreview(), null, 0);
}
}
if (summary == null || StringUtils.isBlank(summary.getSummaryText())) {
//summary text was empty grab the beginning of the file
summary = SummaryHelpers.getDefaultSummary(file);
}
return summary;
}
/**
* Run the file search. Caching new results for access at later time.
*
* @param userName The name of the user performing the search.
* @param filters The filters to apply
* @param groupAttributeType The attribute to use for grouping
* @param groupSortingType The method to use to sort the groups
* @param fileSortingMethod The method to use to sort the files within the
* groups
* @param caseDb The case database
* @param centralRepoDb The central repository database. Can be null if
* not needed.
*
* @return A LinkedHashMap grouped and sorted according to the parameters
*
* @throws DiscoveryException
*/
private static Map<GroupKey, List<Result>> runFileSearch(String userName,
List<AbstractFilter> filters,
AttributeType groupAttributeType,
Group.GroupSortingAlgorithm groupSortingType,
ResultsSorter.SortingMethod fileSortingMethod,
SleuthkitCase caseDb, CentralRepository centralRepoDb) throws DiscoveryException {
// Make a list of attributes that we want to add values for. This ensures the
// ResultFile objects will have all needed fields set when it's time to group
// and sort them. For example, if we're grouping by central repo frequency, we need
// to make sure we've loaded those values before grouping.
List<AttributeType> attributesNeededForGroupingOrSorting = new ArrayList<>();
attributesNeededForGroupingOrSorting.add(groupAttributeType);
attributesNeededForGroupingOrSorting.addAll(fileSortingMethod.getRequiredAttributes());
// Run the queries for each filter
List<Result> results = SearchFiltering.runQueries(filters, caseDb, centralRepoDb);
// Add the data to resultFiles for any attributes needed for sorting and grouping
addAttributes(attributesNeededForGroupingOrSorting, results, caseDb, centralRepoDb);
// Collect everything in the search results
SearchResults searchResults = new SearchResults(groupSortingType, groupAttributeType, fileSortingMethod);
searchResults.add(results);
Map<GroupKey, List<Result>> resultHashMap = searchResults.toLinkedHashMap();
SearchKey searchKey = new SearchKey(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod);
synchronized (searchCache) {
searchCache.put(searchKey, resultHashMap);
}
// Return a version of the results in general Java objects
return resultHashMap;
}
/**
* Add any attributes corresponding to the attribute list to the given
* result files. For example, specifying the KeywordListAttribute will
* populate the list of keyword set names in the ResultFile objects.
*
* @param attrs The attributes to add to the list of result files
* @param resultFiles The result files
* @param caseDb The case database
* @param centralRepoDb The central repository database. Can be null if not
* needed.
*
* @throws DiscoveryException
*/
private static void addAttributes(List<AttributeType> attrs, List<Result> results, SleuthkitCase caseDb, CentralRepository centralRepoDb)
throws DiscoveryException {
for (AttributeType attr : attrs) {
attr.addAttributeToResults(results, caseDb, centralRepoDb);
}
}
private FileSearch() {
// Class should not be instantiated
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Copyright 2019-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -16,48 +16,49 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.search;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.discovery.FileSearch.GroupKey;
/**
* Class for storing files that belong to a particular group.
* Class for storing results that belong to a particular group.
*/
class FileGroup implements Comparable<FileGroup> {
public class Group implements Comparable<Group> {
private final FileGroup.GroupSortingAlgorithm groupSortingType;
private final GroupKey groupKey;
private final List<ResultFile> files;
private final Group.GroupSortingAlgorithm groupSortingType;
private final DiscoveryKeyUtils.GroupKey groupKey;
private final List<Result> results;
private final String displayName;
/**
* Create a FileGroup object with its first file.
* Create a Group object with its first result.
*
* @param groupSortingType The method for sorting the group
* @param groupKey The GroupKey for this group
*/
FileGroup(FileGroup.GroupSortingAlgorithm groupSortingType, GroupKey groupKey) {
public Group(Group.GroupSortingAlgorithm groupSortingType, DiscoveryKeyUtils.GroupKey groupKey) {
this.groupSortingType = groupSortingType;
this.groupKey = groupKey;
files = new ArrayList<>();
results = new ArrayList<>();
this.displayName = groupKey.getDisplayName();
}
/**
* Add a ResultFile to the group. Will not be sorted at this time.
* Add a Result to the group. Will not be sorted at this time.
*
* @param file The ResultFile to add to the FileGroup
* @param result The Result to add to the Group.
*/
void addFile(ResultFile file) {
if (files.contains(file)) {
ResultFile existingCopy = files.get(files.indexOf(file)); //get the copy of this which exists in the list
existingCopy.addDuplicate(file.getFirstInstance());
void addResult(Result result) {
if (result.getType() != SearchData.Type.DOMAIN && results.contains(result)) {
//dedupe files and show instances
ResultFile existingCopy = (ResultFile) results.get(results.indexOf(result)); //get the copy of this which exists in the list
existingCopy.addDuplicate(((ResultFile) result).getFirstInstance());
} else {
files.add(file);
//Domains and non files are not being deduped currently
results.add(result);
}
}
@ -66,7 +67,7 @@ class FileGroup implements Comparable<FileGroup> {
*
* @return The display name of the group.
*/
String getDisplayName() {
public String getDisplayName() {
return displayName; // NON-NLS
}
@ -75,28 +76,28 @@ class FileGroup implements Comparable<FileGroup> {
*
* @return The unique key for the group.
*/
GroupKey getGroupKey() {
public DiscoveryKeyUtils.GroupKey getGroupKey() {
return groupKey;
}
/**
* Sort all the files in the group
* Sort all the results in the group
*/
void sortFiles(FileSorter sorter) {
Collections.sort(files, sorter);
public void sortResults(ResultsSorter sorter) {
Collections.sort(results, sorter);
}
/**
* Compare this group to another group for sorting. Uses the algorithm
* specified in groupSortingType.
*
* @param otherGroup the group to compare this one to
* @param otherGroup The group to compare this one to.
*
* @return -1 if this group should be displayed before the other group, 1
* otherwise
* otherwise.
*/
@Override
public int compareTo(FileGroup otherGroup) {
public int compareTo(Group otherGroup) {
switch (groupSortingType) {
case BY_GROUP_SIZE:
@ -108,14 +109,14 @@ class FileGroup implements Comparable<FileGroup> {
}
/**
* Compare two groups based on the group key
* Compare two groups based on the group key.
*
* @param group1
* @param group2
* @param group1 The first group to be compared.
* @param group2 The second group to be compared.
*
* @return -1 if group1 should be displayed before group2, 1 otherwise
* @return -1 if group1 should be displayed before group2, 1 otherwise.
*/
private static int compareGroupsByGroupKey(FileGroup group1, FileGroup group2) {
private static int compareGroupsByGroupKey(Group group1, Group group2) {
return group1.getGroupKey().compareTo(group2.getGroupKey());
}
@ -123,14 +124,14 @@ class FileGroup implements Comparable<FileGroup> {
* Compare two groups based on the group size. Falls back on the group key
* if the groups are the same size.
*
* @param group1
* @param group2
* @param group1 The first group to be compared.
* @param group2 The second group to be compared.
*
* @return -1 if group1 should be displayed before group2, 1 otherwise
* @return -1 if group1 should be displayed before group2, 1 otherwise.
*/
private static int compareGroupsBySize(FileGroup group1, FileGroup group2) {
if (group1.getFiles().size() != group2.getFiles().size()) {
return -1 * Long.compare(group1.getFiles().size(), group2.getFiles().size()); // High to low
private static int compareGroupsBySize(Group group1, Group group2) {
if (group1.getResults().size() != group2.getResults().size()) {
return -1 * Long.compare(group1.getResults().size(), group2.getResults().size()); // High to low
} else {
// If the groups have the same size, fall through to the BY_GROUP_NAME sorting
return compareGroupsByGroupKey(group1, group2);
@ -142,7 +143,7 @@ class FileGroup implements Comparable<FileGroup> {
*/
@Messages({"FileGroup.groupSortingAlgorithm.groupSize.text=Group Size",
"FileGroup.groupSortingAlgorithm.groupName.text=Group Name"})
enum GroupSortingAlgorithm {
public enum GroupSortingAlgorithm {
BY_GROUP_NAME(Bundle.FileGroup_groupSortingAlgorithm_groupName_text()), // Sort using the group key (for example, if grouping by size sort from largest to smallest value)
BY_GROUP_SIZE(Bundle.FileGroup_groupSortingAlgorithm_groupSize_text()); // Sort from largest to smallest group
@ -165,12 +166,12 @@ class FileGroup implements Comparable<FileGroup> {
}
/**
* Get the list of ResultFile objects in the group
* Get the list of Result objects in the group.
*
* @return List of ResultFile objects
* @return The list of Result objects.
*/
List<ResultFile> getFiles() {
return Collections.unmodifiableList(files);
public List<Result> getResults() {
return Collections.unmodifiableList(results);
}
}

View File

@ -0,0 +1,106 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Interface implemented by all types of results.
*/
public abstract class Result {
private SearchData.Frequency frequency;
private final List<String> tagNames = new ArrayList<>();
/**
* Get the Object ID for the data source the result is in.
*
* @return The Object ID of the data source the result is in.
*/
public abstract long getDataSourceObjectId();
/**
* Get the frequency of this result in the central repository.
*
* @return The Frequency enum.
*/
public SearchData.Frequency getFrequency() {
return frequency;
}
/**
* Get the known status of the result.
*
* @return The Known status of the result.
*/
public abstract TskData.FileKnown getKnown();
/**
* Set the frequency of this result in the central repository.
*
* @param frequency The frequency of the result as an enum.
*/
final public void setFrequency(SearchData.Frequency frequency) {
this.frequency = frequency;
}
/**
* Get the data source associated with this result.
*
* @return The data source this result came from.
*
* @throws TskCoreException
*/
public abstract Content getDataSource() throws TskCoreException;
/**
* Get the type of this result.
*
* @return The type of items being searched for.
*/
public abstract SearchData.Type getType();
/**
* Add a tag name that matched this file.
*
* @param tagName
*/
public void addTagName(String tagName) {
if (!tagNames.contains(tagName)) {
tagNames.add(tagName);
}
// Sort the list so the getTagNames() will be consistent regardless of the order added
Collections.sort(tagNames);
}
/**
* Get the tag names for this file
*
* @return the tag names that matched this file.
*/
public List<String> getTagNames() {
return Collections.unmodifiableList(tagNames);
}
}

View File

@ -0,0 +1,141 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Container for domains that holds all necessary data for grouping and sorting.
*/
public class ResultDomain extends Result {
private final String domain;
private final Long activityStart;
private final Long activityEnd;
private final Long totalVisits;
private final Long visitsInLast60;
private final Long filesDownloaded;
private final Content dataSource;
private final long dataSourceId;
/**
* Create a ResultDomain from a String.
*
* @param domain The domain the result is being created from.
*/
ResultDomain(String domain, Long activityStart, Long activityEnd, Long totalVisits,
Long visitsInLast60, Long filesDownloaded, Content dataSource) {
this.domain = domain;
this.dataSource = dataSource;
this.dataSourceId = dataSource.getId();
this.activityStart = activityStart;
this.activityEnd = activityEnd;
this.totalVisits = totalVisits;
this.visitsInLast60 = visitsInLast60;
this.filesDownloaded = filesDownloaded;
this.setFrequency(SearchData.Frequency.UNKNOWN);
}
/**
* Get the domain represented as a String.
*
* @return The String representation of the domain this result is for.
*/
public String getDomain() {
return this.domain;
}
/**
* Get the date of first activity for this domain.
*
* @return The date of first activity for this domain.
*/
public Long getActivityStart() {
return activityStart;
}
/**
* Get the date of most recent activity for this domain.
*
* @return The date of most recent activity for this domain.
*/
public Long getActivityEnd() {
return activityEnd;
}
/**
* Get the total number of visits that this domain has had.
*
* @return The total number of visits that this domain has had.
*/
public Long getTotalVisits() {
return totalVisits;
}
/**
* Get the number of visits that this domain has had in the last 60 days.
*
* @return The number of visits that this domain has had in the last 60
* days.
*/
public Long getVisitsInLast60() {
return visitsInLast60;
}
/**
* Get the number of files downloaded associated with this domain.
*
* @return The number of files downloaded associated with this domain.
*/
public Long getFilesDownloaded() {
return filesDownloaded;
}
@Override
public long getDataSourceObjectId() {
return this.dataSourceId;
}
@Override
public Content getDataSource() throws TskCoreException {
return this.dataSource;
}
@Override
public TskData.FileKnown getKnown() {
return TskData.FileKnown.UNKNOWN;
}
@Override
public SearchData.Type getType() {
return SearchData.Type.DOMAIN;
}
@Override
public String toString() {
return "[domain=" + this.domain + ", data_source=" + this.dataSourceId + ", start="
+ this.activityStart + ", end=" + this.activityEnd + ", totalVisits=" + this.totalVisits + ", visitsLast60="
+ this.visitsInLast60 + ", downloads=" + this.filesDownloaded + ", frequency="
+ this.getFrequency() + "]";
}
}

View File

@ -16,9 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.search;
import org.sleuthkit.autopsy.discovery.FileSearchData.FileType;
import org.sleuthkit.autopsy.discovery.search.SearchData.Type;
import org.sleuthkit.datamodel.AbstractFile;
import java.util.ArrayList;
import java.util.Collections;
@ -29,7 +29,9 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.discovery.search.SearchData.Type.OTHER;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.HashUtility;
import org.sleuthkit.datamodel.Tag;
@ -39,27 +41,25 @@ import org.sleuthkit.datamodel.TskData;
/**
* Container for files that holds all necessary data for grouping and sorting.
*/
class ResultFile {
public class ResultFile extends Result {
private final static Logger logger = Logger.getLogger(ResultFile.class.getName());
private FileSearchData.Frequency frequency;
private final List<String> keywordListNames;
private final List<String> hashSetNames;
private final List<String> tagNames;
private final List<String> interestingSetNames;
private final List<String> objectDetectedNames;
private final List<AbstractFile> instances = new ArrayList<>();
private DataResultViewerTable.Score currentScore = DataResultViewerTable.Score.NO_SCORE;
private String scoreDescription = null;
private boolean deleted = false;
private FileType fileType;
private Type fileType;
/**
* Create a ResultFile from an AbstractFile
*
* @param abstractFile
*/
ResultFile(AbstractFile abstractFile) {
public ResultFile(AbstractFile abstractFile) {
try {
//call get uniquePath to cache the path
abstractFile.getUniquePath();
@ -72,31 +72,11 @@ class ResultFile {
deleted = true;
}
updateScoreAndDescription(abstractFile);
this.frequency = FileSearchData.Frequency.UNKNOWN;
keywordListNames = new ArrayList<>();
hashSetNames = new ArrayList<>();
tagNames = new ArrayList<>();
interestingSetNames = new ArrayList<>();
objectDetectedNames = new ArrayList<>();
fileType = FileType.fromMIMEtype(abstractFile.getMIMEType());
}
/**
* Get the frequency of this file in the central repository
*
* @return The Frequency enum
*/
FileSearchData.Frequency getFrequency() {
return frequency;
}
/**
* Set the frequency of this file from the central repository
*
* @param frequency The frequency of the file as an enum
*/
void setFrequency(FileSearchData.Frequency frequency) {
this.frequency = frequency;
fileType = fromMIMEtype(abstractFile.getMIMEType());
}
/**
@ -105,12 +85,12 @@ class ResultFile {
*
* @param duplicate The abstract file to add as a duplicate.
*/
void addDuplicate(AbstractFile duplicate) {
public void addDuplicate(AbstractFile duplicate) {
if (deleted && !duplicate.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
deleted = false;
}
if (fileType == FileType.OTHER) {
fileType = FileType.fromMIMEtype(duplicate.getMIMEType());
if (fileType == Type.OTHER) {
fileType = fromMIMEtype(duplicate.getMIMEType());
}
updateScoreAndDescription(duplicate);
try {
@ -128,7 +108,7 @@ class ResultFile {
*
* @return The score of this ResultFile.
*/
DataResultViewerTable.Score getScore() {
public DataResultViewerTable.Score getScore() {
return currentScore;
}
@ -137,7 +117,7 @@ class ResultFile {
*
* @return The score description of this ResultFile.
*/
String getScoreDescription() {
public String getScoreDescription() {
return scoreDescription;
}
@ -147,7 +127,7 @@ class ResultFile {
*
* @return The deleted status of this ResultFile.
*/
boolean isDeleted() {
public boolean isDeleted() {
return deleted;
}
@ -158,7 +138,7 @@ class ResultFile {
* @return The list of AbstractFiles which have been identified as instances
* of this file.
*/
List<AbstractFile> getAllInstances() {
public List<AbstractFile> getAllInstances() {
return Collections.unmodifiableList(instances);
}
@ -167,7 +147,7 @@ class ResultFile {
*
* @return The FileType enum.
*/
FileType getFileType() {
public Type getFileType() {
return fileType;
}
@ -176,7 +156,7 @@ class ResultFile {
*
* @param keywordListName
*/
void addKeywordListName(String keywordListName) {
public void addKeywordListName(String keywordListName) {
if (!keywordListNames.contains(keywordListName)) {
keywordListNames.add(keywordListName);
}
@ -190,7 +170,7 @@ class ResultFile {
*
* @return the keyword list names that matched this file.
*/
List<String> getKeywordListNames() {
public List<String> getKeywordListNames() {
return Collections.unmodifiableList(keywordListNames);
}
@ -199,7 +179,7 @@ class ResultFile {
*
* @param hashSetName
*/
void addHashSetName(String hashSetName) {
public void addHashSetName(String hashSetName) {
if (!hashSetNames.contains(hashSetName)) {
hashSetNames.add(hashSetName);
}
@ -213,39 +193,16 @@ class ResultFile {
*
* @return The hash set names that matched this file.
*/
List<String> getHashSetNames() {
public List<String> getHashSetNames() {
return Collections.unmodifiableList(hashSetNames);
}
/**
* Add a tag name that matched this file.
*
* @param tagName
*/
void addTagName(String tagName) {
if (!tagNames.contains(tagName)) {
tagNames.add(tagName);
}
// Sort the list so the getTagNames() will be consistent regardless of the order added
Collections.sort(tagNames);
}
/**
* Get the tag names for this file
*
* @return the tag names that matched this file.
*/
List<String> getTagNames() {
return Collections.unmodifiableList(tagNames);
}
/**
* Add an interesting file set name that matched this file.
*
* @param interestingSetName
*/
void addInterestingSetName(String interestingSetName) {
public void addInterestingSetName(String interestingSetName) {
if (!interestingSetNames.contains(interestingSetName)) {
interestingSetNames.add(interestingSetName);
}
@ -259,7 +216,7 @@ class ResultFile {
*
* @return the interesting item set names that matched this file.
*/
List<String> getInterestingSetNames() {
public List<String> getInterestingSetNames() {
return Collections.unmodifiableList(interestingSetNames);
}
@ -268,7 +225,7 @@ class ResultFile {
*
* @param objectDetectedName
*/
void addObjectDetectedName(String objectDetectedName) {
public void addObjectDetectedName(String objectDetectedName) {
if (!objectDetectedNames.contains(objectDetectedName)) {
objectDetectedNames.add(objectDetectedName);
}
@ -282,7 +239,7 @@ class ResultFile {
*
* @return the objects detected in this file.
*/
List<String> getObjectDetectedNames() {
public List<String> getObjectDetectedNames() {
return Collections.unmodifiableList(objectDetectedNames);
}
@ -291,7 +248,7 @@ class ResultFile {
*
* @return the AbstractFile object
*/
AbstractFile getFirstInstance() {
public AbstractFile getFirstInstance() {
return instances.get(0);
}
@ -299,7 +256,7 @@ class ResultFile {
public String toString() {
return getFirstInstance().getName() + "(" + getFirstInstance().getId() + ") - "
+ getFirstInstance().getSize() + ", " + getFirstInstance().getParentPath() + ", "
+ getFirstInstance().getDataSourceObjectId() + ", " + frequency.toString() + ", "
+ getFirstInstance().getDataSourceObjectId() + ", " + getFrequency().toString() + ", "
+ String.join(",", keywordListNames) + ", " + getFirstInstance().getMIMEType();
}
@ -380,4 +337,40 @@ class ResultFile {
}
}
}
/**
* Get the enum matching the given MIME type.
*
* @param mimeType The MIME type for the file.
*
* @return the corresponding enum (will be OTHER if no types matched)
*/
public static Type fromMIMEtype(String mimeType) {
for (Type type : Type.values()) {
if (type.getMediaTypes().contains(mimeType)) {
return type;
}
}
return OTHER;
}
@Override
public long getDataSourceObjectId() {
return getFirstInstance().getDataSourceObjectId();
}
@Override
public Content getDataSource() throws TskCoreException {
return getFirstInstance().getDataSource();
}
@Override
public TskData.FileKnown getKnown() {
return getFirstInstance().getKnown();
}
@Override
public Type getType() {
return fileType;
}
}

View File

@ -0,0 +1,388 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.openide.util.NbBundle;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Class used to sort Results using the supplied method.
*/
public class ResultsSorter implements Comparator<Result> {
private final List<Comparator<Result>> comparators = new ArrayList<>();
/**
* Set up the sorter using the supplied sorting method. The sorting is
* defined by a list of Result comparators. These comparators will be run in
* order until one returns a non-zero result.
*
* @param method The method that should be used to sort the results.
*/
public ResultsSorter(SortingMethod method) {
// Set up the primary comparators that should applied to the results
switch (method) {
case BY_DATA_SOURCE:
comparators.add(getDataSourceComparator());
break;
case BY_FILE_SIZE:
comparators.add(getFileSizeComparator());
break;
case BY_FILE_TYPE:
comparators.add(getTypeComparator());
comparators.add(getMIMETypeComparator());
break;
case BY_FREQUENCY:
comparators.add(getFrequencyComparator());
break;
case BY_KEYWORD_LIST_NAMES:
comparators.add(getKeywordListNameComparator());
break;
case BY_FULL_PATH:
comparators.add(getParentPathComparator());
break;
case BY_FILE_NAME:
comparators.add(getFileNameComparator());
break;
case BY_DOMAIN_NAME:
comparators.add(getDomainNameComparator());
break;
default:
// The default comparator will be added afterward
break;
}
// Add the default comparator to the end. This will ensure a consistent sort
// order regardless of the order the results were added to the list.
comparators.add(getDefaultComparator());
}
@Override
public int compare(Result result1, Result result2) {
int result = 0;
for (Comparator<Result> comp : comparators) {
result = comp.compare(result1, result2);
if (result != 0) {
return result;
}
}
// The results are the same
return result;
}
/**
* Compare results using data source ID. Will order smallest to largest.
*
* @return -1 if result1 has the lower data source ID, 0 if equal, 1
* otherwise.
*/
private static Comparator<Result> getDataSourceComparator() {
return (Result result1, Result result2) -> Long.compare(result1.getDataSourceObjectId(), result2.getDataSourceObjectId());
}
/**
* Compare results using their Type enum. Orders based on the ranking in the
* Type enum.
*
* @return -1 if result1 has the lower Type value, 0 if equal, 1 otherwise.
*/
private static Comparator<Result> getTypeComparator() {
return (Result result1, Result result2) -> Integer.compare(result1.getType().getRanking(), result2.getType().getRanking());
}
/**
* Compare files using a concatenated version of keyword list names.
* Alphabetical by the list names with files with no keyword list hits going
* last.
*
* @return -1 if result1 has the earliest combined keyword list name, 0 if
* equal, 1 otherwise.
*/
private static Comparator<Result> getKeywordListNameComparator() {
return (Result result1, Result result2) -> {
// Put empty lists at the bottom
if (result1.getType() == SearchData.Type.DOMAIN) {
return 0;
}
ResultFile file1 = (ResultFile) result1;
ResultFile file2 = (ResultFile) result2;
if (file1.getKeywordListNames().isEmpty()) {
if (file2.getKeywordListNames().isEmpty()) {
return 0;
}
return 1;
} else if (file2.getKeywordListNames().isEmpty()) {
return -1;
}
String list1 = String.join(",", file1.getKeywordListNames());
String list2 = String.join(",", file2.getKeywordListNames());
return compareStrings(list1, list2);
};
}
/**
* Compare files based on parent path. Order alphabetically.
*
* @return -1 if result1's path comes first alphabetically, 0 if equal, 1
* otherwise.
*/
private static Comparator<Result> getParentPathComparator() {
return new Comparator<Result>() {
@Override
public int compare(Result result1, Result result2) {
if (result1.getType() == SearchData.Type.DOMAIN) {
return 0;
}
ResultFile file1 = (ResultFile) result1;
ResultFile file2 = (ResultFile) result2;
String file1ParentPath;
try {
file1ParentPath = file1.getFirstInstance().getParent().getUniquePath();
} catch (TskCoreException ingored) {
file1ParentPath = file1.getFirstInstance().getParentPath();
}
String file2ParentPath;
try {
file2ParentPath = file2.getFirstInstance().getParent().getUniquePath();
} catch (TskCoreException ingored) {
file2ParentPath = file2.getFirstInstance().getParentPath();
}
return compareStrings(file1ParentPath.toLowerCase(), file2ParentPath.toLowerCase());
}
};
}
/**
* Compare results based on number of occurrences in the central repository.
* Order from most rare to least rare Frequency enum.
*
* @return -1 if result1's rarity is lower than result2, 0 if equal, 1
* otherwise.
*/
private static Comparator<Result> getFrequencyComparator() {
return (Result result1, Result result2) -> Integer.compare(result1.getFrequency().getRanking(), result2.getFrequency().getRanking());
}
/**
* Compare files based on MIME type. Order is alphabetical.
*
* @return -1 if result1's MIME type comes before result2's, 0 if equal, 1
* otherwise.
*/
private static Comparator<Result> getMIMETypeComparator() {
return (Result result1, Result result2) -> {
if (result1.getType() == SearchData.Type.DOMAIN) {
return 0;
}
return compareStrings(((ResultFile) result1).getFirstInstance().getMIMEType(), ((ResultFile) result2).getFirstInstance().getMIMEType());
};
}
/**
* Compare files based on size. Order large to small.
*
* @return -1 if result1 is larger than result2, 0 if equal, 1 otherwise.
*/
private static Comparator<Result> getFileSizeComparator() {
return (Result result1, Result result2) -> {
if (result1.getType() == SearchData.Type.DOMAIN) {
return 0;
}
return -1 * Long.compare(((ResultFile) result1).getFirstInstance().getSize(), ((ResultFile) result2).getFirstInstance().getSize()); // Sort large to small
};
}
/**
* Compare files based on file name. Order alphabetically.
*
* @return -1 if result1 comes before result2, 0 if equal, 1 otherwise.
*/
private static Comparator<Result> getFileNameComparator() {
return (Result result1, Result result2) -> {
if (result1.getType() == SearchData.Type.DOMAIN) {
return 0;
}
return compareStrings(((ResultFile) result1).getFirstInstance().getName().toLowerCase(), (((ResultFile) result2).getFirstInstance().getName().toLowerCase()));
};
}
/**
* Sorts domain names in lexographical order, ignoring case.
*
* @return -1 if domain1 comes before domain2, 0 if equal, 1 otherwise.
*/
private static Comparator<Result> getDomainNameComparator() {
return (Result domain1, Result domain2) -> {
if (domain1.getType() != SearchData.Type.DOMAIN) {
return 0;
}
ResultDomain first = (ResultDomain) domain1;
ResultDomain second = (ResultDomain) domain2;
return compareStrings(first.getDomain().toLowerCase(), second.getDomain().toLowerCase());
};
}
/**
* Sorts results by most recent date time.
*
* @return -1 if domain1 comes before domain2, 0 if equal, 1 otherwise.
*/
private static Comparator<Result> getMostRecentDateTimeComparator() {
return (Result result1, Result result2) -> {
if (result1.getType() != SearchData.Type.DOMAIN) {
return 0;
}
ResultDomain first = (ResultDomain) result1;
ResultDomain second = (ResultDomain) result2;
return Long.compare(second.getActivityEnd(), first.getActivityEnd());
};
}
/**
* A final default comparison between two ResultFile objects. Currently this
* is on file name and then object ID. It can be changed but should always
* include something like the object ID to ensure a consistent sorting when
* the rest of the compared fields are the same.
*
* @return -1 if file1 comes before file2, 0 if equal, 1 otherwise.
*/
private static Comparator<Result> getDefaultComparator() {
return (Result result1, Result result2) -> {
// Compare file names and then object ID (to ensure a consistent sort)
if (result1.getType() == SearchData.Type.DOMAIN) {
return getFrequencyComparator().compare(result1, result2);
} else {
ResultFile file1 = (ResultFile) result1;
ResultFile file2 = (ResultFile) result2;
int result = getFileNameComparator().compare(file1, file2);
if (result == 0) {
return Long.compare(file1.getFirstInstance().getId(), file2.getFirstInstance().getId());
}
return result;
}
};
}
/**
* Compare two strings alphabetically. Nulls are allowed.
*
* @param s1
* @param s2
*
* @return -1 if s1 comes before s2, 0 if equal, 1 otherwise.
*/
private static int compareStrings(String s1, String s2) {
String string1 = s1 == null ? "" : s1;
String string2 = s2 == null ? "" : s2;
return string1.compareTo(string2);
}
/**
* Enum for selecting the primary method for sorting result files.
*/
@NbBundle.Messages({
"FileSorter.SortingMethod.datasource.displayName=Data Source",
"FileSorter.SortingMethod.filename.displayName=File Name",
"FileSorter.SortingMethod.filesize.displayName=File Size",
"FileSorter.SortingMethod.filetype.displayName=File Type",
"FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency",
"FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names",
"FileSorter.SortingMethod.fullPath.displayName=Full Path",
"FileSorter.SortingMethod.domain.displayName=Domain"})
public enum SortingMethod {
BY_FILE_NAME(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_filename_displayName()), // Sort alphabetically by file name
BY_DATA_SOURCE(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_datasource_displayName()), // Sort in increasing order of data source ID
BY_FILE_SIZE(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_filesize_displayName()), // Sort in decreasing order of size
BY_FILE_TYPE(Arrays.asList(new DiscoveryAttributes.FileTypeAttribute()),
Bundle.FileSorter_SortingMethod_filetype_displayName()), // Sort in order of file type (defined in FileType enum), with secondary sort on MIME type
BY_FREQUENCY(Arrays.asList(new DiscoveryAttributes.FrequencyAttribute()),
Bundle.FileSorter_SortingMethod_frequency_displayName()), // Sort by decreasing rarity in the central repository
BY_KEYWORD_LIST_NAMES(Arrays.asList(new DiscoveryAttributes.KeywordListAttribute()),
Bundle.FileSorter_SortingMethod_keywordlist_displayName()), // Sort alphabetically by list of keyword list names found
BY_FULL_PATH(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_fullPath_displayName()), // Sort alphabetically by path
BY_DOMAIN_NAME(new ArrayList<>(),
Bundle.FileSorter_SortingMethod_domain_displayName());
private final String displayName;
private final List<DiscoveryAttributes.AttributeType> requiredAttributes;
/**
* Construct a new SortingMethod enum value.
*
* @param attributes The list of DiscoveryAttributes required by this
* enum value.
* @param displayName The display name for this enum value.
*/
SortingMethod(List<DiscoveryAttributes.AttributeType> attributes, String displayName) {
this.requiredAttributes = attributes;
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
/**
* Get the list of DiscoveryAttributes required by this enum value.
*
* @return The list of DiscoveryAttributes required by this enum value.
*/
public List<DiscoveryAttributes.AttributeType> getRequiredAttributes() {
return Collections.unmodifiableList(requiredAttributes);
}
/**
* Get the list of enum values that are valid for ordering files.
*
* @return Enum values that can be used to ordering files.
*/
public static List<SortingMethod> getOptionsForOrderingFiles() {
return Arrays.asList(BY_FILE_SIZE, BY_FULL_PATH, BY_FILE_NAME, BY_DATA_SOURCE);
}
/**
* Get the list of enum values that are valid for ordering files.
*
* @return Enum values that can be used to ordering files.
*/
public static List<SortingMethod> getOptionsForOrderingDomains() {
return Arrays.asList(BY_DOMAIN_NAME, BY_DATA_SOURCE);
}
}
}

View File

@ -0,0 +1,484 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Utility enums for searches made for files with Discovery.
*/
public final class SearchData {
private final static long BYTES_PER_MB = 1000000;
private static final Set<BlackboardArtifact.ARTIFACT_TYPE> DOMAIN_ARTIFACT_TYPES = EnumSet.of(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY);
/**
* Enum representing how often the result occurs in the Central Repository.
*/
@NbBundle.Messages({
"SearchData.Frequency.unique.displayName=Unique (1)",
"SearchData.Frequency.rare.displayName=Rare (2-10)",
"SearchData.Frequency.common.displayName=Common (11 - 100)",
"SearchData.Frequency.verycommon.displayName=Very Common (100+)",
"SearchData.Frequency.known.displayName=Known (NSRL)",
"SearchData.Frequency.unknown.displayName=Unknown",})
public enum Frequency {
UNIQUE(0, 1, Bundle.SearchData_Frequency_unique_displayName()),
RARE(1, 10, Bundle.SearchData_Frequency_rare_displayName()),
COMMON(2, 100, Bundle.SearchData_Frequency_common_displayName()),
VERY_COMMON(3, 0, Bundle.SearchData_Frequency_verycommon_displayName()),
KNOWN(4, 0, Bundle.SearchData_Frequency_known_displayName()),
UNKNOWN(5, 0, Bundle.SearchData_Frequency_unknown_displayName());
private final int ranking;
private final String displayName;
private final int maxOccur;
/**
* Construct a new frequency enum value.
*
* @param ranking The rank for sorting.
* @param maxOccur The max occurrences this enum value is for.
* @param displayName The display name for this enum value.
*/
Frequency(int ranking, int maxOccur, String displayName) {
this.ranking = ranking;
this.maxOccur = maxOccur;
this.displayName = displayName;
}
/**
* Get the rank for sorting.
*
* @return The rank (lower should be displayed first).
*/
public int getRanking() {
return ranking;
}
/**
* Get the enum matching the given occurrence count.
*
* @param count Number of times a result is in the Central Repository.
*
* @return The corresponding enum.
*/
public static Frequency fromCount(long count) {
if (count <= UNIQUE.getMaxOccur()) {
return UNIQUE;
} else if (count <= RARE.getMaxOccur()) {
return RARE;
} else if (count <= COMMON.getMaxOccur()) {
return COMMON;
}
return VERY_COMMON;
}
/**
* Get the list of enums that are valid for filtering when a CR is
* enabled.
*
* @return enums that can be used to filter with a CR.
*/
public static List<Frequency> getOptionsForFilteringWithCr() {
return Arrays.asList(UNIQUE, RARE, COMMON, VERY_COMMON, KNOWN);
}
/**
* Get the list of enums that are valid for filtering when no CR is
* enabled.
*
* @return enums that can be used to filter without a CR.
*/
public static List<Frequency> getOptionsForFilteringWithoutCr() {
return Arrays.asList(KNOWN, UNKNOWN);
}
@Override
public String toString() {
return displayName;
}
/**
* Get the maximum number of occurrences this enum value is for.
*
* @return The maximum number of occurrences this enum value is for.
*/
public int getMaxOccur() {
return maxOccur;
}
}
/**
* Enum representing the file size.
*/
@NbBundle.Messages({
"SearchData.FileSize.XXLARGE.displayName=XXLarge",
"SearchData.FileSize.XLARGE.displayName=XLarge",
"SearchData.FileSize.LARGE.displayName=Large",
"SearchData.FileSize.MEDIUM.displayName=Medium",
"SearchData.FileSize.SMALL.displayName=Small",
"SearchData.FileSize.XSMALL.displayName=XSmall",
"SearchData.FileSize.10PlusGb=: 10GB+",
"SearchData.FileSize.5gbto10gb=: 5-10GB",
"SearchData.FileSize.1gbto5gb=: 1-5GB",
"SearchData.FileSize.100mbto1gb=: 100MB-1GB",
"SearchData.FileSize.200PlusMb=: 200MB+",
"SearchData.FileSize.50mbto200mb=: 50-200MB",
"SearchData.FileSize.500kbto100mb=: 500KB-100MB",
"SearchData.FileSize.1mbto50mb=: 1-50MB",
"SearchData.FileSize.100kbto1mb=: 100KB-1MB",
"SearchData.FileSize.16kbto100kb=: 16-100KB",
"SearchData.FileSize.upTo500kb=: 0-500KB",
"SearchData.FileSize.upTo16kb=: 0-16KB",})
public enum FileSize {
XXLARGE_VIDEO(0, 10000 * BYTES_PER_MB, -1, Bundle.SearchData_FileSize_XXLARGE_displayName(), Bundle.SearchData_FileSize_10PlusGb()),
XLARGE_VIDEO(1, 5000 * BYTES_PER_MB, 10000 * BYTES_PER_MB, Bundle.SearchData_FileSize_XLARGE_displayName(), Bundle.SearchData_FileSize_5gbto10gb()),
LARGE_VIDEO(2, 1000 * BYTES_PER_MB, 5000 * BYTES_PER_MB, Bundle.SearchData_FileSize_LARGE_displayName(), Bundle.SearchData_FileSize_1gbto5gb()),
MEDIUM_VIDEO(3, 100 * BYTES_PER_MB, 1000 * BYTES_PER_MB, Bundle.SearchData_FileSize_MEDIUM_displayName(), Bundle.SearchData_FileSize_100mbto1gb()),
SMALL_VIDEO(4, 500000, 100 * BYTES_PER_MB, Bundle.SearchData_FileSize_SMALL_displayName(), Bundle.SearchData_FileSize_500kbto100mb()),
XSMALL_VIDEO(5, 0, 500000, Bundle.SearchData_FileSize_XSMALL_displayName(), Bundle.SearchData_FileSize_upTo500kb()),
XXLARGE_IMAGE(6, 200 * BYTES_PER_MB, -1, Bundle.SearchData_FileSize_XXLARGE_displayName(), Bundle.SearchData_FileSize_200PlusMb()),
XLARGE_IMAGE(7, 50 * BYTES_PER_MB, 200 * BYTES_PER_MB, Bundle.SearchData_FileSize_XLARGE_displayName(), Bundle.SearchData_FileSize_50mbto200mb()),
LARGE_IMAGE(8, 1 * BYTES_PER_MB, 50 * BYTES_PER_MB, Bundle.SearchData_FileSize_LARGE_displayName(), Bundle.SearchData_FileSize_1mbto50mb()),
MEDIUM_IMAGE(9, 100000, 1 * BYTES_PER_MB, Bundle.SearchData_FileSize_MEDIUM_displayName(), Bundle.SearchData_FileSize_100kbto1mb()),
SMALL_IMAGE(10, 16000, 100000, Bundle.SearchData_FileSize_SMALL_displayName(), Bundle.SearchData_FileSize_16kbto100kb()),
XSMALL_IMAGE(11, 0, 16000, Bundle.SearchData_FileSize_XSMALL_displayName(), Bundle.SearchData_FileSize_upTo16kb());
private final int ranking; // Must be unique for each value
private final long minBytes; // Note that the size must be strictly greater than this to match
private final long maxBytes;
private final String sizeGroup;
private final String displaySize;
final static long NO_MAXIMUM = -1;
/**
* Construct a new FileSize enum value.
*
* @param ranking The rank for sorting.
* @param minB The minimum size included in this enum value.
* @param maxB The maximum size included in this enum value.
* @param displayName The display name for this enum value.
* @param displaySize The size to display in association with this enum
* value.
*/
FileSize(int ranking, long minB, long maxB, String displayName, String displaySize) {
this.ranking = ranking;
this.minBytes = minB;
if (maxB >= 0) {
this.maxBytes = maxB;
} else {
this.maxBytes = NO_MAXIMUM;
}
this.sizeGroup = displayName;
this.displaySize = displaySize;
}
/**
* Get the enum corresponding to the given file size for image files.
* The file size must be strictly greater than minBytes.
*
* @param size The file size.
*
* @return The enum whose range contains the file size.
*/
public static FileSize fromImageSize(long size) {
if (size > XXLARGE_IMAGE.getMinBytes()) {
return XXLARGE_IMAGE;
} else if (size > XLARGE_IMAGE.getMinBytes()) {
return XLARGE_IMAGE;
} else if (size > LARGE_IMAGE.getMinBytes()) {
return LARGE_IMAGE;
} else if (size > MEDIUM_IMAGE.getMinBytes()) {
return MEDIUM_IMAGE;
} else if (size > SMALL_IMAGE.getMinBytes()) {
return SMALL_IMAGE;
} else {
return XSMALL_IMAGE;
}
}
/**
* Get the enum corresponding to the given file size for video files.
* The file size must be strictly greater than minBytes.
*
* @param size The file size.
*
* @return The enum whose range contains the file size.
*/
public static FileSize fromVideoSize(long size) {
if (size > XXLARGE_VIDEO.getMinBytes()) {
return XXLARGE_VIDEO;
} else if (size > XLARGE_VIDEO.getMinBytes()) {
return XLARGE_VIDEO;
} else if (size > LARGE_VIDEO.getMinBytes()) {
return LARGE_VIDEO;
} else if (size > MEDIUM_VIDEO.getMinBytes()) {
return MEDIUM_VIDEO;
} else if (size > SMALL_VIDEO.getMinBytes()) {
return SMALL_VIDEO;
} else {
return XSMALL_VIDEO;
}
}
/**
* Get the upper limit of the range.
*
* @return The maximum file size that will fit in this range.
*/
public long getMaxBytes() {
return maxBytes;
}
/**
* Get the lower limit of the range.
*
* @return The maximum file size that is not part of this range.
*/
public long getMinBytes() {
return minBytes;
}
/**
* Get the rank for sorting.
*
* @return The rank (lower should be displayed first).
*/
public int getRanking() {
return ranking;
}
@Override
public String toString() {
return sizeGroup + displaySize;
}
/**
* Get the name of the size group. For example Small.
*
* @return The name of the size group. For example Small.
*/
public String getSizeGroup() {
return sizeGroup;
}
/**
* Get the list of enums that are valid for most file sizes.
*
* @return Enums that can be used to filter most file including images
* by size.
*/
public static List<FileSize> getDefaultSizeOptions() {
return Arrays.asList(XXLARGE_IMAGE, XLARGE_IMAGE, LARGE_IMAGE, MEDIUM_IMAGE, SMALL_IMAGE, XSMALL_IMAGE);
}
/**
* Get the list of enums that are valid for video sizes.
*
* @return enums that can be used to filter videos by size.
*/
public static List<FileSize> getOptionsForVideos() {
return Arrays.asList(XXLARGE_VIDEO, XLARGE_VIDEO, LARGE_VIDEO, MEDIUM_VIDEO, SMALL_VIDEO, XSMALL_VIDEO);
}
}
//Discovery uses a different list of document mime types than FileTypeUtils.FileTypeCategory.DOCUMENTS
private static final ImmutableSet<String> DOCUMENT_MIME_TYPES
= new ImmutableSet.Builder<String>()
.add("text/html", //NON-NLS
"text/csv", //NON-NLS
"application/rtf", //NON-NLS
"application/pdf", //NON-NLS
"application/xhtml+xml", //NON-NLS
"application/x-msoffice", //NON-NLS
"application/msword", //NON-NLS
"application/msword2", //NON-NLS
"application/vnd.wordperfect", //NON-NLS
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
"application/vnd.ms-powerpoint", //NON-NLS
"application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
"application/vnd.ms-excel", //NON-NLS
"application/vnd.ms-excel.sheet.4", //NON-NLS
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
"application/vnd.oasis.opendocument.presentation", //NON-NLS
"application/vnd.oasis.opendocument.spreadsheet", //NON-NLS
"application/vnd.oasis.opendocument.text" //NON-NLS
).build();
private static final ImmutableSet<String> IMAGE_UNSUPPORTED_DOC_TYPES
= new ImmutableSet.Builder<String>()
.add("application/pdf", //NON-NLS
"application/xhtml+xml").build(); //NON-NLS
/**
* Get the list of document types for which image extraction is not
* supported.
*
* @return The list of document types for which image extraction is not
* supported.
*/
public static Collection<String> getDocTypesWithoutImageExtraction() {
return Collections.unmodifiableCollection(IMAGE_UNSUPPORTED_DOC_TYPES);
}
/**
* Enum representing the type.
*/
@NbBundle.Messages({
"SearchData.FileType.Audio.displayName=Audio",
"SearchData.FileType.Video.displayName=Video",
"SearchData.FileType.Image.displayName=Image",
"SearchData.FileType.Documents.displayName=Document",
"SearchData.FileType.Executables.displayName=Executable",
"SearchData.AttributeType.Domain.displayName=Domain",
"SearchData.FileType.Other.displayName=Other/Unknown"})
public enum Type {
IMAGE(0, Bundle.SearchData_FileType_Image_displayName(), FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes(), new ArrayList<>()),
AUDIO(1, Bundle.SearchData_FileType_Audio_displayName(), FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes(), new ArrayList<>()),
VIDEO(2, Bundle.SearchData_FileType_Video_displayName(), FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes(), new ArrayList<>()),
EXECUTABLE(3, Bundle.SearchData_FileType_Executables_displayName(), FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes(), new ArrayList<>()),
DOCUMENT(4, Bundle.SearchData_FileType_Documents_displayName(), DOCUMENT_MIME_TYPES, new ArrayList<>()),
DOMAIN(6, Bundle.SearchData_AttributeType_Domain_displayName(), new ArrayList<>(), DOMAIN_ARTIFACT_TYPES),
OTHER(5, Bundle.SearchData_FileType_Other_displayName(), new ArrayList<>(), new ArrayList<>());
private final int ranking; // For ordering in the UI
private final String displayName;
private final Collection<String> mediaTypes;
private final Collection<BlackboardArtifact.ARTIFACT_TYPE> artifactTypes;
/**
* Construct a new Type enum value.
*
* @param value Integer value for comparison.
* @param displayName The display name for this type.
* @param mediaTypes The list of mime types this type is defined by
* if it is file type.
* @param artifactTypes The list of artifact types this type is defined
* by if it is an attribute type.
*/
Type(int value, String displayName, Collection<String> mediaTypes, Collection<BlackboardArtifact.ARTIFACT_TYPE> artifactTypes) {
this.ranking = value;
this.displayName = displayName;
this.mediaTypes = mediaTypes;
this.artifactTypes = artifactTypes;
}
/**
* Get the MIME types matching this category.
*
* @return Collection of MIME type strings
*/
public Collection<String> getMediaTypes() {
return Collections.unmodifiableCollection(mediaTypes);
}
/**
* Get the BlackboardArtifact types matching this category.
*
* @return Collection of BlackboardArtifact.ARTIFACT_TYPE objects.
*/
public Collection<BlackboardArtifact.ARTIFACT_TYPE> getArtifactTypes() {
return Collections.unmodifiableCollection(artifactTypes);
}
@Override
public String toString() {
return displayName;
}
/**
* Get the rank for sorting.
*
* @return the rank (lower should be displayed first)
*/
public int getRanking() {
return ranking;
}
}
/**
* Enum representing the score of the item.
*/
@NbBundle.Messages({
"SearchData.Score.notable.displayName=Notable",
"SearchData.Score.interesting.displayName=Interesting",
"SearchData.Score.unknown.displayName=Unknown",})
public enum Score {
NOTABLE(0, Bundle.SearchData_Score_notable_displayName()),
INTERESTING(1, Bundle.SearchData_Score_interesting_displayName()),
UNKNOWN(2, Bundle.SearchData_Score_unknown_displayName());
private final int ranking;
private final String displayName;
/**
* Construct a new Score enum value.
*
* @param ranking The rank for sorting.
* @param displayName The display name for this enum value.
*/
Score(int ranking, String displayName) {
this.ranking = ranking;
this.displayName = displayName;
}
/**
* Get the rank for sorting.
*
* @return The rank (lower should be displayed first).
*/
public int getRanking() {
return ranking;
}
/**
* Get the list of enums that are valid for filtering.
*
* @return Enums that can be used to filter.
*/
public static List<Score> getOptionsForFiltering() {
return Arrays.asList(NOTABLE, INTERESTING);
}
@Override
public String toString() {
return displayName;
}
}
/**
* Private constructor for SearchData class.
*/
private SearchData() {
// Class should not be instantiated
}
}

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.search;
import java.util.ArrayList;
import java.util.Collections;
@ -25,19 +25,19 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.sleuthkit.autopsy.discovery.FileSearch.GroupKey;
import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey;
/**
* Class to hold the results of the filtering/grouping/sorting operations.
*/
class SearchResults {
private final FileGroup.GroupSortingAlgorithm groupSortingType;
private final FileSearch.AttributeType attrType;
private final FileSorter fileSorter;
private final Group.GroupSortingAlgorithm groupSortingType;
private final DiscoveryAttributes.AttributeType attrType;
private final ResultsSorter fileSorter;
private final Map<FileSearch.GroupKey, FileGroup> groupMap = new HashMap<>();
private List<FileGroup> groupList = new ArrayList<>();
private final Map<GroupKey, Group> groupMap = new HashMap<>();
private List<Group> groupList = new ArrayList<>();
private static final long MAX_OUTPUT_FILES = 2000; // For debug UI - maximum number of lines to print
@ -50,11 +50,11 @@ class SearchResults {
* @param fileSortingMethod The method that should be used to
* sortGroupsAndFiles the files in each group.
*/
SearchResults(FileGroup.GroupSortingAlgorithm groupSortingType, FileSearch.AttributeType attrType,
FileSorter.SortingMethod fileSortingMethod) {
SearchResults(Group.GroupSortingAlgorithm groupSortingType, DiscoveryAttributes.AttributeType attrType,
ResultsSorter.SortingMethod fileSortingMethod) {
this.groupSortingType = groupSortingType;
this.attrType = attrType;
this.fileSorter = new FileSorter(fileSortingMethod);
this.fileSorter = new ResultsSorter(fileSortingMethod);
}
/**
@ -62,9 +62,9 @@ class SearchResults {
* the search is finished.
*/
SearchResults() {
this.groupSortingType = FileGroup.GroupSortingAlgorithm.BY_GROUP_NAME;
this.attrType = new FileSearch.FileSizeAttribute();
this.fileSorter = new FileSorter(FileSorter.SortingMethod.BY_FILE_NAME);
this.groupSortingType = Group.GroupSortingAlgorithm.BY_GROUP_NAME;
this.attrType = new DiscoveryAttributes.FileSizeAttribute();
this.fileSorter = new ResultsSorter(ResultsSorter.SortingMethod.BY_FILE_NAME);
}
/**
@ -72,15 +72,15 @@ class SearchResults {
*
* @param files The list of ResultFiles to add.
*/
void add(List<ResultFile> files) {
for (ResultFile file : files) {
void add(List<Result> results) {
for (Result result : results) {
// Add the file to the appropriate group, creating it if necessary
FileSearch.GroupKey groupKey = attrType.getGroupKey(file);
GroupKey groupKey = attrType.getGroupKey(result);
if (!groupMap.containsKey(groupKey)) {
groupMap.put(groupKey, new FileGroup(groupSortingType, groupKey));
groupMap.put(groupKey, new Group(groupSortingType, groupKey));
}
groupMap.get(groupKey).addFile(file);
groupMap.get(groupKey).addResult(result);
}
}
@ -91,8 +91,8 @@ class SearchResults {
void sortGroupsAndFiles() {
// First sortGroupsAndFiles the files
for (FileGroup group : groupMap.values()) {
group.sortFiles(fileSorter);
for (Group group : groupMap.values()) {
group.sortResults(fileSorter);
}
// Now put the groups in a list and sortGroupsAndFiles them
@ -102,25 +102,25 @@ class SearchResults {
@Override
public String toString() {
String result = "";
String resultString = "";
if (groupList == null) {
return result;
return resultString;
}
long count = 0;
for (FileGroup group : groupList) {
result += group.getDisplayName() + "\n";
for (Group group : groupList) {
resultString += group.getDisplayName() + "\n";
for (ResultFile file : group.getFiles()) {
result += " " + file.toString() + "\n";
for (Result result : group.getResults()) {
resultString += " " + result.toString() + "\n";
count++;
if (count > MAX_OUTPUT_FILES) {
result += "(truncated)";
return result;
resultString += "(truncated)";
return resultString;
}
}
}
return result;
return resultString;
}
/**
@ -129,7 +129,7 @@ class SearchResults {
* @return The list of group names.
*/
List<String> getGroupNamesWithCounts() {
return groupList.stream().map(p -> p.getDisplayName() + " (" + p.getFiles().size() + ")").collect(Collectors.toList());
return groupList.stream().map(p -> p.getDisplayName() + " (" + p.getResults().size() + ")").collect(Collectors.toList());
}
/**
@ -139,13 +139,13 @@ class SearchResults {
*
* @return The list of result files.
*/
List<ResultFile> getResultFilesInGroup(String groupName) {
List<Result> getResultFilesInGroup(String groupName) {
if (groupName != null) {
final String modifiedGroupName = groupName.replaceAll(" \\([0-9]+\\)$", "");
java.util.Optional<FileGroup> fileGroup = groupList.stream().filter(p -> p.getDisplayName().equals(modifiedGroupName)).findFirst();
if (fileGroup.isPresent()) {
return fileGroup.get().getFiles();
java.util.Optional<Group> group = groupList.stream().filter(p -> p.getDisplayName().equals(modifiedGroupName)).findFirst();
if (group.isPresent()) {
return group.get().getResults();
}
}
return new ArrayList<>();
@ -156,15 +156,15 @@ class SearchResults {
*
* @return The grouped and sorted results.
*/
Map<GroupKey, List<ResultFile>> toLinkedHashMap() throws FileSearchException {
Map<GroupKey, List<ResultFile>> map = new LinkedHashMap<>();
Map<GroupKey, List<Result>> toLinkedHashMap() throws DiscoveryException {
Map<GroupKey, List<Result>> map = new LinkedHashMap<>();
// Sort the groups and files
sortGroupsAndFiles();
// groupList is sorted and a LinkedHashMap will preserve that order.
for (FileGroup group : groupList) {
map.put(group.getGroupKey(), group.getFiles());
for (Group group : groupList) {
map.put(group.getGroupKey(), group.getResults());
}
return map;

View File

@ -0,0 +1,243 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.search;
import com.google.common.io.Files;
import java.awt.Image;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.textextractors.TextExtractor;
import org.sleuthkit.autopsy.textextractors.TextExtractorFactory;
import org.sleuthkit.autopsy.textsummarizer.TextSummarizer;
import org.sleuthkit.autopsy.textsummarizer.TextSummary;
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
import org.sleuthkit.autopsy.texttranslation.TranslationException;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utility class for code which helps create summaries for Discovery.
*/
class SummaryHelpers {
private static final int PREVIEW_SIZE = 256;
private final static Logger logger = Logger.getLogger(SummaryHelpers.class.getName());
private static volatile TextSummarizer summarizerToUse = null;
private SummaryHelpers() {
// Class should not be instantiated
}
/**
* Get the default text summary for the document.
*
* @param file The file to summarize.
*
* @return The TextSummary object which is a default summary for the file.
*/
static TextSummary getDefaultSummary(AbstractFile file) {
Image image = null;
int countOfImages = 0;
try {
Content largestChild = null;
for (Content child : file.getChildren()) {
if (child instanceof AbstractFile && ImageUtils.isImageThumbnailSupported((AbstractFile) child)) {
countOfImages++;
if (largestChild == null || child.getSize() > largestChild.getSize()) {
largestChild = child;
}
}
}
if (largestChild != null) {
image = ImageUtils.getThumbnail(largestChild, ImageUtils.ICON_SIZE_LARGE);
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error getting children for file: " + file.getId(), ex);
}
image = image == null ? image : image.getScaledInstance(ImageUtils.ICON_SIZE_MEDIUM, ImageUtils.ICON_SIZE_MEDIUM,
Image.SCALE_SMOOTH);
String summaryText = null;
if (file.getMd5Hash() != null) {
try {
summaryText = getSavedSummary(Paths.get(Case.getCurrentCaseThrows().getCacheDirectory(), "summaries", file.getMd5Hash() + "-default-" + PREVIEW_SIZE + "-translated.txt").toString());
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to retrieve saved summary. No case is open.", ex);
}
}
if (StringUtils.isBlank(summaryText)) {
String firstLines = getFirstLines(file);
String translatedFirstLines = getTranslatedVersion(firstLines);
if (!StringUtils.isBlank(translatedFirstLines)) {
summaryText = translatedFirstLines;
if (file.getMd5Hash() != null) {
try {
saveSummary(summaryText, Paths.get(Case.getCurrentCaseThrows().getCacheDirectory(), "summaries", file.getMd5Hash() + "-default-" + PREVIEW_SIZE + "-translated.txt").toString());
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to save translated summary. No case is open.", ex);
}
}
} else {
summaryText = firstLines;
}
}
return new TextSummary(summaryText, image, countOfImages);
}
/**
* Provide an English version of the specified String if it is not English,
* translation is enabled, and it can be translated.
*
* @param documentString The String to provide an English version of.
*
* @return The English version of the provided String, or null if no
* translation occurred.
*/
static String getTranslatedVersion(String documentString) {
try {
TextTranslationService translatorInstance = TextTranslationService.getInstance();
if (translatorInstance.hasProvider()) {
String translatedResult = translatorInstance.translate(documentString);
if (translatedResult.isEmpty() == false) {
return translatedResult;
}
}
} catch (NoServiceProviderException | TranslationException ex) {
logger.log(Level.INFO, "Error translating string for summary", ex);
}
return null;
}
/**
* Find and load a saved summary from the case folder for the specified
* file.
*
* @param summarySavePath The full path for the saved summary file.
*
* @return The summary found given the specified path, null if no summary
* was found.
*/
static String getSavedSummary(String summarySavePath) {
if (summarySavePath == null) {
return null;
}
File savedFile = new File(summarySavePath);
if (savedFile.exists()) {
try (BufferedReader bReader = new BufferedReader(new FileReader(savedFile))) {
// pass the path to the file as a parameter
StringBuilder sBuilder = new StringBuilder(PREVIEW_SIZE);
String sCurrentLine = bReader.readLine();
while (sCurrentLine != null) {
sBuilder.append(sCurrentLine).append('\n');
sCurrentLine = bReader.readLine();
}
return sBuilder.toString();
} catch (IOException ingored) {
//summary file may not exist or may be incomplete in which case return null so a summary can be generated
return null; //no saved summary was able to be found
}
} else {
try { //if the file didn't exist make sure the parent directories exist before we move on to creating a summary
Files.createParentDirs(savedFile);
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to create summaries directory in case folder for file at: " + summarySavePath, ex);
}
return null; //no saved summary was able to be found
}
}
/**
* Save a summary at the specified location.
*
* @param summary The text of the summary being saved.
* @param summarySavePath The full path for the saved summary file.
*/
static void saveSummary(String summary, String summarySavePath) {
if (summarySavePath == null) {
return; //can't save a summary if we don't have a path
}
try (FileWriter myWriter = new FileWriter(summarySavePath)) {
myWriter.write(summary);
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to save summary at: " + summarySavePath, ex);
}
}
/**
* Get the beginning of text from the specified AbstractFile.
*
* @param file The AbstractFile to get text from.
*
* @return The beginning of text from the specified AbstractFile.
*/
static String getFirstLines(AbstractFile file) {
TextExtractor extractor;
try {
extractor = TextExtractorFactory.getExtractor(file, null);
} catch (TextExtractorFactory.NoTextExtractorFound ignored) {
//no extractor found, use Strings Extractor
extractor = TextExtractorFactory.getStringsExtractor(file, null);
}
try (Reader reader = extractor.getReader()) {
char[] cbuf = new char[PREVIEW_SIZE];
reader.read(cbuf, 0, PREVIEW_SIZE);
return new String(cbuf);
} catch (IOException ex) {
return Bundle.FileSearch_documentSummary_noBytes();
} catch (TextExtractor.InitReaderException ex) {
return Bundle.FileSearch_documentSummary_noPreview();
}
}
/**
* Get the first TextSummarizer found by a lookup of TextSummarizers.
*
* @return The first TextSummarizer found by a lookup of TextSummarizers.
*
* @throws IOException
*/
static TextSummarizer getLocalSummarizer() {
if (summarizerToUse == null) {
Collection<? extends TextSummarizer> summarizers
= Lookup.getDefault().lookupAll(TextSummarizer.class
);
if (!summarizers.isEmpty()) {
summarizerToUse = summarizers.iterator().next();
}
}
return summarizerToUse;
}
}

View File

@ -16,8 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import java.awt.event.ActionListener;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
@ -69,7 +70,8 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
/**
* Check if this filter is configured to valid settings.
*
* @return If the settings are invalid returns the error that has occurred, otherwise returns empty string.
* @return If the settings are invalid returns the error that has occurred,
* otherwise returns empty string.
*/
abstract String checkForError();
@ -90,12 +92,12 @@ abstract class AbstractDiscoveryFilterPanel extends javax.swing.JPanel {
}
/**
* Get the FileFilter which is represented by this Panel.
* Get the AbstractFilter which is represented by this Panel.
*
* @return The FileFilter for the selected settings, null if the settings
* are not in use.
* @return The AbstractFilter for the selected settings, null if the
* settings are not in use.
*/
abstract FileSearchFiltering.FileFilter getFilter();
abstract AbstractFilter getFilter();
/**
* Remove listeners from the checkbox and the list if they exist.

View File

@ -16,8 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
@ -31,6 +32,11 @@ import javax.swing.JSplitPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.GroupingAttributeType;
import org.sleuthkit.autopsy.discovery.search.Group;
import org.sleuthkit.autopsy.discovery.search.ResultsSorter.SortingMethod;
import org.sleuthkit.autopsy.discovery.search.SearchData;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering;
/**
* Abstract class extending JPanel for displaying all the filters associated
@ -52,6 +58,9 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
private final JPanel secondColumnPanel = new JPanel();
private int firstColumnY = 0;
private int secondColumnY = 0;
private SortingMethod lastSortingMethod = SortingMethod.BY_FILE_NAME;
private GroupingAttributeType lastGroupingAttributeType = GroupingAttributeType.PARENT_PATH;
private Group.GroupSortingAlgorithm lastGroupSortingAlg = Group.GroupSortingAlgorithm.BY_GROUP_SIZE;
/**
* Setup necessary for implementations of this abstract class.
@ -66,7 +75,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return The type of results this panel filters.
*/
abstract FileSearchData.FileType getFileType();
abstract SearchData.Type getType();
/**
* Add a DiscoveryFilterPanel to the specified column with the specified
@ -242,12 +251,15 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
*
* @return The list of filters selected by the user.
*/
synchronized List<FileSearchFiltering.FileFilter> getFilters() {
List<FileSearchFiltering.FileFilter> filtersToUse = new ArrayList<>();
filtersToUse.add(new FileSearchFiltering.FileTypeFilter(getFileType()));
synchronized List<AbstractFilter> getFilters() {
List<AbstractFilter> filtersToUse = new ArrayList<>();
if (getType() != SearchData.Type.DOMAIN) { //Domain type does not have a file type
filtersToUse.add(new SearchFiltering.FileTypeFilter(getType()));
}
for (AbstractDiscoveryFilterPanel filterPanel : filters) {
if (filterPanel.getCheckbox().isSelected()) {
FileSearchFiltering.FileFilter filter = filterPanel.getFilter();
AbstractFilter filter = filterPanel.getFilter();
if (filter != null) {
filtersToUse.add(filter);
}
@ -265,4 +277,60 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
}
}
/**
* Get the most recently used sorting method.
*
* @return The most recently used sorting method.
*/
SortingMethod getLastSortingMethod() {
return lastSortingMethod;
}
/**
* Set the most recently used sorting method.
*
* @param lastSortingMethod The most recently used sorting method.
*/
final void setLastSortingMethod(SortingMethod lastSortingMethod) {
this.lastSortingMethod = lastSortingMethod;
}
/**
* Get the most recently used grouping attribute.
*
* @return The most recently used grouping attribute.
*/
GroupingAttributeType getLastGroupingAttributeType() {
return lastGroupingAttributeType;
}
/**
* Set the most recently used grouping attribute.
*
* @param lastGroupingAttributeType The most recently used grouping
* attribute.
*/
final void setLastGroupingAttributeType(GroupingAttributeType lastGroupingAttributeType) {
this.lastGroupingAttributeType = lastGroupingAttributeType;
}
/**
* Get the most recently used group sorting algorithm.
*
* @return The most recently used group sorting algorithm.
*/
Group.GroupSortingAlgorithm getLastGroupSortingAlg() {
return lastGroupSortingAlg;
}
/**
* Set the group sorting algorithm that was used most recently.
*
* @param lastGroupSortingAlg The most recently used group sorting
* algorithm.
*/
final void setLastGroupSortingAlg(Group.GroupSortingAlgorithm lastGroupSortingAlg) {
this.lastGroupSortingAlg = lastGroupSortingAlg;
}
}

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.JCheckBox" name="artifactTypeCheckbox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="ArtifactTypeFilterPanel.artifactTypeCheckbox.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="artifactTypeCheckboxActionPerformed"/>
</Events>
</Component>
</NonVisualComponents>
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[27, 27]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<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"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="artifactTypeScrollPane" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="artifactTypeScrollPane" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="artifactTypeScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[27, 27]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JList" name="artifactList">
<Properties>
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new DefaultListModel&lt;ArtifactTypeItem&gt;()" type="code"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;ArtifactTypeItem&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,195 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.ui;
import java.util.ArrayList;
import java.util.List;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import javax.swing.DefaultListModel;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.discovery.search.SearchData;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Filter for selection of a specific Artifact type to limit results to.
*/
class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
private static final long serialVersionUID = 1L;
/**
* Creates new form ArtifactTypeFilterPanel
*/
ArtifactTypeFilterPanel() {
initComponents();
setUpArtifactTypeFilter();
}
/**
* Initialize the data source filter.
*/
private void setUpArtifactTypeFilter() {
int count = 0;
DefaultListModel<ArtifactTypeItem> artifactTypeModel = (DefaultListModel<ArtifactTypeItem>) artifactList.getModel();
artifactTypeModel.removeAllElements();
for (BlackboardArtifact.ARTIFACT_TYPE artifactType : SearchData.Type.DOMAIN.getArtifactTypes()) {
artifactTypeModel.add(count, new ArtifactTypeItem(artifactType));
count++;
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
artifactTypeCheckbox = new javax.swing.JCheckBox();
artifactTypeScrollPane = new javax.swing.JScrollPane();
artifactList = new javax.swing.JList<>();
org.openide.awt.Mnemonics.setLocalizedText(artifactTypeCheckbox, org.openide.util.NbBundle.getMessage(ArtifactTypeFilterPanel.class, "ArtifactTypeFilterPanel.artifactTypeCheckbox.text")); // NOI18N
artifactTypeCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
artifactTypeCheckboxActionPerformed(evt);
}
});
setPreferredSize(new java.awt.Dimension(27, 27));
artifactTypeScrollPane.setPreferredSize(new java.awt.Dimension(27, 27));
artifactList.setModel(new DefaultListModel<ArtifactTypeItem>());
artifactList.setEnabled(false);
artifactTypeScrollPane.setViewportView(artifactList);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(artifactTypeScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(artifactTypeScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
private void artifactTypeCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_artifactTypeCheckboxActionPerformed
artifactTypeScrollPane.setEnabled(artifactTypeCheckbox.isSelected());
artifactList.setEnabled(artifactTypeCheckbox.isSelected());
}//GEN-LAST:event_artifactTypeCheckboxActionPerformed
@Override
void configurePanel(boolean selected, int[] indicesSelected) {
artifactTypeCheckbox.setSelected(selected);
if (artifactTypeCheckbox.isEnabled() && artifactTypeCheckbox.isSelected()) {
artifactTypeScrollPane.setEnabled(true);
artifactList.setEnabled(true);
if (indicesSelected != null) {
artifactList.setSelectedIndices(indicesSelected);
}
} else {
artifactTypeScrollPane.setEnabled(false);
artifactList.setEnabled(false);
}
}
@Override
JCheckBox getCheckbox() {
return artifactTypeCheckbox;
}
@Override
JList<?> getList() {
return artifactList;
}
@Override
JLabel getAdditionalLabel() {
return null;
}
@NbBundle.Messages({"ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected."})
@Override
String checkForError() {
if (artifactTypeCheckbox.isSelected() && artifactList.getSelectedValuesList().isEmpty()) {
return Bundle.ArtifactTypeFilterPanel_selectionNeeded_text();
}
return "";
}
@Override
AbstractFilter getFilter() {
if (artifactTypeCheckbox.isSelected() && !artifactList.getSelectedValuesList().isEmpty()) {
List<BlackboardArtifact.ARTIFACT_TYPE> artifactTypeList = new ArrayList<>();
for (ArtifactTypeItem item : artifactList.getSelectedValuesList()) {
artifactTypeList.add(item.getArtifactType());
}
return new ArtifactTypeFilter(artifactTypeList);
}
return null;
}
/**
* Utility class to allow us to display the AritfactType display name
* instead of the name.
*/
private class ArtifactTypeItem {
private final BlackboardArtifact.ARTIFACT_TYPE artifactType;
/**
* Construct a new ArtifactTypeItem.
*
* @param ds The artifact type being wrapped.
*/
ArtifactTypeItem(BlackboardArtifact.ARTIFACT_TYPE artifactType) {
this.artifactType = artifactType;
}
/**
* Get the ArtifactType represented by this ArtifactTypeItem.
*
* @return The ArtifactType represented by this ArtifactTypeItem.
*/
BlackboardArtifact.ARTIFACT_TYPE getArtifactType() {
return artifactType;
}
@Override
public String toString() {
return artifactType.getDisplayName();
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JList<ArtifactTypeItem> artifactList;
private javax.swing.JCheckBox artifactTypeCheckbox;
private javax.swing.JScrollPane artifactTypeScrollPane;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,60 @@
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
ResultsPanel.currentPageLabel.text=Page: -
DiscoveryDialog.sortingPanel.border.title=Step 3: Choose display settings
DiscoveryDialog.searchButton.text=Search
DiscoveryDialog.domainsButton.text=Domains
DiscoveryDialog.groupByLabel.text=Group By:
DiscoveryDialog.step1Label.text=Step 1: Choose result type
DiscoveryDialog.orderByLabel.text=Order Within Groups By:
DiscoveryDialog.documentsButton.text=Documents
DiscoveryDialog.orderGroupsByLabel.text=Order Groups By:
DiscoveryDialog.videosButton.text=Videos
DiscoveryDialog.imagesButton.text=Images
VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
DataSourceFilterPanel.dataSourceCheckbox.text=Data Source:
ParentFolderFilterPanel.parentLabel.text_1=(All will be used)
ParentFolderFilterPanel.parentCheckbox.text_1=Parent Folder:
ParentFolderFilterPanel.addButton.text_1=Add
ParentFolderFilterPanel.deleteButton.text_1=Delete
ParentFolderFilterPanel.excludeRadioButton.text_1=Exclude
ParentFolderFilterPanel.substringRadioButton.text_1=Substring
ParentFolderFilterPanel.includeRadioButton.text_1=Include
ParentFolderFilterPanel.fullRadioButton.text_1=Full
UserCreatedFilterPanel.userCreatedCheckbox.text_1=Possibly User Created
GroupListPanel.groupKeyList.border.title=Groups
ResultsSplitPaneDivider.detailsLabel.text=Details Area
ResultsSplitPaneDivider.showButton.text=
ResultsSplitPaneDivider.hideButton.text=
ImageFilterPanel.imageFiltersSplitPane.toolTipText=
ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
ArtifactTypeFilterPanel.artifactTypeCheckbox.text=Result Type:
InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item:
DocumentPanel.fileSizeLabel.toolTipText=
DocumentPanel.isDeletedLabel.toolTipText=
DomainFilterPanel.domainFiltersSplitPane.toolTipText=
DomainFilterPanel.domainFiltersSplitPane.border.title=Step 2: Filter which domains to show
SizeFilterPanel.sizeCheckbox.text=File Size:
DateFilterPanel.endCheckBox.text=End:
DateFilterPanel.startCheckBox.text=Start:
DateFilterPanel.daysLabel.text=days of activity
ImageThumbnailPanel.isDeletedLabel.toolTipText=
ResultsPanel.pageControlsLabel.text=Pages:
ResultsPanel.currentPageLabel.text=Page: -
ResultsPanel.pageSizeLabel.text=Page Size:
ResultsPanel.gotoPageLabel.text=Go to Page:
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
ObjectDetectedFilterPanel.text=Object Detected:
DetailsPanel.instancesList.border.title=Instances
DateFilterPanel.mostRecentRadioButton.text=Only last:
DateFilterPanel.dateFilterCheckBox.text=Date Filter:
DomainSummaryPanel.activityLabel.text=
DomainSummaryPanel.pagesLabel.text=
DomainSummaryPanel.filesDownloadedLabel.text=
DomainSummaryPanel.totalVisitsLabel.text=

View File

@ -0,0 +1,146 @@
ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected.
CTL_OpenDiscoveryAction=Discovery
DataSourceFilterPanel.error.text=At least one data source must be selected.
# {0} - dataSourceName
DataSourceModuleWrapper.exifModule.text=Picture Analyzer module was not run on data source: {0}\n
# {0} - dataSourceName
DataSourceModuleWrapper.fileTypeModule.text=File Type Identification module was not run on data source: {0}\n
# {0} - dataSourceName
DataSourceModuleWrapper.hashModule.text=Hash Lookup module was not run on data source: {0}\n
# {0} - timeZone
DateFilterPanel.dateRange.text=Date Range ({0}):
DateFilterPanel.invalidRange.text=Range or Only Last must be selected.
DateFilterPanel.startAfterEnd.text=Start date should be before the end date when both are enabled.
DateFilterPanel.startOrEndNeeded.text=A start or end date must be specified to use the range filter.
DiscoveryDialog.name.text=Discovery
DiscoveryTopComponent.additionalFilters.text=;
DiscoveryTopComponent.cancelButton.text=Cancel Search
DiscoveryTopComponent.domainSearch.text=Type: Domain
DiscoveryTopComponent.name=\ Discovery
DiscoveryTopComponent.newSearch.text=New Search
DiscoveryTopComponent.searchCancelled.text=Search has been cancelled.
# {0} - search
DiscoveryTopComponent.searchComplete.text=Results with {0}
DiscoveryTopComponent.searchError.text=Error no type specified for search.
# {0} - searchType
DiscoveryTopComponent.searchInProgress.text=Performing search for results of type {0}. Please wait.
DiscoveryUiUtility.bytes.text=bytes
DiscoveryUiUtility.gigaBytes.text=GB
DiscoveryUiUtility.kiloBytes.text=KB
DiscoveryUiUtility.megaBytes.text=MB
# {0} - fileSize
# {1} - units
DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}
DiscoveryUiUtility.terraBytes.text=TB
# {0} - file name
DiscoveryUiUtils.genVideoThumb.progress.text=extracting temporary file {0}
DiscoveryUiUtils.isDeleted.text=All instances of file are deleted.
DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete
# {0} - otherInstanceCount
DocumentPanel.nameLabel.more.text=\ and {0} more
DocumentPanel.noImageExtraction.text=0 of ? images
DocumentPanel.numberOfImages.noImages=No images
# {0} - numberOfImages
DocumentPanel.numberOfImages.text=1 of {0} images
DocumentWrapper.previewInitialValue=Preview not generated yet.
# {0} - startDate
# {1} - endDate
DomainSummaryPanel.activity.text=Activity: {0} to {1}
DomainSummaryPanel.downloads.text=Files downloaded:
DomainSummaryPanel.loadingImages.text=Loading thumbnail...
DomainSummaryPanel.pages.text=Pages in past 60 days:
DomainSummaryPanel.totalPages.text=Total visits:
GroupsListPanel.noDomainResults.message.text=No domains were found for the selected filters.\n\nReminder:\n -The Recent Activity module must be run on each data source you want to find results in.\n -The Central Repository module must be run on each data source if you want to filter or sort by past occurrences.\n -The iOS Analyzer (iLEAPP) module must be run on each data source which contains data from an iOS device.\n
GroupsListPanel.noFileResults.message.text=No files were found for the selected filters.\n\nReminder:\n -The File Type Identification module must be run on each data source you want to find results in.\n -The Hash Lookup module must be run on each data source if you want to filter by past occurrence.\n -The Picture Analyzer module must be run on each data source if you are filtering by User Created content.
GroupsListPanel.noResults.title.text=No results found
HashSetFilterPanel.error.text=At least one hash set name must be selected.
ImageThumbnailPanel.isDeleted.text=All instances of file are deleted.
# {0} - otherInstanceCount
ImageThumbnailPanel.nameLabel.more.text=\ and {0} more
InterestingItemsFilterPanel.error.text=At least one interesting file set name must be selected.
ObjectDetectedFilterPanel.error.text=At least one object type name must be selected.
ParentFolderFilterPanel.error.text=At least one parent path must be entered.
PastOccurrencesFilterPanel.error.text=At least one value in the past occurrence filter must be selected.
# {0} - currentPage
# {1} - totalPages
ResultsPanel.currentPage.displayValue=Page: {0} of {1}
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
ResultsPanel.currentPageLabel.text=Page: -
DiscoveryDialog.sortingPanel.border.title=Step 3: Choose display settings
DiscoveryDialog.searchButton.text=Search
DiscoveryDialog.domainsButton.text=Domains
DiscoveryDialog.groupByLabel.text=Group By:
DiscoveryDialog.step1Label.text=Step 1: Choose result type
DiscoveryDialog.orderByLabel.text=Order Within Groups By:
DiscoveryDialog.documentsButton.text=Documents
DiscoveryDialog.orderGroupsByLabel.text=Order Groups By:
DiscoveryDialog.videosButton.text=Videos
DiscoveryDialog.imagesButton.text=Images
ResultsPanel.documentPreview.text=Document preview creation cancelled.
# {0} - selectedPage
# {1} - maxPage
ResultsPanel.invalidPageNumber.message=The selected page number {0} does not exist. Please select a value from 1 to {1}.
ResultsPanel.invalidPageNumber.title=Invalid Page Number
ResultsPanel.openInExternalViewer.name=Open in External Viewer
ResultsPanel.unableToCreate.text=Unable to create summary.
ResultsPanel.viewFileInDir.name=View File in Directory
SizeFilterPanel.error.text=At least one size must be selected.
VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
DataSourceFilterPanel.dataSourceCheckbox.text=Data Source:
ParentFolderFilterPanel.parentLabel.text_1=(All will be used)
ParentFolderFilterPanel.parentCheckbox.text_1=Parent Folder:
ParentFolderFilterPanel.addButton.text_1=Add
ParentFolderFilterPanel.deleteButton.text_1=Delete
ParentFolderFilterPanel.excludeRadioButton.text_1=Exclude
ParentFolderFilterPanel.substringRadioButton.text_1=Substring
ParentFolderFilterPanel.includeRadioButton.text_1=Include
ParentFolderFilterPanel.fullRadioButton.text_1=Full
UserCreatedFilterPanel.userCreatedCheckbox.text_1=Possibly User Created
GroupListPanel.groupKeyList.border.title=Groups
ResultsSplitPaneDivider.detailsLabel.text=Details Area
ResultsSplitPaneDivider.showButton.text=
ResultsSplitPaneDivider.hideButton.text=
ImageFilterPanel.imageFiltersSplitPane.toolTipText=
ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
ArtifactTypeFilterPanel.artifactTypeCheckbox.text=Result Type:
InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item:
DocumentPanel.fileSizeLabel.toolTipText=
DocumentPanel.isDeletedLabel.toolTipText=
DomainFilterPanel.domainFiltersSplitPane.toolTipText=
DomainFilterPanel.domainFiltersSplitPane.border.title=Step 2: Filter which domains to show
SizeFilterPanel.sizeCheckbox.text=File Size:
DateFilterPanel.endCheckBox.text=End:
DateFilterPanel.startCheckBox.text=Start:
DateFilterPanel.daysLabel.text=days of activity
ImageThumbnailPanel.isDeletedLabel.toolTipText=
ResultsPanel.pageControlsLabel.text=Pages:
ResultsPanel.currentPageLabel.text=Page: -
ResultsPanel.pageSizeLabel.text=Page Size:
ResultsPanel.gotoPageLabel.text=Go to Page:
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
HashSetFilterPanel.hashSetCheckbox.text=Hash Set:
PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences:
DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show
ObjectDetectedFilterPanel.text=Object Detected:
DetailsPanel.instancesList.border.title=Instances
DateFilterPanel.mostRecentRadioButton.text=Only last:
DateFilterPanel.dateFilterCheckBox.text=Date Filter:
DomainSummaryPanel.activityLabel.text=
DomainSummaryPanel.pagesLabel.text=
DomainSummaryPanel.filesDownloadedLabel.text=
DomainSummaryPanel.totalVisitsLabel.text=
VideoThumbnailPanel.bytes.text=bytes
VideoThumbnailPanel.deleted.text=All instances of file are deleted.
VideoThumbnailPanel.gigaBytes.text=GB
VideoThumbnailPanel.kiloBytes.text=KB
VideoThumbnailPanel.megaBytes.text=MB
# {0} - otherInstanceCount
VideoThumbnailPanel.nameLabel.more.text=\ and {0} more
# {0} - fileSize
# {1} - units
VideoThumbnailPanel.sizeLabel.text=Size: {0} {1}
VideoThumbnailPanel.terraBytes.text=TB

View File

@ -5,7 +5,7 @@
<Component class="javax.swing.JCheckBox" name="dataSourceCheckbox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DataSourceFilterPanel.dataSourceCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DataSourceFilterPanel.dataSourceCheckbox.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="[150, 25]"/>

View File

@ -16,8 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -25,8 +26,10 @@ import javax.swing.DefaultListModel;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
@ -140,6 +143,7 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
dsListModel.removeAllElements();
for (DataSource ds : Case.getCurrentCase().getSleuthkitCase().getDataSources()) {
dsListModel.add(count, new DataSourceItem(ds));
count++;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error loading data sources", ex);
@ -185,19 +189,20 @@ final class DataSourceFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
@NbBundle.Messages({"DataSourceFilterPanel.error.text=At least one data source must be selected."})
@Override
String checkForError() {
if (dataSourceCheckbox.isSelected() && dataSourceList.getSelectedValuesList().isEmpty()) {
return "At least one size must be selected";
return Bundle.DataSourceFilterPanel_error_text();
}
return "";
}
@Override
FileSearchFiltering.FileFilter getFilter() {
AbstractFilter getFilter() {
if (dataSourceCheckbox.isSelected()) {
List<DataSource> dataSources = dataSourceList.getSelectedValuesList().stream().map(t -> t.getDataSource()).collect(Collectors.toList());
return new FileSearchFiltering.DataSourceFilter(dataSources);
return new SearchFiltering.DataSourceFilter(dataSources);
}
return null;
}

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;

View File

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="buttonGroup1">
</Component>
<Component class="javax.swing.JCheckBox" name="dateFilterCheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DateFilterPanel.dateFilterCheckBox.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="dateFilterCheckBoxActionPerformed"/>
</Events>
</Component>
</NonVisualComponents>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<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"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="jPanel1" max="32767" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="jPanel1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="jPanel1">
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="mostRecentRadioButton" min="-2" pref="90" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="daysSpinner" min="-2" pref="80" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="daysLabel" pref="132" max="32767" attributes="0"/>
</Group>
<Component id="rangeRadioButton" alignment="1" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="-2" pref="30" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="endCheckBox" min="-2" max="-2" attributes="0"/>
<Component id="startCheckBox" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="endDatePicker" max="32767" attributes="0"/>
<Component id="startDatePicker" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="mostRecentRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="daysSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="daysLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="rangeRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="startCheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="startDatePicker" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="endCheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="endDatePicker" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JSpinner" name="daysSpinner">
<Properties>
<Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
<SpinnerModel initial="7" maximum="100000" minimum="1" numberType="java.lang.Integer" stepSize="1" type="number"/>
</Property>
<Property name="editor" type="javax.swing.JComponent" editor="org.netbeans.modules.form.editors.SpinnerEditorEditor">
<SpinnerEditor format="" type="3"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[75, 26]"/>
</Property>
<Property name="value" type="java.lang.Object" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="7" type="code"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="daysLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DateFilterPanel.daysLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="mostRecentRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup1"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DateFilterPanel.mostRecentRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="mostRecentRadioButtonStateChanged"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="startCheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DateFilterPanel.startCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="startCheckBoxStateChanged"/>
</Events>
</Component>
<Component class="com.github.lgooddatepicker.components.DatePicker" name="startDatePicker">
<Properties>
<Property name="date" type="java.time.LocalDate" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="LocalDate.now()" type="code"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[60, 22]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[110, 22]"/>
</Property>
</Properties>
</Component>
<Component class="com.github.lgooddatepicker.components.DatePicker" name="endDatePicker">
<Properties>
<Property name="date" type="java.time.LocalDate" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="LocalDate.now()" type="code"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[60, 22]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[110, 22]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="endCheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DateFilterPanel.endCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="endCheckBoxStateChanged"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="rangeRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup1"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="rangeRadioButtonStateChanged"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,357 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.ui;
import com.github.lgooddatepicker.optionalusertools.DateChangeListener;
import com.github.lgooddatepicker.zinternaltools.DateChangeEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JSpinner;
import javax.swing.event.ListSelectionListener;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.communications.Utils;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering;
/**
* Filter panel for allowing the user to filter on date.
*/
class DateFilterPanel extends AbstractDiscoveryFilterPanel {
private static final long serialVersionUID = 1L;
private static final long SECS_PER_DAY = 86400;
/**
* Creates new form DateFilterPanel.
*/
@NbBundle.Messages({"# {0} - timeZone",
"DateFilterPanel.dateRange.text=Date Range ({0}):"})
DateFilterPanel() {
initComponents();
rangeRadioButton.setText(Bundle.DateFilterPanel_dateRange_text(Utils.getUserPreferredZoneId().toString()));
//Disable manual entry in the spinner
((JSpinner.DefaultEditor) daysSpinner.getEditor()).getTextField().setEditable(false);
//Disable manual entry in the date pickers
startDatePicker.getComponentDateTextField().setEditable(false);
endDatePicker.getComponentDateTextField().setEditable(false);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonGroup1 = new javax.swing.ButtonGroup();
dateFilterCheckBox = new javax.swing.JCheckBox();
jPanel1 = new javax.swing.JPanel();
daysSpinner = new javax.swing.JSpinner();
daysLabel = new javax.swing.JLabel();
mostRecentRadioButton = new javax.swing.JRadioButton();
startCheckBox = new javax.swing.JCheckBox();
startDatePicker = new com.github.lgooddatepicker.components.DatePicker();
endDatePicker = new com.github.lgooddatepicker.components.DatePicker();
endCheckBox = new javax.swing.JCheckBox();
rangeRadioButton = new javax.swing.JRadioButton();
org.openide.awt.Mnemonics.setLocalizedText(dateFilterCheckBox, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.dateFilterCheckBox.text")); // NOI18N
dateFilterCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
dateFilterCheckBoxActionPerformed(evt);
}
});
daysSpinner.setModel(new javax.swing.SpinnerNumberModel(7, 1, 100000, 1));
daysSpinner.setEditor(new javax.swing.JSpinner.NumberEditor(daysSpinner, ""));
daysSpinner.setEnabled(false);
daysSpinner.setPreferredSize(new java.awt.Dimension(75, 26));
daysSpinner.setValue(7);
org.openide.awt.Mnemonics.setLocalizedText(daysLabel, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.daysLabel.text")); // NOI18N
daysLabel.setEnabled(false);
buttonGroup1.add(mostRecentRadioButton);
mostRecentRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(mostRecentRadioButton, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.mostRecentRadioButton.text")); // NOI18N
mostRecentRadioButton.setEnabled(false);
mostRecentRadioButton.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
mostRecentRadioButtonStateChanged(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(startCheckBox, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.startCheckBox.text")); // NOI18N
startCheckBox.setEnabled(false);
startCheckBox.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
startCheckBoxStateChanged(evt);
}
});
startDatePicker.setDate(LocalDate.now());
startDatePicker.setEnabled(false);
startDatePicker.setMinimumSize(new java.awt.Dimension(60, 22));
startDatePicker.setPreferredSize(new java.awt.Dimension(110, 22));
endDatePicker.setDate(LocalDate.now());
endDatePicker.setEnabled(false);
endDatePicker.setMinimumSize(new java.awt.Dimension(60, 22));
endDatePicker.setPreferredSize(new java.awt.Dimension(110, 22));
org.openide.awt.Mnemonics.setLocalizedText(endCheckBox, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.endCheckBox.text")); // NOI18N
endCheckBox.setEnabled(false);
endCheckBox.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
endCheckBoxStateChanged(evt);
}
});
buttonGroup1.add(rangeRadioButton);
rangeRadioButton.setEnabled(false);
rangeRadioButton.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
rangeRadioButtonStateChanged(evt);
}
});
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(mostRecentRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(daysSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(daysLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 132, Short.MAX_VALUE))
.addComponent(rangeRadioButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addGap(30, 30, 30)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(endCheckBox)
.addComponent(startCheckBox))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(endDatePicker, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(startDatePicker, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(mostRecentRadioButton)
.addComponent(daysSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(daysLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(rangeRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(startCheckBox)
.addComponent(startDatePicker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(endCheckBox)
.addComponent(endDatePicker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(0, 0, 0))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(0, 0, 0))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(8, 8, 8))
);
}// </editor-fold>//GEN-END:initComponents
private void startCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_startCheckBoxStateChanged
startDatePicker.setEnabled(startCheckBox.isEnabled() && startCheckBox.isSelected());
}//GEN-LAST:event_startCheckBoxStateChanged
private void endCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_endCheckBoxStateChanged
endDatePicker.setEnabled(endCheckBox.isEnabled() && endCheckBox.isSelected());
}//GEN-LAST:event_endCheckBoxStateChanged
private void dateFilterCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dateFilterCheckBoxActionPerformed
rangeRadioButton.setEnabled(dateFilterCheckBox.isSelected());
mostRecentRadioButton.setEnabled(dateFilterCheckBox.isSelected());
rangeRadioButton.firePropertyChange("DateFilterChange", !rangeRadioButton.isEnabled(), rangeRadioButton.isEnabled());
mostRecentRadioButton.firePropertyChange("DateFilterChange", !mostRecentRadioButton.isEnabled(), mostRecentRadioButton.isEnabled());
}//GEN-LAST:event_dateFilterCheckBoxActionPerformed
private void mostRecentRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_mostRecentRadioButtonStateChanged
daysSpinner.setEnabled(mostRecentRadioButton.isSelected());
daysLabel.setEnabled(mostRecentRadioButton.isSelected());
}//GEN-LAST:event_mostRecentRadioButtonStateChanged
private void rangeRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_rangeRadioButtonStateChanged
startCheckBox.setEnabled(rangeRadioButton.isEnabled() && rangeRadioButton.isSelected());
endCheckBox.setEnabled(rangeRadioButton.isEnabled() && rangeRadioButton.isSelected());
startCheckBox.firePropertyChange("StartButtonChange", true, false);
endCheckBox.firePropertyChange("EndButtonChange", true, false);
}//GEN-LAST:event_rangeRadioButtonStateChanged
@Override
void configurePanel(boolean selected, int[] indicesSelected) {
dateFilterCheckBox.setSelected(selected);
if (dateFilterCheckBox.isEnabled() && dateFilterCheckBox.isSelected()) {
mostRecentRadioButton.setEnabled(true);
rangeRadioButton.setEnabled(true);
mostRecentRadioButton.setSelected(true);
} else {
mostRecentRadioButton.setEnabled(false);
rangeRadioButton.setEnabled(false);
}
}
@Override
JCheckBox getCheckbox() {
return dateFilterCheckBox;
}
@Override
JList<?> getList() {
return null;
}
@Override
JLabel getAdditionalLabel() {
return null;
}
@Override
void addListeners(ActionListener actionListener, ListSelectionListener listListener) {
dateFilterCheckBox.addActionListener(actionListener);
startCheckBox.addActionListener(actionListener);
endCheckBox.addActionListener(actionListener);
rangeRadioButton.addActionListener(actionListener);
mostRecentRadioButton.addActionListener(actionListener);
startDatePicker.addDateChangeListener(new DateChangeListener() {
@Override
public void dateChanged(DateChangeEvent event) {
actionListener.actionPerformed(new ActionEvent(startDatePicker, ActionEvent.ACTION_PERFORMED, "StartDateChanged"));
}
});
endDatePicker.addDateChangeListener(new DateChangeListener() {
@Override
public void dateChanged(DateChangeEvent event) {
actionListener.actionPerformed(new ActionEvent(endDatePicker, ActionEvent.ACTION_PERFORMED, "EndDateChanged"));
}
});
}
@Override
void removeListeners() {
for (ActionListener listener : dateFilterCheckBox.getActionListeners()) {
dateFilterCheckBox.removeActionListener(listener);
}
for (ActionListener listener : rangeRadioButton.getActionListeners()) {
rangeRadioButton.removeActionListener(listener);
}
for (ActionListener listener : mostRecentRadioButton.getActionListeners()) {
mostRecentRadioButton.removeActionListener(listener);
}
for (ActionListener listener : rangeRadioButton.getActionListeners()) {
rangeRadioButton.removeActionListener(listener);
}
for (ActionListener listener : startCheckBox.getActionListeners()) {
startCheckBox.removeActionListener(listener);
}
for (ActionListener listener : endCheckBox.getActionListeners()) {
endCheckBox.removeActionListener(listener);
}
for (DateChangeListener listener : endDatePicker.getDateChangeListeners()) {
endDatePicker.removeDateChangeListener(listener);
}
for (DateChangeListener listener : startDatePicker.getDateChangeListeners()) {
startDatePicker.removeDateChangeListener(listener);
}
}
@NbBundle.Messages({"DateFilterPanel.invalidRange.text=Range or Only Last must be selected.",
"DateFilterPanel.startOrEndNeeded.text=A start or end date must be specified to use the range filter.",
"DateFilterPanel.startAfterEnd.text=Start date should be before the end date when both are enabled."})
@Override
String checkForError() {
if (dateFilterCheckBox.isSelected()) {
if (!(rangeRadioButton.isSelected() || mostRecentRadioButton.isSelected())) {
return Bundle.DateFilterPanel_invalidRange_text();
} else if (rangeRadioButton.isSelected() && !(startCheckBox.isSelected() || endCheckBox.isSelected())) {
return Bundle.DateFilterPanel_startOrEndNeeded_text();
} else if (startCheckBox.isSelected() && endCheckBox.isSelected() && startDatePicker.getDate().isAfter(endDatePicker.getDate())) {
//if the dates are equal it will effectively search just that day due to the rounding up of the end date in the getFilter code
return Bundle.DateFilterPanel_startAfterEnd_text();
}
}
return "";
}
@Override
AbstractFilter getFilter() {
if (dateFilterCheckBox.isSelected()) {
LocalDate startDate = LocalDate.MIN;
LocalDate endDate = LocalDate.MAX;
ZoneId zone = Utils.getUserPreferredZoneId();
if (rangeRadioButton.isSelected() && (startCheckBox.isSelected() || endCheckBox.isSelected())) {
if (startCheckBox.isSelected() && startDatePicker.getDate() != null) {
startDate = startDatePicker.getDate();
}
if (endCheckBox.isSelected() && endDatePicker.getDate() != null) {
endDate = endDatePicker.getDate();
}
} else if (dateFilterCheckBox.isSelected() && mostRecentRadioButton.isSelected()) {
endDate = LocalDate.now();
startDate = LocalDate.now().minus(Period.ofDays((Integer) daysSpinner.getValue()));
}
return new SearchFiltering.ArtifactDateRangeFilter(startDate.atStartOfDay(zone).toEpochSecond(), endDate.atStartOfDay(zone).toEpochSecond() + SECS_PER_DAY);//to insure end date is inclusive
}
return null;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.JCheckBox dateFilterCheckBox;
private javax.swing.JLabel daysLabel;
private javax.swing.JSpinner daysSpinner;
private javax.swing.JCheckBox endCheckBox;
private com.github.lgooddatepicker.components.DatePicker endDatePicker;
private javax.swing.JPanel jPanel1;
private javax.swing.JRadioButton mostRecentRadioButton;
private javax.swing.JRadioButton rangeRadioButton;
private javax.swing.JCheckBox startCheckBox;
private com.github.lgooddatepicker.components.DatePicker startDatePicker;
// End of variables declaration//GEN-END:variables
}

View File

@ -106,7 +106,7 @@
<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="Instances">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DetailsPanel.instancesList.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DetailsPanel.instancesList.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import com.google.common.eventbus.Subscribe;
import java.awt.Component;
@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils;
import org.sleuthkit.autopsy.modules.hashdatabase.AddContentToHashDbAction;
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
import org.sleuthkit.datamodel.AbstractFile;

View File

@ -6,9 +6,6 @@
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[600, 300]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1000, 650]"/>
</Property>
</Properties>
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
@ -48,12 +45,14 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="10" pref="10" max="-2" attributes="0"/>
<Component id="imagesButton" min="-2" pref="110" max="-2" attributes="0"/>
<Component id="imagesButton" linkSize="1" min="-2" pref="110" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="videosButton" min="-2" pref="110" max="-2" attributes="0"/>
<Component id="videosButton" linkSize="1" min="-2" pref="110" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="documentsButton" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="370" max="32767" attributes="0"/>
<Component id="documentsButton" linkSize="1" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="domainsButton" linkSize="1" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="190" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="step1Label" min="-2" pref="243" max="-2" attributes="0"/>
@ -78,6 +77,7 @@
<Component id="videosButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="imagesButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="documentsButton" alignment="3" min="-2" pref="43" max="-2" attributes="0"/>
<Component id="domainsButton" alignment="3" min="-2" pref="43" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
</Group>
@ -91,7 +91,7 @@
<Image iconType="3" name="/org/sleuthkit/autopsy/images/pictures-icon.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.imagesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.imagesButton.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/images/pictures-icon.png"/>
@ -118,7 +118,7 @@
<Image iconType="3" name="/org/sleuthkit/autopsy/images/video-icon.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.videosButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.videosButton.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/images/video-icon.png"/>
@ -148,7 +148,7 @@
<Image iconType="3" name="/org/sleuthkit/autopsy/images/documents-icon.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.documentsButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.documentsButton.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/images/documents-icon.png"/>
@ -165,7 +165,7 @@
<Component class="javax.swing.JLabel" name="step1Label">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.step1Label.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.step1Label.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
@ -191,6 +191,26 @@
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.HorizontalStrut"/>
</AuxValues>
</Component>
<Component class="javax.swing.JButton" name="domainsButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/images/domain-32.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.domainsButton.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/images/domain-32.png"/>
</Property>
<Property name="disabledSelectedIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/images/domain-32.png"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="domainsButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="displaySettingsPanel">
@ -240,7 +260,7 @@
<Component class="javax.swing.JButton" name="searchButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.searchButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.searchButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
@ -259,7 +279,7 @@
<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="Step 3: Choose display settings">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.sortingPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.sortingPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
@ -338,7 +358,7 @@
<Component class="javax.swing.JLabel" name="orderGroupsByLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.orderGroupsByLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.orderGroupsByLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
@ -349,7 +369,7 @@
<Component class="javax.swing.JLabel" name="orderByLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.orderByLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.orderByLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
@ -360,7 +380,7 @@
<Component class="javax.swing.JLabel" name="groupByLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DiscoveryDialog.groupByLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DiscoveryDialog.groupByLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>

View File

@ -16,17 +16,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import static java.awt.BorderLayout.CENTER;
import java.awt.Color;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
@ -35,24 +40,26 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.discovery.FileGroup.GroupSortingAlgorithm;
import static org.sleuthkit.autopsy.discovery.FileGroup.GroupSortingAlgorithm.BY_GROUP_SIZE;
import org.sleuthkit.autopsy.discovery.FileSearch.GroupingAttributeType;
import static org.sleuthkit.autopsy.discovery.FileSearch.GroupingAttributeType.PARENT_PATH;
import org.sleuthkit.autopsy.discovery.FileSorter.SortingMethod;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes;
import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils;
import org.sleuthkit.autopsy.discovery.search.Group;
import org.sleuthkit.autopsy.discovery.search.Group.GroupSortingAlgorithm;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.GroupingAttributeType;
import org.sleuthkit.autopsy.discovery.search.ResultsSorter;
import org.sleuthkit.autopsy.discovery.search.ResultsSorter.SortingMethod;
import org.sleuthkit.autopsy.discovery.search.SearchData;
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.TskCoreException;
import static org.sleuthkit.autopsy.discovery.FileSorter.SortingMethod.BY_FILE_NAME;
/**
* Dialog for displaying the controls and filters for configuration of a
* Discovery search.
*/
final class DiscoveryDialog extends javax.swing.JDialog {
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
Case.Events.DATA_SOURCE_ADDED, Case.Events.DATA_SOURCE_DELETED);
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
@ -61,12 +68,13 @@ final class DiscoveryDialog extends javax.swing.JDialog {
private ImageFilterPanel imageFilterPanel = null;
private VideoFilterPanel videoFilterPanel = null;
private DocumentFilterPanel documentFilterPanel = null;
private DomainFilterPanel domainFilterPanel = null;
private static final Color SELECTED_COLOR = new Color(216, 230, 242);
private static final Color UNSELECTED_COLOR = new Color(240, 240, 240);
private SearchWorker searchWorker = null;
private static DiscoveryDialog discDialog;
private static volatile boolean shouldUpdate = false;
private FileSearchData.FileType fileType = FileSearchData.FileType.IMAGE;
private SearchData.Type type = SearchData.Type.IMAGE;
private final PropertyChangeListener listener;
private final Set<BlackboardAttribute> objectsDetected = new HashSet<>();
private final Set<BlackboardAttribute> interestingItems = new HashSet<>();
@ -103,12 +111,41 @@ final class DiscoveryDialog extends javax.swing.JDialog {
}
}
};
for (GroupSortingAlgorithm groupSortAlgorithm : GroupSortingAlgorithm.values()) {
groupSortingComboBox.addItem(groupSortAlgorithm);
}
updateSearchSettings();
groupByCombobox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent event) {
if (event.getStateChange() == ItemEvent.SELECTED) {
SwingUtilities.invokeLater(() -> {
getSelectedFilterPanel().setLastGroupingAttributeType(groupByCombobox.getItemAt(groupByCombobox.getSelectedIndex()));
});
}
}
});
orderByCombobox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent event) {
if (event.getStateChange() == ItemEvent.SELECTED) {
SwingUtilities.invokeLater(() -> {
getSelectedFilterPanel().setLastSortingMethod(orderByCombobox.getItemAt(orderByCombobox.getSelectedIndex()));
});
}
}
});
groupSortingComboBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent event) {
if (event.getStateChange() == ItemEvent.SELECTED) {
SwingUtilities.invokeLater(() -> {
getSelectedFilterPanel().setLastGroupSortingAlg(groupSortingComboBox.getItemAt(groupSortingComboBox.getSelectedIndex()));
});
}
}
});
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this.new CasePropertyChangeListener());
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, this.new ModuleChangeListener());
setPreferredSize(new java.awt.Dimension(1000, 650));
}
/**
@ -122,43 +159,90 @@ final class DiscoveryDialog extends javax.swing.JDialog {
imageFilterPanel = new ImageFilterPanel();
videoFilterPanel = new VideoFilterPanel();
documentFilterPanel = new DocumentFilterPanel();
domainFilterPanel = new DomainFilterPanel();
unselectAllButtons();
imagesButton.setSelected(true);
imagesButton.setEnabled(false);
imagesButton.setBackground(SELECTED_COLOR);
imagesButton.setForeground(Color.BLACK);
type = SearchData.Type.IMAGE;
add(imageFilterPanel, CENTER);
imageFilterPanel.addPropertyChangeListener(listener);
updateComboBoxes();
pack();
repaint();
}
/**
* Set the type buttons to a default state where none are selected.
*/
private void unselectAllButtons() {
imagesButton.setSelected(false);
imagesButton.setEnabled(true);
imagesButton.setBackground(UNSELECTED_COLOR);
videosButton.setSelected(false);
videosButton.setEnabled(true);
videosButton.setBackground(UNSELECTED_COLOR);
documentsButton.setSelected(false);
documentsButton.setEnabled(true);
documentsButton.setBackground(UNSELECTED_COLOR);
fileType = FileSearchData.FileType.IMAGE;
add(imageFilterPanel, CENTER);
imageFilterPanel.addPropertyChangeListener(listener);
updateComboBoxes();
groupSortingComboBox.setSelectedItem(BY_GROUP_SIZE);
pack();
repaint();
domainsButton.setSelected(false);
domainsButton.setEnabled(true);
domainsButton.setBackground(UNSELECTED_COLOR);
}
/**
* Private helper method to perform update of comboboxes update.
*/
private void updateComboBoxes() {
groupByCombobox.removeAllItems();
// Set up the grouping attributes
for (FileSearch.GroupingAttributeType type : FileSearch.GroupingAttributeType.getOptionsForGrouping()) {
addTypeToGroupByComboBox(type);
List<GroupingAttributeType> groupingAttrs = new ArrayList<>();
List<SortingMethod> sortingMethods = new ArrayList<>();
groupByCombobox.removeAllItems();
if (type == SearchData.Type.DOMAIN) {
groupingAttrs.addAll(GroupingAttributeType.getOptionsForGroupingForDomains());
sortingMethods.addAll(SortingMethod.getOptionsForOrderingDomains());
} else {
groupingAttrs.addAll(GroupingAttributeType.getOptionsForGroupingForFiles());
sortingMethods.addAll(SortingMethod.getOptionsForOrderingFiles());
}
groupByCombobox.setSelectedItem(PARENT_PATH);
for (GroupingAttributeType groupingType : groupingAttrs) {
addTypeToGroupByComboBox(groupingType);
}
groupByCombobox.setSelectedItem(getSelectedFilterPanel().getLastGroupingAttributeType());
orderByCombobox.removeAllItems();
// Set up the file order list
for (FileSorter.SortingMethod method : FileSorter.SortingMethod.getOptionsForOrdering()) {
for (SortingMethod method : sortingMethods) {
if (method != SortingMethod.BY_FREQUENCY || CentralRepository.isEnabled()) {
orderByCombobox.addItem(method);
}
}
orderByCombobox.setSelectedItem(BY_FILE_NAME);
orderByCombobox.setSelectedItem(getSelectedFilterPanel().getLastSortingMethod());
groupSortingComboBox.removeAllItems();
for (GroupSortingAlgorithm groupSortAlgorithm : GroupSortingAlgorithm.values()) {
groupSortingComboBox.addItem(groupSortAlgorithm);
}
groupSortingComboBox.setSelectedItem(getSelectedFilterPanel().getLastGroupSortingAlg());
}
/**
* Private helper method to get the correct panel for the selected type.
*
* @return The panel that corresponds to the currently selected type.
*/
private AbstractFiltersPanel getSelectedFilterPanel() {
switch (type) {
case IMAGE:
return imageFilterPanel;
case VIDEO:
return videoFilterPanel;
case DOCUMENT:
return documentFilterPanel;
case DOMAIN:
return domainFilterPanel;
default:
return imageFilterPanel;
}
}
/**
@ -196,27 +280,12 @@ final class DiscoveryDialog extends javax.swing.JDialog {
}
/**
* Validate the current filter settings of the selected type.
* Validate the filter settings for File type filters.
*/
synchronized void validateDialog() {
switch (fileType) {
case IMAGE:
if (imageFilterPanel != null) {
imageFilterPanel.validateFields();
}
return;
case VIDEO:
if (videoFilterPanel != null) {
videoFilterPanel.validateFields();
}
return;
case DOCUMENTS:
if (documentFilterPanel != null) {
documentFilterPanel.validateFields();
}
break;
default:
break;
AbstractFiltersPanel panel = getSelectedFilterPanel();
if (panel != null) {
panel.validateFields();
}
}
@ -235,6 +304,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
documentsButton = new javax.swing.JButton();
javax.swing.JLabel step1Label = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(104, 0), new java.awt.Dimension(104, 0), new java.awt.Dimension(104, 32767));
domainsButton = new javax.swing.JButton();
javax.swing.JPanel displaySettingsPanel = new javax.swing.JPanel();
searchButton = new javax.swing.JButton();
errorLabel = new javax.swing.JLabel();
@ -248,7 +318,6 @@ final class DiscoveryDialog extends javax.swing.JDialog {
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setMinimumSize(new java.awt.Dimension(600, 300));
setPreferredSize(new java.awt.Dimension(1000, 650));
imagesButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/pictures-icon.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(imagesButton, org.openide.util.NbBundle.getMessage(DiscoveryDialog.class, "DiscoveryDialog.imagesButton.text")); // NOI18N
@ -292,6 +361,17 @@ final class DiscoveryDialog extends javax.swing.JDialog {
org.openide.awt.Mnemonics.setLocalizedText(step1Label, org.openide.util.NbBundle.getMessage(DiscoveryDialog.class, "DiscoveryDialog.step1Label.text")); // NOI18N
domainsButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/domain-32.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(domainsButton, org.openide.util.NbBundle.getMessage(DiscoveryDialog.class, "DiscoveryDialog.domainsButton.text")); // NOI18N
domainsButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/domain-32.png"))); // NOI18N
domainsButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/domain-32.png"))); // NOI18N
domainsButton.setFocusable(false);
domainsButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
domainsButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout toolBarPanelLayout = new javax.swing.GroupLayout(toolBarPanel);
toolBarPanel.setLayout(toolBarPanelLayout);
toolBarPanelLayout.setHorizontalGroup(
@ -306,13 +386,18 @@ final class DiscoveryDialog extends javax.swing.JDialog {
.addComponent(videosButton, javax.swing.GroupLayout.PREFERRED_SIZE, 110, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(documentsButton)
.addContainerGap(370, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(domainsButton)
.addContainerGap(190, Short.MAX_VALUE))
.addGroup(toolBarPanelLayout.createSequentialGroup()
.addComponent(step1Label, javax.swing.GroupLayout.PREFERRED_SIZE, 243, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(391, Short.MAX_VALUE))))
);
toolBarPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {documentsButton, domainsButton, imagesButton, videosButton});
toolBarPanelLayout.setVerticalGroup(
toolBarPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(toolBarPanelLayout.createSequentialGroup()
@ -324,7 +409,8 @@ final class DiscoveryDialog extends javax.swing.JDialog {
.addGroup(toolBarPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(videosButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(imagesButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(documentsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 43, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(documentsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 43, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(domainsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 43, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(8, 8, 8))
);
@ -417,18 +503,14 @@ final class DiscoveryDialog extends javax.swing.JDialog {
private void imagesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_imagesButtonActionPerformed
removeAllPanels();
add(imageFilterPanel, CENTER);
unselectAllButtons();
imagesButton.setSelected(true);
imagesButton.setEnabled(false);
imagesButton.setBackground(SELECTED_COLOR);
imagesButton.setForeground(Color.BLACK);
videosButton.setSelected(false);
videosButton.setEnabled(true);
videosButton.setBackground(UNSELECTED_COLOR);
documentsButton.setSelected(false);
documentsButton.setEnabled(true);
documentsButton.setBackground(UNSELECTED_COLOR);
fileType = FileSearchData.FileType.IMAGE;
type = SearchData.Type.IMAGE;
imageFilterPanel.addPropertyChangeListener(listener);
updateComboBoxes();
validateDialog();
pack();
repaint();
@ -437,18 +519,14 @@ final class DiscoveryDialog extends javax.swing.JDialog {
private void videosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_videosButtonActionPerformed
removeAllPanels();
add(videoFilterPanel, CENTER);
imagesButton.setSelected(false);
imagesButton.setEnabled(true);
imagesButton.setBackground(UNSELECTED_COLOR);
unselectAllButtons();
videosButton.setSelected(true);
videosButton.setEnabled(false);
videosButton.setBackground(SELECTED_COLOR);
videosButton.setForeground(Color.BLACK);
documentsButton.setSelected(false);
documentsButton.setEnabled(true);
documentsButton.setBackground(UNSELECTED_COLOR);
videoFilterPanel.addPropertyChangeListener(listener);
fileType = FileSearchData.FileType.VIDEO;
type = SearchData.Type.VIDEO;
updateComboBoxes();
validateDialog();
pack();
repaint();
@ -457,18 +535,14 @@ final class DiscoveryDialog extends javax.swing.JDialog {
private void documentsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsButtonActionPerformed
removeAllPanels();
add(documentFilterPanel, CENTER);
unselectAllButtons();
documentsButton.setSelected(true);
documentsButton.setEnabled(false);
documentsButton.setBackground(SELECTED_COLOR);
documentsButton.setForeground(Color.BLACK);
videosButton.setSelected(false);
videosButton.setEnabled(true);
videosButton.setBackground(UNSELECTED_COLOR);
imagesButton.setSelected(false);
imagesButton.setEnabled(true);
imagesButton.setBackground(UNSELECTED_COLOR);
fileType = FileSearchData.FileType.DOCUMENTS;
type = SearchData.Type.DOCUMENT;
documentFilterPanel.addPropertyChangeListener(listener);
updateComboBoxes();
validateDialog();
pack();
repaint();
@ -482,6 +556,10 @@ final class DiscoveryDialog extends javax.swing.JDialog {
remove(imageFilterPanel);
imageFilterPanel.removePropertyChangeListener(listener);
}
if (domainFilterPanel != null) {
remove(domainFilterPanel);
domainFilterPanel.removePropertyChangeListener(listener);
}
if (documentFilterPanel != null) {
remove(documentFilterPanel);
documentFilterPanel.removePropertyChangeListener(listener);
@ -503,22 +581,28 @@ final class DiscoveryDialog extends javax.swing.JDialog {
tc.open();
}
tc.resetTopComponent();
List<FileSearchFiltering.FileFilter> filters;
List<AbstractFilter> filters;
if (videosButton.isSelected()) {
filters = videoFilterPanel.getFilters();
} else if (documentsButton.isSelected()) {
filters = documentFilterPanel.getFilters();
} else {
} else if (imagesButton.isSelected()) {
filters = imageFilterPanel.getFilters();
} else if (domainsButton.isSelected()) {
filters = domainFilterPanel.getFilters();
} else {
logger.log(Level.SEVERE, "No filter type selected");
filters = new ArrayList<>();
}
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.SearchStartedEvent(fileType));
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.SearchStartedEvent(type));
// Get the grouping attribute and group sorting method
FileSearch.AttributeType groupingAttr = groupByCombobox.getItemAt(groupByCombobox.getSelectedIndex()).getAttributeType();
FileGroup.GroupSortingAlgorithm groupSortAlgorithm = groupSortingComboBox.getItemAt(groupSortingComboBox.getSelectedIndex());
DiscoveryAttributes.AttributeType groupingAttr = groupByCombobox.getItemAt(groupByCombobox.getSelectedIndex()).getAttributeType();
Group.GroupSortingAlgorithm groupSortAlgorithm = groupSortingComboBox.getItemAt(groupSortingComboBox.getSelectedIndex());
// Get the file sorting method
FileSorter.SortingMethod fileSort = (FileSorter.SortingMethod) orderByCombobox.getSelectedItem();
ResultsSorter.SortingMethod fileSort = (ResultsSorter.SortingMethod) orderByCombobox.getSelectedItem();
CentralRepository centralRepoDb = null;
if (CentralRepository.isEnabled()) {
try {
@ -528,13 +612,29 @@ final class DiscoveryDialog extends javax.swing.JDialog {
logger.log(Level.SEVERE, "Error loading central repository database, no central repository options will be available for Discovery", ex);
}
}
searchWorker = new SearchWorker(centralRepoDb, filters, groupingAttr, groupSortAlgorithm, fileSort);
searchWorker = new SearchWorker(centralRepoDb, type, filters, groupingAttr, groupSortAlgorithm, fileSort);
searchWorker.execute();
dispose();
tc.toFront();
tc.requestActive();
}//GEN-LAST:event_searchButtonActionPerformed
private void domainsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_domainsButtonActionPerformed
removeAllPanels();
add(domainFilterPanel, CENTER);
unselectAllButtons();
domainsButton.setSelected(true);
domainsButton.setEnabled(false);
domainsButton.setBackground(SELECTED_COLOR);
domainsButton.setForeground(Color.BLACK);
type = SearchData.Type.DOMAIN;
domainFilterPanel.addPropertyChangeListener(listener);
updateComboBoxes();
validateDialog();
pack();
repaint();
}//GEN-LAST:event_domainsButtonActionPerformed
@Override
public void dispose() {
setVisible(false);
@ -569,6 +669,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton documentsButton;
private javax.swing.JButton domainsButton;
private javax.swing.JLabel errorLabel;
private javax.swing.JComboBox<GroupingAttributeType> groupByCombobox;
private javax.swing.JComboBox<GroupSortingAlgorithm> groupSortingComboBox;
@ -583,7 +684,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
* filters available.
*/
private class CasePropertyChangeListener implements PropertyChangeListener {
@Override
@SuppressWarnings("fallthrough")
public void propertyChange(PropertyChangeEvent evt) {

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.directorytree.actionhelpers.ExtractActionHelper;
import java.awt.event.ActionEvent;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import java.util.Arrays;
import java.util.HashSet;

View File

@ -105,8 +105,8 @@
<SubComponents>
<Component class="javax.swing.JButton" name="newSearchButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="FileSearchDialog.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="Bundle.DiscoveryTopComponent_cancelButton_text()" type="code"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[110, 26]"/>

View File

@ -16,8 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import com.google.common.eventbus.Subscribe;
import java.awt.BorderLayout;
import java.awt.Color;
@ -36,24 +37,27 @@ import org.openide.windows.RetainLocation;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.discovery.FileSearchFiltering.FileFilter;
import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils;
import org.sleuthkit.autopsy.discovery.search.SearchData.Type;
import static org.sleuthkit.autopsy.discovery.search.SearchData.Type.DOMAIN;
/**
* Create a dialog for displaying the Discovery results.
*/
@TopComponent.Description(preferredID = "Discovery", persistenceType = TopComponent.PERSISTENCE_NEVER)
@TopComponent.Description(preferredID = "DiscoveryTc", persistenceType = TopComponent.PERSISTENCE_NEVER)
@TopComponent.Registration(mode = "discovery", openAtStartup = false)
@RetainLocation("discovery")
@NbBundle.Messages("DiscoveryTopComponent.name= Discovery")
public final class DiscoveryTopComponent extends TopComponent {
private static final long serialVersionUID = 1L;
private static final String PREFERRED_ID = "Discovery"; // NON-NLS
private static final String PREFERRED_ID = "DiscoveryTc"; // NON-NLS
private static final int ANIMATION_INCREMENT = 30;
private volatile static int resultsAreaSize = 250;
private final GroupListPanel groupListPanel;
private final DetailsPanel detailsPanel;
private final ResultsPanel resultsPanel;
private Type searchType;
private int dividerLocation = -1;
private SwingAnimator animator = null;
@ -182,7 +186,7 @@ public final class DiscoveryTopComponent extends TopComponent {
add(mainSplitPane, java.awt.BorderLayout.CENTER);
org.openide.awt.Mnemonics.setLocalizedText(newSearchButton, org.openide.util.NbBundle.getMessage(DiscoveryTopComponent.class, "FileSearchDialog.cancelButton.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(newSearchButton, Bundle.DiscoveryTopComponent_cancelButton_text());
newSearchButton.setMaximumSize(new java.awt.Dimension(110, 26));
newSearchButton.setMinimumSize(new java.awt.Dimension(110, 26));
newSearchButton.setPreferredSize(new java.awt.Dimension(110, 26));
@ -257,17 +261,19 @@ public final class DiscoveryTopComponent extends TopComponent {
*/
@Subscribe
void handleDetailsVisibleEvent(DiscoveryEventUtils.DetailsVisibleEvent detailsVisibleEvent) {
if (animator != null && animator.isRunning()) {
animator.stop();
animator = null;
if (resultsPanel.getActiveType() != DOMAIN) {
if (animator != null && animator.isRunning()) {
animator.stop();
animator = null;
}
dividerLocation = rightSplitPane.getDividerLocation();
if (detailsVisibleEvent.isShowDetailsArea()) {
animator = new SwingAnimator(new ShowDetailsAreaCallback());
} else {
animator = new SwingAnimator(new HideDetailsAreaCallback());
}
animator.start();
}
dividerLocation = rightSplitPane.getDividerLocation();
if (detailsVisibleEvent.isShowDetailsArea()) {
animator = new SwingAnimator(new ShowDetailsAreaCallback());
} else {
animator = new SwingAnimator(new HideDetailsAreaCallback());
}
animator.start();
}
/**
@ -278,12 +284,16 @@ public final class DiscoveryTopComponent extends TopComponent {
*/
@Messages({"DiscoveryTopComponent.cancelButton.text=Cancel Search",
"# {0} - searchType",
"DiscoveryTopComponent.searchInProgress.text=Performing search for results of type {0}. Please wait."})
"DiscoveryTopComponent.searchInProgress.text=Performing search for results of type {0}. Please wait.",
"DiscoveryTopComponent.searchError.text=Error no type specified for search."})
@Subscribe
void handleSearchStartedEvent(DiscoveryEventUtils.SearchStartedEvent searchStartedEvent) {
newSearchButton.setText(Bundle.DiscoveryTopComponent_cancelButton_text());
progressMessageTextArea.setForeground(Color.red);
progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchInProgress_text(searchStartedEvent.getType().name()));
searchType = searchStartedEvent.getType();
progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchInProgress_text(searchType.name()));
rightSplitPane.getComponent(1).setVisible(searchStartedEvent.getType() != DOMAIN);
rightSplitPane.getComponent(2).setVisible(searchStartedEvent.getType() != DOMAIN);
}
/**
@ -295,11 +305,22 @@ public final class DiscoveryTopComponent extends TopComponent {
@Subscribe
@Messages({"DiscoveryTopComponent.newSearch.text=New Search",
"# {0} - search",
"DiscoveryTopComponent.searchComplete.text=Results with {0}"})
"DiscoveryTopComponent.searchComplete.text=Results with {0}",
"DiscoveryTopComponent.domainSearch.text=Type: Domain",
"DiscoveryTopComponent.additionalFilters.text=; "})
void handleSearchCompleteEvent(DiscoveryEventUtils.SearchCompleteEvent searchCompleteEvent) {
newSearchButton.setText(Bundle.DiscoveryTopComponent_newSearch_text());
progressMessageTextArea.setForeground(Color.black);
progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchComplete_text(searchCompleteEvent.getFilters().stream().map(FileFilter::getDesc).collect(Collectors.joining("; "))));
String descriptionText = "";
if (searchType == DOMAIN) {
//domain does not have a file type filter to add the type information so it is manually added
descriptionText = Bundle.DiscoveryTopComponent_domainSearch_text();
if (!searchCompleteEvent.getFilters().isEmpty()) {
descriptionText += Bundle.DiscoveryTopComponent_additionalFilters_text();
}
}
descriptionText += searchCompleteEvent.getFilters().stream().map(AbstractFilter::getDesc).collect(Collectors.joining("; "));
progressMessageTextArea.setText(Bundle.DiscoveryTopComponent_searchComplete_text(descriptionText));
progressMessageTextArea.setCaretPosition(0);
}

View File

@ -0,0 +1,542 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.ui;
import com.google.common.io.Files;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.imgscalr.Scalr;
import org.netbeans.api.progress.ProgressHandle;
import org.opencv.core.Mat;
import org.opencv.highgui.VideoCapture;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.coreutils.VideoUtils.getVideoFileInTempDir;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.discovery.search.ResultFile;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utility class for the various user interface elements used by Discovery.
*/
final class DiscoveryUiUtils {
private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
private static final int BYTE_UNIT_CONVERSION = 1000;
private static final int ICON_SIZE = 16;
private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
private static final String DELETE_ICON_PATH = "org/sleuthkit/autopsy/images/file-icon-deleted.png";
private static final String UNSUPPORTED_DOC_PATH = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false));
private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, false));
private static final String THUMBNAIL_FORMAT = "png"; //NON-NLS
private static final String VIDEO_THUMBNAIL_DIR = "video-thumbnails"; //NON-NLS
private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
@NbBundle.Messages({"# {0} - fileSize",
"# {1} - units",
"DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
"DiscoveryUiUtility.bytes.text=bytes",
"DiscoveryUiUtility.kiloBytes.text=KB",
"DiscoveryUiUtility.megaBytes.text=MB",
"DiscoveryUiUtility.gigaBytes.text=GB",
"DiscoveryUiUtility.terraBytes.text=TB"})
/**
* Convert a size in bytes to a string with representing the size in the
* largest units which represent the value as being greater than or equal to
* one. Result will be rounded down to the nearest whole number of those
* units.
*
* @param bytes Size in bytes.
*/
static String getFileSizeString(long bytes) {
long size = bytes;
int unitsSwitchValue = 0;
while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
size /= BYTE_UNIT_CONVERSION;
unitsSwitchValue++;
}
String units;
switch (unitsSwitchValue) {
case 1:
units = Bundle.DiscoveryUiUtility_kiloBytes_text();
break;
case 2:
units = Bundle.DiscoveryUiUtility_megaBytes_text();
break;
case 3:
units = Bundle.DiscoveryUiUtility_gigaBytes_text();
break;
case 4:
units = Bundle.DiscoveryUiUtility_terraBytes_text();
break;
default:
units = Bundle.DiscoveryUiUtility_bytes_text();
break;
}
return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
}
/**
* Get the image to use when the document type does not support image
* extraction.
*
* @return An image that indicates we don't know if there are images.
*/
static ImageIcon getUnsupportedImageThumbnail() {
return UNSUPPORTED_DOCUMENT_THUMBNAIL;
}
/**
* Get the names of the sets which exist in the case database for the
* specified artifact and attribute types.
*
* @param artifactType The artifact type to get the list of sets for.
* @param setNameAttribute The attribute type which contains the set names.
*
* @return A list of set names which exist in the case for the specified
* artifact and attribute types.
*
* @throws TskCoreException
*/
static List<String> getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
List<BlackboardArtifact> arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
List<String> setNames = new ArrayList<>();
for (BlackboardArtifact art : arts) {
for (BlackboardAttribute attr : art.getAttributes()) {
if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
String setName = attr.getValueString();
if (!setNames.contains(setName)) {
setNames.add(setName);
}
}
}
}
Collections.sort(setNames);
return setNames;
}
/**
* Helper method to see if point is on the icon.
*
* @param comp The component to check if the cursor is over the icon of
* @param point The point the cursor is at.
*
* @return True if the point is over the icon, false otherwise.
*/
static boolean isPointOnIcon(Component comp, Point point) {
return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
}
/**
* Method to set the icon and tool tip text for a label to show deleted
* status.
*
* @param isDeleted True if the label should reflect deleted status,
* false otherwise.
* @param isDeletedLabel The label to set the icon and tooltip for.
*/
@NbBundle.Messages({"DiscoveryUiUtils.isDeleted.text=All instances of file are deleted."})
static void setDeletedIcon(boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
if (isDeleted) {
isDeletedLabel.setIcon(DELETED_ICON);
isDeletedLabel.setToolTipText(Bundle.DiscoveryUiUtils_isDeleted_text());
} else {
isDeletedLabel.setIcon(null);
isDeletedLabel.setToolTipText(null);
}
}
/**
* Method to set the icon and tool tip text for a label to show the score.
*
* @param resultFile The result file which the label should reflect the
* score of.
* @param scoreLabel The label to set the icon and tooltip for.
*/
static void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
switch (resultFile.getScore()) {
case NOTABLE_SCORE:
scoreLabel.setIcon(NOTABLE_SCORE_ICON);
break;
case INTERESTING_SCORE:
scoreLabel.setIcon(INTERESTING_SCORE_ICON);
break;
case NO_SCORE: // empty case - this is interpreted as an intentional fall-through
default:
scoreLabel.setIcon(null);
break;
}
scoreLabel.setToolTipText(resultFile.getScoreDescription());
}
/**
* Get the size of the icons used by the UI.
*
* @return
*/
static int getIconSize() {
return ICON_SIZE;
}
/**
* Helper method to display an error message when the results of the
* Discovery Top component may be incomplete.
*/
@NbBundle.Messages({"DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete"})
static void displayErrorMessage(DiscoveryDialog dialog) {
//check if modules run and assemble message
try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
Map<Long, DataSourceModulesWrapper> dataSourceIngestModules = new HashMap<>();
for (DataSource dataSource : skCase.getDataSources()) {
dataSourceIngestModules.put(dataSource.getId(), new DataSourceModulesWrapper(dataSource.getName()));
}
for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
}
String message = "";
for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
message += dsmodulesWrapper.getMessage();
}
if (!message.isEmpty()) {
JScrollPane messageScrollPane = new JScrollPane();
JTextPane messageTextPane = new JTextPane();
messageTextPane.setText(message);
messageTextPane.setVisible(true);
messageTextPane.setEditable(false);
messageTextPane.setCaretPosition(0);
messageScrollPane.setMaximumSize(new Dimension(600, 100));
messageScrollPane.setPreferredSize(new Dimension(600, 100));
messageScrollPane.setViewportView(messageTextPane);
JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.DiscoveryUiUtils_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
}
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.WARNING, "Exception while determining which modules have been run for Discovery", ex);
}
dialog.validateDialog();
}
/**
* Get the video thumbnails for a file which exists in a
* VideoThumbnailsWrapper and update the VideoThumbnailsWrapper to include
* them.
*
* @param thumbnailWrapper the object which contains the file to generate
* thumbnails for.
*
*/
@NbBundle.Messages({"# {0} - file name",
"DiscoveryUiUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
String cacheDirectory;
try {
cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
} catch (NoCurrentCaseException ex) {
cacheDirectory = null;
logger.log(Level.WARNING, "Unable to get cache directory, video thumbnails will not be saved", ex);
}
if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
java.io.File tempFile;
try {
tempFile = getVideoFileInTempDir(file);
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS
int[] framePositions = new int[]{
0,
0,
0,
0};
thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
return;
}
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
ProgressHandle progress = ProgressHandle.createHandle(Bundle.DiscoveryUiUtils_genVideoThumb_progress_text(file.getName()));
progress.start(100);
try {
Files.createParentDirs(tempFile);
if (Thread.interrupted()) {
int[] framePositions = new int[]{
0,
0,
0,
0};
thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
return;
}
ContentUtils.writeToFile(file, tempFile, progress, null, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error extracting temporary file for " + file.getParentPath() + "/" + file.getName(), ex); //NON-NLS
} finally {
progress.finish();
}
}
VideoCapture videoFile = new VideoCapture(); // will contain the video
BufferedImage bufferedImage = null;
try {
if (!videoFile.open(tempFile.toString())) {
logger.log(Level.WARNING, "Error opening {0} for preview generation.", file.getParentPath() + "/" + file.getName()); //NON-NLS
int[] framePositions = new int[]{
0,
0,
0,
0};
thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
return;
}
double fps = videoFile.get(5); // gets frame per second
double totalFrames = videoFile.get(7); // gets total frames
if (fps <= 0 || totalFrames <= 0) {
logger.log(Level.WARNING, "Error getting fps or total frames for {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
int[] framePositions = new int[]{
0,
0,
0,
0};
thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
return;
}
if (Thread.interrupted()) {
int[] framePositions = new int[]{
0,
0,
0,
0};
thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
return;
}
double duration = 1000 * (totalFrames / fps); //total milliseconds
int[] framePositions = new int[]{
(int) (duration * .01),
(int) (duration * .25),
(int) (duration * .5),
(int) (duration * .75),};
Mat imageMatrix = new Mat();
List<Image> videoThumbnails = new ArrayList<>();
if (cacheDirectory == null || file.getMd5Hash() == null) {
cacheDirectory = null;
} else {
try {
FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
} catch (IOException ex) {
cacheDirectory = null;
logger.log(Level.WARNING, "Unable to make video thumbnails directory, thumbnails will not be saved", ex);
}
}
for (int i = 0; i < framePositions.length; i++) {
if (!videoFile.set(0, framePositions[i])) {
logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
// If we can't set the time, continue to the next frame position and try again.
videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
if (cacheDirectory != null) {
try {
ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
}
}
continue;
}
// Read the frame into the image/matrix.
if (!videoFile.read(imageMatrix)) {
logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
// If the image is bad for some reason, continue to the next frame position and try again.
videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
if (cacheDirectory != null) {
try {
ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
}
}
continue;
}
// If the image is empty, return since no buffered image can be created.
if (imageMatrix.empty()) {
videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
if (cacheDirectory != null) {
try {
ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
}
}
continue;
}
int matrixColumns = imageMatrix.cols();
int matrixRows = imageMatrix.rows();
// Convert the matrix that contains the frame to a buffered image.
if (bufferedImage == null) {
bufferedImage = new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
}
byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
imageMatrix.get(0, 0, data); //copy the image to data
if (imageMatrix.channels() == 3) {
for (int k = 0; k < data.length; k += 3) {
byte temp = data[k];
data[k] = data[k + 2];
data[k + 2] = temp;
}
}
bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
if (Thread.interrupted()) {
thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
try {
FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to delete directory for cancelled video thumbnail process", ex);
}
return;
}
BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
//We are height limited here so it can be wider than it can be tall.Scalr maintains the aspect ratio.
videoThumbnails.add(thumbnail);
if (cacheDirectory != null) {
try {
ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to save video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
}
}
}
thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
} finally {
videoFile.release(); // close the file}
}
} else {
loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
}
}
/**
* Get the default image to display when a thumbnail is not available.
*
* @return The default video thumbnail.
*/
private static BufferedImage getDefaultVideoThumbnail() {
try {
return ImageIO.read(ImageUtils.class
.getResourceAsStream("/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));//NON-NLS
} catch (IOException ex) {
logger.log(Level.SEVERE, "Failed to load 'failed to create video' placeholder.", ex); //NON-NLS
}
return null;
}
/**
* Load the thumbnails that exist in the cache directory for the specified
* video file.
*
* @param cacheDirectory The directory which exists for the video
* thumbnails.
* @param thumbnailWrapper The VideoThumbnailWrapper object which contains
* information about the file and the thumbnails
* associated with it.
*/
private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
int[] framePositions = new int[4];
List<Image> videoThumbnails = new ArrayList<>();
int thumbnailNumber = 0;
String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
try {
videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
} catch (IOException ex) {
videoThumbnails.add(failedVideoThumbImage);
logger.log(Level.WARNING, "Unable to read saved video thumbnail " + fileName + " for " + md5, ex);
}
int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
framePositions[thumbnailNumber] = framePos;
thumbnailNumber++;
}
thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
}
/**
* Private helper method for creating video thumbnails, for use when no
* thumbnails are created.
*
* @return List containing the default thumbnail.
*/
private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
List<Image> videoThumbnails = new ArrayList<>();
videoThumbnails.add(failedVideoThumbImage);
videoThumbnails.add(failedVideoThumbImage);
videoThumbnails.add(failedVideoThumbImage);
videoThumbnails.add(failedVideoThumbImage);
return videoThumbnails;
}
/**
* Private constructor for DiscoveryUiUtils utility class.
*/
private DiscoveryUiUtils() {
//private constructor in a utility class intentionally left blank
}
}

View File

@ -58,14 +58,14 @@
<SubComponents>
<Container class="javax.swing.JSplitPane" name="documentsFiltersSplitPane">
<Properties>
<Property name="resizeWeight" type="double" value="0.5"/>
<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="Step 2: Filter which documents to show">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DocumentFilterPanel.documentsFiltersSplitPane.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DocumentFilterPanel.documentsFiltersSplitPane.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="resizeWeight" type="double" value="0.5"/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>

View File

@ -16,9 +16,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.discovery.search.SearchData;
/**
* Class which displays all filters available for the Documents search type.
@ -26,7 +27,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
final class DocumentFilterPanel extends AbstractFiltersPanel {
private static final long serialVersionUID = 1L;
private static final FileSearchData.FileType FILE_TYPE = FileSearchData.FileType.DOCUMENTS;
private static final SearchData.Type TYPE = SearchData.Type.DOCUMENT;
/**
* Constructs a new DocumentFilterPanel.
@ -34,7 +35,7 @@ final class DocumentFilterPanel extends AbstractFiltersPanel {
DocumentFilterPanel() {
super();
initComponents();
SizeFilterPanel sizeFilterPanel = new SizeFilterPanel(FILE_TYPE);
SizeFilterPanel sizeFilterPanel = new SizeFilterPanel(TYPE);
int[] sizeIndicesSelected = {3, 4, 5};
addFilter(sizeFilterPanel, true, sizeIndicesSelected, 0);
addFilter(new DataSourceFilterPanel(), false, null, 0);
@ -44,7 +45,7 @@ final class DocumentFilterPanel extends AbstractFiltersPanel {
} else {
pastOccurrencesIndices = new int[]{2, 3, 4};
}
addFilter(new PastOccurrencesFilterPanel(), true, pastOccurrencesIndices, 0);
addFilter(new PastOccurrencesFilterPanel(TYPE), true, pastOccurrencesIndices, 0);
addFilter(new HashSetFilterPanel(), false, null, 1);
addFilter(new InterestingItemsFilterPanel(), false, null, 1);
addFilter(new ParentFolderFilterPanel(), false, null, 1);
@ -91,8 +92,8 @@ final class DocumentFilterPanel extends AbstractFiltersPanel {
add(documentFiltersScrollPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
@Override
FileSearchData.FileType getFileType() {
return FILE_TYPE;
SearchData.Type getType() {
return TYPE;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JSplitPane documentsFiltersSplitPane;

View File

@ -80,16 +80,16 @@
<Image iconType="3" name="/org/sleuthkit/autopsy/images/file-icon-deleted.png"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DocumentPanel.isDeletedLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DocumentPanel.isDeletedLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize())" type="code"/>
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize())" type="code"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize())" type="code"/>
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize())" type="code"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize())" type="code"/>
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize())" type="code"/>
</Property>
</Properties>
</Component>
@ -100,20 +100,20 @@
</Property>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize())" type="code"/>
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize())" type="code"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize())" type="code"/>
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize())" type="code"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize())" type="code"/>
<Connection code="new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize())" type="code"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="fileSizeLabel">
<Properties>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="DocumentPanel.fileSizeLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DocumentPanel.fileSizeLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import java.awt.Color;
import java.awt.Component;
@ -29,6 +29,7 @@ import javax.swing.JList;
import javax.swing.ListCellRenderer;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.corecomponents.AutoWrappingJTextPane;
import org.sleuthkit.autopsy.discovery.search.SearchData;
/**
* Class which displays a preview and details about a document.
@ -67,15 +68,15 @@ class DocumentPanel extends javax.swing.JPanel implements ListCellRenderer<Docum
isDeletedLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/file-icon-deleted.png"))); // NOI18N
isDeletedLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DocumentPanel.class, "DocumentPanel.isDeletedLabel.toolTipText")); // NOI18N
isDeletedLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize()));
scoreLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/red-circle-exclamation.png"))); // NOI18N
scoreLabel.setToolTipText("");
scoreLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize()));
scoreLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize()));
scoreLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.discovery.ui.DiscoveryUiUtils.getIconSize()));
fileSizeLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DocumentPanel.class, "DocumentPanel.fileSizeLabel.toolTipText")); // NOI18N
@ -163,7 +164,7 @@ class DocumentPanel extends javax.swing.JPanel implements ListCellRenderer<Docum
if (value.getSummary().getNumberOfImages() > 0) {
numberOfImagesLabel.setText(Bundle.DocumentPanel_numberOfImages_text(value.getSummary().getNumberOfImages()));
sampleImageLabel.setIcon(new ImageIcon(value.getSummary().getSampleImage()));
} else if (FileSearchData.getDocTypesWithoutImageExtraction().contains(value.getResultFile().getFirstInstance().getMIMEType())) {
} else if (SearchData.getDocTypesWithoutImageExtraction().contains(value.getResultFile().getFirstInstance().getMIMEType())) {
numberOfImagesLabel.setText(Bundle.DocumentPanel_noImageExtraction_text());
sampleImageLabel.setIcon(DiscoveryUiUtils.getUnsupportedImageThumbnail());
} else {

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import java.util.ArrayList;
import java.util.List;

View File

@ -16,9 +16,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery;
package org.sleuthkit.autopsy.discovery.ui;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.discovery.search.ResultFile;
import org.sleuthkit.autopsy.textsummarizer.TextSummary;
/**

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[225, 70]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<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,0,68,0,0,0,-31"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="domainFiltersScrollPane">
<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.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="domainFiltersPanel">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
<Component id="domainFiltersSplitPane" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
<Component id="domainFiltersSplitPane" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="domainFiltersSplitPane">
<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="Step 2: Filter which domains to show">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DomainFilterPanel.domainFiltersSplitPane.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="resizeWeight" type="double" value="0.5"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DomainFilterPanel.domainFiltersSplitPane.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,104 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.ui;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes;
import org.sleuthkit.autopsy.discovery.search.ResultsSorter;
import org.sleuthkit.autopsy.discovery.search.SearchData;
/**
* Filter panel for searching domain attributes with Discovery.
*/
public class DomainFilterPanel extends AbstractFiltersPanel {
private static final long serialVersionUID = 1L;
private static final SearchData.Type TYPE = SearchData.Type.DOMAIN;
/**
* Creates new form DomainFilterPanel.
*/
public DomainFilterPanel() {
super();
initComponents();
addFilter(new DataSourceFilterPanel(), false, null, 0);
addFilter(new ArtifactTypeFilterPanel(), false, null, 1);
addFilter(new DateFilterPanel(), false, null, 1);
int[] pastOccurrencesIndices = null;
if (CentralRepository.isEnabled()) {
pastOccurrencesIndices = new int[]{2, 3, 4};
}
addFilter(new PastOccurrencesFilterPanel(TYPE), true, pastOccurrencesIndices, 0);
addPanelsToScrollPane(domainFiltersSplitPane);
setLastGroupingAttributeType(DiscoveryAttributes.GroupingAttributeType.MOST_RECENT_DATE);
setLastSortingMethod(ResultsSorter.SortingMethod.BY_DOMAIN_NAME);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
javax.swing.JScrollPane domainFiltersScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel domainFiltersPanel = new javax.swing.JPanel();
domainFiltersSplitPane = new javax.swing.JSplitPane();
setPreferredSize(new java.awt.Dimension(225, 70));
setLayout(new java.awt.BorderLayout());
domainFiltersSplitPane.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(DomainFilterPanel.class, "DomainFilterPanel.domainFiltersSplitPane.border.title"))); // NOI18N
domainFiltersSplitPane.setResizeWeight(0.5);
domainFiltersSplitPane.setToolTipText(org.openide.util.NbBundle.getMessage(DomainFilterPanel.class, "DomainFilterPanel.domainFiltersSplitPane.toolTipText")); // NOI18N
javax.swing.GroupLayout domainFiltersPanelLayout = new javax.swing.GroupLayout(domainFiltersPanel);
domainFiltersPanel.setLayout(domainFiltersPanelLayout);
domainFiltersPanelLayout.setHorizontalGroup(
domainFiltersPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(domainFiltersPanelLayout.createSequentialGroup()
.addGap(8, 8, 8)
.addComponent(domainFiltersSplitPane)
.addGap(8, 8, 8))
);
domainFiltersPanelLayout.setVerticalGroup(
domainFiltersPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(domainFiltersPanelLayout.createSequentialGroup()
.addGap(8, 8, 8)
.addComponent(domainFiltersSplitPane)
.addGap(8, 8, 8))
);
domainFiltersScrollPane.setViewportView(domainFiltersPanel);
add(domainFiltersScrollPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
@Override
SearchData.Type getType() {
return TYPE;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JSplitPane domainFiltersSplitPane;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<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"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="domainNameLabel" pref="539" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="47" max="-2" attributes="0"/>
</Group>
<Component id="activityLabel" alignment="0" max="32767" attributes="0"/>
<Component id="pagesLabel" max="32767" attributes="0"/>
<Component id="filesDownloadedLabel" alignment="0" max="32767" attributes="0"/>
<Component id="totalVisitsLabel" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="numberOfImagesLabel" max="32767" attributes="0"/>
<Component id="sampleImageLabel" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="numberOfImagesLabel" min="-2" pref="17" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="sampleImageLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="domainNameLabel" min="-2" pref="32" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="activityLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="11" max="-2" attributes="0"/>
<Component id="totalVisitsLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="11" max="-2" attributes="0"/>
<Component id="pagesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="filesDownloadedLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="domainNameLabel">
</Component>
<Component class="javax.swing.JLabel" name="sampleImageLabel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="iconTextGap" type="int" value="0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 100]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 100]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 100]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="numberOfImagesLabel">
</Component>
<Component class="javax.swing.JLabel" name="activityLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DomainSummaryPanel.activityLabel.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/discovery/ui/Bundle.properties" key="DomainSummaryPanel.pagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="filesDownloadedLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DomainSummaryPanel.filesDownloadedLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="totalVisitsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="DomainSummaryPanel.totalVisitsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,185 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import org.openide.util.NbBundle;
/**
* Class which displays a preview and details about a domain.
*/
class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer<DomainWrapper> {
private static final long serialVersionUID = 1L;
private static final Color SELECTION_COLOR = new Color(0, 120, 215);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd yyyy", Locale.getDefault());
/**
* Creates new form DomainPanel.
*/
DomainSummaryPanel() {
initComponents();
domainNameLabel.setFont(domainNameLabel.getFont().deriveFont(domainNameLabel.getFont().getStyle(), domainNameLabel.getFont().getSize() + 6));
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
domainNameLabel = new javax.swing.JLabel();
sampleImageLabel = new javax.swing.JLabel();
numberOfImagesLabel = new javax.swing.JLabel();
activityLabel = new javax.swing.JLabel();
pagesLabel = new javax.swing.JLabel();
filesDownloadedLabel = new javax.swing.JLabel();
totalVisitsLabel = new javax.swing.JLabel();
setBorder(javax.swing.BorderFactory.createEtchedBorder());
sampleImageLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
sampleImageLabel.setIconTextGap(0);
sampleImageLabel.setMaximumSize(new java.awt.Dimension(100, 100));
sampleImageLabel.setMinimumSize(new java.awt.Dimension(100, 100));
sampleImageLabel.setPreferredSize(new java.awt.Dimension(100, 100));
org.openide.awt.Mnemonics.setLocalizedText(activityLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.activityLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(pagesLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.pagesLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(filesDownloadedLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.filesDownloadedLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(totalVisitsLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.totalVisitsLabel.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(domainNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 539, Short.MAX_VALUE)
.addGap(47, 47, 47))
.addComponent(activityLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(pagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(filesDownloadedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(totalVisitsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(numberOfImagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(sampleImageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(numberOfImagesLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(sampleImageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(domainNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(activityLabel)
.addGap(11, 11, 11)
.addComponent(totalVisitsLabel)
.addGap(11, 11, 11)
.addComponent(pagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(filesDownloadedLabel)
.addGap(0, 0, Short.MAX_VALUE)))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel activityLabel;
private javax.swing.JLabel domainNameLabel;
private javax.swing.JLabel filesDownloadedLabel;
private javax.swing.JLabel numberOfImagesLabel;
private javax.swing.JLabel pagesLabel;
private javax.swing.JLabel sampleImageLabel;
private javax.swing.JLabel totalVisitsLabel;
// End of variables declaration//GEN-END:variables
@NbBundle.Messages({"# {0} - startDate",
"# {1} - endDate",
"DomainSummaryPanel.activity.text=Activity: {0} to {1}",
"DomainSummaryPanel.pages.text=Pages in past 60 days: ",
"DomainSummaryPanel.totalPages.text=Total visits: ",
"DomainSummaryPanel.downloads.text=Files downloaded: ",
"DomainSummaryPanel.loadingImages.text=Loading thumbnail..."})
@Override
public Component getListCellRendererComponent(JList<? extends DomainWrapper> list, DomainWrapper value, int index, boolean isSelected, boolean cellHasFocus) {
domainNameLabel.setText(value.getResultDomain().getDomain());
String startDate = dateFormat.format(new Date(value.getResultDomain().getActivityStart() * 1000));
String endDate = dateFormat.format(new Date(value.getResultDomain().getActivityEnd() * 1000));
activityLabel.setText(Bundle.DomainSummaryPanel_activity_text(startDate, endDate));
totalVisitsLabel.setText(Bundle.DomainSummaryPanel_totalPages_text() + value.getResultDomain().getTotalVisits());
pagesLabel.setText(Bundle.DomainSummaryPanel_pages_text() + value.getResultDomain().getVisitsInLast60());
filesDownloadedLabel.setText(Bundle.DomainSummaryPanel_downloads_text() + value.getResultDomain().getFilesDownloaded());
if (value.getThumbnail() == null) {
numberOfImagesLabel.setText(Bundle.DomainSummaryPanel_loadingImages_text());
sampleImageLabel.setIcon(null);
} else {
numberOfImagesLabel.setText(null);
sampleImageLabel.setIcon(new ImageIcon(value.getThumbnail()));
}
setBackground(isSelected ? SELECTION_COLOR : list.getBackground());
return this;
}
@Override
public String getToolTipText(MouseEvent event) {
if (event != null) {
//gets tooltip of internal panel item mouse is over
Point point = event.getPoint();
for (Component comp : getComponents()) {
if (DiscoveryUiUtils.isPointOnIcon(comp, point)) {
String toolTip = ((JComponent) comp).getToolTipText();
if (toolTip == null || toolTip.isEmpty()) {
return null;
} else {
return toolTip;
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="domainScrollPane">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JList" name="domainList">
<Properties>
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="domainListModel" type="code"/>
</Property>
<Property name="cellRenderer" type="javax.swing.ListCellRenderer" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new DomainSummaryPanel()" type="code"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;DomainWrapper&gt;"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,86 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.ui;
import javax.swing.DefaultListModel;
/**
* A JPanel to display domain summaries.
*/
public class DomainSummaryViewer extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private final DefaultListModel<DomainWrapper> domainListModel = new DefaultListModel<>();
/**
* Clear the list of documents being displayed.
*/
void clearViewer() {
synchronized (this) {
domainListModel.removeAllElements();
domainScrollPane.getVerticalScrollBar().setValue(0);
}
}
/**
* Creates new form DomainSummaryPanel
*/
public DomainSummaryViewer() {
initComponents();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
domainScrollPane = new javax.swing.JScrollPane();
javax.swing.JList<DomainWrapper> domainList = new javax.swing.JList<>();
setLayout(new java.awt.BorderLayout());
domainList.setModel(domainListModel);
domainList.setCellRenderer(new DomainSummaryPanel());
domainScrollPane.setViewportView(domainList);
add(domainScrollPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane domainScrollPane;
// End of variables declaration//GEN-END:variables
/**
* Add the summary for a domain to the panel.
*
* @param domainWrapper The object which contains the domain summary which
* will be displayed.
*/
void addDomain(DomainWrapper domainWrapper) {
synchronized (this) {
domainListModel.addElement(domainWrapper);
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.discovery.ui;
import java.awt.Image;
import org.sleuthkit.autopsy.discovery.search.ResultDomain;
/**
* Class to wrap all the information necessary for a domain summary to be
* displayed.
*/
public class DomainWrapper {
private final ResultDomain domain;
private Image thumbnail = null;
/**
* Construct a new DocumentWrapper.
*
* @param file The ResultFile which represents the document which the
* summary is created for.
*/
DomainWrapper(ResultDomain domain) {
this.domain = domain;
}
/**
* Set the thumbnail which exists.
*
* @param thumbnail The image object which will be used to represent this
* domain object.
*/
void setThumbnail(Image thumbnail) {
this.thumbnail = thumbnail;
}
/**
* Get the ResultDomain which represents the Domain the summary was created
* for.
*
* @return The ResultDomain which represents the domain attribute which the
* summary was created for.
*/
ResultDomain getResultDomain() {
return domain;
}
/**
* Get the image to be used for the domain.
*
* @return The image which represents the domain.
*/
Image getThumbnail() {
return thumbnail;
}
}

View File

@ -46,7 +46,7 @@
<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="Groups">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/Bundle.properties" key="GroupListPanel.groupKeyList.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/discovery/ui/Bundle.properties" key="GroupListPanel.groupKeyList.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>

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