mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #6528 from sleuthkit/develop
Merge develop into Java 11 upgrade branch
This commit is contained in:
commit
a82d88a3a5
@ -5,6 +5,7 @@ jobs:
|
||||
- os: linux
|
||||
dist: bionic
|
||||
- os: osx
|
||||
osx_image: xcode12.2
|
||||
|
||||
env:
|
||||
global:
|
||||
|
@ -54,6 +54,11 @@
|
||||
<fileset dir="${thirdparty.dir}/iLeapp"/>
|
||||
</copy>
|
||||
|
||||
<!--Copy aLeapp to release-->
|
||||
<copy todir="${basedir}/release/aLeapp" >
|
||||
<fileset dir="${thirdparty.dir}/aLeapp"/>
|
||||
</copy>
|
||||
|
||||
<!--Copy 7-Zip to release-->
|
||||
<copy todir="${basedir}/release/7-Zip" >
|
||||
<fileset dir="${thirdparty.dir}/7-Zip"/>
|
||||
|
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.sleuthkit.autopsy.contentviewers.contextviewer.ContextViewer.DateTimePanel;
|
||||
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
|
||||
@ -29,27 +30,36 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOC
|
||||
* usage, if known.
|
||||
*
|
||||
*/
|
||||
public final class ContextSourcePanel extends javax.swing.JPanel {
|
||||
public final class ContextSourcePanel extends javax.swing.JPanel implements DateTimePanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// defines a list of artifacts that provide context for a file
|
||||
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
|
||||
|
||||
static {
|
||||
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
|
||||
}
|
||||
|
||||
private final BlackboardArtifact sourceContextArtifact;
|
||||
|
||||
private final Long dateTime;
|
||||
|
||||
/**
|
||||
* Creates new form ContextViewer
|
||||
*/
|
||||
public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) {
|
||||
public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
|
||||
|
||||
initComponents();
|
||||
sourceContextArtifact = associatedArtifact;
|
||||
setSourceName(sourceName);
|
||||
setSourceText(sourceText);
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,27 +29,36 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOC
|
||||
* usage, if known.
|
||||
*
|
||||
*/
|
||||
public final class ContextUsagePanel extends javax.swing.JPanel {
|
||||
public final class ContextUsagePanel extends javax.swing.JPanel implements ContextViewer.DateTimePanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// defines a list of artifacts that provide context for a file
|
||||
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
|
||||
|
||||
static {
|
||||
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
|
||||
}
|
||||
|
||||
private final BlackboardArtifact sourceContextArtifact;
|
||||
|
||||
private final Long dateTime;
|
||||
|
||||
/**
|
||||
* Creates new form ContextViewer
|
||||
*/
|
||||
public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) {
|
||||
public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
|
||||
|
||||
initComponents();
|
||||
sourceContextArtifact = associatedArtifact;
|
||||
setUsageName(sourceName);
|
||||
setUsageText(sourceText);
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -56,8 +58,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
|
||||
// defines a list of artifacts that provide context for a file
|
||||
private static final List<BlackboardArtifact.ARTIFACT_TYPE> CONTEXT_ARTIFACTS = new ArrayList<>();
|
||||
private final List<javax.swing.JPanel> contextSourcePanels = new ArrayList<>();
|
||||
private final List<javax.swing.JPanel> contextUsagePanels = new ArrayList<>();
|
||||
private final List<ContextSourcePanel> contextSourcePanels = new ArrayList<>();
|
||||
private final List<ContextUsagePanel> contextUsagePanels = new ArrayList<>();
|
||||
|
||||
static {
|
||||
CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
|
||||
@ -338,32 +340,36 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
"ContextViewer.programExecution=Program Execution: "
|
||||
})
|
||||
private void addArtifactToPanels(BlackboardArtifact associatedArtifact) throws TskCoreException {
|
||||
Long dateTime = getArtifactDateTime(associatedArtifact);
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_attachmentSource();
|
||||
String sourceText = msgArtifactToAbbreviatedString(associatedArtifact);
|
||||
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextSourcePanels.add(sourcePanel);
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID()
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_downloadSource();
|
||||
String sourceText = webDownloadArtifactToString(associatedArtifact);
|
||||
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextSourcePanels.add(sourcePanel);
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_recentDocs();
|
||||
String sourceText = recentDocArtifactToString(associatedArtifact);
|
||||
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextUsagePanels.add(usagePanel);
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_programExecution();
|
||||
String sourceText = programExecArtifactToString(associatedArtifact);
|
||||
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
|
||||
ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
|
||||
contextUsagePanels.add(usagePanel);
|
||||
}
|
||||
|
||||
Collections.sort(contextSourcePanels, new SortByDateTime());
|
||||
Collections.sort(contextUsagePanels, new SortByDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -533,6 +539,59 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
return attributeMap;
|
||||
}
|
||||
|
||||
interface DateTimePanel {
|
||||
/**
|
||||
* Return the date time value for this panel.
|
||||
*
|
||||
* @return Date time value or null of one is not available.
|
||||
*/
|
||||
Long getDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dateTime value for the given message artifact.
|
||||
*
|
||||
* @param artifact
|
||||
*
|
||||
* @return Long dateTime value or null if the attribute was not found.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private Long getArtifactDateTime(BlackboardArtifact artifact) throws TskCoreException {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME));
|
||||
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == artifact.getArtifactTypeID()) {
|
||||
attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT));
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifact.getArtifactTypeID()
|
||||
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == artifact.getArtifactTypeID()) {
|
||||
attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED));
|
||||
}
|
||||
return (attribute != null ? attribute.getValueLong() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for sorting lists of DateTimePanels.
|
||||
*/
|
||||
class SortByDateTime implements Comparator<DateTimePanel> {
|
||||
|
||||
@Override
|
||||
public int compare(DateTimePanel panel1, DateTimePanel panel2) {
|
||||
Long dateTime1 = panel1.getDateTime();
|
||||
Long dateTime2 = panel2.getDateTime();
|
||||
|
||||
if(dateTime1 == null && dateTime2 == null) {
|
||||
return 0;
|
||||
} else if(dateTime1 == null) {
|
||||
return -1;
|
||||
} else if(dateTime2 == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return dateTime1.compareTo(dateTime2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JScrollPane jScrollPane;
|
||||
|
@ -89,6 +89,7 @@ public final class UserPreferences {
|
||||
private static final String GEO_OSM_TILE_ZIP_PATH = "GeolocationOsmZipPath";
|
||||
private static final String GEO_OSM_SERVER_ADDRESS = "GeolocationOsmServerAddress";
|
||||
private static final String GEO_MBTILES_FILE_PATH = "GeolcoationMBTilesFilePath";
|
||||
private static final String HEALTH_MONITOR_REPORT_PATH = "HealthMonitorReportPath";
|
||||
|
||||
// Prevent instantiation.
|
||||
private UserPreferences() {
|
||||
@ -668,4 +669,22 @@ public final class UserPreferences {
|
||||
return Paths.get(UserMachinePreferences.getBaseTempDirectory(), getAppName())
|
||||
.toAbsolutePath().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last used health monitor report path.
|
||||
*
|
||||
* @param reportPath Last used health monitor report path.
|
||||
*/
|
||||
public static void setHealthMonitorReportPath(String reportPath) {
|
||||
preferences.put(HEALTH_MONITOR_REPORT_PATH, reportPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last used health monitor report path.
|
||||
*
|
||||
* @return Last used health monitor report path. Empty string if no value has been recorded.
|
||||
*/
|
||||
public static String getHealthMonitorReportPath() {
|
||||
return preferences.get(HEALTH_MONITOR_REPORT_PATH, "");
|
||||
}
|
||||
}
|
197
Core/src/org/sleuthkit/autopsy/coreutils/DomainTokenizer.java
Normal file
197
Core/src/org/sleuthkit/autopsy/coreutils/DomainTokenizer.java
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.coreutils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Attempts to get the domain from a url/domain provided removing the
|
||||
* subdomain(s).
|
||||
*/
|
||||
class DomainTokenizer {
|
||||
|
||||
/**
|
||||
* This is a node in the trie. Children in the hashmap are identified by
|
||||
* token. So for example, for a domain google.co.uk, the top level category
|
||||
* would have an entry for "uk" linking to a domain category with "co".
|
||||
*/
|
||||
private static class DomainCategory extends HashMap<String, DomainCategory> {
|
||||
|
||||
private DomainCategory getOrAddChild(String childKey) {
|
||||
DomainCategory cat = this.get(childKey);
|
||||
if (cat == null) {
|
||||
cat = new DomainCategory();
|
||||
this.put(childKey, cat);
|
||||
}
|
||||
|
||||
return cat;
|
||||
}
|
||||
}
|
||||
|
||||
// Character for joining domain segments.
|
||||
private static final String JOINER = ".";
|
||||
// delimiter when used with regex
|
||||
private static final String DELIMITER = "\\" + JOINER;
|
||||
|
||||
private static final String WILDCARD = "*";
|
||||
private static final String EXCEPTION_PREFIX = "!";
|
||||
|
||||
// taken from https://publicsuffix.org/list/public_suffix_list.dat
|
||||
// file containing line seperated suffixes
|
||||
// rules for parsing can be found here: https://publicsuffix.org/list/
|
||||
private static final String DOMAIN_LIST = "public_suffix_list.dat";
|
||||
|
||||
// token for comments
|
||||
private static final String COMMENT_TOKEN = "//";
|
||||
|
||||
// singleton instance of this class.
|
||||
private static DomainTokenizer categorizer = null;
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of this class.
|
||||
*
|
||||
* @return The DomainCategorizer instance.
|
||||
* @throws IOException
|
||||
*/
|
||||
static DomainTokenizer getInstance() throws IOException {
|
||||
if (categorizer == null) {
|
||||
categorizer = load();
|
||||
}
|
||||
|
||||
return categorizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a DomainCategorizer instance using the public suffix list.
|
||||
*
|
||||
* @return The DomainCategorizer instance.
|
||||
* @throws IOException If there is an error reading the file.
|
||||
*/
|
||||
private static DomainTokenizer load() throws IOException {
|
||||
try (InputStream is = DomainTokenizer.class.getResourceAsStream(DOMAIN_LIST);
|
||||
InputStreamReader isReader = new InputStreamReader(is, StandardCharsets.UTF_8);
|
||||
BufferedReader reader = new BufferedReader(isReader)) {
|
||||
|
||||
DomainTokenizer categorizer = new DomainTokenizer();
|
||||
while (reader.ready()) {
|
||||
String line = reader.readLine();
|
||||
String trimmed = line.trim();
|
||||
if (!StringUtils.isBlank(trimmed) && !trimmed.startsWith(COMMENT_TOKEN)) {
|
||||
categorizer.addDomainSuffix(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
return categorizer;
|
||||
}
|
||||
}
|
||||
|
||||
private DomainTokenizer() {
|
||||
}
|
||||
|
||||
// The top-level trie node.
|
||||
private final DomainCategory trie = new DomainCategory();
|
||||
|
||||
/**
|
||||
* Parses a domain suffix and adds components to the trie in reverse order
|
||||
* (i.e. ".co.uk" becomes "uk" -> "co").
|
||||
*
|
||||
* @param domainSuffix The domain suffix.
|
||||
*/
|
||||
private void addDomainSuffix(String domainSuffix) {
|
||||
if (StringUtils.isBlank(domainSuffix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] tokens = domainSuffix.toLowerCase().trim().split(DELIMITER);
|
||||
|
||||
DomainCategory cat = trie;
|
||||
for (int i = tokens.length - 1; i >= 0; i--) {
|
||||
String token = tokens[i];
|
||||
if (StringUtils.isBlank(token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cat = cat.getOrAddChild(tokens[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the domain by attempting to identify the host without the subdomain.
|
||||
* If no domain can be determined, the domain is returned.
|
||||
*
|
||||
* @param domain The domain to query for.
|
||||
* @return If provided argument is blank, null is returned. If no domain
|
||||
* suffixes can be identified, the full host is returned. If a host and
|
||||
* suffixes are identified, the domain (all suffixes with a prefix of the
|
||||
* next token) are returned.
|
||||
*/
|
||||
String getDomain(String domain) {
|
||||
if (StringUtils.isBlank(domain)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<String> tokens = Stream.of(domain.toLowerCase().split(DELIMITER))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int idx = tokens.size() - 1;
|
||||
DomainCategory cat = trie;
|
||||
|
||||
for (; idx >= 0; idx--) {
|
||||
// an exception rule must be at the beginning of a suffix, and, in
|
||||
// practice, indicates a domain that would otherwise be a further
|
||||
// suffix with a wildcard rule per: https://publicsuffix.org/list/
|
||||
if (cat.get(EXCEPTION_PREFIX + tokens.get(idx)) != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
DomainCategory newCat = cat.get(tokens.get(idx));
|
||||
|
||||
// if no matching token can be found, look for wildcard token
|
||||
if (newCat == null) {
|
||||
// if no wildcard token can be found, the portion found
|
||||
// so far is the suffix.
|
||||
newCat = cat.get(WILDCARD);
|
||||
if (newCat == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cat = newCat;
|
||||
}
|
||||
|
||||
// if first suffix cannot be found, return the whole domain
|
||||
if (idx == tokens.size() - 1) {
|
||||
return domain;
|
||||
} else {
|
||||
int minIndex = Math.max(0, idx);
|
||||
List<String> subList = tokens.subList(minIndex, tokens.size());
|
||||
return String.join(JOINER, subList);
|
||||
}
|
||||
}
|
||||
}
|
@ -18,13 +18,17 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.coreutils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
public class NetworkUtils {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(NetworkUtils.class.getName());
|
||||
|
||||
private NetworkUtils() {
|
||||
}
|
||||
|
||||
@ -70,31 +74,20 @@ public class NetworkUtils {
|
||||
host = cleanUrl;
|
||||
}
|
||||
|
||||
//get the domain part from host (last 2)
|
||||
StringTokenizer tok = new StringTokenizer(host, ".");
|
||||
StringBuilder hostB = new StringBuilder();
|
||||
int toks = tok.countTokens();
|
||||
|
||||
for (int count = 0; count < toks; ++count) {
|
||||
String part = tok.nextToken();
|
||||
int diff = toks - count;
|
||||
if (diff < 3) {
|
||||
hostB.append(part);
|
||||
}
|
||||
if (diff == 2) {
|
||||
hostB.append(".");
|
||||
}
|
||||
String base = host;
|
||||
try {
|
||||
base = DomainTokenizer.getInstance().getDomain(host);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Unable to load resources for domain categorization.", ex);
|
||||
}
|
||||
|
||||
|
||||
String base = hostB.toString();
|
||||
// verify there are no special characters in there
|
||||
if (base.matches(".*[~`!@#$%^&\\*\\(\\)\\+={}\\[\\];:\\?<>,/ ].*")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
//verify that the base domain actually has a '.', details JIRA-4609
|
||||
if(!base.contains(".")) {
|
||||
if (!base.contains(".")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -102,9 +95,8 @@ public class NetworkUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to extract the domain from a URL.
|
||||
* Will start by using the built-in URL class, and if that fails will
|
||||
* try to extract it manually.
|
||||
* Attempt to extract the domain from a URL. Will start by using the
|
||||
* built-in URL class, and if that fails will try to extract it manually.
|
||||
*
|
||||
* @param urlString The URL to extract the domain from
|
||||
* @return empty string if no domain name was found
|
||||
@ -113,19 +105,21 @@ public class NetworkUtils {
|
||||
if (urlString == null) {
|
||||
return "";
|
||||
}
|
||||
String result = "";
|
||||
String urlHost = null;
|
||||
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
result = url.getHost();
|
||||
urlHost = url.getHost();
|
||||
} catch (MalformedURLException ex) {
|
||||
//do not log if not a valid URL - we will try to extract it ourselves
|
||||
}
|
||||
|
||||
//was not a valid URL, try a less picky method
|
||||
if (result == null || result.trim().isEmpty()) {
|
||||
return getBaseDomain(urlString);
|
||||
}
|
||||
// if there is a valid url host, get base domain from that host
|
||||
// otherwise use urlString and parse the domain
|
||||
String result = (StringUtils.isNotBlank(urlHost))
|
||||
? getBaseDomain(urlHost)
|
||||
: getBaseDomain(urlString);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
13494
Core/src/org/sleuthkit/autopsy/coreutils/public_suffix_list.dat
Normal file
13494
Core/src/org/sleuthkit/autopsy/coreutils/public_suffix_list.dat
Normal file
File diff suppressed because it is too large
Load Diff
@ -121,6 +121,10 @@ public final class IconsUtil {
|
||||
imageFile = "web-account-type.png"; //NON-NLS
|
||||
} else if (typeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) {
|
||||
imageFile = "web-form-address.png"; //NON-NLS
|
||||
} else if (typeID == ARTIFACT_TYPE.TSK_WEB_CATEGORIZATION.getTypeID()) {
|
||||
imageFile = "domain-16.png"; //NON-NLS
|
||||
} else if (typeID == ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID()) {
|
||||
imageFile = "gps-area.png"; //NON-NLS
|
||||
} else {
|
||||
imageFile = "artifact-icon.png"; //NON-NLS
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return new RecentFileDetails(path, lastOpened);
|
||||
return new RecentFileDetails(artifact, path, lastOpened);
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
if (StringUtils.isBlank(path) || accessedTime == null || accessedTime == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return new RecentDownloadDetails(path, accessedTime, domain);
|
||||
return new RecentDownloadDetails(artifact, path, accessedTime, domain);
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,7 +298,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
if (date == null || date == 0 || StringUtils.isBlank(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new RecentAttachmentDetails(path, date, sender);
|
||||
return new RecentAttachmentDetails(messageArtifact, path, date, sender);
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,14 +324,17 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
|
||||
private final String path;
|
||||
private final long date;
|
||||
private final BlackboardArtifact artifact;
|
||||
|
||||
/**
|
||||
* Constructor for files with just a path and date.
|
||||
*
|
||||
* @param artifact The relevant artifact.
|
||||
* @param path File path.
|
||||
* @param date File access date\time in seconds with java epoch
|
||||
*/
|
||||
RecentFileDetails(String path, long date) {
|
||||
RecentFileDetails(BlackboardArtifact artifact, String path, long date) {
|
||||
this.artifact = artifact;
|
||||
this.path = path;
|
||||
this.date = date;
|
||||
}
|
||||
@ -364,6 +367,12 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The pertinent artifact for this recent file hit.
|
||||
*/
|
||||
public BlackboardArtifact getArtifact() {
|
||||
return artifact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -376,12 +385,13 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
/**
|
||||
* Constructor for files with just a path and date.
|
||||
*
|
||||
* @param artifact The relevant artifact.
|
||||
* @param path File path.
|
||||
* @param date File access date\time in seconds with java epoch.
|
||||
* @param webDomain The webdomain from which the file was downloaded.
|
||||
*/
|
||||
RecentDownloadDetails(String path, long date, String webDomain) {
|
||||
super(path, date);
|
||||
RecentDownloadDetails(BlackboardArtifact artifact, String path, long date, String webDomain) {
|
||||
super(artifact, path, date);
|
||||
this.webDomain = webDomain;
|
||||
}
|
||||
|
||||
@ -407,13 +417,14 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
|
||||
* Constructor for recent download files which have a path, date and
|
||||
* domain value.
|
||||
*
|
||||
* @param artifact The relevant artifact.
|
||||
* @param path File path.
|
||||
* @param date File crtime.
|
||||
* @param sender The sender of the message from which the file was
|
||||
* attached.
|
||||
*/
|
||||
RecentAttachmentDetails(String path, long date, String sender) {
|
||||
super(path, date);
|
||||
RecentAttachmentDetails(BlackboardArtifact artifact, String path, long date, String sender) {
|
||||
super(artifact, path, date);
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineModule;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.FilterState;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TimelineFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.DataSourceFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.RootFilter;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Utilities for interacting with Timeline in relation to data sources.
|
||||
*/
|
||||
public class TimelineDataSourceUtils {
|
||||
|
||||
private static TimelineDataSourceUtils instance = null;
|
||||
|
||||
/**
|
||||
* @return Singleton instance of this class.
|
||||
*/
|
||||
public static TimelineDataSourceUtils getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new TimelineDataSourceUtils();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main constructor. Should be instantiated through getInstance().
|
||||
*/
|
||||
private TimelineDataSourceUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a RootFilter based on the default filter state but only the
|
||||
* specified dataSource is selected.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @return The root filter representing a default filter with only this data
|
||||
* source selected.
|
||||
* @throws NoCurrentCaseException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public RootFilter getDataSourceFilter(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
|
||||
RootFilterState filterState = getDataSourceFilterState(dataSource);
|
||||
return filterState == null ? null : filterState.getActiveFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a TimeLineController based on the default filter state but only
|
||||
* the specified dataSource is selected.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @return The root filter state representing a default filter with only
|
||||
* this data source selected.
|
||||
* @throws NoCurrentCaseException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public RootFilterState getDataSourceFilterState(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
|
||||
TimeLineController controller = TimeLineModule.getController();
|
||||
RootFilterState dataSourceState = controller.getEventsModel().getDefaultEventFilterState().copyOf();
|
||||
|
||||
for (FilterState<? extends TimelineFilter.DataSourceFilter> filterState : dataSourceState.getDataSourcesFilterState().getSubFilterStates()) {
|
||||
DataSourceFilter dsFilter = filterState.getFilter();
|
||||
if (dsFilter != null) {
|
||||
filterState.setSelected(dsFilter.getDataSourceID() == dataSource.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return dataSourceState;
|
||||
}
|
||||
}
|
@ -38,12 +38,11 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TimelineEvent;
|
||||
import org.sleuthkit.datamodel.TimelineEventType;
|
||||
import org.sleuthkit.datamodel.TimelineFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.DataSourcesFilter;
|
||||
import org.sleuthkit.datamodel.TimelineFilter.RootFilter;
|
||||
import org.sleuthkit.datamodel.TimelineManager;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import java.util.function.Supplier;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
|
||||
/**
|
||||
@ -51,6 +50,23 @@ import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
*/
|
||||
public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
|
||||
/**
|
||||
* A function for obtaining a Timeline RootFilter filtered to the specific
|
||||
* data source.
|
||||
*/
|
||||
public interface DataSourceFilterFunction {
|
||||
|
||||
/**
|
||||
* Obtains a Timeline RootFilter filtered to the specific data source.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @return The timeline root filter.
|
||||
* @throws NoCurrentCaseException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
RootFilter apply(DataSource dataSource) throws NoCurrentCaseException, TskCoreException;
|
||||
}
|
||||
|
||||
private static final long DAY_SECS = 24 * 60 * 60;
|
||||
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS = new HashSet<>(
|
||||
Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED));
|
||||
@ -64,23 +80,29 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
|
||||
private final SleuthkitCaseProvider caseProvider;
|
||||
private final Supplier<TimeZone> timeZoneProvider;
|
||||
private final DataSourceFilterFunction filterFunction;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public TimelineSummary() {
|
||||
this(SleuthkitCaseProvider.DEFAULT, () -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays()));
|
||||
this(SleuthkitCaseProvider.DEFAULT,
|
||||
() -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays()),
|
||||
(ds) -> TimelineDataSourceUtils.getInstance().getDataSourceFilter(ds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct object with given SleuthkitCaseProvider
|
||||
*
|
||||
* @param caseProvider SleuthkitCaseProvider provider, cannot be null.
|
||||
* @param timeZoneProvider The timezone provider, cannot be null.
|
||||
* @param caseProvider SleuthkitCaseProvider provider; cannot be null.
|
||||
* @param timeZoneProvider The timezone provider; cannot be null.
|
||||
* @param filterFunction Provides the default root filter function filtered
|
||||
* to the data source; cannot be null.
|
||||
*/
|
||||
public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier<TimeZone> timeZoneProvider) {
|
||||
public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier<TimeZone> timeZoneProvider, DataSourceFilterFunction filterFunction) {
|
||||
this.caseProvider = caseProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.filterFunction = filterFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -113,8 +135,9 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
* @return The retrieved data.
|
||||
* @throws SleuthkitCaseProviderException
|
||||
* @throws TskCoreException
|
||||
* @throws NoCurrentCaseException
|
||||
*/
|
||||
public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException {
|
||||
public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException, NoCurrentCaseException {
|
||||
TimeZone timeZone = this.timeZoneProvider.get();
|
||||
TimelineManager timelineManager = this.caseProvider.get().getTimelineManager();
|
||||
|
||||
@ -144,7 +167,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
// get most recent days activity
|
||||
List<DailyActivityAmount> mostRecentActivityAmt = getMostRecentActivityAmounts(dateCounts, minRecentDay, maxDay);
|
||||
|
||||
return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt);
|
||||
return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt, dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,25 +204,15 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
* belongs.
|
||||
* @return A Map mapping days from epoch to the activity for that day.
|
||||
* @throws TskCoreException
|
||||
* @throws NoCurrentCaseException
|
||||
*/
|
||||
private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone) throws TskCoreException {
|
||||
|
||||
DataSourcesFilter dataSourceFilter = new DataSourcesFilter();
|
||||
dataSourceFilter.addSubFilter(new TimelineFilter.DataSourceFilter(dataSource.getName(), dataSource.getId()));
|
||||
|
||||
RootFilter dataSourceRootFilter = new RootFilter(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
dataSourceFilter,
|
||||
null,
|
||||
Collections.emptySet());
|
||||
private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone)
|
||||
throws TskCoreException, NoCurrentCaseException {
|
||||
RootFilter rootFilter = this.filterFunction.apply(dataSource);
|
||||
|
||||
// get events for data source
|
||||
long curRunTime = System.currentTimeMillis();
|
||||
List<TimelineEvent> events = timelineManager.getEvents(new Interval(1, curRunTime), dataSourceRootFilter);
|
||||
List<TimelineEvent> events = timelineManager.getEvents(new Interval(1, curRunTime), rootFilter);
|
||||
|
||||
// get counts of events per day (left is file system events, right is everything else)
|
||||
Map<Long, DailyActivityAmount> dateCounts = new HashMap<>();
|
||||
@ -233,6 +246,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
private final Date minDate;
|
||||
private final Date maxDate;
|
||||
private final List<DailyActivityAmount> histogramActivity;
|
||||
private final DataSource dataSource;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
@ -240,12 +254,15 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
* @param minDate Earliest usage date recorded for the data source.
|
||||
* @param maxDate Latest usage date recorded for the data source.
|
||||
* @param recentDaysActivity A list of activity prior to and including
|
||||
* the latest usage date by day.
|
||||
* max date sorted by min to max date.
|
||||
* @param dataSource The data source for which this data applies. the
|
||||
* latest usage date by day.
|
||||
*/
|
||||
TimelineSummaryData(Date minDate, Date maxDate, List<DailyActivityAmount> recentDaysActivity) {
|
||||
TimelineSummaryData(Date minDate, Date maxDate, List<DailyActivityAmount> recentDaysActivity, DataSource dataSource) {
|
||||
this.minDate = minDate;
|
||||
this.maxDate = maxDate;
|
||||
this.histogramActivity = (recentDaysActivity == null) ? Collections.emptyList() : Collections.unmodifiableList(recentDaysActivity);
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,11 +281,18 @@ public class TimelineSummary implements DefaultUpdateGovernor {
|
||||
|
||||
/**
|
||||
* @return A list of activity prior to and including the latest usage
|
||||
* date by day.
|
||||
* date by day sorted min to max date.
|
||||
*/
|
||||
public List<DailyActivityAmount> getMostRecentDaysActivity() {
|
||||
return histogramActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The data source that this data applies to.
|
||||
*/
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,8 +108,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT";
|
||||
private static final String WINDOWS_PREFIX = "/WINDOWS";
|
||||
|
||||
private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess());
|
||||
private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed());
|
||||
private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccessed().compareTo(b.getLastAccessed());
|
||||
private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccessed().compareTo(b.getLastAccessed());
|
||||
|
||||
/**
|
||||
* Sorts TopProgramsResults pushing highest run time count then most recent
|
||||
@ -126,8 +126,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
// second priority for sorting is the last run date
|
||||
// if non-0, this is the return value for the comparator
|
||||
int lastRunCompare = nullableCompare(
|
||||
a.getLastRun() == null ? null : a.getLastRun().getTime(),
|
||||
b.getLastRun() == null ? null : b.getLastRun().getTime());
|
||||
a.getLastAccessed() == null ? null : a.getLastAccessed().getTime(),
|
||||
b.getLastAccessed() == null ? null : b.getLastAccessed().getTime());
|
||||
|
||||
if (lastRunCompare != 0) {
|
||||
return -lastRunCompare;
|
||||
@ -219,14 +219,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Pair<Long, Map<String, List<Long>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
|
||||
Pair<Long, Map<String, List<Pair<BlackboardArtifact, Long>>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
|
||||
// if no recent domains, return accordingly
|
||||
if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final long mostRecentMs = mostRecentAndGroups.getLeft();
|
||||
Map<String, List<Long>> groups = mostRecentAndGroups.getRight();
|
||||
Map<String, List<Pair<BlackboardArtifact, Long>>> groups = mostRecentAndGroups.getRight();
|
||||
|
||||
return groups.entrySet().stream()
|
||||
.map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs))
|
||||
@ -243,24 +243,32 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* within DOMAIN_WINDOW_MS of mostRecentMs.
|
||||
*
|
||||
* @param domain The domain.
|
||||
* @param visits The number of visits.
|
||||
* @param visits The list of the artifact and its associated time in
|
||||
* milliseconds.
|
||||
* @param mostRecentMs The most recent visit of any domain.
|
||||
*
|
||||
* @return The TopDomainsResult or null if no visits to this domain within
|
||||
* 30 days of mostRecentMs.
|
||||
*/
|
||||
private TopDomainsResult getDomainsResult(String domain, List<Long> visits, long mostRecentMs) {
|
||||
private TopDomainsResult getDomainsResult(String domain, List<Pair<BlackboardArtifact, Long>> visits, long mostRecentMs) {
|
||||
long visitCount = 0;
|
||||
Long thisMostRecentMs = null;
|
||||
BlackboardArtifact thisMostRecentArtifact = null;
|
||||
|
||||
for (Long visitMs : visits) {
|
||||
for (Pair<BlackboardArtifact, Long> visitInstance : visits) {
|
||||
BlackboardArtifact artifact = visitInstance.getLeft();
|
||||
Long visitMs = visitInstance.getRight();
|
||||
// make sure that visit is within window of mostRecentMS; otherwise skip it.
|
||||
if (visitMs + DOMAIN_WINDOW_MS < mostRecentMs) {
|
||||
if (visitMs == null || visitMs + DOMAIN_WINDOW_MS < mostRecentMs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if visit is within window, increment the count and get most recent
|
||||
visitCount++;
|
||||
if (thisMostRecentMs == null || visitMs > thisMostRecentMs) {
|
||||
thisMostRecentMs = visitMs;
|
||||
thisMostRecentArtifact = artifact;
|
||||
}
|
||||
thisMostRecentMs = getMax(thisMostRecentMs, visitMs);
|
||||
}
|
||||
|
||||
@ -269,7 +277,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return null;
|
||||
} else {
|
||||
// create a top domain result with the domain, count, and most recent visit date
|
||||
return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs));
|
||||
return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs), thisMostRecentArtifact);
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,17 +289,18 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
*
|
||||
* @return A tuple where the first value is the latest web history accessed
|
||||
* date in milliseconds and the second value maps normalized (lowercase;
|
||||
* trimmed) domain names to when those domains were visited.
|
||||
* trimmed) domain names to when those domains were visited and the relevant
|
||||
* artifact.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws SleuthkitCaseProviderException
|
||||
*/
|
||||
private Pair<Long, Map<String, List<Long>>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException {
|
||||
private Pair<Long, Map<String, List<Pair<BlackboardArtifact, Long>>>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException {
|
||||
List<BlackboardArtifact> artifacts = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_WEB_HISTORY,
|
||||
dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0);
|
||||
|
||||
Long mostRecentMs = null;
|
||||
Map<String, List<Long>> domainVisits = new HashMap<>();
|
||||
Map<String, List<Pair<BlackboardArtifact, Long>>> domainVisits = new HashMap<>();
|
||||
|
||||
for (BlackboardArtifact art : artifacts) {
|
||||
Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED);
|
||||
@ -312,13 +321,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
domain = domain.toLowerCase().trim();
|
||||
|
||||
// add this visit date to the list of dates for the domain
|
||||
List<Long> domainVisitList = domainVisits.get(domain);
|
||||
List<Pair<BlackboardArtifact, Long>> domainVisitList = domainVisits.get(domain);
|
||||
if (domainVisitList == null) {
|
||||
domainVisitList = new ArrayList<>();
|
||||
domainVisits.put(domain, domainVisitList);
|
||||
}
|
||||
|
||||
domainVisitList.add(artifactDateMs);
|
||||
domainVisitList.add(Pair.of(art, artifactDateMs));
|
||||
}
|
||||
|
||||
return Pair.of(mostRecentMs, domainVisits);
|
||||
@ -354,7 +363,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT);
|
||||
Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED);
|
||||
return (StringUtils.isNotBlank(searchString) && dateAccessed != null)
|
||||
? new TopWebSearchResult(searchString, dateAccessed)
|
||||
? new TopWebSearchResult(searchString, dateAccessed, artifact)
|
||||
: null;
|
||||
}
|
||||
|
||||
@ -456,15 +465,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @return The most recent one with a non-null date.
|
||||
*/
|
||||
private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) {
|
||||
if (r2.getDateAccessed() == null) {
|
||||
if (r2.getLastAccessed()== null) {
|
||||
return r1;
|
||||
}
|
||||
|
||||
if (r1.getDateAccessed() == null) {
|
||||
if (r1.getLastAccessed() == null) {
|
||||
return r2;
|
||||
}
|
||||
|
||||
return r1.getDateAccessed().compareTo(r2.getDateAccessed()) >= 0 ? r1 : r2;
|
||||
return r1.getLastAccessed().compareTo(r2.getLastAccessed()) >= 0 ? r1 : r2;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -495,7 +504,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_ID),
|
||||
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE),
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL)
|
||||
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL),
|
||||
artifact
|
||||
);
|
||||
})
|
||||
// remove Root Hub identifier
|
||||
@ -524,7 +534,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE);
|
||||
Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME);
|
||||
return (StringUtils.isNotBlank(type) && date != null)
|
||||
? new TopAccountResult(type, date)
|
||||
? new TopAccountResult(type, date, artifact)
|
||||
: null;
|
||||
}
|
||||
|
||||
@ -552,7 +562,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
}
|
||||
|
||||
return (StringUtils.isNotBlank(type) && latestDate != null)
|
||||
? new TopAccountResult(type, latestDate)
|
||||
? new TopAccountResult(type, latestDate, artifact)
|
||||
: null;
|
||||
}
|
||||
|
||||
@ -690,7 +700,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
programName,
|
||||
path,
|
||||
longCount,
|
||||
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME)
|
||||
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
|
||||
artifact
|
||||
);
|
||||
}
|
||||
|
||||
@ -786,11 +797,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
res.getProgramPath() == null ? null : res.getProgramPath().toUpperCase()),
|
||||
res -> res,
|
||||
(res1, res2) -> {
|
||||
Long maxRunTimes = getMax(res1.getRunTimes(), res2.getRunTimes());
|
||||
Date maxDate = getMax(res1.getLastAccessed(), res2.getLastAccessed());
|
||||
TopProgramsResult maxResult = TOP_PROGRAMS_RESULT_COMPARE.compare(res1, res2) >= 0 ? res1 : res2;
|
||||
return new TopProgramsResult(
|
||||
res1.getProgramName(),
|
||||
res1.getProgramPath(),
|
||||
getMax(res1.getRunTimes(), res2.getRunTimes()),
|
||||
getMax(res1.getLastRun(), res2.getLastRun()));
|
||||
maxResult.getProgramName(),
|
||||
maxResult.getProgramPath(),
|
||||
maxRunTimes,
|
||||
maxDate,
|
||||
maxResult.getArtifact());
|
||||
})).values();
|
||||
|
||||
List<TopProgramsResult> orderedResults = results.stream()
|
||||
@ -803,7 +818,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
// if run times / last run information is available, the first item should have some value,
|
||||
// and then the items should be limited accordingly.
|
||||
if (isPositiveNum(topResult.getRunTimes())
|
||||
|| (topResult.getLastRun() != null && isPositiveNum(topResult.getLastRun().getTime()))) {
|
||||
|| (topResult.getLastAccessed() != null && isPositiveNum(topResult.getLastAccessed().getTime()))) {
|
||||
return orderedResults.stream().limit(count).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -812,13 +827,47 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return orderedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class including date of last access and the relevant blackboard
|
||||
* artifact.
|
||||
*/
|
||||
public static class LastAccessedArtifact {
|
||||
|
||||
private final Date lastAccessed;
|
||||
private final BlackboardArtifact artifact;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param lastAccessed The date of last access.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public LastAccessedArtifact(Date lastAccessed, BlackboardArtifact artifact) {
|
||||
this.lastAccessed = lastAccessed;
|
||||
this.artifact = artifact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date of last access.
|
||||
*/
|
||||
public Date getLastAccessed() {
|
||||
return lastAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The associated artifact.
|
||||
*/
|
||||
public BlackboardArtifact getArtifact() {
|
||||
return artifact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object containing information about a web search artifact.
|
||||
*/
|
||||
public static class TopWebSearchResult {
|
||||
public static class TopWebSearchResult extends LastAccessedArtifact {
|
||||
|
||||
private final String searchString;
|
||||
private final Date dateAccessed;
|
||||
private String translatedResult;
|
||||
|
||||
/**
|
||||
@ -826,10 +875,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
*
|
||||
* @param searchString The search string.
|
||||
* @param dateAccessed The latest date searched.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public TopWebSearchResult(String searchString, Date dateAccessed) {
|
||||
public TopWebSearchResult(String searchString, Date dateAccessed, BlackboardArtifact artifact) {
|
||||
super(dateAccessed, artifact);
|
||||
this.searchString = searchString;
|
||||
this.dateAccessed = dateAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -854,22 +904,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public String getSearchString() {
|
||||
return searchString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date for the search.
|
||||
*/
|
||||
public Date getDateAccessed() {
|
||||
return dateAccessed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A record of a device attached.
|
||||
*/
|
||||
public static class TopDeviceAttachedResult {
|
||||
public static class TopDeviceAttachedResult extends LastAccessedArtifact {
|
||||
|
||||
private final String deviceId;
|
||||
private final Date dateAccessed;
|
||||
private final String deviceMake;
|
||||
private final String deviceModel;
|
||||
|
||||
@ -880,10 +922,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @param dateAccessed The date last attached.
|
||||
* @param deviceMake The device make.
|
||||
* @param deviceModel The device model.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel) {
|
||||
public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel, BlackboardArtifact artifact) {
|
||||
super(dateAccessed, artifact);
|
||||
this.deviceId = deviceId;
|
||||
this.dateAccessed = dateAccessed;
|
||||
this.deviceMake = deviceMake;
|
||||
this.deviceModel = deviceModel;
|
||||
}
|
||||
@ -895,13 +938,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date last attached.
|
||||
*/
|
||||
public Date getDateAccessed() {
|
||||
return dateAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The device make.
|
||||
*/
|
||||
@ -921,20 +957,20 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* A record of an account and the last time it was used determined by
|
||||
* messages.
|
||||
*/
|
||||
public static class TopAccountResult {
|
||||
public static class TopAccountResult extends LastAccessedArtifact {
|
||||
|
||||
private final String accountType;
|
||||
private final Date lastAccess;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param accountType The account type.
|
||||
* @param lastAccess The date the account was last accessed.
|
||||
* @param artifact The artifact indicating last access.
|
||||
*/
|
||||
public TopAccountResult(String accountType, Date lastAccess) {
|
||||
public TopAccountResult(String accountType, Date lastAccess, BlackboardArtifact artifact) {
|
||||
super(lastAccess, artifact);
|
||||
this.accountType = accountType;
|
||||
this.lastAccess = lastAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -943,23 +979,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public String getAccountType() {
|
||||
return accountType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date the account was last accessed.
|
||||
*/
|
||||
public Date getLastAccess() {
|
||||
return lastAccess;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a result of a program run on a datasource.
|
||||
*/
|
||||
public static class TopDomainsResult {
|
||||
public static class TopDomainsResult extends LastAccessedArtifact {
|
||||
|
||||
private final String domain;
|
||||
private final Long visitTimes;
|
||||
private final Date lastVisit;
|
||||
|
||||
/**
|
||||
* Describes a top domain result.
|
||||
@ -967,11 +995,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @param domain The domain.
|
||||
* @param visitTimes The number of times it was visited.
|
||||
* @param lastVisit The date of the last visit.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
public TopDomainsResult(String domain, Long visitTimes, Date lastVisit) {
|
||||
public TopDomainsResult(String domain, Long visitTimes, Date lastVisit, BlackboardArtifact artifact) {
|
||||
super(lastVisit, artifact);
|
||||
this.domain = domain;
|
||||
this.visitTimes = visitTimes;
|
||||
this.lastVisit = lastVisit;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -987,24 +1016,16 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public Long getVisitTimes() {
|
||||
return visitTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date of the last visit.
|
||||
*/
|
||||
public Date getLastVisit() {
|
||||
return lastVisit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a result of a program run on a datasource.
|
||||
*/
|
||||
public static class TopProgramsResult {
|
||||
public static class TopProgramsResult extends LastAccessedArtifact {
|
||||
|
||||
private final String programName;
|
||||
private final String programPath;
|
||||
private final Long runTimes;
|
||||
private final Date lastRun;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
@ -1012,12 +1033,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
* @param programName The name of the program.
|
||||
* @param programPath The path of the program.
|
||||
* @param runTimes The number of runs.
|
||||
* @param artifact The relevant blackboard artifact.
|
||||
*/
|
||||
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
|
||||
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun, BlackboardArtifact artifact) {
|
||||
super(lastRun, artifact);
|
||||
this.programName = programName;
|
||||
this.programPath = programPath;
|
||||
this.runTimes = runTimes;
|
||||
this.lastRun = lastRun;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1040,12 +1062,5 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
|
||||
public Long getRunTimes() {
|
||||
return runTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The last time the program was run or null if not present.
|
||||
*/
|
||||
public Date getLastRun() {
|
||||
return lastRun;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,20 +19,29 @@
|
||||
package org.sleuthkit.autopsy.datasourcesummary.ui;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
|
||||
@ -41,6 +50,8 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.SwingWorkerSequentialExecutor;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor;
|
||||
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
|
||||
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
@ -53,6 +64,8 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
/**
|
||||
* Base class from which other tabs in data source summary derive.
|
||||
*/
|
||||
@Messages({"BaseDataSourceSummaryPanel_goToArtifact=View Source Result",
|
||||
"BaseDataSourceSummaryPanel_goToFile=View Source File in Directory"})
|
||||
abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@ -64,6 +77,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
private final EventUpdateHandler updateHandler;
|
||||
private final List<UpdateGovernor> governors;
|
||||
|
||||
private Runnable notifyParentClose = null;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
@ -227,6 +242,104 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
this.updateHandler.register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the relevant artifact, navigates to the artifact in the tree and
|
||||
* closes data source summary dialog if open.
|
||||
*
|
||||
* @param artifact The artifact.
|
||||
* @return The menu item for a go to artifact menu item.
|
||||
*/
|
||||
protected CellModelTableCellRenderer.MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
|
||||
if (artifact == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CellModelTableCellRenderer.DefaultMenuItem(
|
||||
Bundle.BaseDataSourceSummaryPanel_goToArtifact(),
|
||||
() -> {
|
||||
final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
|
||||
|
||||
// Navigate to the source context artifact.
|
||||
if (dtc != null && artifact != null) {
|
||||
dtc.viewArtifact(artifact);
|
||||
}
|
||||
|
||||
notifyParentClose();
|
||||
});
|
||||
}
|
||||
|
||||
private static final Pattern windowsDrivePattern = Pattern.compile("^[A-Za-z]\\:(.*)$");
|
||||
|
||||
/**
|
||||
* Normalizes the path for lookup in the sleuthkit database (unix endings;
|
||||
* remove C:\).
|
||||
*
|
||||
* @param path The path to normalize.
|
||||
* @return The normalized path.
|
||||
*/
|
||||
private String normalizePath(String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmed = path.trim();
|
||||
Matcher match = windowsDrivePattern.matcher(trimmed);
|
||||
if (match.find()) {
|
||||
return FilenameUtils.normalize(match.group(1), true);
|
||||
} else {
|
||||
return FilenameUtils.normalize(trimmed, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a menu item to navigate to a file.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @return The menu item or null if file cannot be found in data source.
|
||||
*/
|
||||
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(String path) {
|
||||
if (StringUtils.isNotBlank(path)) {
|
||||
Path p = Paths.get(path);
|
||||
String fileName = normalizePath(p.getFileName().toString());
|
||||
String directory = normalizePath(p.getParent().toString());
|
||||
|
||||
if (fileName != null && directory != null) {
|
||||
try {
|
||||
List<AbstractFile> files = Case.getCurrentCaseThrows().getSleuthkitCase().findFiles(getDataSource(), fileName, directory);
|
||||
if (CollectionUtils.isNotEmpty(files)) {
|
||||
return getFileNavigateItem(files.get(0));
|
||||
}
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "There was an error fetching file for path: " + path, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the relevant artifact, navigates to the artifact's content in the
|
||||
* tree and closes data source summary dialog if open.
|
||||
*
|
||||
* @param artifact The artifact.
|
||||
* @return The menu item list for a go to artifact menu item.
|
||||
*/
|
||||
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(AbstractFile file) {
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CellModelTableCellRenderer.DefaultMenuItem(
|
||||
Bundle.BaseDataSourceSummaryPanel_goToFile(),
|
||||
() -> {
|
||||
new ViewContextAction(Bundle.BaseDataSourceSummaryPanel_goToFile(), file)
|
||||
.actionPerformed(null);
|
||||
|
||||
notifyParentClose();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes listeners and resources.
|
||||
*/
|
||||
@ -246,6 +359,24 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
onNewDataSource(this.dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends event that parent should close.
|
||||
*/
|
||||
protected void notifyParentClose() {
|
||||
if (notifyParentClose != null) {
|
||||
notifyParentClose.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener for parent close events.
|
||||
*
|
||||
* @param action The action to run when parent is to close.
|
||||
*/
|
||||
void setParentCloseListener(Runnable action) {
|
||||
notifyParentClose = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current data source.
|
||||
*/
|
||||
@ -375,8 +506,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* @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').
|
||||
* @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();
|
||||
@ -395,8 +525,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
|
||||
* 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').
|
||||
* @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) {
|
||||
|
@ -43,3 +43,12 @@ PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as
|
||||
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
|
||||
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
|
||||
TimelinePanel.activityRangeLabel.text=Activity Range
|
||||
RecentFilesPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
|
||||
TimelinePanel.viewInTimelineBtn.text=View in Timeline
|
||||
|
@ -3,6 +3,8 @@ AnalysisPanel_keyColumn_title=Name
|
||||
AnalysisPanel_keywordSearchModuleName=Keyword Search
|
||||
# {0} - module name
|
||||
BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source.
|
||||
BaseDataSourceSummaryPanel_goToArtifact=View Source Result
|
||||
BaseDataSourceSummaryPanel_goToFile=View Source File in Directory
|
||||
ContainerPanel_setFieldsForNonImageDataSource_na=N/A
|
||||
CTL_DataSourceSummaryAction=Data Source Summary
|
||||
DataSourceSummaryDialog.closeButton.text=Close
|
||||
@ -103,6 +105,15 @@ PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as
|
||||
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
|
||||
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
|
||||
TimelinePanel.activityRangeLabel.text=Activity Range
|
||||
RecentFilesPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
RecentFilesPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions1.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions2.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
|
||||
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
|
||||
TimelinePanel.viewInTimelineBtn.text=View in Timeline
|
||||
UserActivityPanel_noDataExists=No communication data exists
|
||||
UserActivityPanel_tab_title=User Activity
|
||||
UserActivityPanel_TopAccountTableModel_accountType_header=Account Type
|
||||
|
@ -61,6 +61,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
|
||||
Map<Long, Long> fileCountsMap = CaseDataSourcesSummary.getCountsOfFiles();
|
||||
dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap);
|
||||
dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane();
|
||||
dataSourceSummaryTabbedPane.setParentCloseListener(() -> DataSourceSummaryDialog.this.dispose());
|
||||
initComponents();
|
||||
dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel);
|
||||
dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> {
|
||||
|
@ -47,7 +47,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
* Records of tab information (i.e. title, component, function to call on
|
||||
* new data source).
|
||||
*/
|
||||
private static class DataSourceTab {
|
||||
private class DataSourceTab {
|
||||
|
||||
private final String tabTitle;
|
||||
private final Component component;
|
||||
@ -74,8 +74,12 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
*
|
||||
* @param tabTitle The title of the tab.
|
||||
* @param panel The component to be displayed in the tab.
|
||||
* @param notifyParentClose Notifies parent to trigger a close.
|
||||
*/
|
||||
DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) {
|
||||
|
||||
panel.setParentCloseListener(() -> notifyParentClose());
|
||||
|
||||
this.tabTitle = tabTitle;
|
||||
this.component = panel;
|
||||
this.onDataSource = panel::setDataSource;
|
||||
@ -116,6 +120,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
private static final String TABBED_PANE = "tabbedPane";
|
||||
private static final String NO_DATASOURCE_PANE = "noDataSourcePane";
|
||||
|
||||
private Runnable notifyParentClose = null;
|
||||
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
|
||||
|
||||
private final List<DataSourceTab> tabs = Arrays.asList(
|
||||
@ -142,6 +147,24 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
|
||||
postInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends event that parent should close.
|
||||
*/
|
||||
private void notifyParentClose() {
|
||||
if (notifyParentClose != null) {
|
||||
notifyParentClose.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener for parent close events.
|
||||
*
|
||||
* @param parentCloseAction The observer.
|
||||
*/
|
||||
void setParentCloseListener(Runnable parentCloseAction) {
|
||||
notifyParentClose = parentCloseAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called right after initComponents during initialization.
|
||||
*/
|
||||
|
@ -94,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="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"/>
|
||||
<GridBagConstraints gridX="0" gridY="5" 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>
|
||||
|
||||
@ -106,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="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"/>
|
||||
<GridBagConstraints gridX="0" gridY="8" 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>
|
||||
|
||||
@ -140,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="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"/>
|
||||
<GridBagConstraints gridX="0" gridY="4" 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>
|
||||
@ -156,7 +156,55 @@
|
||||
</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="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="7" 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>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" 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>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="6" 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>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions3">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="9" 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>
|
||||
|
@ -21,13 +21,16 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
|
||||
@ -77,6 +80,36 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
initalizeTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a base class of RecentFileDetails and provides the pertinent menu
|
||||
* items.
|
||||
*
|
||||
* @param record The RecentFileDetails instance.
|
||||
* @return The menu items list containing one action or navigating to the
|
||||
* appropriate artifact/file and closing the data source summary dialog if
|
||||
* open.
|
||||
*/
|
||||
private Supplier<List<MenuItem>> getPopupFunct(RecentFileDetails record) {
|
||||
return () -> {
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<MenuItem> toRet = new ArrayList<>();
|
||||
|
||||
MenuItem fileNav = getFileNavigateItem(record.getPath());
|
||||
if (fileNav != null) {
|
||||
toRet.add(fileNav);
|
||||
}
|
||||
|
||||
if (record.getArtifact() != null) {
|
||||
toRet.add(getArtifactNavigateItem(record.getArtifact()));
|
||||
}
|
||||
|
||||
return (toRet.size() > 0) ? toRet : null;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fetchInformation(DataSource dataSource) {
|
||||
fetchInformation(dataFetchComponents, dataSource);
|
||||
@ -113,11 +146,13 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
List<ColumnModel<RecentFileDetails>> list = Arrays.asList(
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getPath());
|
||||
return new DefaultCellModel(prog.getPath())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 250),
|
||||
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getDateAsString());
|
||||
return new DefaultCellModel(prog.getDateAsString())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 80));
|
||||
|
||||
ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(list);
|
||||
@ -126,6 +161,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
pane.setModel(tableModel);
|
||||
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
|
||||
pane.setKeyFunction((recentFile) -> recentFile.getPath());
|
||||
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
tablePanelList.add(pane);
|
||||
|
||||
DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker
|
||||
@ -148,15 +184,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
List<ColumnModel<RecentDownloadDetails>> list = Arrays.asList(
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getWebDomain());
|
||||
return new DefaultCellModel(prog.getWebDomain())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 100),
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getPath());
|
||||
return new DefaultCellModel(prog.getPath())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 250),
|
||||
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getDateAsString());
|
||||
return new DefaultCellModel(prog.getDateAsString())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 80));
|
||||
|
||||
ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(list);
|
||||
@ -165,6 +204,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
pane.setModel(tableModel);
|
||||
pane.setKeyFunction((download) -> download.getPath());
|
||||
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
|
||||
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
tablePanelList.add(pane);
|
||||
|
||||
DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker
|
||||
@ -187,15 +227,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
List<ColumnModel<RecentAttachmentDetails>> list = Arrays.asList(
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getPath());
|
||||
return new DefaultCellModel(prog.getPath())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 250),
|
||||
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getDateAsString());
|
||||
return new DefaultCellModel(prog.getDateAsString())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 80),
|
||||
new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getSender());
|
||||
return new DefaultCellModel(prog.getSender())
|
||||
.setPopupMenuRetriever(getPopupFunct(prog));
|
||||
}, 150));
|
||||
|
||||
ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(list);
|
||||
@ -204,6 +247,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
pane.setModel(tableModel);
|
||||
pane.setKeyFunction((attachment) -> attachment.getPath());
|
||||
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
|
||||
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
tablePanelList.add(pane);
|
||||
|
||||
DataFetchWorker.DataFetchComponents<DataSource, List<RecentAttachmentDetails>> worker
|
||||
@ -234,6 +278,9 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
javax.swing.JLabel openDocsLabel = new javax.swing.JLabel();
|
||||
javax.swing.JLabel downloadLabel = new javax.swing.JLabel();
|
||||
javax.swing.JLabel attachmentLabel = new javax.swing.JLabel();
|
||||
javax.swing.JLabel rightClickForMoreOptions1 = new javax.swing.JLabel();
|
||||
javax.swing.JLabel rightClickForMoreOptions2 = new javax.swing.JLabel();
|
||||
javax.swing.JLabel rightClickForMoreOptions3 = new javax.swing.JLabel();
|
||||
|
||||
setLayout(new java.awt.BorderLayout());
|
||||
|
||||
@ -263,7 +310,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
tablePanel.add(openedDocPane, gridBagConstraints);
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 4;
|
||||
gridBagConstraints.gridy = 5;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
@ -272,7 +319,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
tablePanel.add(downloadsPane, gridBagConstraints);
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 6;
|
||||
gridBagConstraints.gridy = 8;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
@ -291,7 +338,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
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 = 3;
|
||||
gridBagConstraints.gridy = 4;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
|
||||
tablePanel.add(downloadLabel, gridBagConstraints);
|
||||
@ -299,12 +346,36 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
|
||||
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 = 5;
|
||||
gridBagConstraints.gridy = 7;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
|
||||
tablePanel.add(attachmentLabel, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions1, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions1.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 3;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
tablePanel.add(rightClickForMoreOptions1, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions2, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions2.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 6;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
tablePanel.add(rightClickForMoreOptions2, gridBagConstraints);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions3, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions3.text")); // NOI18N
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 9;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
tablePanel.add(rightClickForMoreOptions3, gridBagConstraints);
|
||||
|
||||
scrollPane.setViewportView(tablePanel);
|
||||
|
||||
add(scrollPane, java.awt.BorderLayout.CENTER);
|
||||
|
@ -171,7 +171,7 @@
|
||||
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalStrut"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="sameIdPanel">
|
||||
<Container class="javax.swing.JPanel" name="last30DaysPanel">
|
||||
<Properties>
|
||||
<Property name="alignmentX" type="float" value="0.0"/>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -193,6 +193,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="viewInTimelineBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="TimelinePanel.viewInTimelineBtn.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="viewInTimelineBtnActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler5">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
|
@ -26,8 +26,15 @@ import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.util.actions.CallableSystemAction;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineDataSourceUtils;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData;
|
||||
@ -41,7 +48,11 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetch
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
|
||||
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineModule;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A tab shown in data source summary displaying information about a data
|
||||
@ -55,6 +66,7 @@ import org.sleuthkit.datamodel.DataSource;
|
||||
"TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",})
|
||||
public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(TimelinePanel.class.getName());
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy");
|
||||
private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d");
|
||||
@ -75,6 +87,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
private final LoadableLabel earliestLabel = new LoadableLabel(Bundle.TimelinePanel_earliestLabel_title());
|
||||
private final LoadableLabel latestLabel = new LoadableLabel(Bundle.TimelinePanel_latestLabel_title());
|
||||
private final BarChartPanel last30DaysChart = new BarChartPanel(Bundle.TimlinePanel_last30DaysChart_title(), "", "");
|
||||
private final TimelineDataSourceUtils timelineUtils = TimelineDataSourceUtils.getInstance();
|
||||
|
||||
// all loadable components on this tab
|
||||
private final List<LoadableComponent<?>> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart);
|
||||
@ -151,6 +164,9 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts));
|
||||
}
|
||||
|
||||
private final Object timelineBtnLock = new Object();
|
||||
private TimelineSummaryData curTimelineData = null;
|
||||
|
||||
/**
|
||||
* Handles displaying the result for each displayable item in the
|
||||
* TimelinePanel by breaking the TimelineSummaryData result into its
|
||||
@ -163,6 +179,87 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT)));
|
||||
latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT)));
|
||||
last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity())));
|
||||
|
||||
if (result != null
|
||||
&& result.getResultType() == DataFetchResult.ResultType.SUCCESS
|
||||
&& result.getData() != null) {
|
||||
|
||||
synchronized (this.timelineBtnLock) {
|
||||
this.curTimelineData = result.getData();
|
||||
this.viewInTimelineBtn.setEnabled(true);
|
||||
}
|
||||
} else {
|
||||
synchronized (this.timelineBtnLock) {
|
||||
this.viewInTimelineBtn.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that occurs when 'View in Timeline' button is pressed.
|
||||
*/
|
||||
private void openFilteredChart() {
|
||||
DataSource dataSource = null;
|
||||
Date minDate = null;
|
||||
Date maxDate = null;
|
||||
|
||||
// get date from current timelineData if that data exists.
|
||||
synchronized (this.timelineBtnLock) {
|
||||
if (curTimelineData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataSource = curTimelineData.getDataSource();
|
||||
if (CollectionUtils.isNotEmpty(curTimelineData.getMostRecentDaysActivity())) {
|
||||
minDate = curTimelineData.getMostRecentDaysActivity().get(0).getDay();
|
||||
maxDate = curTimelineData.getMostRecentDaysActivity().get(curTimelineData.getMostRecentDaysActivity().size() - 1).getDay();
|
||||
// set outer bound to end of day instead of beginning
|
||||
if (maxDate != null) {
|
||||
maxDate = new Date(maxDate.getTime() + 1000 * 60 * 60 * 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openFilteredChart(dataSource, minDate, maxDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that occurs when 'View in Timeline' button is pressed.
|
||||
*
|
||||
* @param dataSource The data source to filter to.
|
||||
* @param minDate The min date for the zoom of the window.
|
||||
* @param maxDate The max date for the zoom of the window.
|
||||
*/
|
||||
private void openFilteredChart(DataSource dataSource, Date minDate, Date maxDate) {
|
||||
OpenTimelineAction openTimelineAction = CallableSystemAction.get(OpenTimelineAction.class);
|
||||
if (openTimelineAction == null) {
|
||||
logger.log(Level.WARNING, "No OpenTimelineAction provided by CallableSystemAction; taking no redirect action.");
|
||||
}
|
||||
|
||||
// notify dialog (if in dialog) should close.
|
||||
TimelinePanel.this.notifyParentClose();
|
||||
|
||||
Interval timeSpan = null;
|
||||
|
||||
try {
|
||||
final TimeLineController controller = TimeLineModule.getController();
|
||||
|
||||
if (dataSource != null) {
|
||||
controller.pushFilters(timelineUtils.getDataSourceFilterState(dataSource));
|
||||
}
|
||||
|
||||
if (minDate != null && maxDate != null) {
|
||||
timeSpan = new Interval(new DateTime(minDate), new DateTime(maxDate));
|
||||
}
|
||||
} catch (NoCurrentCaseException | TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Unable to view time range in Timeline view", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
openTimelineAction.showTimeline(timeSpan);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "An unexpected exception occurred while opening the timeline.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -198,7 +295,8 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
javax.swing.JPanel earliestLabelPanel = earliestLabel;
|
||||
javax.swing.JPanel latestLabelPanel = latestLabel;
|
||||
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.JPanel sameIdPanel = last30DaysChart;
|
||||
javax.swing.JPanel last30DaysPanel = last30DaysChart;
|
||||
viewInTimelineBtn = new javax.swing.JButton();
|
||||
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));
|
||||
@ -233,12 +331,21 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
filler2.setAlignmentX(0.0F);
|
||||
mainContentPanel.add(filler2);
|
||||
|
||||
sameIdPanel.setAlignmentX(0.0F);
|
||||
sameIdPanel.setMaximumSize(new java.awt.Dimension(600, 300));
|
||||
sameIdPanel.setMinimumSize(new java.awt.Dimension(600, 300));
|
||||
sameIdPanel.setPreferredSize(new java.awt.Dimension(600, 300));
|
||||
sameIdPanel.setVerifyInputWhenFocusTarget(false);
|
||||
mainContentPanel.add(sameIdPanel);
|
||||
last30DaysPanel.setAlignmentX(0.0F);
|
||||
last30DaysPanel.setMaximumSize(new java.awt.Dimension(600, 300));
|
||||
last30DaysPanel.setMinimumSize(new java.awt.Dimension(600, 300));
|
||||
last30DaysPanel.setPreferredSize(new java.awt.Dimension(600, 300));
|
||||
last30DaysPanel.setVerifyInputWhenFocusTarget(false);
|
||||
mainContentPanel.add(last30DaysPanel);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(viewInTimelineBtn, org.openide.util.NbBundle.getMessage(TimelinePanel.class, "TimelinePanel.viewInTimelineBtn.text")); // NOI18N
|
||||
viewInTimelineBtn.setEnabled(false);
|
||||
viewInTimelineBtn.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
viewInTimelineBtnActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
mainContentPanel.add(viewInTimelineBtn);
|
||||
|
||||
filler5.setAlignmentX(0.0F);
|
||||
mainContentPanel.add(filler5);
|
||||
@ -257,7 +364,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void viewInTimelineBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewInTimelineBtnActionPerformed
|
||||
openFilteredChart();
|
||||
}//GEN-LAST:event_viewInTimelineBtnActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton viewInTimelineBtn;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -135,6 +135,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler3">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -204,6 +215,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler4">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -273,6 +295,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions3">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler6">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -342,6 +375,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions4">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions4.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.Box$Filler" name="filler8">
|
||||
<Properties>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -411,6 +455,17 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions5">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions5.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
|
@ -29,12 +29,14 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.LastAccessedArtifact;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
|
||||
@ -92,7 +94,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
Bundle.UserActivityPanel_TopProgramsTableModel_name_header(),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(prog.getProgramName())
|
||||
.setTooltip(prog.getProgramPath());
|
||||
.setTooltip(prog.getProgramPath())
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
250),
|
||||
// program folder column
|
||||
@ -103,7 +106,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
getShortFolderName(
|
||||
prog.getProgramPath(),
|
||||
prog.getProgramName()))
|
||||
.setTooltip(prog.getProgramPath());
|
||||
.setTooltip(prog.getProgramPath())
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
150),
|
||||
// run count column
|
||||
@ -111,13 +115,17 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
Bundle.UserActivityPanel_TopProgramsTableModel_count_header(),
|
||||
(prog) -> {
|
||||
String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes());
|
||||
return new DefaultCellModel(runTimes);
|
||||
return new DefaultCellModel(runTimes)
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
80),
|
||||
// last run date column
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopProgramsTableModel_lastrun_header(),
|
||||
(prog) -> new DefaultCellModel(getFormatted(prog.getLastRun())),
|
||||
(prog) -> {
|
||||
return new DefaultCellModel(getFormatted(prog.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(prog));
|
||||
},
|
||||
150)
|
||||
))
|
||||
.setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName());
|
||||
@ -127,20 +135,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// domain column
|
||||
new ColumnModel<TopDomainsResult>(
|
||||
Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(),
|
||||
(recentDomain) -> new DefaultCellModel(recentDomain.getDomain()),
|
||||
(recentDomain) -> {
|
||||
return new DefaultCellModel(recentDomain.getDomain())
|
||||
.setPopupMenu(getPopup(recentDomain));
|
||||
},
|
||||
250),
|
||||
// count column
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopDomainsTableModel_count_header(),
|
||||
(recentDomain) -> {
|
||||
String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes());
|
||||
return new DefaultCellModel(visitTimes);
|
||||
return new DefaultCellModel(visitTimes)
|
||||
.setPopupMenu(getPopup(recentDomain));
|
||||
},
|
||||
100),
|
||||
// last accessed column
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(),
|
||||
(recentDomain) -> new DefaultCellModel(getFormatted(recentDomain.getLastVisit())),
|
||||
(recentDomain) -> {
|
||||
return new DefaultCellModel(getFormatted(recentDomain.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(recentDomain));
|
||||
},
|
||||
150)
|
||||
))
|
||||
.setKeyFunction((domain) -> domain.getDomain());
|
||||
@ -150,19 +165,28 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// search string column
|
||||
new ColumnModel<TopWebSearchResult>(
|
||||
Bundle.UserActivityPanel_TopWebSearchTableModel_searchString_header(),
|
||||
(webSearch) -> new DefaultCellModel(webSearch.getSearchString()),
|
||||
(webSearch) -> {
|
||||
return new DefaultCellModel(webSearch.getSearchString())
|
||||
.setPopupMenu(getPopup(webSearch));
|
||||
},
|
||||
250
|
||||
),
|
||||
// last accessed
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopWebSearchTableModel_dateAccessed_header(),
|
||||
(webSearch) -> new DefaultCellModel(getFormatted(webSearch.getDateAccessed())),
|
||||
(webSearch) -> {
|
||||
return new DefaultCellModel(getFormatted(webSearch.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(webSearch));
|
||||
},
|
||||
150
|
||||
),
|
||||
// translated value
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopWebSearchTableModel_translatedResult_header(),
|
||||
(webSearch) -> new DefaultCellModel(webSearch.getTranslatedResult()),
|
||||
(webSearch) -> {
|
||||
return new DefaultCellModel(webSearch.getTranslatedResult())
|
||||
.setPopupMenu(getPopup(webSearch));
|
||||
},
|
||||
250
|
||||
)
|
||||
))
|
||||
@ -173,13 +197,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// device id column
|
||||
new ColumnModel<TopDeviceAttachedResult>(
|
||||
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(),
|
||||
(device) -> new DefaultCellModel(device.getDeviceId()),
|
||||
(device) -> {
|
||||
return new DefaultCellModel(device.getDeviceId())
|
||||
.setPopupMenu(getPopup(device));
|
||||
},
|
||||
250
|
||||
),
|
||||
// last accessed
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(),
|
||||
(device) -> new DefaultCellModel(getFormatted(device.getDateAccessed())),
|
||||
(device) -> {
|
||||
return new DefaultCellModel(getFormatted(device.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(device));
|
||||
},
|
||||
150
|
||||
),
|
||||
// make and model
|
||||
@ -191,7 +221,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
String makeModelString = (make.isEmpty() || model.isEmpty())
|
||||
? make + model
|
||||
: String.format("%s - %s", make, model);
|
||||
return new DefaultCellModel(makeModelString);
|
||||
return new DefaultCellModel(makeModelString)
|
||||
.setPopupMenu(getPopup(device));
|
||||
},
|
||||
250
|
||||
)
|
||||
@ -203,13 +234,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
// account type column
|
||||
new ColumnModel<TopAccountResult>(
|
||||
Bundle.UserActivityPanel_TopAccountTableModel_accountType_header(),
|
||||
(account) -> new DefaultCellModel(account.getAccountType()),
|
||||
(account) -> {
|
||||
return new DefaultCellModel(account.getAccountType())
|
||||
.setPopupMenu(getPopup(account));
|
||||
},
|
||||
250
|
||||
),
|
||||
// last accessed
|
||||
new ColumnModel<>(
|
||||
Bundle.UserActivityPanel_TopAccountTableModel_lastAccess_header(),
|
||||
(account) -> new DefaultCellModel(getFormatted(account.getLastAccess())),
|
||||
(account) -> {
|
||||
return new DefaultCellModel(getFormatted(account.getLastAccessed()))
|
||||
.setPopupMenu(getPopup(account));
|
||||
},
|
||||
150
|
||||
)
|
||||
))
|
||||
@ -292,6 +329,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a base class of LastAccessedArtifact and provides the pertinent
|
||||
* menu items. going to artifact.
|
||||
*
|
||||
* @param record The LastAccessedArtifact instance.
|
||||
* @param navigateToArtifact Navigate right tot the artifact.
|
||||
* @return The menu items list containing one action or navigating to the
|
||||
* appropriate artifact and closing the data source summary dialog if open.
|
||||
*/
|
||||
private List<MenuItem> getPopup(LastAccessedArtifact record) {
|
||||
return record == null ? null : Arrays.asList(getArtifactNavigateItem(record.getArtifact()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries DataSourceTopProgramsSummary instance for short folder name.
|
||||
*
|
||||
@ -335,22 +385,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
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;
|
||||
javax.swing.JLabel rightClickForMoreOptions1 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler3 = 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 recentDomainsLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler2 = 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 recentDomainsTablePanel = recentDomainsTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions2 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler4 = 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 topWebSearchLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler5 = 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 topWebSearches = topWebSearchesTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions3 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler6 = 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 topDevicesAttachedLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler7 = 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 recentDevicesAttached = topDevicesAttachedTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions4 = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler8 = 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 recentAccountsLabel = new javax.swing.JLabel();
|
||||
javax.swing.Box.Filler filler9 = 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 topAccounts = topAccountsTable;
|
||||
javax.swing.JLabel rightClickForMoreOptions5 = new javax.swing.JLabel();
|
||||
|
||||
setLayout(new java.awt.BorderLayout());
|
||||
|
||||
@ -379,6 +434,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(topProgramsTablePanel);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions1, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions1.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions1);
|
||||
contentPanel.add(filler3);
|
||||
|
||||
recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -391,6 +449,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(recentDomainsTablePanel);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions2, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions2.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions2);
|
||||
contentPanel.add(filler4);
|
||||
|
||||
topWebSearchLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -403,6 +464,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
topWebSearches.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
topWebSearches.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(topWebSearches);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions3, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions3.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions3);
|
||||
contentPanel.add(filler6);
|
||||
|
||||
topDevicesAttachedLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -415,6 +479,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
recentDevicesAttached.setMinimumSize(new java.awt.Dimension(10, 106));
|
||||
recentDevicesAttached.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(recentDevicesAttached);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions4, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions4.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions4);
|
||||
contentPanel.add(filler8);
|
||||
|
||||
recentAccountsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
|
||||
@ -428,6 +495,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
|
||||
topAccounts.setPreferredSize(new java.awt.Dimension(10, 106));
|
||||
contentPanel.add(topAccounts);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions5, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions5.text")); // NOI18N
|
||||
contentPanel.add(rightClickForMoreOptions5);
|
||||
|
||||
contentScrollPane.setViewportView(contentPanel);
|
||||
|
||||
add(contentScrollPane, java.awt.BorderLayout.CENTER);
|
||||
|
@ -304,4 +304,5 @@ public class BarChartPanel extends AbstractLoadableComponent<List<BarChartPanel.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,12 +20,22 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseEvent;
|
||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseListener;
|
||||
|
||||
/**
|
||||
* A Table cell renderer that renders a cell of a table based off of the
|
||||
@ -64,6 +74,53 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu item to be used within a popup menu.
|
||||
*/
|
||||
public interface MenuItem {
|
||||
|
||||
/**
|
||||
* @return The title for that popup menu item.
|
||||
*/
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* @return The action if that popup menu item is clicked.
|
||||
*/
|
||||
Runnable getAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of a menu item.
|
||||
*/
|
||||
public static class DefaultMenuItem implements MenuItem {
|
||||
|
||||
private final String title;
|
||||
private final Runnable action;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param title The title for the menu item.
|
||||
* @param action The action should the menu item be clicked.
|
||||
*/
|
||||
public DefaultMenuItem(String title, Runnable action) {
|
||||
this.title = title;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic interface for a cell model.
|
||||
*/
|
||||
@ -88,6 +145,12 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
* @return The insets for the cell text.
|
||||
*/
|
||||
Insets getInsets();
|
||||
|
||||
/**
|
||||
* @return The popup menu associated with this cell or null if no popup
|
||||
* menu should be shown for this cell.
|
||||
*/
|
||||
List<MenuItem> getPopupMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,6 +162,8 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
private String tooltip;
|
||||
private HorizontalAlign horizontalAlignment;
|
||||
private Insets insets;
|
||||
private List<MenuItem> popupMenu;
|
||||
private Supplier<List<MenuItem>> menuItemSupplier;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
@ -166,6 +231,41 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MenuItem> getPopupMenu() {
|
||||
if (popupMenu != null) {
|
||||
return Collections.unmodifiableList(popupMenu);
|
||||
}
|
||||
|
||||
if (menuItemSupplier != null) {
|
||||
return this.menuItemSupplier.get();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a function to lazy load the popup menu items.
|
||||
*
|
||||
* @param menuItemSupplier The lazy load function for popup items.
|
||||
* @return
|
||||
*/
|
||||
public DefaultCellModel setPopupMenuRetriever(Supplier<List<MenuItem>> menuItemSupplier) {
|
||||
this.menuItemSupplier = menuItemSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of items for a popup menu
|
||||
*
|
||||
* @param popupMenu
|
||||
* @return As a utility, returns this.
|
||||
*/
|
||||
public DefaultCellModel setPopupMenu(List<MenuItem> popupMenu) {
|
||||
this.popupMenu = popupMenu == null ? null : new ArrayList<>(popupMenu);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getText();
|
||||
@ -231,4 +331,42 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
return defaultCell;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default cell mouse listener that triggers popups for non-primary
|
||||
* button events.
|
||||
*/
|
||||
private static final CellMouseListener DEFAULT_CELL_MOUSE_LISTENER = new CellMouseListener() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(CellMouseEvent cellEvent) {
|
||||
if (cellEvent.getCellValue() instanceof CellModel && cellEvent.getMouseEvent().getButton() != MouseEvent.BUTTON1) {
|
||||
cellEvent.getTable().setRowSelectionInterval(cellEvent.getRow(), cellEvent.getRow());
|
||||
CellModel cellModel = (CellModel) cellEvent.getCellValue();
|
||||
List<MenuItem> menuItems = cellModel.getPopupMenu();
|
||||
|
||||
// if there are menu items, show a popup menu for
|
||||
// this item with all the menu items.
|
||||
if (CollectionUtils.isNotEmpty(menuItems)) {
|
||||
final JPopupMenu popupMenu = new JPopupMenu();
|
||||
for (MenuItem mItem : menuItems) {
|
||||
JMenuItem jMenuItem = new JMenuItem(mItem.getTitle());
|
||||
if (mItem.getAction() != null) {
|
||||
jMenuItem.addActionListener((evt) -> mItem.getAction().run());
|
||||
}
|
||||
popupMenu.add(jMenuItem);
|
||||
}
|
||||
popupMenu.show(cellEvent.getTable(), cellEvent.getMouseEvent().getX(), cellEvent.getMouseEvent().getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return The default cell mouse listener that triggers popups for
|
||||
* non-primary button events.
|
||||
*/
|
||||
public static CellMouseListener getMouseListener() {
|
||||
return DEFAULT_CELL_MOUSE_LISTENER;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@ -40,6 +42,85 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRendere
|
||||
*/
|
||||
public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
|
||||
/**
|
||||
* An event that wraps a swing MouseEvent also providing context within the
|
||||
* table cell.
|
||||
*/
|
||||
public static class CellMouseEvent {
|
||||
|
||||
private final MouseEvent e;
|
||||
private final JTable table;
|
||||
private final int row;
|
||||
private final int col;
|
||||
private final Object cellValue;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param e The underlying mouse event.
|
||||
* @param table The table that was the target of the mouse event.
|
||||
* @param row The row within the table that the event occurs.
|
||||
* @param col The column within the table that the event occurs.
|
||||
* @param cellValue The value within the cell.
|
||||
*/
|
||||
public CellMouseEvent(MouseEvent e, JTable table, int row, int col, Object cellValue) {
|
||||
this.e = e;
|
||||
this.table = table;
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.cellValue = cellValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The underlying mouse event.
|
||||
*/
|
||||
public MouseEvent getMouseEvent() {
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The table that was the target of the mouse event.
|
||||
*/
|
||||
public JTable getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The row within the table that the event occurs.
|
||||
*/
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The column within the table that the event occurs.
|
||||
*/
|
||||
public int getCol() {
|
||||
return col;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value within the cell.
|
||||
*/
|
||||
public Object getCellValue() {
|
||||
return cellValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles mouse events for cells in the table.
|
||||
*/
|
||||
public interface CellMouseListener {
|
||||
|
||||
/**
|
||||
* Handles mouse events at a cell level for the table.
|
||||
*
|
||||
* @param e The event containing information about the cell, the mouse
|
||||
* event, and the table.
|
||||
*/
|
||||
void mouseClicked(CellMouseEvent e);
|
||||
}
|
||||
|
||||
/**
|
||||
* JTables don't allow displaying messages. So this LayerUI is used to
|
||||
* display the contents of a child JLabel. Inspired by TableWaitLayerTest
|
||||
@ -197,14 +278,18 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
*/
|
||||
public static <T> JTablePanel<T> getJTablePanel(List<ColumnModel<T>> columns) {
|
||||
ListTableModel<T> tableModel = getTableModel(columns);
|
||||
JTablePanel<T> resultTable = new JTablePanel<>(tableModel);
|
||||
return resultTable.setColumnModel(getTableColumnModel(columns));
|
||||
JTablePanel<T> resultTable = new JTablePanel<>(tableModel)
|
||||
.setColumnModel(getTableColumnModel(columns))
|
||||
.setCellListener(CellModelTableCellRenderer.getMouseListener());
|
||||
|
||||
return resultTable;
|
||||
}
|
||||
|
||||
private JScrollPane tableScrollPane;
|
||||
private Overlay overlayLayer;
|
||||
private ListTableModel<T> tableModel;
|
||||
private JTable table;
|
||||
private CellMouseListener cellListener = null;
|
||||
private Function<T, ? extends Object> keyFunction = (rowItem) -> rowItem;
|
||||
|
||||
/**
|
||||
@ -222,6 +307,26 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
*/
|
||||
public JTablePanel() {
|
||||
initComponents();
|
||||
this.table.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// make sure click event isn't primary button and table is present
|
||||
if (cellListener != null) {
|
||||
int row = table.rowAtPoint(e.getPoint());
|
||||
int col = table.columnAtPoint(e.getPoint());
|
||||
|
||||
// make sure there is a value at the row,col of click event.
|
||||
if (tableModel != null
|
||||
&& row >= 0 && row < tableModel.getRowCount()
|
||||
&& col >= 0 && col < tableModel.getColumnCount()) {
|
||||
|
||||
Object cellValue = tableModel.getValueAt(row, col);
|
||||
cellListener.mouseClicked(new CellMouseEvent(e, table, row, col, cellValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -242,6 +347,26 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current listener for mouse events. The events provided to
|
||||
* this listener will have cell and table context.
|
||||
*/
|
||||
public CellMouseListener getCellListener() {
|
||||
return cellListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current listener for mouse events.
|
||||
*
|
||||
* @param cellListener The event listener that will receive these events
|
||||
* with cell and table context.
|
||||
* @return
|
||||
*/
|
||||
public JTablePanel<T> setCellListener(CellMouseListener cellListener) {
|
||||
this.cellListener = cellListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The underlying JTable's column model.
|
||||
*/
|
||||
@ -263,8 +388,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
|
||||
/**
|
||||
* @return The function for determining the key for a data row. This key is
|
||||
* used to maintain current selection in the table despite changing
|
||||
* rows.
|
||||
* used to maintain current selection in the table despite changing rows.
|
||||
*/
|
||||
public Function<T, ? extends Object> getKeyFunction() {
|
||||
return keyFunction;
|
||||
@ -330,7 +454,6 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
|
||||
private void initComponents() {
|
||||
table = new JTable();
|
||||
table.getTableHeader().setReorderingAllowed(false);
|
||||
|
||||
overlayLayer = new Overlay();
|
||||
tableScrollPane = new JScrollPane(table);
|
||||
JLayer<JComponent> dualLayer = new JLayer<>(tableScrollPane, overlayLayer);
|
||||
|
@ -22,6 +22,7 @@ import com.google.common.cache.CacheLoader;
|
||||
import java.awt.Image;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -38,17 +39,22 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.openide.util.ImageUtilities;
|
||||
|
||||
/**
|
||||
* Loads a thumbnail for the given request. Thumbnail candidates are JPEG files
|
||||
* that have either TSK_WEB_DOWNLOAD or TSK_WEB_CACHE artifacts that match the
|
||||
* domain name (see the DomainSearch getArtifacts() API). JPEG files are sorted
|
||||
* Loads a thumbnail for the given request. Thumbnail candidates types are defined below.
|
||||
* These candidates types must be the source of either TSK_WEB_DOWNLOAD or
|
||||
* TSK_WEB_CACHE artifacts that match the
|
||||
* domain name (see the DomainSearch getArtifacts() API). Candidate files are sorted
|
||||
* by most recent if sourced from TSK_WEB_DOWNLOADs and by size if sourced from
|
||||
* TSK_WEB_CACHE artifacts. The first suitable thumbnail is selected.
|
||||
*/
|
||||
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 static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("jpg", "svg", "png", "webp", "ico", "gif");
|
||||
private static final List<String> SUPPORTED_MIMETYPES = Arrays.asList(
|
||||
"image/gif", "image/jpeg", "image/png", "image/webp",
|
||||
"image/svg+xml", "image/vnd.microsoft.icon", "image/x-icon");
|
||||
private static final String ICO_EXTENSION = "ico";
|
||||
private static final List<String> ICO_MIMETYPES = Arrays.asList("image/vnd.microsoft.icon", "image/x-icon");
|
||||
private final DomainSearchArtifactsCache artifactsCache;
|
||||
|
||||
/**
|
||||
@ -75,8 +81,21 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
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()));
|
||||
final List<AbstractFile> webDownloadPictures = getCandidatesFromWebDownloads(caseDb, webDownloads);
|
||||
Collections.sort(webDownloadPictures, (file1, file2) -> {
|
||||
// Push ICO to the back of the sorted collection, so that ICO
|
||||
// is a last resort matching type.
|
||||
if (isIco(file1) && isIco(file2)) {
|
||||
return Long.compare(file1.getCrtime(), file2.getCrtime());
|
||||
} else if (isIco(file1)) {
|
||||
return 1;
|
||||
} else if (isIco(file2)) {
|
||||
return -1;
|
||||
} else {
|
||||
return Long.compare(file1.getCrtime(), file2.getCrtime());
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = webDownloadPictures.size() - 1; i >= 0; i--) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@ -92,8 +111,20 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
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()));
|
||||
final List<AbstractFile> webCachePictures = getCandidatesFromWebCache(caseDb, webCacheArtifacts);
|
||||
Collections.sort(webCachePictures, (file1, file2) -> {
|
||||
// Push ICO to the back of the sorted collection, so that ICO
|
||||
// is a last resort matching type.
|
||||
if (isIco(file1) && isIco(file2)) {
|
||||
return Long.compare(file1.getSize(), file2.getSize());
|
||||
} else if (isIco(file1)) {
|
||||
return 1;
|
||||
} else if (isIco(file2)) {
|
||||
return -1;
|
||||
} else {
|
||||
return Long.compare(file1.getSize(), file2.getSize());
|
||||
}
|
||||
});
|
||||
for (int i = webCachePictures.size() - 1; i >= 0; i--) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@ -109,40 +140,45 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all JPEG source files from TSK_WEB_DOWNLOAD instances.
|
||||
* Finds all supported image files from TSK_WEB_DOWNLOAD instances.
|
||||
*
|
||||
* @param caseDb The case database being searched.
|
||||
* @param artifacts The list of artifacts to get jpegs from.
|
||||
* @param artifacts The list of artifacts to search.
|
||||
*
|
||||
* @return The list of AbstractFiles representing jpegs which were
|
||||
* @return The list of AbstractFiles representing supported images which were
|
||||
* associated with the artifacts.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private List<AbstractFile> getJpegsFromWebDownload(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
final List<AbstractFile> jpegs = new ArrayList<>();
|
||||
private List<AbstractFile> getCandidatesFromWebDownloads(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
final List<AbstractFile> candidates = new ArrayList<>();
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
final Content sourceContent = caseDb.getContentById(artifact.getObjectID());
|
||||
addIfJpeg(jpegs, sourceContent);
|
||||
addIfSupported(candidates, sourceContent);
|
||||
}
|
||||
return jpegs;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private boolean isIco(AbstractFile file) {
|
||||
return ICO_EXTENSION.equals(file.getNameExtension())
|
||||
|| ICO_MIMETYPES.contains(file.getMIMEType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all JPEG source files from TSK_WEB_CACHE instances.
|
||||
* Finds all supported image files from TSK_WEB_CACHE instances.
|
||||
*
|
||||
* @param caseDb The case database being searched.
|
||||
* @param artifacts The list of artifacts to get jpegs from.
|
||||
* @param artifacts The list of artifacts to get images from.
|
||||
*
|
||||
* @return The list of AbstractFiles representing jpegs which were
|
||||
* @return The list of AbstractFiles representing supported images which were
|
||||
* associated with the artifacts.
|
||||
*/
|
||||
private List<AbstractFile> getJpegsFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
private List<AbstractFile> getCandidatesFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
|
||||
final BlackboardAttribute.Type TSK_PATH_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID);
|
||||
final List<AbstractFile> jpegs = new ArrayList<>();
|
||||
final List<AbstractFile> candidates = new ArrayList<>();
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@ -150,24 +186,23 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
|
||||
final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID);
|
||||
if (tskPathId != null) {
|
||||
final Content sourceContent = caseDb.getContentById(tskPathId.getValueLong());
|
||||
addIfJpeg(jpegs, sourceContent);
|
||||
addIfSupported(candidates, sourceContent);
|
||||
}
|
||||
}
|
||||
return jpegs;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the candidate source content is indeed a JPEG file.
|
||||
* Checks if the candidate source content is indeed a supported type.
|
||||
*
|
||||
* @param files The list of source content files which are jpegs to
|
||||
* add to.
|
||||
* @param files The list of source content files which are supported.
|
||||
* @param sourceContent The source content to check and possibly add.
|
||||
*/
|
||||
private void addIfJpeg(List<AbstractFile> files, Content sourceContent) {
|
||||
private void addIfSupported(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())) {
|
||||
if (SUPPORTED_EXTENSIONS.contains(file.getNameExtension())
|
||||
|| SUPPORTED_MIMETYPES.contains(file.getMIMEType())) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
@ -22,11 +22,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javafx.util.Pair;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Area;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Track;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
|
||||
@ -81,7 +81,7 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
|
||||
* @param wasEntirelySuccessful True if no errors occurred while processing.
|
||||
*/
|
||||
abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks,
|
||||
boolean wasEntirelySuccessful);
|
||||
List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful);
|
||||
|
||||
@Override
|
||||
public void process(GeoLocationParseResult<Waypoint> waypointResults) {
|
||||
@ -94,35 +94,53 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
|
||||
}
|
||||
}
|
||||
|
||||
Pair<List<Waypoint>, List<List<Waypoint>>> waypointsAndTracks = createWaypointList(
|
||||
GeoLocationParseResult<Area> areaResults = null;
|
||||
if (filters.getArtifactTypes().contains(ARTIFACT_TYPE.TSK_GPS_AREA)) {
|
||||
try {
|
||||
areaResults = Area.getAreas(Case.getCurrentCase().getSleuthkitCase(), filters.getDataSources());
|
||||
} catch (GeoLocationDataException ex) {
|
||||
logger.log(Level.WARNING, "Exception thrown while retrieving list of Areas", ex);
|
||||
}
|
||||
}
|
||||
|
||||
GeoDataSet geoDataSet = createWaypointList(
|
||||
waypointResults.getItems(),
|
||||
(trackResults == null) ? new ArrayList<Track>() : trackResults.getItems());
|
||||
(trackResults == null) ? new ArrayList<>() : trackResults.getItems(),
|
||||
(areaResults == null) ? new ArrayList<>() : areaResults.getItems());
|
||||
|
||||
|
||||
final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(waypointsAndTracks.getKey());
|
||||
final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(geoDataSet.getWaypoints());
|
||||
final List<Set<MapWaypoint>> trackSets = new ArrayList<>();
|
||||
for (List<Waypoint> t : waypointsAndTracks.getValue()) {
|
||||
for (List<Waypoint> t : geoDataSet.getTracks()) {
|
||||
trackSets.add(MapWaypoint.getWaypoints(t));
|
||||
}
|
||||
final List<Set<MapWaypoint>> areaSets = new ArrayList<>();
|
||||
for (List<Waypoint> t : geoDataSet.getAreas()) {
|
||||
areaSets.add(MapWaypoint.getWaypoints(t));
|
||||
}
|
||||
|
||||
handleFilteredWaypointSet(
|
||||
pointSet, trackSets,
|
||||
(trackResults == null || trackResults.isSuccessfullyParsed()) && waypointResults.isSuccessfullyParsed());
|
||||
pointSet, trackSets, areaSets,
|
||||
(trackResults == null || trackResults.isSuccessfullyParsed())
|
||||
&& (areaResults == null || areaResults.isSuccessfullyParsed())
|
||||
&& waypointResults.isSuccessfullyParsed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a complete list of waypoints including the tracks. Takes into
|
||||
* Returns a complete list of waypoints including the tracks and areas. Takes into
|
||||
* account the current filters and includes waypoints as approprate.
|
||||
*
|
||||
* @param waypoints List of waypoints
|
||||
* @param tracks List of tracks
|
||||
* @param areas List of areas
|
||||
*
|
||||
* @return A list of waypoints including the tracks based on the current
|
||||
* filters.
|
||||
* @return A GeoDataSet object containing a list of waypoints including the tracks and areas based on the current
|
||||
filters.
|
||||
*/
|
||||
private Pair<List<Waypoint>, List<List<Waypoint>>> createWaypointList(List<Waypoint> waypoints, List<Track> tracks) {
|
||||
private GeoDataSet createWaypointList(List<Waypoint> waypoints, List<Track> tracks, List<Area> areas) {
|
||||
final List<Waypoint> completeList = new ArrayList<>();
|
||||
List<List<Waypoint>> filteredTracks = new ArrayList<>();
|
||||
List<List<Waypoint>> filteredAreas = new ArrayList<>();
|
||||
|
||||
if (tracks != null) {
|
||||
Long timeRangeEnd;
|
||||
@ -149,7 +167,14 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
|
||||
} else {
|
||||
completeList.addAll(waypoints);
|
||||
}
|
||||
return new Pair<>(completeList, filteredTracks);
|
||||
|
||||
// Areas don't have timestamps so add all of them
|
||||
for (Area area : areas) {
|
||||
completeList.addAll(area.getPath());
|
||||
filteredAreas.add(area.getPath());
|
||||
}
|
||||
|
||||
return new GeoDataSet(completeList, filteredTracks, filteredAreas);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,4 +295,31 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
|
||||
|
||||
return -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to collect filtered GPS objects.
|
||||
*/
|
||||
static class GeoDataSet {
|
||||
private final List<Waypoint> waypoints;
|
||||
private final List<List<Waypoint>> tracks;
|
||||
private final List<List<Waypoint>> areas;
|
||||
|
||||
GeoDataSet(List<Waypoint> waypoints, List<List<Waypoint>> tracks, List<List<Waypoint>> areas) {
|
||||
this.waypoints = waypoints;
|
||||
this.tracks = tracks;
|
||||
this.areas = areas;
|
||||
}
|
||||
|
||||
List<Waypoint> getWaypoints() {
|
||||
return waypoints;
|
||||
}
|
||||
|
||||
List<List<Waypoint>> getTracks() {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
List<List<Waypoint>> getAreas() {
|
||||
return areas;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,8 @@ class GeoFilterPanel extends javax.swing.JPanel {
|
||||
ARTIFACT_TYPE.TSK_GPS_SEARCH,
|
||||
ARTIFACT_TYPE.TSK_GPS_TRACK,
|
||||
ARTIFACT_TYPE.TSK_GPS_TRACKPOINT,
|
||||
ARTIFACT_TYPE.TSK_METADATA_EXIF
|
||||
ARTIFACT_TYPE.TSK_METADATA_EXIF,
|
||||
ARTIFACT_TYPE.TSK_GPS_AREA
|
||||
};
|
||||
|
||||
/**
|
||||
@ -523,6 +524,7 @@ class GeoFilterPanel extends javax.swing.JPanel {
|
||||
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()
|
||||
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS.getTypeID()
|
||||
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID()
|
||||
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS.getTypeID()
|
||||
+ " )"
|
||||
+ " ) as innerTable";
|
||||
try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr);
|
||||
|
@ -115,7 +115,8 @@ public final class GeolocationTopComponent extends TopComponent {
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID())) {
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID())) {
|
||||
|
||||
showRefreshPanel(true);
|
||||
}
|
||||
@ -330,7 +331,7 @@ public final class GeolocationTopComponent extends TopComponent {
|
||||
*
|
||||
* @param waypointList
|
||||
*/
|
||||
void addWaypointsToMap(Set<MapWaypoint> waypointList, List<Set<MapWaypoint>> tracks) {
|
||||
void addWaypointsToMap(Set<MapWaypoint> waypointList, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -348,6 +349,7 @@ public final class GeolocationTopComponent extends TopComponent {
|
||||
mapPanel.clearWaypoints();
|
||||
mapPanel.setWaypoints(waypointList);
|
||||
mapPanel.setTracks(tracks);
|
||||
mapPanel.setAreas(areas);
|
||||
mapPanel.initializePainter();
|
||||
setWaypointLoading(false);
|
||||
geoFilterPanel.setEnabled(true);
|
||||
@ -505,8 +507,9 @@ public final class GeolocationTopComponent extends TopComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, boolean wasEntirelySuccessful) {
|
||||
addWaypointsToMap(mapWaypoints, tracks);
|
||||
void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks,
|
||||
List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful) {
|
||||
addWaypointsToMap(mapWaypoints, tracks, areas);
|
||||
|
||||
// if there is an error, present to the user.
|
||||
if (!wasEntirelySuccessful) {
|
||||
|
@ -23,6 +23,7 @@ import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
@ -30,6 +31,7 @@ import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
@ -91,11 +93,14 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Set<Integer> DOT_WAYPOINT_TYPES = new HashSet<>();
|
||||
private static final int DOT_SIZE = 12;
|
||||
private static final Set<Integer> VERY_SMALL_DOT_WAYPOINT_TYPES = new HashSet<>();
|
||||
private static final int VERY_SMALL_DOT_SIZE = 6;
|
||||
|
||||
private boolean zoomChanging;
|
||||
private KdTree<MapWaypoint> waypointTree;
|
||||
private Set<MapWaypoint> waypointSet;
|
||||
private List<Set<MapWaypoint>> tracks = new ArrayList<>();
|
||||
private List<Set<MapWaypoint>> areas = new ArrayList<>();
|
||||
|
||||
private Popup currentPopup;
|
||||
private final PopupFactory popupFactory;
|
||||
@ -108,12 +113,13 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
private BufferedImage transparentWaypointImage;
|
||||
|
||||
private MapWaypoint currentlySelectedWaypoint;
|
||||
private Set<MapWaypoint> currentlySelectedTrack;
|
||||
private Set<MapWaypoint> currentlySelectedSet;
|
||||
|
||||
static {
|
||||
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID());
|
||||
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID());
|
||||
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID());
|
||||
VERY_SMALL_DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,6 +247,7 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
waypointPainter.setRenderer(new MapWaypointRenderer());
|
||||
|
||||
ArrayList<Painter<JXMapViewer>> painters = new ArrayList<>();
|
||||
painters.add(new MapAreaRenderer(areas));
|
||||
painters.add(new MapTrackRenderer(tracks));
|
||||
painters.add(waypointPainter);
|
||||
|
||||
@ -343,6 +350,15 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
this.tracks = tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given list of areas from which to draw paths later
|
||||
*
|
||||
* @param areas
|
||||
*/
|
||||
void setAreas(List<Set<MapWaypoint>> areas) {
|
||||
this.areas = areas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current zoom level.
|
||||
*
|
||||
@ -361,7 +377,7 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
void clearWaypoints() {
|
||||
waypointTree = null;
|
||||
currentlySelectedWaypoint = null;
|
||||
currentlySelectedTrack = null;
|
||||
currentlySelectedSet = null;
|
||||
if (currentPopup != null) {
|
||||
currentPopup.hide();
|
||||
}
|
||||
@ -514,6 +530,13 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
DOT_SIZE,
|
||||
DOT_SIZE
|
||||
);
|
||||
} else if (VERY_SMALL_DOT_WAYPOINT_TYPES.contains(nextWaypoint.getArtifactTypeID())) {
|
||||
rect = new Rectangle(
|
||||
pointX - (VERY_SMALL_DOT_SIZE / 2),
|
||||
pointY - (VERY_SMALL_DOT_SIZE / 2),
|
||||
VERY_SMALL_DOT_SIZE,
|
||||
VERY_SMALL_DOT_SIZE
|
||||
);
|
||||
} else {
|
||||
rect = new Rectangle(
|
||||
pointX - (whiteWaypointImage.getWidth() / 2),
|
||||
@ -717,16 +740,24 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
if (waypoints.size() > 0) {
|
||||
MapWaypoint selection = waypoints.get(0);
|
||||
currentlySelectedWaypoint = selection;
|
||||
currentlySelectedTrack = null;
|
||||
currentlySelectedSet = null;
|
||||
for (Set<MapWaypoint> track : tracks) {
|
||||
if (track.contains(selection)) {
|
||||
currentlySelectedTrack = track;
|
||||
currentlySelectedSet = track;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentlySelectedSet == null) {
|
||||
for (Set<MapWaypoint> area : areas) {
|
||||
if (area.contains(selection)) {
|
||||
currentlySelectedSet = area;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentlySelectedWaypoint = null;
|
||||
currentlySelectedTrack = null;
|
||||
currentlySelectedSet = null;
|
||||
}
|
||||
showDetailsPopup();
|
||||
}
|
||||
@ -755,6 +786,7 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
private class MapWaypointRenderer implements WaypointRenderer<MapWaypoint> {
|
||||
|
||||
private final Map<Color, BufferedImage> dotImageCache = new HashMap<>();
|
||||
private final Map<Color, BufferedImage> verySmallDotImageCache = new HashMap<>();
|
||||
private final Map<Color, BufferedImage> waypointImageCache = new HashMap<>();
|
||||
|
||||
/**
|
||||
@ -766,7 +798,7 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
private Color getColor(MapWaypoint waypoint) {
|
||||
Color baseColor = waypoint.getColor();
|
||||
if (waypoint.equals(currentlySelectedWaypoint)
|
||||
|| (currentlySelectedTrack != null && currentlySelectedTrack.contains(waypoint))) {
|
||||
|| (currentlySelectedSet != null && currentlySelectedSet.contains(waypoint))) {
|
||||
// Highlight this waypoint since it is selected
|
||||
return Color.YELLOW;
|
||||
} else {
|
||||
@ -778,11 +810,11 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
* Creates a dot image with the specified color
|
||||
*
|
||||
* @param color the color of the new image
|
||||
* @param s the size of the dot
|
||||
*
|
||||
* @return the new dot image
|
||||
*/
|
||||
private BufferedImage createTrackDotImage(Color color) {
|
||||
int s = DOT_SIZE;
|
||||
private BufferedImage createTrackDotImage(Color color, int s) {
|
||||
|
||||
BufferedImage ret = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = ret.createGraphics();
|
||||
@ -831,7 +863,13 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
|
||||
if (DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) {
|
||||
image = dotImageCache.computeIfAbsent(color, k -> {
|
||||
return createTrackDotImage(color);
|
||||
return createTrackDotImage(color, DOT_SIZE);
|
||||
});
|
||||
// Center the dot on the GPS coordinate
|
||||
y -= image.getHeight() / 2;
|
||||
} else if (VERY_SMALL_DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) {
|
||||
image = verySmallDotImageCache.computeIfAbsent(color, k -> {
|
||||
return createTrackDotImage(color, VERY_SMALL_DOT_SIZE);
|
||||
});
|
||||
// Center the dot on the GPS coordinate
|
||||
y -= image.getHeight() / 2;
|
||||
@ -903,4 +941,74 @@ final public class MapPanel extends javax.swing.JPanel {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer for map areas
|
||||
*/
|
||||
private class MapAreaRenderer implements Painter<JXMapViewer> {
|
||||
|
||||
private final List<Set<MapWaypoint>> areas;
|
||||
|
||||
MapAreaRenderer(List<Set<MapWaypoint>> areas) {
|
||||
this.areas = areas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shade in the area on the map.
|
||||
*
|
||||
* @param area The waypoints defining the outline of the area.
|
||||
* @param g Graphics2D
|
||||
* @param map JXMapViewer
|
||||
*/
|
||||
private void drawArea(Set<MapWaypoint> area, Graphics2D g, JXMapViewer map) {
|
||||
if (area.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
boolean first = true;
|
||||
|
||||
GeneralPath polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, area.size());
|
||||
|
||||
for (MapWaypoint wp : area) {
|
||||
Point2D p = map.getTileFactory().geoToPixel(wp.getPosition(), map.getZoom());
|
||||
int thisX = (int) p.getX();
|
||||
int thisY = (int) p.getY();
|
||||
|
||||
if (first) {
|
||||
polygon.moveTo(thisX, thisY);
|
||||
first = false;
|
||||
} else {
|
||||
polygon.lineTo(thisX, thisY);
|
||||
}
|
||||
}
|
||||
polygon.closePath();
|
||||
|
||||
Color areaColor = area.iterator().next().getColor();
|
||||
final double maxColorValue = 255.0;
|
||||
g.setPaint(new Color((float)(areaColor.getRed() / maxColorValue),
|
||||
(float)(areaColor.getGreen() / maxColorValue),
|
||||
(float)(areaColor.getBlue() / maxColorValue),
|
||||
.2f));
|
||||
g.fill(polygon);
|
||||
g.draw(polygon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics2D g, JXMapViewer map, int w, int h) {
|
||||
Graphics2D g2d = (Graphics2D) g.create();
|
||||
|
||||
Rectangle bounds = map.getViewportBounds();
|
||||
g2d.translate(-bounds.x, -bounds.y);
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.setStroke(new BasicStroke(2));
|
||||
|
||||
for (Set<MapWaypoint> area : areas) {
|
||||
drawArea(area, g2d, map);
|
||||
}
|
||||
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
|
||||
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID(), Color.ORANGE);
|
||||
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID(), Color.ORANGE);
|
||||
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID(), Color.MAGENTA);
|
||||
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID(), new Color(0x8a2be2)); // Blue violet
|
||||
}
|
||||
|
||||
private final Waypoint dataModelWaypoint;
|
||||
|
171
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Area.java
Normal file
171
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Area.java
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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.geolocation.datamodel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoAreaPoints;
|
||||
|
||||
/**
|
||||
* A GPS track with which wraps the TSK_GPS_AREA artifact.
|
||||
*/
|
||||
public final class Area extends GeoPath {
|
||||
/**
|
||||
* Construct a new Area for the given artifact.
|
||||
*
|
||||
* @param artifact
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
public Area(BlackboardArtifact artifact) throws GeoLocationDataException {
|
||||
this(artifact, Waypoint.getAttributesFromArtifactAsMap(artifact));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an Area for the given artifact and attributeMap.
|
||||
*
|
||||
* @param artifact TSK_GPD_TRACK artifact
|
||||
* @param attributeMap Map of the artifact attributes
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
private Area(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
|
||||
super(artifact, getAreaName(attributeMap));
|
||||
|
||||
GeoAreaPoints points = getPointsList(attributeMap);
|
||||
buildPath(points, artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the area from the attributeMap. Track name is stored
|
||||
* in the attribute TSK_NAME
|
||||
*
|
||||
* @param attributeMap
|
||||
*
|
||||
* @return Area name or empty string if none was available.
|
||||
*/
|
||||
private static String getAreaName(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) {
|
||||
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
|
||||
|
||||
return attribute != null ? attribute.getValueString() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the list of AreaWaypoints from the GeoTrackPoint list.
|
||||
*
|
||||
* @param points GeoAreaPoints object.
|
||||
* @param artifact The artifact to which these points belong
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
@Messages({
|
||||
"# {0} - area name",
|
||||
"GEOArea_point_label_header=Area outline point for area: {0}"
|
||||
})
|
||||
private void buildPath(GeoAreaPoints points, BlackboardArtifact artifact) throws GeoLocationDataException {
|
||||
for (GeoAreaPoints.AreaPoint point : points) {
|
||||
addToPath(new AreaWaypoint(artifact, Bundle.GEOArea_point_label_header(getLabel()), point));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of GeoAreaPoints from the attributeMap. Creates the
|
||||
* GeoAreaPoint list from the TSK_GEO_AREAPOINTS attribute.
|
||||
*
|
||||
* @param attributeMap Map of artifact attributes.
|
||||
*
|
||||
* @return GeoTrackPoint list empty list if the attribute was not found.
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
private GeoAreaPoints getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
|
||||
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS);
|
||||
if (attribute == null) {
|
||||
throw new GeoLocationDataException("No TSK_GEO_AREAPOINTS attribute present in attribute map to parse.");
|
||||
}
|
||||
|
||||
try {
|
||||
return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoAreaPoints.class);
|
||||
} catch (InvalidJsonException ex) {
|
||||
throw new GeoLocationDataException("Unable to parse area points in TSK_GEO_AREAPOINTS attribute", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Waypoint subclass for the points of an area outline.
|
||||
*/
|
||||
final class AreaWaypoint extends Waypoint {
|
||||
|
||||
private final List<Waypoint.Property> propertyList;
|
||||
|
||||
/**
|
||||
* Construct a AreaWaypoint.
|
||||
*
|
||||
* @param artifact the artifact to which this waypoint belongs
|
||||
*
|
||||
* @param pointLabel the label for the waypoint
|
||||
*
|
||||
* @param point GeoAreaPoint
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
AreaWaypoint(BlackboardArtifact artifact, String pointLabel, GeoAreaPoints.AreaPoint point) throws GeoLocationDataException {
|
||||
super(artifact, pointLabel,
|
||||
null,
|
||||
point.getLatitude(),
|
||||
point.getLongitude(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Area.this);
|
||||
|
||||
propertyList = createPropertyList(point);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded to return a property list that is generated from the
|
||||
* GeoTrackPoint instead of an artifact.
|
||||
*
|
||||
* @return unmodifiable list of Waypoint.Property
|
||||
*/
|
||||
@Override
|
||||
public List<Waypoint.Property> getOtherProperties() {
|
||||
return Collections.unmodifiableList(propertyList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a propertyList specific to GeoAreaPoints.
|
||||
*
|
||||
* @param point GeoAreaPoint to get values from.
|
||||
*
|
||||
* @return A list of Waypoint.properies.
|
||||
*/
|
||||
private List<Waypoint.Property> createPropertyList(GeoAreaPoints.AreaPoint point) {
|
||||
List<Waypoint.Property> list = new ArrayList<>();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
# {0} - area name
|
||||
GEOArea_point_label_header=Area outline point for area: {0}
|
||||
# {0} - track name
|
||||
GEOTrack_point_label_header=Trackpoint for track: {0}
|
||||
LastKnownWaypoint_Label=Last Known Location
|
||||
|
@ -22,6 +22,8 @@ package org.sleuthkit.autopsy.geolocation.datamodel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
@ -31,6 +33,8 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* Class representing a series of waypoints that form a path.
|
||||
*/
|
||||
public class GeoPath {
|
||||
private static final Logger LOGGER = Logger.getLogger(GeoPath.class.getName());
|
||||
|
||||
private final List<Waypoint> waypointList;
|
||||
private final String pathName;
|
||||
private final BlackboardArtifact artifact;
|
||||
@ -62,13 +66,13 @@ public class GeoPath {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of Routes from the TSK_GPS_TRACK artifacts.
|
||||
* Gets the list of Tracks from the TSK_GPS_TRACK artifacts.
|
||||
*
|
||||
* @param skCase Currently open SleuthkitCase
|
||||
* @param sourceList List of source to return tracks from, maybe null to
|
||||
* return tracks from all sources
|
||||
*
|
||||
* @return List of Route objects, empty list will be returned if no Routes
|
||||
* @return List of Track objects, empty list will be returned if no tracks
|
||||
* were found
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
@ -85,6 +89,7 @@ public class GeoPath {
|
||||
tracks.add(new Track(artifact));
|
||||
|
||||
} catch (GeoLocationDataException e) {
|
||||
LOGGER.log(Level.WARNING, "Error loading track from artifact with ID " + artifact.getArtifactID(), e);
|
||||
allParsedSuccessfully = false;
|
||||
}
|
||||
}
|
||||
@ -95,6 +100,41 @@ public class GeoPath {
|
||||
return new GeoLocationParseResult<Track>(tracks, allParsedSuccessfully);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of Areas from the TSK_GPS_AREA artifacts.
|
||||
*
|
||||
* @param skCase Currently open SleuthkitCase
|
||||
* @param sourceList List of source to return areas from, may be null to
|
||||
* return areas from all sources
|
||||
*
|
||||
* @return List of Area objects, empty list will be returned if no areas
|
||||
* were found
|
||||
*
|
||||
* @throws GeoLocationDataException
|
||||
*/
|
||||
public static GeoLocationParseResult<Area> getAreas(SleuthkitCase skCase, List<? extends Content> sourceList) throws GeoLocationDataException {
|
||||
List<BlackboardArtifact> artifacts;
|
||||
boolean allParsedSuccessfully = true;
|
||||
List<Area> areas = new ArrayList<>();
|
||||
try {
|
||||
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA);
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
if (sourceList == null || sourceList.contains(artifact.getDataSource())) {
|
||||
try {
|
||||
areas.add(new Area(artifact));
|
||||
|
||||
} catch (GeoLocationDataException e) {
|
||||
LOGGER.log(Level.WARNING, "Error loading track from artifact with ID " + artifact.getArtifactID(), e);
|
||||
allParsedSuccessfully = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);
|
||||
}
|
||||
return new GeoLocationParseResult<>(areas, allParsedSuccessfully);
|
||||
}
|
||||
|
||||
/**
|
||||
* Path constructor.
|
||||
*
|
||||
|
@ -22,20 +22,17 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* A GPS track with which wraps the TSK_GPS_TRACK artifact.
|
||||
*/
|
||||
public final class Track extends GeoPath {
|
||||
private static final Logger LOGGER = Logger.getLogger(Track.class.getName());
|
||||
|
||||
private final Long startTimestamp;
|
||||
private final Long endTimeStamp;
|
||||
@ -134,14 +131,12 @@ public final class Track extends GeoPath {
|
||||
private GeoTrackPoints getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
|
||||
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS);
|
||||
if (attribute == null) {
|
||||
LOGGER.log(Level.SEVERE, "No TSK_GEO_TRACKPOINTS attribute was present on the artifact.");
|
||||
throw new GeoLocationDataException("No TSK_GEO_TRACKPOINTS attribute present in attribute map to parse.");
|
||||
}
|
||||
|
||||
try {
|
||||
return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class);
|
||||
} catch (InvalidJsonException ex) {
|
||||
LOGGER.log(Level.SEVERE, "TSK_GEO_TRACKPOINTS could not be properly parsed from TSK_GEO_TRACKPOINTS attribute.");
|
||||
throw new GeoLocationDataException("Unable to parse track points in TSK_GEO_TRACKPOINTS attribute", ex);
|
||||
}
|
||||
}
|
||||
|
@ -179,6 +179,28 @@ public final class WaypointBuilder {
|
||||
return trackList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of areas from the given list of waypoints.
|
||||
*
|
||||
* @param waypoints A list of waypoints
|
||||
*
|
||||
* @return A list of areas or an empty list if none were found.
|
||||
*/
|
||||
public static List<Area> getAreas(List<Waypoint> waypoints) {
|
||||
List<Area> areaList = new ArrayList<>();
|
||||
for (Waypoint point : waypoints) {
|
||||
GeoPath path = point.getParentGeoPath();
|
||||
if (path instanceof Area) {
|
||||
Area area = (Area) path;
|
||||
if (!areaList.contains(area)) {
|
||||
areaList.add(area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return areaList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts.
|
||||
*
|
||||
|
@ -7,6 +7,10 @@ HealthMonitorDashboard.createTimingControlPanel.skipOutliers=Do not plot outlier
|
||||
HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled
|
||||
HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics
|
||||
HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display
|
||||
# {0} - Report file name
|
||||
HealthMonitorDashboard.createUserControlPanel.reportDone=Report saved to: {0}
|
||||
HealthMonitorDashboard.createUserControlPanel.reportError=Error generating report
|
||||
HealthMonitorDashboard.createUserControlPanel.userReportButton=Generate Report
|
||||
HealthMonitorDashboard.createUserPanel.noData=No data to display - monitor is not enabled
|
||||
HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics
|
||||
HealthMonitorDashboard.DateRange.oneDay=One day
|
||||
|
@ -65,7 +65,7 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
private final static Logger logger = Logger.getLogger(HealthMonitor.class.getName());
|
||||
private final static String DATABASE_NAME = "HealthMonitor";
|
||||
private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes
|
||||
private final static CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 1);
|
||||
private final static CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 2);
|
||||
|
||||
private final static AtomicBoolean isEnabled = new AtomicBoolean(false);
|
||||
private static HealthMonitor instance;
|
||||
@ -77,6 +77,7 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
private BasicDataSource connectionPool = null;
|
||||
private CaseDbConnectionInfo connectionSettingsInUse = null;
|
||||
private String hostName;
|
||||
private final String username;
|
||||
|
||||
private HealthMonitor() throws HealthMonitorException {
|
||||
|
||||
@ -97,6 +98,9 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex);
|
||||
}
|
||||
|
||||
// Get the user name
|
||||
username = System.getProperty("user.name");
|
||||
|
||||
// Read from the database to determine if the module is enabled
|
||||
updateFromGlobalEnabledStatus();
|
||||
|
||||
@ -154,7 +158,7 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
initializeDatabaseSchema();
|
||||
}
|
||||
|
||||
if (!CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) {
|
||||
if (getVersion().compareTo(CURRENT_DB_SCHEMA_VERSION) < 0) {
|
||||
upgradeDatabaseSchema();
|
||||
}
|
||||
|
||||
@ -179,10 +183,15 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
if (conn == null) {
|
||||
throw new HealthMonitorException("Error getting database connection");
|
||||
}
|
||||
ResultSet resultSet = null;
|
||||
|
||||
try (Statement statement = conn.createStatement()) {
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
// NOTE: Due to a bug in the upgrade code, earlier versions of Autopsy will erroneously
|
||||
// run the upgrade if the database is a higher version than it expects. Therefore all
|
||||
// table changes must account for the possiblility of running multiple times.
|
||||
|
||||
// Upgrade from 1.0 to 1.1
|
||||
// Changes: user_data table added
|
||||
if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) {
|
||||
@ -198,6 +207,19 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
+ ")");
|
||||
}
|
||||
|
||||
// Upgrade from 1.1 to 1.2
|
||||
// Changes: username added to user_data table
|
||||
if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 2)) < 0) {
|
||||
|
||||
resultSet = statement.executeQuery("SELECT column_name " +
|
||||
"FROM information_schema.columns " +
|
||||
"WHERE table_name='user_data' and column_name='username'");
|
||||
if (! resultSet.next()) {
|
||||
// Add the user_data table
|
||||
statement.execute("ALTER TABLE user_data ADD COLUMN username text");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the schema version
|
||||
statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "' WHERE name='SCHEMA_VERSION'");
|
||||
statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "' WHERE name='SCHEMA_MINOR_VERSION'");
|
||||
@ -212,6 +234,13 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
}
|
||||
throw new HealthMonitorException("Error upgrading database", ex);
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException ex2) {
|
||||
logger.log(Level.SEVERE, "Error closing result set");
|
||||
}
|
||||
}
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException ex) {
|
||||
@ -499,7 +528,7 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
|
||||
// Add metrics to the database
|
||||
String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
String addUserInfoSql = "INSERT INTO user_data (host, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?)";
|
||||
String addUserInfoSql = "INSERT INTO user_data (host, username, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql);
|
||||
PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) {
|
||||
|
||||
@ -519,10 +548,11 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
|
||||
for (UserData userInfo : userDataCopy) {
|
||||
userStatement.setString(1, hostName);
|
||||
userStatement.setLong(2, userInfo.getTimestamp());
|
||||
userStatement.setInt(3, userInfo.getEventType().getEventValue());
|
||||
userStatement.setBoolean(4, userInfo.isExaminerNode());
|
||||
userStatement.setString(5, userInfo.getCaseName());
|
||||
userStatement.setString(2, username);
|
||||
userStatement.setLong(3, userInfo.getTimestamp());
|
||||
userStatement.setInt(4, userInfo.getEventType().getEventValue());
|
||||
userStatement.setBoolean(5, userInfo.isExaminerNode());
|
||||
userStatement.setString(6, userInfo.getCaseName());
|
||||
userStatement.execute();
|
||||
}
|
||||
|
||||
@ -903,7 +933,8 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
+ "timestamp bigint NOT NULL,"
|
||||
+ "event_type int NOT NULL,"
|
||||
+ "is_examiner BOOLEAN NOT NULL,"
|
||||
+ "case_name text NOT NULL"
|
||||
+ "case_name text NOT NULL,"
|
||||
+ "username text"
|
||||
+ ")");
|
||||
|
||||
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')");
|
||||
@ -953,7 +984,7 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
getInstance().writeCurrentStateToDatabase();
|
||||
}
|
||||
} catch (HealthMonitorException ex) {
|
||||
logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Error recording health monitor metrics", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -1340,12 +1371,13 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
* Class holding user metric data. Can be used for storing new events or
|
||||
* retrieving events out of the database.
|
||||
*/
|
||||
static class UserData {
|
||||
static class UserData implements Comparable<UserData> {
|
||||
|
||||
private final UserEvent eventType;
|
||||
private long timestamp;
|
||||
private final boolean isExaminer;
|
||||
private final String hostname;
|
||||
private String username;
|
||||
private String caseName;
|
||||
|
||||
/**
|
||||
@ -1359,6 +1391,7 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode());
|
||||
this.hostname = "";
|
||||
this.username = "";
|
||||
|
||||
// If there's a case open, record the name
|
||||
try {
|
||||
@ -1383,6 +1416,10 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
this.eventType = UserEvent.valueOf(resultSet.getInt("event_type"));
|
||||
this.isExaminer = resultSet.getBoolean("is_examiner");
|
||||
this.caseName = resultSet.getString("case_name");
|
||||
this.username = resultSet.getString("username");
|
||||
if (this.username == null) {
|
||||
this.username = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1443,6 +1480,20 @@ public final class HealthMonitor implements PropertyChangeListener {
|
||||
String getCaseName() {
|
||||
return caseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user name for this metric
|
||||
*
|
||||
* @return the user name. Will be the empty string for older data.
|
||||
*/
|
||||
String getUserName() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(UserData otherData) {
|
||||
return Long.compare(getTimestamp(), otherData.getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,12 +24,17 @@ import java.awt.Dimension;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
@ -40,13 +45,19 @@ import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.BorderFactory;
|
||||
import java.util.Map;
|
||||
import javax.swing.BoxLayout;
|
||||
import java.awt.GridLayout;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.JFileChooser;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
@ -443,7 +454,11 @@ public class HealthMonitorDashboard {
|
||||
* Create the panel with controls for the user panel
|
||||
* @return the control panel
|
||||
*/
|
||||
@NbBundle.Messages({"HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display"})
|
||||
@NbBundle.Messages({"HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display",
|
||||
"HealthMonitorDashboard.createUserControlPanel.userReportButton=Generate Report",
|
||||
"HealthMonitorDashboard.createUserControlPanel.reportError=Error generating report",
|
||||
"# {0} - Report file name",
|
||||
"HealthMonitorDashboard.createUserControlPanel.reportDone=Report saved to: {0}"})
|
||||
private JPanel createUserControlPanel() {
|
||||
JPanel userControlPanel = new JPanel();
|
||||
|
||||
@ -473,9 +488,132 @@ public class HealthMonitorDashboard {
|
||||
userControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays()));
|
||||
userControlPanel.add(userDateComboBox);
|
||||
|
||||
// Create a button to create a user report
|
||||
JButton reportButton = new JButton(Bundle.HealthMonitorDashboard_createUserControlPanel_userReportButton());
|
||||
|
||||
// Set up a listener on the report button
|
||||
reportButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
JFileChooser reportFileChooser = new JFileChooser();
|
||||
reportFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||
reportFileChooser.setCurrentDirectory(new File(UserPreferences.getHealthMonitorReportPath()));
|
||||
final DateFormat csvTimestampFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
|
||||
String fileName = "UserReport_" + csvTimestampFormat.format(new Date())+ ".csv";
|
||||
reportFileChooser.setSelectedFile(new File(fileName));
|
||||
|
||||
int returnVal = reportFileChooser.showSaveDialog(userControlPanel);
|
||||
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||
|
||||
File selectedFile = reportFileChooser.getSelectedFile();
|
||||
UserPreferences.setHealthMonitorReportPath(selectedFile.getParent());
|
||||
try {
|
||||
dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
generateCSVUserReport(selectedFile);
|
||||
MessageNotifyUtil.Message.info(Bundle.HealthMonitorDashboard_createUserControlPanel_reportDone(selectedFile.getAbsoluteFile()));
|
||||
} catch (HealthMonitorException ex) {
|
||||
logger.log(Level.SEVERE, "Error generating report", ex);
|
||||
MessageNotifyUtil.Message.error(Bundle.HealthMonitorDashboard_createUserControlPanel_reportError());
|
||||
} finally {
|
||||
dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
userControlPanel.add(reportButton);
|
||||
|
||||
return userControlPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a csv report for the last week of user data.
|
||||
*
|
||||
* @param reportFile
|
||||
*
|
||||
* @throws HealthMonitorException
|
||||
*/
|
||||
private void generateCSVUserReport(File reportFile) throws HealthMonitorException {
|
||||
final DateFormat timestampFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile), StandardCharsets.UTF_8))) {
|
||||
// Write header
|
||||
writer.write("Case open,Case close,Duration,Host,User,Case name");
|
||||
writer.newLine();
|
||||
|
||||
// Get the list of user data sorted by timestamp
|
||||
List<HealthMonitor.UserData> dataForReport = HealthMonitor.getInstance().getUserMetricsFromDatabase(DateRange.ONE_WEEK.getTimestampRange());
|
||||
Collections.sort(dataForReport);
|
||||
|
||||
// Go through the list of events in order of timestamp. For each case open event, look for the next case closed
|
||||
// event for that host/user/case name.
|
||||
for (int caseOpenIndex = 0; caseOpenIndex < dataForReport.size() - 1; caseOpenIndex++) {
|
||||
if (! dataForReport.get(caseOpenIndex).getEventType().equals(HealthMonitor.UserEvent.CASE_OPEN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to find the next event logged for this user/host. We do not check that
|
||||
// it is a case closed event.
|
||||
HealthMonitor.UserData caseOpenEvent = dataForReport.get(caseOpenIndex);
|
||||
HealthMonitor.UserData nextEventAfterCaseOpen = null;
|
||||
for (int nextEventIndex = caseOpenIndex + 1; nextEventIndex < dataForReport.size(); nextEventIndex++) {
|
||||
HealthMonitor.UserData nextEvent = dataForReport.get(nextEventIndex);
|
||||
// If the user and host name do not match, ignore this event
|
||||
if ( nextEvent.getHostname().equals(caseOpenEvent.getHostname())
|
||||
&& nextEvent.getUserName().equals(caseOpenEvent.getUserName())) {
|
||||
nextEventAfterCaseOpen = nextEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the columns
|
||||
String caseOpenTime = timestampFormat.format(caseOpenEvent.getTimestamp());
|
||||
|
||||
// If everything is recorded properly then the next event for a given user after
|
||||
// a case open will be a case close. In this case we record the close time and
|
||||
// how long the case was open. If the next event was not a case close event
|
||||
// or if there is no next event (which could happen if Autopsy crashed or if
|
||||
// there were network issues, or if the user simply still has the case open),
|
||||
// leave the close time and duration blank.
|
||||
String caseCloseTime = "";
|
||||
String duration = "";
|
||||
if (nextEventAfterCaseOpen != null
|
||||
&& nextEventAfterCaseOpen.getEventType().equals(HealthMonitor.UserEvent.CASE_CLOSE)
|
||||
&& nextEventAfterCaseOpen.getCaseName().equals(caseOpenEvent.getCaseName())) {
|
||||
caseCloseTime = timestampFormat.format(nextEventAfterCaseOpen.getTimestamp());
|
||||
duration = getDuration(caseOpenEvent.getTimestamp(), nextEventAfterCaseOpen.getTimestamp());
|
||||
}
|
||||
|
||||
String host = caseOpenEvent.getHostname();
|
||||
String user = caseOpenEvent.getUserName();
|
||||
String caseName = caseOpenEvent.getCaseName();
|
||||
|
||||
String csvEntry = caseOpenTime + "," + caseCloseTime + "," + duration + "," + host + "," + user + ",\"" + caseName + "\"";
|
||||
writer.write(csvEntry);
|
||||
writer.newLine();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new HealthMonitorException("Error writing to output file " + reportFile.getAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a string representing the time between
|
||||
* the given timestamps.
|
||||
*
|
||||
* @param start The starting timestamp.
|
||||
* @param end The ending timestamp.
|
||||
*
|
||||
* @return The duration as a string.
|
||||
*/
|
||||
private String getDuration(long start, long end) {
|
||||
long durationInSeconds = (end - start) / 1000;
|
||||
long second = durationInSeconds % 60;
|
||||
long minute = (durationInSeconds / 60) % 60;
|
||||
long hours = durationInSeconds / (60 * 60);
|
||||
|
||||
return String.format("%d:%02d:%02d", hours, minute, second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user graphs.
|
||||
* @throws HealthMonitorException
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/images/domain-16.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/domain-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
BIN
Core/src/org/sleuthkit/autopsy/images/gps-area.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/gps-area.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 984 B |
@ -1,4 +0,0 @@
|
||||
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
@ -1,30 +0,0 @@
|
||||
ILeappAnalyzerIngestModule.completed=iLeapp Processing Completed
|
||||
ILeappAnalyzerIngestModule.error.creating.output.dir=Error creating iLeapp module output directory.
|
||||
ILeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize ILeappProcessFile
|
||||
ILeappAnalyzerIngestModule.error.running.iLeapp=Error running iLeapp, see log file.
|
||||
ILeappAnalyzerIngestModule.executable.not.found=iLeapp Executable Not Found.
|
||||
ILeappAnalyzerIngestModule.has.run=iLeapp
|
||||
ILeappAnalyzerIngestModule.iLeapp.cancelled=iLeapp run was canceled
|
||||
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
||||
ILeappAnalyzerIngestModule.report.name=iLeapp Html Report
|
||||
ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows.
|
||||
ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp
|
||||
ILeappAnalyzerIngestModule.starting.iLeapp=Starting iLeapp
|
||||
ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices.
|
||||
ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP)
|
||||
ILeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.
|
||||
ILeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.
|
||||
ILeappFileProcessor.completed=iLeapp Processing Completed
|
||||
ILeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts.
|
||||
ILeappFileProcessor.error.creating.output.dir=Error creating iLeapp module output directory.
|
||||
ILeappFileProcessor.error.reading.iLeapp.directory=Error reading iLeapp Output Directory
|
||||
ILeappFileProcessor.error.running.iLeapp=Error running iLeapp, see log file.
|
||||
ILeappFileProcessor.has.run=iLeapp
|
||||
ILeappFileProcessor.iLeapp.cancelled=iLeapp run was canceled
|
||||
ILeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact
|
||||
ILeappFileProcessor.running.iLeapp=Running iLeapp
|
||||
ILeappFileProcessor.starting.iLeapp=Starting iLeapp
|
||||
ILeappFileProcessor_cannotParseXml=Cannot Parse XML file.
|
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* 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.modules.leappanalyzers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.openide.modules.InstalledFileLocator;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.getCurrentCase;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
import org.sleuthkit.autopsy.coreutils.ExecUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
|
||||
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator;
|
||||
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestMessage;
|
||||
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.LocalFilesDataSource;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Data source ingest module that runs aLeapp against logical iOS files.
|
||||
*/
|
||||
public class ALeappAnalyzerIngestModule implements DataSourceIngestModule {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ALeappAnalyzerIngestModule.class.getName());
|
||||
private static final String MODULE_NAME = ALeappAnalyzerModuleFactory.getModuleName();
|
||||
|
||||
private static final String ALEAPP = "aLeapp"; //NON-NLS
|
||||
private static final String ALEAPP_FS = "fs_"; //NON-NLS
|
||||
private static final String ALEAPP_EXECUTABLE = "aleapp.exe";//NON-NLS
|
||||
private static final String ALEAPP_PATHS_FILE = "aLeapp_paths.txt"; //NON-NLS
|
||||
|
||||
private static final String XMLFILE = "aleap-artifact-attribute-reference.xml"; //NON-NLS
|
||||
|
||||
|
||||
private File aLeappExecutable;
|
||||
|
||||
private IngestJobContext context;
|
||||
|
||||
private LeappFileProcessor aLeappFileProcessor;
|
||||
|
||||
ALeappAnalyzerIngestModule() {
|
||||
// This constructor is intentionally empty. Nothing special is needed here.
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ALeappAnalyzerIngestModule.executable.not.found=aLeapp Executable Not Found.",
|
||||
"ALeappAnalyzerIngestModule.requires.windows=aLeapp module requires windows.",
|
||||
"ALeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize aLeappProcessFile"})
|
||||
@Override
|
||||
public void startUp(IngestJobContext context) throws IngestModuleException {
|
||||
this.context = context;
|
||||
|
||||
if (false == PlatformUtil.isWindowsOS()) {
|
||||
throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_requires_windows());
|
||||
}
|
||||
|
||||
try {
|
||||
aLeappFileProcessor = new LeappFileProcessor(XMLFILE);
|
||||
} catch (IOException | IngestModuleException | NoCurrentCaseException ex) {
|
||||
throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex);
|
||||
}
|
||||
|
||||
try {
|
||||
aLeappExecutable = locateExecutable(ALEAPP_EXECUTABLE);
|
||||
} catch (FileNotFoundException exception) {
|
||||
logger.log(Level.WARNING, "aLeapp executable not found.", exception); //NON-NLS
|
||||
throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_executable_not_found(), exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ALeappAnalyzerIngestModule.error.running.aLeapp=Error running aLeapp, see log file.",
|
||||
"ALeappAnalyzerIngestModule.error.creating.output.dir=Error creating aLeapp module output directory.",
|
||||
"ALeappAnalyzerIngestModule.starting.aLeapp=Starting aLeapp",
|
||||
"ALeappAnalyzerIngestModule.running.aLeapp=Running aLeapp",
|
||||
"ALeappAnalyzerIngestModule.has.run=aLeapp",
|
||||
"ALeappAnalyzerIngestModule.aLeapp.cancelled=aLeapp run was canceled",
|
||||
"ALeappAnalyzerIngestModule.completed=aLeapp Processing Completed",
|
||||
"ALeappAnalyzerIngestModule.report.name=aLeapp Html Report"})
|
||||
@Override
|
||||
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
|
||||
|
||||
Case currentCase = Case.getCurrentCase();
|
||||
Path tempOutputPath = Paths.get(currentCase.getTempDirectory(), ALEAPP, ALEAPP_FS + dataSource.getId());
|
||||
try {
|
||||
Files.createDirectories(tempOutputPath);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error creating aLeapp output directory %s", tempOutputPath.toString()), ex);
|
||||
return ProcessResult.ERROR;
|
||||
}
|
||||
|
||||
List<String> aLeappPathsToProcess = new ArrayList<>();
|
||||
ProcessBuilder aLeappCommand = buildaLeappListCommand(tempOutputPath);
|
||||
try {
|
||||
int result = ExecUtil.execute(aLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
|
||||
if (result != 0) {
|
||||
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
|
||||
return ProcessResult.ERROR;
|
||||
}
|
||||
aLeappPathsToProcess = loadIleappPathFile(tempOutputPath);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program getting file paths to search"), ex);
|
||||
return ProcessResult.ERROR;
|
||||
}
|
||||
|
||||
statusHelper.progress(Bundle.ALeappAnalyzerIngestModule_starting_aLeapp(), 0);
|
||||
|
||||
List<AbstractFile> aLeappFilesToProcess = new ArrayList<>();
|
||||
|
||||
if (!(context.getDataSource() instanceof LocalFilesDataSource)) {
|
||||
extractFilesFromImage(dataSource, aLeappPathsToProcess, tempOutputPath);
|
||||
statusHelper.switchToDeterminate(aLeappFilesToProcess.size());
|
||||
processALeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString());
|
||||
} else {
|
||||
aLeappFilesToProcess = findaLeappFilesToProcess(dataSource);
|
||||
statusHelper.switchToDeterminate(aLeappFilesToProcess.size());
|
||||
|
||||
Integer filesProcessedCount = 0;
|
||||
for (AbstractFile aLeappFile : aLeappFilesToProcess) {
|
||||
processALeappFile(dataSource, currentCase, statusHelper, filesProcessedCount, aLeappFile);
|
||||
filesProcessedCount++;
|
||||
}
|
||||
// Process the logical image as a fs in aLeapp to make sure this is not a logical fs that was added
|
||||
extractFilesFromImage(dataSource, aLeappPathsToProcess, tempOutputPath);
|
||||
processALeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString());
|
||||
}
|
||||
|
||||
IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
|
||||
Bundle.ALeappAnalyzerIngestModule_has_run(),
|
||||
Bundle.ALeappAnalyzerIngestModule_completed());
|
||||
IngestServices.getInstance().postMessage(message);
|
||||
return ProcessResult.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a file from a logical image using the aLeapp program
|
||||
* @param dataSource datasource to process
|
||||
* @param currentCase current case that is being worked on
|
||||
* @param statusHelper show progress and update what is being processed
|
||||
* @param filesProcessedCount number of files that have been processed
|
||||
* @param aLeappFile the abstract file to process
|
||||
*/
|
||||
private void processALeappFile(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, int filesProcessedCount,
|
||||
AbstractFile aLeappFile) {
|
||||
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
|
||||
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ALEAPP, currentTime);
|
||||
try {
|
||||
Files.createDirectories(moduleOutputPath);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error creating aLeapp output directory %s", moduleOutputPath.toString()), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
statusHelper.progress(NbBundle.getMessage(this.getClass(), "ALeappAnalyzerIngestModule.processing.file", aLeappFile.getName()), filesProcessedCount);
|
||||
ProcessBuilder aLeappCommand = buildaLeappCommand(moduleOutputPath, aLeappFile.getLocalAbsPath(), aLeappFile.getNameExtension());
|
||||
try {
|
||||
int result = ExecUtil.execute(aLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
|
||||
if (result != 0) {
|
||||
logger.log(Level.WARNING, String.format("Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
|
||||
return;
|
||||
}
|
||||
|
||||
addILeappReportToReports(moduleOutputPath, currentCase);
|
||||
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program against file %s", aLeappFile.getLocalAbsPath()), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessResult fileProcessorResult = aLeappFileProcessor.processFiles(dataSource, moduleOutputPath, aLeappFile);
|
||||
|
||||
if (fileProcessorResult == ProcessResult.ERROR) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a image/directory using the aLeapp program
|
||||
* @param dataSource datasource to process
|
||||
* @param currentCase current case being procesed
|
||||
* @param statusHelper show progress and update what is being processed
|
||||
* @param directoryToProcess directory to run aLeapp against
|
||||
*/
|
||||
private void processALeappFs(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, String directoryToProcess) {
|
||||
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
|
||||
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ALEAPP, currentTime);
|
||||
try {
|
||||
Files.createDirectories(moduleOutputPath);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error creating aLeapp output directory %s", moduleOutputPath.toString()), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
statusHelper.progress(NbBundle.getMessage(this.getClass(), "ALeappAnalyzerIngestModule.processing.filesystem"));
|
||||
ProcessBuilder aLeappCommand = buildaLeappCommand(moduleOutputPath, directoryToProcess, "fs");
|
||||
try {
|
||||
int result = ExecUtil.execute(aLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
|
||||
if (result != 0) {
|
||||
logger.log(Level.WARNING, String.format("Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
|
||||
return;
|
||||
}
|
||||
|
||||
addILeappReportToReports(moduleOutputPath, currentCase);
|
||||
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program against file system"), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessResult fileProcessorResult = aLeappFileProcessor.processFileSystem(dataSource, moduleOutputPath);
|
||||
|
||||
if (fileProcessorResult == ProcessResult.ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the files that will be processed by the aLeapp program
|
||||
*
|
||||
* @param dataSource
|
||||
*
|
||||
* @return List of abstract files to process.
|
||||
*/
|
||||
private List<AbstractFile> findaLeappFilesToProcess(Content dataSource) {
|
||||
|
||||
List<AbstractFile> aLeappFiles = new ArrayList<>();
|
||||
|
||||
FileManager fileManager = getCurrentCase().getServices().getFileManager();
|
||||
|
||||
// findFiles use the SQL wildcard % in the file name
|
||||
try {
|
||||
aLeappFiles = fileManager.findFiles(dataSource, "%", "/"); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "No files found to process"); //NON-NLS
|
||||
return aLeappFiles;
|
||||
}
|
||||
|
||||
List<AbstractFile> aLeappFilesToProcess = new ArrayList<>();
|
||||
for (AbstractFile aLeappFile : aLeappFiles) {
|
||||
if (((aLeappFile.getLocalAbsPath() != null)
|
||||
&& (!aLeappFile.getNameExtension().isEmpty() && (!aLeappFile.isVirtual())))
|
||||
&& ((aLeappFile.getName().toLowerCase().contains(".zip") || (aLeappFile.getName().toLowerCase().contains(".tar")))
|
||||
|| aLeappFile.getName().toLowerCase().contains(".tgz"))) {
|
||||
aLeappFilesToProcess.add(aLeappFile);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return aLeappFilesToProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the aLeapp command to run
|
||||
*
|
||||
* @param moduleOutputPath output path for the aLeapp program.
|
||||
* @param sourceFilePath where the source files to process reside.
|
||||
* @param aLeappFileSystemType the filesystem type to process
|
||||
*
|
||||
* @return the command to execute
|
||||
*/
|
||||
private ProcessBuilder buildaLeappCommand(Path moduleOutputPath, String sourceFilePath, String aLeappFileSystemType) {
|
||||
|
||||
ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
|
||||
"\"" + aLeappExecutable + "\"", //NON-NLS
|
||||
"-t", aLeappFileSystemType, //NON-NLS
|
||||
"-i", sourceFilePath, //NON-NLS
|
||||
"-o", moduleOutputPath.toString()
|
||||
);
|
||||
processBuilder.redirectError(moduleOutputPath.resolve("aLeapp_err.txt").toFile()); //NON-NLS
|
||||
processBuilder.redirectOutput(moduleOutputPath.resolve("aLeapp_out.txt").toFile()); //NON-NLS
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private ProcessBuilder buildaLeappListCommand(Path moduleOutputPath) {
|
||||
|
||||
ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
|
||||
"\"" + aLeappExecutable + "\"", //NON-NLS
|
||||
"-p"
|
||||
);
|
||||
processBuilder.redirectError(moduleOutputPath.resolve("aLeapp_paths_error.txt").toFile()); //NON-NLS
|
||||
processBuilder.redirectOutput(moduleOutputPath.resolve("aLeapp_paths.txt").toFile()); //NON-NLS
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
|
||||
/*
|
||||
* Add an environment variable to force aLeapp to run with
|
||||
* the same permissions Autopsy uses.
|
||||
*/
|
||||
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private static File locateExecutable(String executableName) throws FileNotFoundException {
|
||||
String executableToFindName = Paths.get(ALEAPP, executableName).toString();
|
||||
|
||||
File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, ALeappAnalyzerIngestModule.class.getPackage().getName(), false);
|
||||
if (null == exeFile || exeFile.canExecute() == false) {
|
||||
throw new FileNotFoundException(executableName + " executable not found.");
|
||||
}
|
||||
return exeFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index.html file in the aLeapp output directory so it can be
|
||||
* added to reports
|
||||
*/
|
||||
private void addILeappReportToReports(Path aLeappOutputDir, Case currentCase) {
|
||||
List<String> allIndexFiles = new ArrayList<>();
|
||||
|
||||
try (Stream<Path> walk = Files.walk(aLeappOutputDir)) {
|
||||
|
||||
allIndexFiles = walk.map(x -> x.toString())
|
||||
.filter(f -> f.toLowerCase().endsWith("index.html")).collect(Collectors.toList());
|
||||
|
||||
if (!allIndexFiles.isEmpty()) {
|
||||
// Check for existance of directory that holds report data if does not exist then report contains no data
|
||||
String filePath = FilenameUtils.getFullPathNoEndSeparator(allIndexFiles.get(0));
|
||||
File dataFilesDir = new File(Paths.get(filePath, "_TSV Exports").toString());
|
||||
if (dataFilesDir.exists()) {
|
||||
currentCase.addReport(allIndexFiles.get(0), MODULE_NAME, Bundle.ALeappAnalyzerIngestModule_report_name());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException | UncheckedIOException | TskCoreException ex) {
|
||||
// catch the error and continue on as report is not added
|
||||
logger.log(Level.WARNING, String.format("Error finding index file in path %s", aLeappOutputDir.toString()), ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the aLeapp paths file to get the paths that we want to extract
|
||||
*
|
||||
*/
|
||||
private List<String> loadIleappPathFile(Path moduleOutputPath) throws FileNotFoundException, IOException {
|
||||
List<String> aLeappPathsToProcess = new ArrayList<>();
|
||||
|
||||
Path filePath = Paths.get(moduleOutputPath.toString(), ALEAPP_PATHS_FILE);
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toString()))) {
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
if (line.contains("path list generation") || line.length() < 2) {
|
||||
line = reader.readLine();
|
||||
continue;
|
||||
}
|
||||
aLeappPathsToProcess.add(line.trim());
|
||||
line = reader.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
return aLeappPathsToProcess;
|
||||
}
|
||||
|
||||
private void extractFilesFromImage(Content dataSource, List<String> aLeappPathsToProcess, Path moduleOutputPath) {
|
||||
FileManager fileManager = getCurrentCase().getServices().getFileManager();
|
||||
|
||||
for (String fullFilePath : aLeappPathsToProcess) {
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
logger.log(Level.INFO, "aLeapp Analyser ingest module run was canceled"); //NON-NLS
|
||||
break;
|
||||
}
|
||||
|
||||
String ffp = fullFilePath.replaceAll("\\*", "%");
|
||||
ffp = FilenameUtils.normalize(ffp, true);
|
||||
String fileName = FilenameUtils.getName(ffp);
|
||||
String filePath = FilenameUtils.getPath(ffp);
|
||||
|
||||
List<AbstractFile> aLeappFiles = new ArrayList<>();
|
||||
try {
|
||||
if (filePath.isEmpty()) {
|
||||
aLeappFiles = fileManager.findFiles(dataSource, fileName); //NON-NLS
|
||||
} else {
|
||||
aLeappFiles = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "No files found to process"); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
for (AbstractFile aLeappFile : aLeappFiles) {
|
||||
Path parentPath = Paths.get(moduleOutputPath.toString(), aLeappFile.getParentPath());
|
||||
File fileParentPath = new File(parentPath.toString());
|
||||
|
||||
extractFileToOutput(dataSource, aLeappFile, fileParentPath, parentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void extractFileToOutput(Content dataSource, AbstractFile aLeappFile, File fileParentPath, Path parentPath) {
|
||||
if (fileParentPath.exists()) {
|
||||
if (!aLeappFile.isDir()) {
|
||||
writeaLeappFile(dataSource, aLeappFile, fileParentPath.toString());
|
||||
} else {
|
||||
try {
|
||||
Files.createDirectories(Paths.get(parentPath.toString(), aLeappFile.getName()));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.INFO, String.format("Error creating aLeapp output directory %s", parentPath.toString()), ex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Files.createDirectories(parentPath);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.INFO, String.format("Error creating aLeapp output directory %s", parentPath.toString()), ex);
|
||||
}
|
||||
if (!aLeappFile.isDir()) {
|
||||
writeaLeappFile(dataSource, aLeappFile, fileParentPath.toString());
|
||||
} else {
|
||||
try {
|
||||
Files.createDirectories(Paths.get(parentPath.toString(), aLeappFile.getName()));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.INFO, String.format("Error creating aLeapp output directory %s", parentPath.toString()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeaLeappFile(Content dataSource, AbstractFile aLeappFile, String parentPath) {
|
||||
String fileName = aLeappFile.getName().replace(":", "-");
|
||||
if (!fileName.matches(".") && !fileName.matches("..") && !fileName.toLowerCase().endsWith("-slack")) {
|
||||
Path filePath = Paths.get(parentPath, fileName);
|
||||
File localFile = new File(filePath.toString());
|
||||
try {
|
||||
ContentUtils.writeToFile(aLeappFile, localFile, context::dataSourceIngestIsCancelled);
|
||||
} catch (ReadContentInputStream.ReadContentInputStreamException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).",
|
||||
aLeappFile.getName(), aLeappFile.getId()), ex); //NON-NLS
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error writing file local file '%s' (id=%d).",
|
||||
filePath.toString(), aLeappFile.getId()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.modules.leappanalyzers;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.coreutils.Version;
|
||||
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleFactory;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
|
||||
|
||||
/**
|
||||
* A factory that creates data source ingest modules that will run aLeapp
|
||||
* against logical files and saves the output to module output.
|
||||
*/
|
||||
@ServiceProvider(service = IngestModuleFactory.class)
|
||||
public class ALeappAnalyzerModuleFactory extends IngestModuleFactoryAdapter {
|
||||
|
||||
@NbBundle.Messages({"ALeappAnalyzerModuleFactory_moduleName=Android Analyzer (aLEAPP)"})
|
||||
static String getModuleName() {
|
||||
return Bundle.ALeappAnalyzerModuleFactory_moduleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleDisplayName() {
|
||||
return getModuleName();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"ALeappAnalyzerModuleFactory_moduleDesc=Uses aLEAPP to analyze logical acquisitions of Android devices."})
|
||||
@Override
|
||||
public String getModuleDescription() {
|
||||
return Bundle.ALeappAnalyzerModuleFactory_moduleDesc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleVersionNumber() {
|
||||
return Version.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDataSourceIngestModuleFactory() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings ingestJobOptions) {
|
||||
return new ALeappAnalyzerIngestModule();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
||||
ALeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||
ALeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||
ALeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||
ALeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
@ -0,0 +1,47 @@
|
||||
ALeappAnalyzerIngestModule.aLeapp.cancelled=aLeapp run was canceled
|
||||
ALeappAnalyzerIngestModule.completed=aLeapp Processing Completed
|
||||
ALeappAnalyzerIngestModule.error.creating.output.dir=Error creating aLeapp module output directory.
|
||||
ALeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize aLeappProcessFile
|
||||
ALeappAnalyzerIngestModule.error.running.aLeapp=Error running aLeapp, see log file.
|
||||
ALeappAnalyzerIngestModule.executable.not.found=aLeapp Executable Not Found.
|
||||
ALeappAnalyzerIngestModule.has.run=aLeapp
|
||||
ALeappAnalyzerIngestModule.report.name=aLeapp Html Report
|
||||
ALeappAnalyzerIngestModule.requires.windows=aLeapp module requires windows.
|
||||
ALeappAnalyzerIngestModule.running.aLeapp=Running aLeapp
|
||||
ALeappAnalyzerIngestModule.starting.aLeapp=Starting aLeapp
|
||||
ALeappAnalyzerModuleFactory_moduleDesc=Uses aLEAPP to analyze logical acquisitions of Android devices.
|
||||
ALeappAnalyzerModuleFactory_moduleName=Android Analyzer (aLEAPP)
|
||||
ILeappAnalyzerIngestModule.completed=iLeapp Processing Completed
|
||||
ILeappAnalyzerIngestModule.error.creating.output.dir=Error creating iLeapp module output directory.
|
||||
ILeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize ILeappProcessFile
|
||||
ILeappAnalyzerIngestModule.error.running.iLeapp=Error running iLeapp, see log file.
|
||||
ILeappAnalyzerIngestModule.executable.not.found=iLeapp Executable Not Found.
|
||||
ILeappAnalyzerIngestModule.has.run=iLeapp
|
||||
ILeappAnalyzerIngestModule.iLeapp.cancelled=iLeapp run was canceled
|
||||
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
||||
ALeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||
ALeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||
ALeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||
ALeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
||||
ILeappAnalyzerIngestModule.report.name=iLeapp Html Report
|
||||
ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows.
|
||||
ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp
|
||||
ILeappAnalyzerIngestModule.starting.iLeapp=Starting iLeapp
|
||||
ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices.
|
||||
ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP)
|
||||
LeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.
|
||||
LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.
|
||||
LeappFileProcessor.completed=Leapp Processing Completed
|
||||
LeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts.
|
||||
LeappFileProcessor.error.creating.output.dir=Error creating Leapp module output directory.
|
||||
LeappFileProcessor.error.reading.Leapp.directory=Error reading Leapp Output Directory
|
||||
LeappFileProcessor.error.running.Leapp=Error running Leapp, see log file.
|
||||
LeappFileProcessor.has.run=Leapp
|
||||
LeappFileProcessor.Leapp.cancelled=Leapp run was canceled
|
||||
LeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact
|
||||
LeappFileProcessor.running.Leapp=Running Leapp
|
||||
LeappFileProcessor.starting.Leapp=Starting Leapp
|
||||
LeappFileProcessor_cannotParseXml=Cannot Parse XML file.
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.modules.ileappanalyzer;
|
||||
package org.sleuthkit.autopsy.modules.leappanalyzers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@ -71,11 +71,14 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
|
||||
private static final String ILEAPP_EXECUTABLE = "ileapp.exe";//NON-NLS
|
||||
private static final String ILEAPP_PATHS_FILE = "iLeapp_paths.txt"; //NON-NLS
|
||||
|
||||
private static final String XMLFILE = "ileap-artifact-attribute-reference.xml"; //NON-NLS
|
||||
|
||||
|
||||
private File iLeappExecutable;
|
||||
|
||||
private IngestJobContext context;
|
||||
|
||||
private ILeappFileProcessor iLeappFileProcessor;
|
||||
private LeappFileProcessor iLeappFileProcessor;
|
||||
|
||||
ILeappAnalyzerIngestModule() {
|
||||
// This constructor is intentionally empty. Nothing special is needed here.
|
||||
@ -94,7 +97,7 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
|
||||
}
|
||||
|
||||
try {
|
||||
iLeappFileProcessor = new ILeappFileProcessor();
|
||||
iLeappFileProcessor = new LeappFileProcessor(XMLFILE);
|
||||
} catch (IOException | IngestModuleException | NoCurrentCaseException ex) {
|
||||
throw new IngestModuleException(Bundle.ILeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex);
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.modules.ileappanalyzer;
|
||||
package org.sleuthkit.autopsy.modules.leappanalyzers;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.modules.ileappanalyzer;
|
||||
package org.sleuthkit.autopsy.modules.leappanalyzers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@ -63,14 +63,14 @@ import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Find and process output from iLeapp program and bring into Autopsy
|
||||
* Find and process output from Leapp program and bring into Autopsy
|
||||
*/
|
||||
public final class ILeappFileProcessor {
|
||||
public final class LeappFileProcessor {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ILeappFileProcessor.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(LeappFileProcessor.class.getName());
|
||||
private static final String MODULE_NAME = ILeappAnalyzerModuleFactory.getModuleName();
|
||||
|
||||
private static final String XMLFILE = "ileap-artifact-attribute-reference.xml"; //NON-NLS
|
||||
private final String xmlFile; //NON-NLS
|
||||
|
||||
private final Map<String, String> tsvFiles;
|
||||
private final Map<String, String> tsvFileArtifacts;
|
||||
@ -79,11 +79,12 @@ public final class ILeappFileProcessor {
|
||||
|
||||
Blackboard blkBoard;
|
||||
|
||||
public ILeappFileProcessor() throws IOException, IngestModuleException, NoCurrentCaseException {
|
||||
public LeappFileProcessor(String xmlFile) throws IOException, IngestModuleException, NoCurrentCaseException {
|
||||
this.tsvFiles = new HashMap<>();
|
||||
this.tsvFileArtifacts = new HashMap<>();
|
||||
this.tsvFileArtifactComments = new HashMap<>();
|
||||
this.tsvFileAttributes = new HashMap<>();
|
||||
this.xmlFile = xmlFile;
|
||||
|
||||
blkBoard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
|
||||
|
||||
@ -93,22 +94,22 @@ public final class ILeappFileProcessor {
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ILeappFileProcessor.error.running.iLeapp=Error running iLeapp, see log file.",
|
||||
"ILeappFileProcessor.error.creating.output.dir=Error creating iLeapp module output directory.",
|
||||
"ILeappFileProcessor.starting.iLeapp=Starting iLeapp",
|
||||
"ILeappFileProcessor.running.iLeapp=Running iLeapp",
|
||||
"ILeappFileProcessor.has.run=iLeapp",
|
||||
"ILeappFileProcessor.iLeapp.cancelled=iLeapp run was canceled",
|
||||
"ILeappFileProcessor.completed=iLeapp Processing Completed",
|
||||
"ILeappFileProcessor.error.reading.iLeapp.directory=Error reading iLeapp Output Directory"})
|
||||
"LeappFileProcessor.error.running.Leapp=Error running Leapp, see log file.",
|
||||
"LeappFileProcessor.error.creating.output.dir=Error creating Leapp module output directory.",
|
||||
"LeappFileProcessor.starting.Leapp=Starting Leapp",
|
||||
"LeappFileProcessor.running.Leapp=Running Leapp",
|
||||
"LeappFileProcessor.has.run=Leapp",
|
||||
"LeappFileProcessor.Leapp.cancelled=Leapp run was canceled",
|
||||
"LeappFileProcessor.completed=Leapp Processing Completed",
|
||||
"LeappFileProcessor.error.reading.Leapp.directory=Error reading Leapp Output Directory"})
|
||||
|
||||
public ProcessResult processFiles(Content dataSource, Path moduleOutputPath, AbstractFile iLeappFile) {
|
||||
public ProcessResult processFiles(Content dataSource, Path moduleOutputPath, AbstractFile LeappFile) {
|
||||
|
||||
try {
|
||||
List<String> iLeappTsvOutputFiles = findTsvFiles(moduleOutputPath);
|
||||
processiLeappFiles(iLeappTsvOutputFiles, iLeappFile);
|
||||
List<String> LeappTsvOutputFiles = findTsvFiles(moduleOutputPath);
|
||||
processLeappFiles(LeappTsvOutputFiles, LeappFile);
|
||||
} catch (IOException | IngestModuleException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error trying to process iLeapp output files in directory %s. ", moduleOutputPath.toString()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Error trying to process Leapp output files in directory %s. ", moduleOutputPath.toString()), ex); //NON-NLS
|
||||
return ProcessResult.ERROR;
|
||||
}
|
||||
|
||||
@ -118,10 +119,10 @@ public final class ILeappFileProcessor {
|
||||
public ProcessResult processFileSystem(Content dataSource, Path moduleOutputPath) {
|
||||
|
||||
try {
|
||||
List<String> iLeappTsvOutputFiles = findTsvFiles(moduleOutputPath);
|
||||
processiLeappFiles(iLeappTsvOutputFiles, dataSource);
|
||||
List<String> LeappTsvOutputFiles = findTsvFiles(moduleOutputPath);
|
||||
processLeappFiles(LeappTsvOutputFiles, dataSource);
|
||||
} catch (IOException | IngestModuleException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error trying to process iLeapp output files in directory %s. ", moduleOutputPath.toString()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Error trying to process Leapp output files in directory %s. ", moduleOutputPath.toString()), ex); //NON-NLS
|
||||
return ProcessResult.ERROR;
|
||||
}
|
||||
|
||||
@ -129,14 +130,14 @@ public final class ILeappFileProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the tsv files in the iLeapp output directory and match them to files
|
||||
* Find the tsv files in the Leapp output directory and match them to files
|
||||
* we know we want to process and return the list to process those files.
|
||||
*/
|
||||
private List<String> findTsvFiles(Path iLeappOutputDir) throws IngestModuleException {
|
||||
private List<String> findTsvFiles(Path LeappOutputDir) throws IngestModuleException {
|
||||
List<String> allTsvFiles = new ArrayList<>();
|
||||
List<String> foundTsvFiles = new ArrayList<>();
|
||||
|
||||
try (Stream<Path> walk = Files.walk(iLeappOutputDir)) {
|
||||
try (Stream<Path> walk = Files.walk(LeappOutputDir)) {
|
||||
|
||||
allTsvFiles = walk.map(x -> x.toString())
|
||||
.filter(f -> f.toLowerCase().endsWith(".tsv")).collect(Collectors.toList());
|
||||
@ -148,7 +149,7 @@ public final class ILeappFileProcessor {
|
||||
}
|
||||
|
||||
} catch (IOException | UncheckedIOException e) {
|
||||
throw new IngestModuleException(Bundle.ILeappFileProcessor_error_reading_iLeapp_directory() + iLeappOutputDir.toString(), e);
|
||||
throw new IngestModuleException(Bundle.LeappFileProcessor_error_reading_Leapp_directory() + LeappOutputDir.toString(), e);
|
||||
}
|
||||
|
||||
return foundTsvFiles;
|
||||
@ -156,26 +157,26 @@ public final class ILeappFileProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the iLeapp files that were found that match the xml mapping file
|
||||
* Process the Leapp files that were found that match the xml mapping file
|
||||
*
|
||||
* @param iLeappFilesToProcess List of files to process
|
||||
* @param iLeappImageFile Abstract file to create artifact for
|
||||
* @param LeappFilesToProcess List of files to process
|
||||
* @param LeappImageFile Abstract file to create artifact for
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void processiLeappFiles(List<String> iLeappFilesToProcess, AbstractFile iLeappImageFile) throws FileNotFoundException, IOException, IngestModuleException {
|
||||
private void processLeappFiles(List<String> LeappFilesToProcess, AbstractFile LeappImageFile) throws FileNotFoundException, IOException, IngestModuleException {
|
||||
List<BlackboardArtifact> bbartifacts = new ArrayList<>();
|
||||
|
||||
for (String iLeappFileName : iLeappFilesToProcess) {
|
||||
String fileName = FilenameUtils.getName(iLeappFileName);
|
||||
File iLeappFile = new File(iLeappFileName);
|
||||
for (String LeappFileName : LeappFilesToProcess) {
|
||||
String fileName = FilenameUtils.getName(LeappFileName);
|
||||
File LeappFile = new File(LeappFileName);
|
||||
if (tsvFileAttributes.containsKey(fileName)) {
|
||||
List<List<String>> attrList = tsvFileAttributes.get(fileName);
|
||||
try {
|
||||
BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName));
|
||||
|
||||
processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, iLeappImageFile);
|
||||
processFile(LeappFile, attrList, fileName, artifactType, bbartifacts, LeappImageFile);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex);
|
||||
@ -191,26 +192,26 @@ public final class ILeappFileProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the iLeapp files that were found that match the xml mapping file
|
||||
* Process the Leapp files that were found that match the xml mapping file
|
||||
*
|
||||
* @param iLeappFilesToProcess List of files to process
|
||||
* @param iLeappImageFile Abstract file to create artifact for
|
||||
* @param LeappFilesToProcess List of files to process
|
||||
* @param LeappImageFile Abstract file to create artifact for
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void processiLeappFiles(List<String> iLeappFilesToProcess, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException {
|
||||
private void processLeappFiles(List<String> LeappFilesToProcess, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException {
|
||||
List<BlackboardArtifact> bbartifacts = new ArrayList<>();
|
||||
|
||||
for (String iLeappFileName : iLeappFilesToProcess) {
|
||||
String fileName = FilenameUtils.getName(iLeappFileName);
|
||||
File iLeappFile = new File(iLeappFileName);
|
||||
for (String LeappFileName : LeappFilesToProcess) {
|
||||
String fileName = FilenameUtils.getName(LeappFileName);
|
||||
File LeappFile = new File(LeappFileName);
|
||||
if (tsvFileAttributes.containsKey(fileName)) {
|
||||
List<List<String>> attrList = tsvFileAttributes.get(fileName);
|
||||
try {
|
||||
BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName));
|
||||
|
||||
processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, dataSource);
|
||||
processFile(LeappFile, attrList, fileName, artifactType, bbartifacts, dataSource);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex);
|
||||
@ -225,10 +226,10 @@ public final class ILeappFileProcessor {
|
||||
|
||||
}
|
||||
|
||||
private void processFile(File iLeappFile, List<List<String>> attrList, String fileName, BlackboardArtifact.Type artifactType,
|
||||
private void processFile(File LeappFile, List<List<String>> attrList, String fileName, BlackboardArtifact.Type artifactType,
|
||||
List<BlackboardArtifact> bbartifacts, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException,
|
||||
TskCoreException {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(iLeappFile))) {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(LeappFile))) {
|
||||
String line = reader.readLine();
|
||||
// Check first line, if it is null then no heading so nothing to match to, close and go to next file.
|
||||
if (line != null) {
|
||||
@ -236,6 +237,10 @@ public final class ILeappFileProcessor {
|
||||
line = reader.readLine();
|
||||
while (line != null) {
|
||||
Collection<BlackboardAttribute> bbattributes = processReadLine(line, columnNumberToProcess, fileName);
|
||||
if (artifactType == null) {
|
||||
logger.log(Level.SEVERE, "Error trying to process Leapp output files in directory . "); //NON-NLS
|
||||
|
||||
}
|
||||
if (!bbattributes.isEmpty() && !blkBoard.artifactExists(dataSource, BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactType.getTypeID()), bbattributes)) {
|
||||
BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes);
|
||||
if (bbartifact != null) {
|
||||
@ -354,11 +359,11 @@ public final class ILeappFileProcessor {
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ILeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.",
|
||||
"ILeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.",
|
||||
"ILeappFileProcessor_cannotParseXml=Cannot Parse XML file.",
|
||||
"ILeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact",
|
||||
"ILeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts."
|
||||
"LeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.",
|
||||
"LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.",
|
||||
"LeappFileProcessor_cannotParseXml=Cannot Parse XML file.",
|
||||
"LeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact",
|
||||
"LeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts."
|
||||
})
|
||||
|
||||
/**
|
||||
@ -367,18 +372,18 @@ public final class ILeappFileProcessor {
|
||||
private void loadConfigFile() throws IngestModuleException {
|
||||
Document xmlinput;
|
||||
try {
|
||||
String path = PlatformUtil.getUserConfigDirectory() + File.separator + XMLFILE;
|
||||
String path = PlatformUtil.getUserConfigDirectory() + File.separator + xmlFile;
|
||||
File f = new File(path);
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
xmlinput = db.parse(f);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new IngestModuleException(Bundle.ILeappFileProcessor_cannot_load_artifact_xml() + e.getLocalizedMessage(), e); //NON-NLS
|
||||
throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_load_artifact_xml() + e.getLocalizedMessage(), e); //NON-NLS
|
||||
} catch (ParserConfigurationException pce) {
|
||||
throw new IngestModuleException(Bundle.ILeappFileProcessor_cannotBuildXmlParser() + pce.getLocalizedMessage(), pce); //NON-NLS
|
||||
throw new IngestModuleException(Bundle.LeappFileProcessor_cannotBuildXmlParser() + pce.getLocalizedMessage(), pce); //NON-NLS
|
||||
} catch (SAXException sxe) {
|
||||
throw new IngestModuleException(Bundle.ILeappFileProcessor_cannotParseXml() + sxe.getLocalizedMessage(), sxe); //NON-NLS
|
||||
throw new IngestModuleException(Bundle.LeappFileProcessor_cannotParseXml() + sxe.getLocalizedMessage(), sxe); //NON-NLS
|
||||
}
|
||||
|
||||
getFileNode(xmlinput);
|
||||
@ -466,7 +471,7 @@ public final class ILeappFileProcessor {
|
||||
bbart.addAttributes(bbattributes);
|
||||
return bbart;
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.WARNING, Bundle.ILeappFileProcessor_error_creating_new_artifacts(), ex); //NON-NLS
|
||||
logger.log(Level.WARNING, Bundle.LeappFileProcessor_error_creating_new_artifacts(), ex); //NON-NLS
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -490,7 +495,7 @@ public final class ILeappFileProcessor {
|
||||
bbart.addAttributes(bbattributes);
|
||||
return bbart;
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.WARNING, Bundle.ILeappFileProcessor_error_creating_new_artifacts(), ex); //NON-NLS
|
||||
logger.log(Level.WARNING, Bundle.LeappFileProcessor_error_creating_new_artifacts(), ex); //NON-NLS
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -509,17 +514,17 @@ public final class ILeappFileProcessor {
|
||||
try {
|
||||
Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(artifacts, MODULE_NAME);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, Bundle.ILeappFileProcessor_postartifacts_error(), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, Bundle.LeappFileProcessor_postartifacts_error(), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the iLeapp config xml file to the user directory to process
|
||||
* Extract the Leapp config xml file to the user directory to process
|
||||
*
|
||||
* @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException
|
||||
*/
|
||||
private void configExtractor() throws IOException {
|
||||
PlatformUtil.extractResourceToUserConfigDir(ILeappFileProcessor.class, XMLFILE, true);
|
||||
PlatformUtil.extractResourceToUserConfigDir(LeappFileProcessor.class, xmlFile, true);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!---
|
||||
This file contains the parameters for how to map aLeapp plugin output to attributes inside Autopsy for the aleapp Analyser module.
|
||||
|
||||
Each FileName node corresponds to a tab seperated values (tsv) file that is produced from iLeapp.
|
||||
|
||||
A FileName will have an associated TSK artifact assigned to it.
|
||||
|
||||
Each TSK artifact may have multiple attributes that correspond to the columns of the output from the iLeapp program tsv file.
|
||||
|
||||
|
||||
FileName:
|
||||
filename: The aLeapp TSV file that you want to process.
|
||||
description: A description of the tsv file name, this is defined in the iLeapp plugin for each tsv file.
|
||||
|
||||
ArtifactName:
|
||||
artifactname: The artifact that is to be created for the data in the tsv file.
|
||||
comment: This will be the data that will be added to the TSK_COMMENT attribute for each artifact. If the artifact
|
||||
does not need/require a comment then make the value null, a null comment will be ignored.
|
||||
|
||||
AttributeName:
|
||||
attributeName: The TSK attribute that the data corresponds to in the TSV file. If the data has no corresponding TSK attribute then
|
||||
make the value null, this will make sure the data in this column is ignored.
|
||||
columnName: This is the column name that is defined in the tsv file and what the attributeName corresponds to.
|
||||
required: whether the attribute is required or not (yes or no)
|
||||
|
||||
|
||||
-->
|
||||
<aLeap_Files_To_Process>
|
||||
|
||||
<FileName filename="accounts ce 0.tsv" description="Accounts_ce">
|
||||
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="accounts ce 0">
|
||||
<AttributeName attributename="TSK_USER_ID" columnName="Name" required="yes" />
|
||||
<AttributeName attributename="TSK_PROG_NAME" columnName=" Type" required="yes" />
|
||||
<AttributeName attributename="TSK_PASSWORD" columnName=" Password" required="yes" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="authtokens 0.tsv" description="Authtokens">
|
||||
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="Authtokens">
|
||||
<AttributeName attributename="null" columnName="ID" required="no" />
|
||||
<AttributeName attributename="TSK_USER_ID" columnName=" Name" required="yes" />
|
||||
<AttributeName attributename="TSK_PROG_NAME" columnName=" Account Type" required="yes" />
|
||||
<AttributeName attributename="null" columnName="Authtoken Type" required="no" />
|
||||
<AttributeName attributename="TSK_PASSWORD" columnName=" Authtoken" required="yes" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="accounts de 0.tsv" description="Accounts_de">
|
||||
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="accounts de 0">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last password entry" required="no" />
|
||||
<AttributeName attributename="TSK_USER_ID" columnName="Name" required="yes" />
|
||||
<AttributeName attributename="TSK_PROG_NAME" columnName="Type" required="yes" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser Bookmarks.tsv" description="Browser Bookmarks">
|
||||
<ArtifactName artifactname="TSK_WEB_BOOKMARK" comment="Browser Bookmarks">
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED " columnName="Added Date" required="yes" />
|
||||
<AttributeName attributename="TSK_URL" columnName=" URL" required="yes" />
|
||||
<AttributeName attributename="TSK_TITLE" columnName=" Name" required="yes" />
|
||||
<AttributeName attributename="null" columnName=" Parent" required="no" />
|
||||
<AttributeName attributename="null" columnName=" Type" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser cookies.tsv" description="Browser Cookies">
|
||||
<ArtifactName artifactname="TSK_WEB_COOKIE" comment="Browser Cookies">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESS" columnName="Last Access Date" required="yes" />
|
||||
<AttributeName attributename="TSK_DOMAIN" columnName="Host" required="yes" />
|
||||
<AttributeName attributename="TSK_NAME" columnName="Name" required="yes" />
|
||||
<AttributeName attributename="TSK_VALUE" columnName="Value" required="yes" />
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Date" required="yes" />
|
||||
<AttributeName attributename="TSK_DATETIME_END" columnName="Expiration Date" required="yes" />
|
||||
<AttributeName attributename="TSK_PATH" columnName="Path" required="yes" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser History.tsv" description="Browser History">
|
||||
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Browser History">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||
<AttributeName attributename="TSK_TITLE" columnName="Title" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Hidden" required="no"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser keyword search terms.tsv" description="Browser keyword Search Terms">
|
||||
<ArtifactName artifactname="TSK_WEB_SEARCH_QUERY" comment="Browser Keyword Search Terms">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||
<AttributeName attributename="TSK_TEXT" columnName="Term" required="yes"/>
|
||||
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser login data.tsv" description="Browser Login Data">
|
||||
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="Browser Login">
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Time" required="yes" />
|
||||
<AttributeName attributename="TSK_USER_NAME" columnName="Username" required="yes" />
|
||||
<AttributeName attributename="TSK_PASSWORD" columnName="Password" required="yes" />
|
||||
<AttributeName attributename="TSK_URL" columnName="Origin URL" required="no" />
|
||||
<AttributeName attributename="null" columnName="Blacklisted by User" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser offline pages.tsv" description="Browser Offline Pages">
|
||||
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Browser Offline Pages">
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Creation Time" required="yes" />
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Access Time" required="yes" />
|
||||
<AttributeName attributename="TSK_URL" columnName=" Online URL" required="yes" />
|
||||
<AttributeName attributename="null" columnName=" File Path" required="no" />
|
||||
<AttributeName attributename="TSK_TITLE" columnName=" Title" required="no" />
|
||||
<AttributeName attributename="null" columnName=" Access Count" required="no" />
|
||||
<AttributeName attributename="null" columnName=" File Size" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser search terms.tsv" description="Browser Search Terms">
|
||||
<ArtifactName artifactname="TSK_WEB_SEARCH_QUERY" comment="Browser Search Terms">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||
<AttributeName attributename="TSK_TEXT" columnName="Search Term" required="yes"/>
|
||||
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Title" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Browser top sites.tsv" description="Browser Top Sites">
|
||||
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Browser Top Sites">
|
||||
<AttributeName attributename="TSK_URL" columnName="URL" required="yes" />
|
||||
<AttributeName attributename="null" columnName="Rank" required="no" />
|
||||
<AttributeName attributename="TSK_TITLE" columnName="Title" required="no" />
|
||||
<AttributeName attributename="null" columnName="Redirects" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Call Logs.tsv" description="Call logs">
|
||||
<ArtifactName artifactname="TSK_CALLLOG" comment="null">
|
||||
<AttributeName attributename="TSK_DATETIME_START" columnName="Call Date" required="yes"/>
|
||||
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="Phone Account Address" required="yes"/>
|
||||
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="Partner" required="yes"/>
|
||||
<AttributeName attributename="TSK_DIRECTION" columnName="Type" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Duration in Secs" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Partner Location" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Country ISO" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Data" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Mime Type" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Transcription" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Deleted" required="no"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Chrome Bookmarks.tsv" description="Chrome Bookmarks">
|
||||
<ArtifactName artifactname="TSK_WEB_BOOKMARK" comment="Chrome Bookmarks">
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED " columnName="Added Date" required="yes" />
|
||||
<AttributeName attributename="TSK_URL" columnName=" URL" required="yes" />
|
||||
<AttributeName attributename="TSK_TITLE" columnName=" Name" required="yes" />
|
||||
<AttributeName attributename="null" columnName=" Parent" required="no" />
|
||||
<AttributeName attributename="null" columnName=" Type" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Chrome cookies.tsv" description="Chrome Cookies">
|
||||
<ArtifactName artifactname="TSK_WEB_COOKIE" comment="Chrome Cookies">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESS" columnName="Last Access Date" required="yes" />
|
||||
<AttributeName attributename="TSK_DOMAIN" columnName="Host" required="yes" />
|
||||
<AttributeName attributename="TSK_NAME" columnName="Name" required="yes" />
|
||||
<AttributeName attributename="TSK_VALUE" columnName="Value" required="yes" />
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Date" required="yes" />
|
||||
<AttributeName attributename="TSK_DATETIME_END" columnName="Expiration Date" required="yes" />
|
||||
<AttributeName attributename="TSK_PATH" columnName="Path" required="yes" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Chrome History.tsv" description="Chrome History">
|
||||
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Chrome History">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||
<AttributeName attributename="TSK_TITLE" columnName="Title" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Hidden" required="no"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Chrome login data.tsv" description="Chrome Login Data">
|
||||
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="Chrome Login">
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Time" required="yes" />
|
||||
<AttributeName attributename="TSK_USER_NAME" columnName="Username" required="yes" />
|
||||
<AttributeName attributename="TSK_PASSWORD" columnName="Password" required="yes" />
|
||||
<AttributeName attributename="TSK_URL" columnName="Origin URL" required="no" />
|
||||
<AttributeName attributename="null" columnName="Blacklisted by User" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Chrome offline pages.tsv" description="Chrome Offline Pages">
|
||||
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Chrome Offline Pages">
|
||||
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Creation Time" required="yes" />
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Access Time" required="yes" />
|
||||
<AttributeName attributename="TSK_URL" columnName=" Online URL" required="yes" />
|
||||
<AttributeName attributename="null" columnName=" File Path" required="no" />
|
||||
<AttributeName attributename="TSK_TITLE" columnName=" Title" required="no" />
|
||||
<AttributeName attributename="null" columnName=" Access Count" required="no" />
|
||||
<AttributeName attributename="null" columnName=" File Size" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Chrome search terms.tsv" description="Chrome Search Terms">
|
||||
<ArtifactName artifactname="TSK_WEB_SEARCH_QUERY" comment="Chrome Search Terms">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||
<AttributeName attributename="TSK_TEXT" columnName="Search Term" required="yes"/>
|
||||
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Title" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="Chrome top sites.tsv" description="Chrome Top Sites">
|
||||
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Chrome Top Sites">
|
||||
<AttributeName attributename="TSK_URL" columnName="URL" required="yes" />
|
||||
<AttributeName attributename="null" columnName="Rank" required="no" />
|
||||
<AttributeName attributename="TSK_TITLE" columnName="Title" required="no" />
|
||||
<AttributeName attributename="null" columnName="Redirects" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="google play searches.tsv" description="Google Play Searches">
|
||||
<ArtifactName artifactname="TSK_WEB_SEARCH" comment="Google Play Search">
|
||||
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Timestamp" required="yes" />
|
||||
<AttributeName attributename="TSK_PROG_NAME" columnName="Display" required="yes" />
|
||||
<AttributeName attributename="TSK_TEXT" columnName="query" required="yes" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="google quick search box.tsv" description="Google quick search box">
|
||||
<ArtifactName artifactname="TSK_WEB_SEARCH" comment="Google Quick Search Search">
|
||||
<AttributeName attributename="TSK_DATETIME" columnName="File Timestamp" required="yes" />
|
||||
<AttributeName attributename="null" columnName="Type" required="no" />
|
||||
<AttributeName attributename="TSK_TEXT" columnName="Queries Response" required="yes" />
|
||||
<AttributeName attributename="null" columnName="Source File" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="installed apps library.tsv" description="Installed Apps (Library)">
|
||||
<ArtifactName artifactname="TSK_INSTALLED_PROG" comment="Installed Apps (Library)">
|
||||
<AttributeName attributename="TSK_DATETIME" columnName="Purchase Time" required="yes"/>
|
||||
<AttributeName attributename="TSK_USER_NAME" columnName="Account" required="yes"/>
|
||||
<AttributeName attributename="TSK_PROG_NAME" columnName="Doc ID" required="yes"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="installed apps - GMS.tsv" description="Installed Apps">
|
||||
<ArtifactName artifactname="TSK_INSTALLED_PROG" comment="Installed Apps GSM">
|
||||
<AttributeName attributename="TSK_PROG_NAME" columnName="Bundle ID" required="yes" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="installed apps vending.tsv" description="Installed Apps (Vending)">
|
||||
<ArtifactName artifactname="TSK_INSTALLED_PROG" comment="Installed Apps (VEnding)">
|
||||
<AttributeName attributename="TSK_DATETIME" columnName="First Download" required="yes" />
|
||||
<AttributeName attributename="TSK_PROG_NAME" columnName="Package Name" required="yes" />
|
||||
<AttributeName attributename="TSK_TITLE" columnName=" Title" required="yes" />
|
||||
<AttributeName attributename="null" columnName="Install Reason" required="no" />
|
||||
<AttributeName attributename="null" columnName=" Auto Update?" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<FileName filename="mms messages.tsv" description="MMS messages">
|
||||
<ArtifactName artifactname="TSK_MESSAGE" comment="MMS messages">
|
||||
<AttributeName attributename="TSK_DATETIME" columnName="Date" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="MSG ID" required="no"/>
|
||||
<AttributeName attributename="TSK_THREAD_ID" columnName="Thread ID" required="yes"/>
|
||||
<AttributeName attributename="TSK_DATETIME_SENT" columnName="Date sent" required="yes"/>
|
||||
<AttributeName attributename="TSK_READ_STATUS" columnName="Read" required="yes"/>
|
||||
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="From" required="yes"/>
|
||||
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="To" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Cc" required="no"/>
|
||||
<AttributeName attributename="null" columnName="Bcc" required="no"/>
|
||||
<AttributeName attributename="TSK_TEXT" columnName="Body" required="yes"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
<!-- <FileName filename="partner settings.tsv" description="Partner Settings">
|
||||
<ArtifactName artifactname="TSK_" comment="null">
|
||||
<AttributeName attributename="null" columnName="Name" required="no" />
|
||||
<AttributeName attributename="null" columnName="Value ) # Dont remove the comma" required="no" />
|
||||
<AttributeName attributename="null" columnName=" that is required to make this a tuple as there is only 1 eleme" required="no" />
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
-->
|
||||
|
||||
<FileName filename="sms messages.tsv" description="SMS messages">
|
||||
<ArtifactName artifactname="TSK_MESSAGE" comment="SMS messages">
|
||||
<AttributeName attributename="TSK_DATETIME" columnName="Date" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="MSG ID" required="no"/>
|
||||
<AttributeName attributename="TSK_THREAD_ID" columnName="Thread ID" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Address" required="yes" />
|
||||
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="Contact ID" required="yes"/>
|
||||
<AttributeName attributename="TSK_DATETIME_SENT" columnName="Date sent" required="yes"/>
|
||||
<AttributeName attributename="TSK_READ_STATUS" columnName="Read" required="yes"/>
|
||||
<AttributeName attributename="TSK_TEXT" columnName="Body" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Service Center" required="yes"/>
|
||||
<AttributeName attributename="null" columnName="Error Code" required="no"/>
|
||||
</ArtifactName>
|
||||
</FileName>
|
||||
|
||||
</aLeap_Files_To_Process>
|
@ -1793,6 +1793,7 @@ class TableReportGenerator {
|
||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()
|
||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()
|
||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()
|
||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID()
|
||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID()
|
||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()
|
||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID()
|
||||
|
@ -384,6 +384,12 @@ public class HTMLReport implements TableReportModule {
|
||||
case TSK_WEB_FORM_ADDRESS:
|
||||
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS
|
||||
break;
|
||||
case TSK_GPS_AREA:
|
||||
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS
|
||||
break;
|
||||
case TSK_WEB_CATEGORIZATION:
|
||||
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/domain-16.png"); //NON-NLS
|
||||
break;
|
||||
default:
|
||||
logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
|
||||
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
|
||||
|
@ -27,6 +27,7 @@ ReportKML.genReport.reportName=KML Report
|
||||
ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n
|
||||
ReportKML.latLongEndPoint={0};{1};;{2} (End)\n
|
||||
Route_Details_Header=GPS Route
|
||||
Waypoint_Area_Point_Display_String=GPS Area Outline Point
|
||||
Waypoint_Bookmark_Display_String=GPS Bookmark
|
||||
Waypoint_EXIF_Display_String=EXIF Metadata With Location
|
||||
Waypoint_Last_Known_Display_String=GPS Last Known Location
|
||||
|
@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Track;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.Area;
|
||||
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
|
||||
import org.sleuthkit.autopsy.report.GeneralReportSettings;
|
||||
import org.sleuthkit.autopsy.report.ReportBranding;
|
||||
@ -84,6 +85,7 @@ public final class KMLReport implements GeneralReportModule {
|
||||
private Element gpsSearchesFolder;
|
||||
private Element gpsTrackpointsFolder;
|
||||
private Element gpsTracksFolder;
|
||||
private Element gpsAreasFolder;
|
||||
|
||||
private GeneralReportSettings settings;
|
||||
|
||||
@ -154,7 +156,8 @@ public final class KMLReport implements GeneralReportModule {
|
||||
"Waypoint_Track_Display_String=GPS Track",
|
||||
"Route_Details_Header=GPS Route",
|
||||
"ReportBodyFile.ingestWarning.text=Ingest Warning message",
|
||||
"Waypoint_Track_Point_Display_String=GPS Individual Track Point"
|
||||
"Waypoint_Track_Point_Display_String=GPS Individual Track Point",
|
||||
"Waypoint_Area_Point_Display_String=GPS Area Outline Point",
|
||||
})
|
||||
|
||||
public void generateReport(String baseReportDir, ReportProgressPanel progressPanel, List<Waypoint> waypointList) {
|
||||
@ -216,6 +219,11 @@ public final class KMLReport implements GeneralReportModule {
|
||||
result = ReportProgressPanel.ReportStatus.ERROR;
|
||||
errorMessage = Bundle.KMLReport_partialFailure();
|
||||
}
|
||||
entirelySuccessful = makeAreas(skCase);
|
||||
if (!entirelySuccessful) {
|
||||
result = ReportProgressPanel.ReportStatus.ERROR;
|
||||
errorMessage = Bundle.KMLReport_partialFailure();
|
||||
}
|
||||
|
||||
addLocationsToReport(skCase, baseReportDir);
|
||||
} catch (GeoLocationDataException | IOException | TskCoreException ex) {
|
||||
@ -327,6 +335,11 @@ public final class KMLReport implements GeneralReportModule {
|
||||
Element hrefTrack = new Element("href", ns).addContent(cdataTrack); //NON-NLS
|
||||
gpsTracksFolder.addContent(new Element("Icon", ns).addContent(hrefTrack)); //NON-NLS
|
||||
|
||||
gpsAreasFolder = new Element("Folder", ns); //NON-NLS
|
||||
CDATA cdataArea = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS
|
||||
Element hrefArea = new Element("href", ns).addContent(cdataArea); //NON-NLS
|
||||
gpsAreasFolder.addContent(new Element("Icon", ns).addContent(hrefArea)); //NON-NLS
|
||||
|
||||
gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS
|
||||
gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS
|
||||
gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS
|
||||
@ -334,6 +347,7 @@ public final class KMLReport implements GeneralReportModule {
|
||||
gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS
|
||||
gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS
|
||||
gpsTracksFolder.addContent(new Element("name", ns).addContent("GPS Tracks")); //NON-NLS
|
||||
gpsAreasFolder.addContent(new Element("name", ns).addContent("GPS Areas")); //NON-NLS
|
||||
|
||||
document.addContent(gpsExifMetadataFolder);
|
||||
document.addContent(gpsBookmarksFolder);
|
||||
@ -342,6 +356,7 @@ public final class KMLReport implements GeneralReportModule {
|
||||
document.addContent(gpsSearchesFolder);
|
||||
document.addContent(gpsTrackpointsFolder);
|
||||
document.addContent(gpsTracksFolder);
|
||||
document.addContent(gpsAreasFolder);
|
||||
|
||||
return kmlDocument;
|
||||
}
|
||||
@ -571,6 +586,62 @@ public final class KMLReport implements GeneralReportModule {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the area to the area folder in the document.
|
||||
*
|
||||
* @param skCase Currently open case.
|
||||
* @return The operation was entirely successful.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
boolean makeAreas(SleuthkitCase skCase) throws GeoLocationDataException, TskCoreException {
|
||||
List<Area> areas;
|
||||
boolean successful = true;
|
||||
|
||||
if (waypointList == null) {
|
||||
GeoLocationParseResult<Area> result = Area.getAreas(skCase, null);
|
||||
areas = result.getItems();
|
||||
successful = result.isSuccessfullyParsed();
|
||||
} else {
|
||||
areas = WaypointBuilder.getAreas(waypointList);
|
||||
}
|
||||
|
||||
for (Area area : areas) {
|
||||
if(shouldFilterFromReport(area.getArtifact())) {
|
||||
continue;
|
||||
}
|
||||
addAreaToReport(area);
|
||||
}
|
||||
|
||||
return successful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a area to the KML report.
|
||||
*
|
||||
* @param area
|
||||
*/
|
||||
private void addAreaToReport(Area area) {
|
||||
List<Waypoint> areaPoints = area.getPath();
|
||||
|
||||
if (areaPoints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adding a folder with the area name so that all of the
|
||||
// area border points will be grouped together.
|
||||
Element areaFolder = new Element("Folder", ns); //NON-NLS
|
||||
areaFolder.addContent(new Element("name", ns).addContent(area.getLabel())); //NON-NLS
|
||||
gpsAreasFolder.addContent(areaFolder);
|
||||
|
||||
// Create a polygon using the waypoints
|
||||
Element element = makePolygon(areaPoints);
|
||||
Waypoint firstWp = areaPoints.get(0);
|
||||
areaFolder.addContent(makePlacemark("",
|
||||
FeatureColor.GREEN, getFormattedDetails(firstWp, Bundle.Waypoint_Area_Point_Display_String()),
|
||||
firstWp.getTimestamp(), element, formattedCoordinates(firstWp.getLatitude(), firstWp.getLongitude()))); //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a point time stamp (in seconds) to the report format.
|
||||
*
|
||||
@ -662,6 +733,35 @@ public final class KMLReport implements GeneralReportModule {
|
||||
return lineString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Polygon for use in a Placemark.
|
||||
*
|
||||
* @param waypoints The waypoints making up the outline.
|
||||
*
|
||||
* @return the Polygon as an Element
|
||||
*/
|
||||
private Element makePolygon(List<Waypoint> waypoints) {
|
||||
|
||||
Element polygon = new Element("Polygon", ns); //NON-NLS
|
||||
|
||||
Element altitudeMode = new Element("altitudeMode", ns).addContent("clampToGround"); //NON-NLS
|
||||
polygon.addContent(altitudeMode);
|
||||
|
||||
// KML uses lon, lat. Deliberately reversed.
|
||||
Element coordinates = new Element("coordinates", ns);
|
||||
for (Waypoint wp : waypoints) {
|
||||
coordinates.addContent(wp.getLongitude() + "," + wp.getLatitude() + ",0 "); //NON-NLS
|
||||
}
|
||||
// Add the first one again
|
||||
coordinates.addContent(waypoints.get(0).getLongitude() + "," + waypoints.get(0).getLatitude() + ",0 "); //NON-NLS
|
||||
|
||||
Element linearRing = new Element("LinearRing", ns).addContent(coordinates);
|
||||
Element outerBoundary = new Element("outerBoundaryIs", ns).addContent(linearRing);
|
||||
polygon.addContent(outerBoundary);
|
||||
|
||||
return polygon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a Placemark for use in displaying features. Takes a
|
||||
* coordinate-bearing feature (Point, LineString, etc) and places it in the
|
||||
|
@ -24,6 +24,7 @@ import javafx.application.Platform;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JMenuItem;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.awt.ActionID;
|
||||
import org.openide.awt.ActionReference;
|
||||
import org.openide.awt.ActionReferences;
|
||||
@ -46,13 +47,10 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* An Action that opens the Timeline window. Has methods to open the window in
|
||||
* various specific states (e.g., showing a specific artifact in the List View)
|
||||
*/
|
||||
|
||||
|
||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline")
|
||||
@ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false)
|
||||
@ActionReferences(value = {
|
||||
@ActionReference(path = "Menu/Tools", position = 104)
|
||||
,
|
||||
@ActionReference(path = "Menu/Tools", position = 104),
|
||||
@ActionReference(path = "Toolbars/Case", position = 104)})
|
||||
public final class OpenTimelineAction extends CallableSystemAction {
|
||||
|
||||
@ -64,7 +62,6 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
private final JButton toolbarButton = new JButton(getName(),
|
||||
new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS
|
||||
|
||||
|
||||
public OpenTimelineAction() {
|
||||
toolbarButton.addActionListener(actionEvent -> performAction());
|
||||
menuItem = super.getMenuPresenter();
|
||||
@ -74,9 +71,10 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
/**
|
||||
* We used to also check if Case.getCurrentOpenCase().hasData() was true. We
|
||||
* disabled that check because if it is executed while a data source is
|
||||
* being added, it blocks the edt. We still do that in ImageGallery.
|
||||
* We used to also check if Case.getCurrentOpenCase().hasData() was
|
||||
* true. We disabled that check because if it is executed while a data
|
||||
* source is being added, it blocks the edt. We still do that in
|
||||
* ImageGallery.
|
||||
*/
|
||||
return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited();
|
||||
}
|
||||
@ -103,7 +101,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
@NbBundle.Messages({
|
||||
"OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.",
|
||||
"OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."})
|
||||
synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) throws TskCoreException {
|
||||
synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact, Interval timeSpan) throws TskCoreException {
|
||||
try {
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
if (currentCase.hasData() == false) {
|
||||
@ -112,6 +110,15 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
return;
|
||||
}
|
||||
TimeLineController controller = TimeLineModule.getController();
|
||||
// if file or artifact not specified, specify the time range as either
|
||||
// a) full range if timeSpan is null or b) the timeSpan
|
||||
if (file == null && artifact == null) {
|
||||
if (timeSpan == null) {
|
||||
controller.showFullRange();
|
||||
} else {
|
||||
controller.pushTimeRange(timeSpan);
|
||||
}
|
||||
}
|
||||
controller.showTimeLine(file, artifact);
|
||||
} catch (NoCurrentCaseException e) {
|
||||
//there is no case... Do nothing.
|
||||
@ -123,7 +130,18 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showTimeline() throws TskCoreException {
|
||||
showTimeline(null, null);
|
||||
showTimeline(null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open timeline with the given timeSpan time range.
|
||||
*
|
||||
* @param timeSpan The time range to display.
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showTimeline(Interval timeSpan) throws TskCoreException {
|
||||
showTimeline(null, null, timeSpan);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,7 +153,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showFileInTimeline(AbstractFile file) throws TskCoreException {
|
||||
showTimeline(file, null);
|
||||
showTimeline(file, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,7 +164,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException {
|
||||
showTimeline(null, artifact);
|
||||
showTimeline(null, artifact, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +62,6 @@ import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
@ -348,7 +347,7 @@ public class TimeLineController {
|
||||
/**
|
||||
* Show the entire range of the timeline.
|
||||
*/
|
||||
private boolean showFullRange() throws TskCoreException {
|
||||
boolean showFullRange() throws TskCoreException {
|
||||
synchronized (filteredEvents) {
|
||||
return pushTimeRange(filteredEvents.getSpanningInterval());
|
||||
}
|
||||
@ -376,7 +375,6 @@ public class TimeLineController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shuts down the task executor in charge of handling case events.
|
||||
*/
|
||||
@ -395,14 +393,12 @@ public class TimeLineController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add the case and ingest listeners, prompt for rebuilding the database if
|
||||
* necessary, and show the timeline window.
|
||||
*
|
||||
* @param file The AbstractFile from which to choose an event to show in
|
||||
* the List View.
|
||||
* @param file The AbstractFile from which to choose an event to show in the
|
||||
* List View.
|
||||
* @param artifact The BlackboardArtifact to show in the List View.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
@ -421,9 +417,7 @@ public class TimeLineController {
|
||||
try {
|
||||
if (file == null && artifact == null) {
|
||||
SwingUtilities.invokeLater(TimeLineController.this::showWindow);
|
||||
this.showFullRange();
|
||||
} else {
|
||||
|
||||
//prompt user to pick specific event and time range
|
||||
ShowInTimelineDialog showInTimelineDilaog = (file == null)
|
||||
? new ShowInTimelineDialog(this, artifact)
|
||||
@ -497,7 +491,7 @@ public class TimeLineController {
|
||||
topComponent.requestActive();
|
||||
}
|
||||
|
||||
synchronized public TimeLineTopComponent getTopComponent(){
|
||||
synchronized public TimeLineTopComponent getTopComponent() {
|
||||
return topComponent;
|
||||
}
|
||||
|
||||
|
@ -300,7 +300,7 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(acceptedDevice, results.get(0).getDeviceModel());
|
||||
Assert.assertEquals("MAKE " + acceptedDevice, results.get(0).getDeviceMake());
|
||||
Assert.assertEquals("ID " + acceptedDevice, results.get(0).getDeviceId());
|
||||
Assert.assertEquals(time, results.get(0).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals(time, results.get(0).getLastAccessed().getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -353,7 +353,7 @@ public class UserActivitySummaryTest {
|
||||
List<TopDeviceAttachedResult> results = summary.getRecentDevices(dataSource, 10);
|
||||
|
||||
Assert.assertEquals(1, results.size());
|
||||
Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertTrue("ID1".equalsIgnoreCase(results.get(0).getDeviceId()));
|
||||
Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake()));
|
||||
Assert.assertTrue("MODEL1".equalsIgnoreCase(results.get(0).getDeviceModel()));
|
||||
@ -403,9 +403,9 @@ public class UserActivitySummaryTest {
|
||||
|
||||
Assert.assertEquals("Expected two different search queries", 2, results.size());
|
||||
Assert.assertTrue(query1.equalsIgnoreCase(results.get(0).getSearchString()));
|
||||
Assert.assertEquals(DAY_SECONDS * 5, results.get(0).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * 5, results.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertTrue(query2.equalsIgnoreCase(results.get(1).getSearchString()));
|
||||
Assert.assertEquals(DAY_SECONDS * 3, results.get(1).getDateAccessed().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * 3, results.get(1).getLastAccessed().getTime() / 1000);
|
||||
}
|
||||
|
||||
private void webSearchTranslationTest(List<String> queries, boolean hasProvider, String translationSuffix)
|
||||
@ -599,11 +599,11 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(2, domains.size());
|
||||
|
||||
Assert.assertTrue("Expected " + domain1 + " to be first domain", domain1.equalsIgnoreCase(domains.get(0).getDomain()));
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS * 2, domains.get(0).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS * 2, domains.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 2L, domains.get(0).getVisitTimes());
|
||||
|
||||
Assert.assertTrue("Expected " + domain3 + " to be second domain", domain3.equalsIgnoreCase(domains.get(1).getDomain()));
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS, domains.get(1).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS, domains.get(1).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 1L, domains.get(1).getVisitTimes());
|
||||
}
|
||||
|
||||
@ -643,7 +643,7 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(1, domains.size());
|
||||
|
||||
Assert.assertTrue("Expected " + domain1 + " to be most recent domain", domain1.equalsIgnoreCase(domains.get(0).getDomain()));
|
||||
Assert.assertEquals(DAY_SECONDS, domains.get(0).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(DAY_SECONDS, domains.get(0).getLastAccessed().getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -680,11 +680,11 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertEquals(2, domains.size());
|
||||
|
||||
Assert.assertTrue(domain1.equalsIgnoreCase(domains.get(1).getDomain()));
|
||||
Assert.assertEquals(6L, domains.get(1).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(6L, domains.get(1).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 2L, domains.get(1).getVisitTimes());
|
||||
|
||||
Assert.assertTrue(domain2.equalsIgnoreCase(domains.get(0).getDomain()));
|
||||
Assert.assertEquals(4L, domains.get(0).getLastVisit().getTime() / 1000);
|
||||
Assert.assertEquals(4L, domains.get(0).getLastAccessed().getTime() / 1000);
|
||||
Assert.assertEquals((Long) 3L, domains.get(0).getVisitTimes());
|
||||
}
|
||||
|
||||
@ -869,7 +869,8 @@ public class UserActivitySummaryTest {
|
||||
|
||||
// since this may be somewhat variable
|
||||
Assert.assertTrue(expectedItem.getAccountType().equalsIgnoreCase(receivedItem.getAccountType()));
|
||||
Assert.assertEquals(expectedItem.getLastAccess().getTime(), receivedItem.getLastAccess().getTime());
|
||||
Assert.assertEquals(expectedItem.getLastAccessed().getTime(), receivedItem.getLastAccessed().getTime());
|
||||
Assert.assertEquals(expectedItem.getArtifact(), receivedItem.getArtifact());
|
||||
}
|
||||
}
|
||||
|
||||
@ -896,13 +897,13 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, email1,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), email1));
|
||||
|
||||
BlackboardArtifact email2 = getEmailArtifact(2, ds1, null, DAY_SECONDS);
|
||||
getRecentAccountsOneArtTest(ds1, email2,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), email2));
|
||||
|
||||
BlackboardArtifact email3 = getEmailArtifact(3, ds1, null, null);
|
||||
getRecentAccountsOneArtTest(ds1, email3, null);
|
||||
@ -911,19 +912,19 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, email4,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
|
||||
new Date(DAY_SECONDS * 2 * 1000)));
|
||||
new Date(DAY_SECONDS * 2 * 1000), email4));
|
||||
|
||||
BlackboardArtifact callog1 = getCallogArtifact(11, ds1, DAY_SECONDS, null);
|
||||
getRecentAccountsOneArtTest(ds1, callog1,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), callog1));
|
||||
|
||||
BlackboardArtifact callog2 = getCallogArtifact(12, ds1, null, DAY_SECONDS);
|
||||
getRecentAccountsOneArtTest(ds1, callog2,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
|
||||
new Date(DAY_SECONDS * 1000)));
|
||||
new Date(DAY_SECONDS * 1000), callog2));
|
||||
|
||||
BlackboardArtifact callog3 = getCallogArtifact(13, ds1, null, null);
|
||||
getRecentAccountsOneArtTest(ds1, callog3, null);
|
||||
@ -932,7 +933,7 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, callog4,
|
||||
new TopAccountResult(
|
||||
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
|
||||
new Date(DAY_SECONDS * 2 * 1000)));
|
||||
new Date(DAY_SECONDS * 2 * 1000), callog4));
|
||||
|
||||
BlackboardArtifact message1 = getMessageArtifact(21, ds1, "Skype", null);
|
||||
getRecentAccountsOneArtTest(ds1, message1, null);
|
||||
@ -944,7 +945,7 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsOneArtTest(ds1, message3, null);
|
||||
|
||||
BlackboardArtifact message4 = getMessageArtifact(24, ds1, "Skype", DAY_SECONDS);
|
||||
getRecentAccountsOneArtTest(ds1, message4, new TopAccountResult("Skype", new Date(DAY_SECONDS * 1000)));
|
||||
getRecentAccountsOneArtTest(ds1, message4, new TopAccountResult("Skype", new Date(DAY_SECONDS * 1000), message4));
|
||||
|
||||
}
|
||||
|
||||
@ -977,10 +978,10 @@ public class UserActivitySummaryTest {
|
||||
getRecentAccountsTest(ds1, 10,
|
||||
Arrays.asList(email1, email2, email3, callog1, callog2, message1a, message1b, message2a, message2b),
|
||||
Arrays.asList(
|
||||
new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000)),
|
||||
new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000)),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), new Date((DAY_SECONDS + 22) * 1000)),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), new Date((DAY_SECONDS + 13) * 1000))
|
||||
new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000), message2b),
|
||||
new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000), message1b),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), new Date((DAY_SECONDS + 22) * 1000), callog2),
|
||||
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), new Date((DAY_SECONDS + 13) * 1000), email3)
|
||||
));
|
||||
}
|
||||
|
||||
@ -1156,17 +1157,17 @@ public class UserActivitySummaryTest {
|
||||
Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(0).getProgramName()));
|
||||
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(0).getProgramPath()));
|
||||
Assert.assertEquals((Long) 31L, results.get(0).getRunTimes());
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(0).getLastRun().getTime() / 1000));
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(0).getLastAccessed().getTime() / 1000));
|
||||
|
||||
Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(1).getProgramName()));
|
||||
Assert.assertTrue("/Program Files/etc/".equalsIgnoreCase(results.get(1).getProgramPath()));
|
||||
Assert.assertEquals((Long) 21L, results.get(1).getRunTimes());
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(1).getLastRun().getTime() / 1000));
|
||||
Assert.assertEquals((Long) 31L, (Long) (results.get(1).getLastAccessed().getTime() / 1000));
|
||||
|
||||
Assert.assertTrue("program2.exe".equalsIgnoreCase(results.get(2).getProgramName()));
|
||||
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(2).getProgramPath()));
|
||||
Assert.assertEquals((Long) 10L, results.get(2).getRunTimes());
|
||||
Assert.assertEquals((Long) 22L, (Long) (results.get(2).getLastRun().getTime() / 1000));
|
||||
Assert.assertEquals((Long) 22L, (Long) (results.get(2).getLastAccessed().getTime() / 1000));
|
||||
}
|
||||
|
||||
private void assertProgramOrder(DataSource ds1, List<BlackboardArtifact> artifacts, List<String> programNamesReturned)
|
||||
|
@ -16,6 +16,11 @@ DataSourceUsage_FlashDrive=Flash Drive
|
||||
# {0} - OS name
|
||||
DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0})
|
||||
DataSourceUsageAnalyzer.parentModuleName=Recent Activity
|
||||
DomainCategorizer_moduleName_text=DomainCategorizer
|
||||
DomainCategorizer_parentModuleName=Recent Activity
|
||||
DomainCategorizer_Progress_Message_Domain_Types=Finding Domain Types
|
||||
DomainType_disposableMail_displayName=Disposable Email
|
||||
DomainType_webmail_displayName=Web Email
|
||||
Extract.indexError.message=Failed to index artifact for keyword search.
|
||||
Extract.noOpenCase.errMsg=No open case available.
|
||||
ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history
|
||||
|
@ -0,0 +1,488 @@
|
||||
/*
|
||||
* 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.recentactivity;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
|
||||
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModule;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Analyzes a URL to determine if the url host is one of a certain kind of category
|
||||
* (i.e. webmail, disposable mail). If found, a web category artifact is
|
||||
* created.
|
||||
*
|
||||
* CSV entries describing these domain types are compiled from sources.
|
||||
* webmail: https://github.com/mailcheck/mailcheck/wiki/List-of-Popular-Domains
|
||||
* disposable mail: https://www.npmjs.com/package/disposable-email-domains
|
||||
*/
|
||||
@Messages({
|
||||
"DomainCategorizer_moduleName_text=DomainCategorizer",
|
||||
"DomainCategorizer_Progress_Message_Domain_Types=Finding Domain Types",
|
||||
"DomainCategorizer_parentModuleName=Recent Activity"
|
||||
})
|
||||
class DomainCategorizer extends Extract {
|
||||
|
||||
/**
|
||||
* The domain type (i.e. webmail, disposable mail).
|
||||
*/
|
||||
@Messages({
|
||||
"DomainType_disposableMail_displayName=Disposable Email",
|
||||
"DomainType_webmail_displayName=Web Email"
|
||||
})
|
||||
private enum DomainType {
|
||||
DISPOSABLE_EMAIL("Disposable Email", Bundle.DomainType_disposableMail_displayName()),
|
||||
WEBMAIL("Web Email", Bundle.DomainType_webmail_displayName());
|
||||
|
||||
private final String csvId;
|
||||
private final String attrDisplayName;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param csvId The identifier within the csv for this type.
|
||||
* @param attrDisplayName The display name in the artifact for this
|
||||
* domain category.
|
||||
*/
|
||||
private DomainType(String csvId, String attrDisplayName) {
|
||||
this.csvId = csvId;
|
||||
this.attrDisplayName = attrDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The identifier within the csv for this type.
|
||||
*/
|
||||
String getCsvId() {
|
||||
return csvId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The display name in the artifact for this domain category.
|
||||
*/
|
||||
String getAttrDisplayName() {
|
||||
return attrDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node in the trie indicating a domain suffix token. For instance, the
|
||||
* csv entry: "hotmail.com,Web Email" would get parsed to a node, "com" having
|
||||
* a child of "hotmail". That child node, as a leaf, would have a webmail
|
||||
* message type.
|
||||
*/
|
||||
private static class DomainTypeTrieNode {
|
||||
|
||||
private final Map<String, DomainTypeTrieNode> children = new HashMap<>();
|
||||
private DomainType domainType = null;
|
||||
|
||||
/**
|
||||
* Retrieves the child node of the given key. If that child key does not
|
||||
* exist, a child node of that key is created and returned.
|
||||
*
|
||||
* @param childKey The key for the child (i.e. "com").
|
||||
* @return The retrieved or newly created child node.
|
||||
*/
|
||||
DomainTypeTrieNode getOrAddChild(String childKey) {
|
||||
DomainTypeTrieNode child = children.get(childKey);
|
||||
if (child == null) {
|
||||
child = new DomainTypeTrieNode();
|
||||
children.put(childKey, child);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the child node of the given key or returns null if child
|
||||
* does not exist.
|
||||
*
|
||||
* @param childKey The key for the child node (i.e. "com").
|
||||
* @return The child node or null if it does not exist.
|
||||
*/
|
||||
DomainTypeTrieNode getChild(String childKey) {
|
||||
return children.get(childKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If this is a leaf node, the type of domain for this node.
|
||||
*/
|
||||
DomainType getDomainType() {
|
||||
return domainType;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a leaf node, this sets the domain type for this node.
|
||||
*
|
||||
* @param domainType The domain type for this leaf node.
|
||||
*/
|
||||
void setDomainType(DomainType domainType) {
|
||||
this.domainType = domainType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the trie of suffixes from the csv resource file.
|
||||
*
|
||||
* @return The root trie node.
|
||||
* @throws IOException
|
||||
*/
|
||||
private static DomainTypeTrieNode loadTrie() throws IOException {
|
||||
try (InputStream is = DomainCategorizer.class.getResourceAsStream(DOMAIN_TYPE_CSV);
|
||||
InputStreamReader isReader = new InputStreamReader(is, StandardCharsets.UTF_8);
|
||||
BufferedReader reader = new BufferedReader(isReader)) {
|
||||
|
||||
DomainTypeTrieNode trie = new DomainTypeTrieNode();
|
||||
int lineNum = 1;
|
||||
while (reader.ready()) {
|
||||
String line = reader.readLine();
|
||||
if (!StringUtils.isBlank(line)) {
|
||||
addItem(trie, line.trim(), lineNum);
|
||||
lineNum++;
|
||||
}
|
||||
}
|
||||
|
||||
return trie;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trie node based on the csv line.
|
||||
*
|
||||
* @param trie The root trie node.
|
||||
* @param line The line to be parsed.
|
||||
* @param lineNumber The line number of this csv line.
|
||||
*/
|
||||
private static void addItem(DomainTypeTrieNode trie, String line, int lineNumber) {
|
||||
// make sure this isn't a blank line.
|
||||
if (StringUtils.isBlank(line)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] csvItems = line.split(CSV_DELIMITER);
|
||||
// line should be a key value pair
|
||||
if (csvItems.length < 2) {
|
||||
logger.log(Level.WARNING, String.format("Unable to properly parse line of \"%s\" at line %d", line, lineNumber));
|
||||
return;
|
||||
}
|
||||
|
||||
// determine the domain type from the value, and return if can't be determined.
|
||||
String domainTypeStr = csvItems[1].trim();
|
||||
|
||||
DomainType domainType = (StringUtils.isNotBlank(domainTypeStr))
|
||||
? Stream.of(DomainType.values())
|
||||
.filter((m) -> m.getCsvId().equalsIgnoreCase(domainTypeStr))
|
||||
.findFirst()
|
||||
.orElse(null)
|
||||
: null;
|
||||
|
||||
if (domainType == null) {
|
||||
logger.log(Level.WARNING, String.format("Could not determine domain type for this line: \"%s\" at line %d", line, lineNumber));
|
||||
return;
|
||||
}
|
||||
|
||||
// gather the domainSuffix and parse into domain trie tokens
|
||||
String domainSuffix = csvItems[0];
|
||||
if (StringUtils.isBlank(domainSuffix)) {
|
||||
logger.log(Level.WARNING, String.format("Could not determine domain suffix for this line: \"%s\" at line %d", line, lineNumber));
|
||||
return;
|
||||
}
|
||||
|
||||
String[] domainTokens = domainSuffix.trim().toLowerCase().split(DELIMITER);
|
||||
|
||||
// add into the trie
|
||||
DomainTypeTrieNode node = trie;
|
||||
for (int i = domainTokens.length - 1; i >= 0; i--) {
|
||||
String token = domainTokens[i];
|
||||
if (StringUtils.isBlank(token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
node = node.getOrAddChild(domainTokens[i]);
|
||||
}
|
||||
|
||||
node.setDomainType(domainType);
|
||||
|
||||
}
|
||||
|
||||
// Character for joining domain segments.
|
||||
private static final String JOINER = ".";
|
||||
// delimiter when used with regex for domains
|
||||
private static final String DELIMITER = "\\" + JOINER;
|
||||
|
||||
// csv delimiter
|
||||
private static final String CSV_DELIMITER = ",";
|
||||
|
||||
private static final String DOMAIN_TYPE_CSV = "default_domain_categories.csv"; //NON-NLS
|
||||
|
||||
// The url regex is based on the regex provided in https://tools.ietf.org/html/rfc3986#appendix-B
|
||||
// but expanded to be a little more flexible, and also properly parses user info and port in a url
|
||||
// this item has optional colon since some urls were coming through without the colon
|
||||
private static final String URL_REGEX_SCHEME = "(((?<scheme>[^:\\/?#]+):?)?\\/\\/)";
|
||||
|
||||
private static final String URL_REGEX_USERINFO = "((?<userinfo>[^\\/?#@]*)@)";
|
||||
private static final String URL_REGEX_HOST = "(?<host>[^\\/\\.?#:]*\\.[^\\/?#:]*)";
|
||||
private static final String URL_REGEX_PORT = "(:(?<port>[0-9]{1,5}))";
|
||||
private static final String URL_REGEX_AUTHORITY = String.format("(%s?%s?%s?\\/?)", URL_REGEX_USERINFO, URL_REGEX_HOST, URL_REGEX_PORT);
|
||||
|
||||
private static final String URL_REGEX_PATH = "(?<path>([^?#]*)(\\?([^#]*))?(#(.*))?)";
|
||||
|
||||
private static final String URL_REGEX_STR = String.format("^\\s*%s?%s?%s?", URL_REGEX_SCHEME, URL_REGEX_AUTHORITY, URL_REGEX_PATH);
|
||||
private static final Pattern URL_REGEX = Pattern.compile(URL_REGEX_STR);
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DomainCategorizer.class.getName());
|
||||
|
||||
// the root node for the trie containing suffixes for domain categories.
|
||||
private DomainTypeTrieNode rootTrie = null;
|
||||
|
||||
private Content dataSource;
|
||||
private IngestJobContext context;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*/
|
||||
DomainCategorizer() {
|
||||
moduleName = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to determine the host from the url string. If none can be
|
||||
* determined, returns null.
|
||||
*
|
||||
* @param urlString The url string.
|
||||
* @return The host or null if cannot be determined.
|
||||
*/
|
||||
private String getHost(String urlString) {
|
||||
String host = null;
|
||||
try {
|
||||
// try first using the built-in url class to determine the host.
|
||||
URL url = new URL(urlString);
|
||||
if (url != null) {
|
||||
host = url.getHost();
|
||||
}
|
||||
} catch (MalformedURLException ignore) {
|
||||
// ignore this and go to fallback regex
|
||||
}
|
||||
|
||||
// if the built-in url parsing doesn't work, then use more flexible regex.
|
||||
if (StringUtils.isBlank(host)) {
|
||||
Matcher m = URL_REGEX.matcher(urlString);
|
||||
if (m.find()) {
|
||||
host = m.group("host");
|
||||
}
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the host is a known type of domain. If so, returns the
|
||||
* portion of the host suffix that signifies the domain type (i.e.
|
||||
* "hotmail.com" or "mail.google.com") and the domain type.
|
||||
*
|
||||
* @param host The host.
|
||||
* @return A pair of the host suffix and domain type for that suffix if
|
||||
* found. Otherwise, returns null.
|
||||
*/
|
||||
private Pair<String, DomainType> findHostSuffix(String host) {
|
||||
// if no host, return none.
|
||||
if (StringUtils.isBlank(host)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse the tokens splitting on delimiter
|
||||
List<String> tokens = Stream.of(host.toLowerCase().split(DELIMITER))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DomainTypeTrieNode node = rootTrie;
|
||||
// the root node is null indicating we won't be able to do a lookup.
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate through tokens in reverse order
|
||||
int idx = tokens.size() - 1;
|
||||
for (; idx >= 0; idx--) {
|
||||
node = node.getChild(tokens.get(idx));
|
||||
// if we hit a leaf node or we have no matching child node, continue.
|
||||
if (node == null || node.getDomainType() != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DomainType domainType = node != null ? node.getDomainType() : null;
|
||||
|
||||
if (domainType == null) {
|
||||
return null;
|
||||
} else {
|
||||
// if there is a domain type, we have a result. Concatenate the
|
||||
// appropriate domain tokens and return.
|
||||
int minIndex = Math.max(0, idx);
|
||||
List<String> subList = tokens.subList(minIndex, tokens.size());
|
||||
String hostSuffix = String.join(JOINER, subList);
|
||||
return Pair.of(hostSuffix, domainType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through web history artifacts and attempts to determine any hosts of
|
||||
* a domain type. If any are found, a TSK_WEB_CATEGORIZATION artifact is
|
||||
* created (at most one per host suffix).
|
||||
*/
|
||||
private void findDomainTypes() {
|
||||
if (this.rootTrie == null) {
|
||||
logger.log(Level.SEVERE, "Not analyzing domain types. No root trie loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
int artifactsAnalyzed = 0;
|
||||
int domainTypeInstancesFound = 0;
|
||||
|
||||
// only one suffix per ingest is captured so this tracks the suffixes seen.
|
||||
Set<String> domainSuffixesSeen = new HashSet<>();
|
||||
|
||||
try {
|
||||
Collection<BlackboardArtifact> listArtifacts = currentCase.getSleuthkitCase().getBlackboard().getArtifacts(
|
||||
Arrays.asList(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_HISTORY)),
|
||||
Arrays.asList(dataSource.getId()));
|
||||
|
||||
logger.log(Level.INFO, "Processing {0} blackboard artifacts.", listArtifacts.size()); //NON-NLS
|
||||
|
||||
for (BlackboardArtifact artifact : listArtifacts) {
|
||||
// make sure we haven't cancelled
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
break; //User cancelled the process.
|
||||
}
|
||||
|
||||
// make sure there is attached file
|
||||
AbstractFile file = tskCase.getAbstractFileById(artifact.getObjectID());
|
||||
if (file == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the url string from the artifact
|
||||
BlackboardAttribute urlAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||
if (urlAttr == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String urlString = urlAttr.getValueString();
|
||||
|
||||
// atempt to get the host from the url provided.
|
||||
String host = getHost(urlString);
|
||||
if (StringUtils.isBlank(host)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we reached this point, we are at least analyzing this item
|
||||
artifactsAnalyzed++;
|
||||
|
||||
// attempt to get the domain type for the host using the suffix trie
|
||||
Pair<String, DomainType> domainEntryFound = findHostSuffix(host);
|
||||
if (domainEntryFound == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we got this far, we found a domain type, but it may not be unique
|
||||
domainTypeInstancesFound++;
|
||||
|
||||
String hostSuffix = domainEntryFound.getLeft();
|
||||
DomainType domainType = domainEntryFound.getRight();
|
||||
if (StringUtils.isBlank(hostSuffix) || domainType == null || domainSuffixesSeen.contains(hostSuffix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we got this far, this is a unique suffix. Add to the set, so we don't create
|
||||
// multiple of same suffix and add an artifact.
|
||||
domainSuffixesSeen.add(hostSuffix);
|
||||
|
||||
String moduleName = Bundle.DomainCategorizer_parentModuleName();
|
||||
|
||||
Collection<BlackboardAttribute> bbattributes = Arrays.asList(
|
||||
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, moduleName, NetworkUtils.extractDomain(host)),
|
||||
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_HOST, moduleName, host),
|
||||
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, moduleName, domainType.getAttrDisplayName())
|
||||
);
|
||||
postArtifact(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_CATEGORIZATION, file, bbattributes));
|
||||
}
|
||||
} catch (TskCoreException e) {
|
||||
logger.log(Level.SEVERE, "Encountered error retrieving artifacts for messaging domains", e); //NON-NLS
|
||||
} finally {
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
logger.info("Operation terminated by user."); //NON-NLS
|
||||
}
|
||||
logger.log(Level.INFO, String.format("Extracted %s distinct messaging domain(s) from the blackboard. "
|
||||
+ "Of the %s artifact(s) with valid hosts, %s url(s) contained messaging domain suffix.",
|
||||
domainSuffixesSeen.size(), artifactsAnalyzed, domainTypeInstancesFound));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
|
||||
this.dataSource = dataSource;
|
||||
this.context = context;
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Find_Search_Query());
|
||||
this.findDomainTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
void configExtractor() throws IngestModule.IngestModuleException {
|
||||
try {
|
||||
this.rootTrie = loadTrie();
|
||||
} catch (IOException ex) {
|
||||
throw new IngestModule.IngestModuleException("Unable to load domain type csv for domain category analysis", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete() {
|
||||
logger.info("Search Engine URL Query Analyzer has completed."); //NON-NLS
|
||||
}
|
||||
}
|
@ -81,6 +81,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
|
||||
Extract sru = new ExtractSru();
|
||||
Extract prefetch = new ExtractPrefetch();
|
||||
Extract webAccountType = new ExtractWebAccountType();
|
||||
Extract messageDomainType = new DomainCategorizer();
|
||||
|
||||
extractors.add(chrome);
|
||||
extractors.add(firefox);
|
||||
@ -97,6 +98,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
|
||||
extractors.add(recycleBin); // this needs to run after ExtractRegistry and ExtractOS
|
||||
extractors.add(sru);
|
||||
extractors.add(prefetch);
|
||||
extractors.add(messageDomainType);
|
||||
|
||||
browserExtractors.add(chrome);
|
||||
browserExtractors.add(firefox);
|
||||
|
File diff suppressed because it is too large
Load Diff
21
thirdparty/aLeapp/LICENSE
vendored
Normal file
21
thirdparty/aLeapp/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Brigs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
BIN
thirdparty/aLeapp/aleapp.exe
vendored
Normal file
BIN
thirdparty/aLeapp/aleapp.exe
vendored
Normal file
Binary file not shown.
@ -15,10 +15,7 @@ echo "Testing Autopsy..." && echo -en 'travis_fold:start:script.tests\\r'
|
||||
echo "Free Space:"
|
||||
echo `df -h .`
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = "osx" ]; then
|
||||
# if os x, just run it
|
||||
# ant -q test-no-regression
|
||||
elif [ "${TRAVIS_OS_NAME}" = "linux" ]; then
|
||||
if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
|
||||
# if linux use xvfb
|
||||
xvfb-run ant -q test-no-regression
|
||||
fi
|
||||
|
Loading…
x
Reference in New Issue
Block a user