Merge pull request #6528 from sleuthkit/develop

Merge develop into Java 11 upgrade branch
This commit is contained in:
Richard Cordovano 2020-12-07 13:47:44 -05:00 committed by GitHub
commit a82d88a3a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 118384 additions and 486 deletions

View File

@ -5,6 +5,7 @@ jobs:
- os: linux - os: linux
dist: bionic dist: bionic
- os: osx - os: osx
osx_image: xcode12.2
env: env:
global: global:

View File

@ -54,6 +54,11 @@
<fileset dir="${thirdparty.dir}/iLeapp"/> <fileset dir="${thirdparty.dir}/iLeapp"/>
</copy> </copy>
<!--Copy aLeapp to release-->
<copy todir="${basedir}/release/aLeapp" >
<fileset dir="${thirdparty.dir}/aLeapp"/>
</copy>
<!--Copy 7-Zip to release--> <!--Copy 7-Zip to release-->
<copy todir="${basedir}/release/7-Zip" > <copy todir="${basedir}/release/7-Zip" >
<fileset dir="${thirdparty.dir}/7-Zip"/> <fileset dir="${thirdparty.dir}/7-Zip"/>

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.sleuthkit.autopsy.contentviewers.contextviewer.ContextViewer.DateTimePanel;
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; 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. * 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; private static final long serialVersionUID = 1L;
// defines a list of artifacts that provide context for a file // defines a list of artifacts that provide context for a file
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>(); private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
static { static {
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
} }
private final BlackboardArtifact sourceContextArtifact; private final BlackboardArtifact sourceContextArtifact;
private final Long dateTime;
/** /**
* Creates new form ContextViewer * Creates new form ContextViewer
*/ */
public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) { public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
initComponents(); initComponents();
sourceContextArtifact = associatedArtifact; sourceContextArtifact = associatedArtifact;
setSourceName(sourceName); setSourceName(sourceName);
setSourceText(sourceText); setSourceText(sourceText);
this.dateTime = dateTime;
}
@Override
public Long getDateTime() {
return dateTime;
} }
/** /**
@ -140,10 +150,10 @@ public final class ContextSourcePanel extends javax.swing.JPanel {
private void showSourceText(boolean show) { private void showSourceText(boolean show) {
jSourceTextLabel.setVisible(show); jSourceTextLabel.setVisible(show);
} }
private void showSourceButton(boolean show) { private void showSourceButton(boolean show) {
jSourceGoToResultButton.setVisible(show); jSourceGoToResultButton.setVisible(show);
jSourceGoToResultButton.setEnabled(show); jSourceGoToResultButton.setEnabled(show);
} }
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -29,27 +29,36 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOC
* usage, if known. * 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; private static final long serialVersionUID = 1L;
// defines a list of artifacts that provide context for a file // defines a list of artifacts that provide context for a file
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>(); private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
static { static {
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
} }
private final BlackboardArtifact sourceContextArtifact; private final BlackboardArtifact sourceContextArtifact;
private final Long dateTime;
/** /**
* Creates new form ContextViewer * Creates new form ContextViewer
*/ */
public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) { public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
initComponents(); initComponents();
sourceContextArtifact = associatedArtifact; sourceContextArtifact = associatedArtifact;
setUsageName(sourceName); setUsageName(sourceName);
setUsageText(sourceText); setUsageText(sourceText);
this.dateTime = dateTime;
}
@Override
public Long getDateTime() {
return dateTime;
} }
/** /**
@ -138,10 +147,10 @@ public final class ContextUsagePanel extends javax.swing.JPanel {
private void showUsageText(boolean show) { private void showUsageText(boolean show) {
jUsageTextLabel.setVisible(show); jUsageTextLabel.setVisible(show);
} }
private void showUsageButton(boolean show) { private void showUsageButton(boolean show) {
jUsageGoToResultButton.setVisible(show); jUsageGoToResultButton.setVisible(show);
jUsageGoToResultButton.setEnabled(show); jUsageGoToResultButton.setEnabled(show);
} }
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
import java.awt.Component; import java.awt.Component;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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 // defines a list of artifacts that provide context for a file
private static final List<BlackboardArtifact.ARTIFACT_TYPE> CONTEXT_ARTIFACTS = new ArrayList<>(); private static final List<BlackboardArtifact.ARTIFACT_TYPE> CONTEXT_ARTIFACTS = new ArrayList<>();
private final List<javax.swing.JPanel> contextSourcePanels = new ArrayList<>(); private final List<ContextSourcePanel> contextSourcePanels = new ArrayList<>();
private final List<javax.swing.JPanel> contextUsagePanels = new ArrayList<>(); private final List<ContextUsagePanel> contextUsagePanels = new ArrayList<>();
static { static {
CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
@ -338,32 +340,36 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
"ContextViewer.programExecution=Program Execution: " "ContextViewer.programExecution=Program Execution: "
}) })
private void addArtifactToPanels(BlackboardArtifact associatedArtifact) throws TskCoreException { private void addArtifactToPanels(BlackboardArtifact associatedArtifact) throws TskCoreException {
Long dateTime = getArtifactDateTime(associatedArtifact);
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID() if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) { || BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_attachmentSource(); String sourceName = Bundle.ContextViewer_attachmentSource();
String sourceText = msgArtifactToAbbreviatedString(associatedArtifact); String sourceText = msgArtifactToAbbreviatedString(associatedArtifact);
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact); ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextSourcePanels.add(sourcePanel); contextSourcePanels.add(sourcePanel);
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID() } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID()
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) { || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_downloadSource(); String sourceName = Bundle.ContextViewer_downloadSource();
String sourceText = webDownloadArtifactToString(associatedArtifact); String sourceText = webDownloadArtifactToString(associatedArtifact);
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact); ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextSourcePanels.add(sourcePanel); contextSourcePanels.add(sourcePanel);
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) { } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_recentDocs(); String sourceName = Bundle.ContextViewer_recentDocs();
String sourceText = recentDocArtifactToString(associatedArtifact); String sourceText = recentDocArtifactToString(associatedArtifact);
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact); ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextUsagePanels.add(usagePanel); contextUsagePanels.add(usagePanel);
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) { } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_programExecution(); String sourceName = Bundle.ContextViewer_programExecution();
String sourceText = programExecArtifactToString(associatedArtifact); String sourceText = programExecArtifactToString(associatedArtifact);
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact); ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextUsagePanels.add(usagePanel); contextUsagePanels.add(usagePanel);
} }
Collections.sort(contextSourcePanels, new SortByDateTime());
Collections.sort(contextUsagePanels, new SortByDateTime());
} }
/** /**
@ -532,6 +538,59 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
return attributeMap; 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 // Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -89,6 +89,7 @@ public final class UserPreferences {
private static final String GEO_OSM_TILE_ZIP_PATH = "GeolocationOsmZipPath"; private static final String GEO_OSM_TILE_ZIP_PATH = "GeolocationOsmZipPath";
private static final String GEO_OSM_SERVER_ADDRESS = "GeolocationOsmServerAddress"; private static final String GEO_OSM_SERVER_ADDRESS = "GeolocationOsmServerAddress";
private static final String GEO_MBTILES_FILE_PATH = "GeolcoationMBTilesFilePath"; private static final String GEO_MBTILES_FILE_PATH = "GeolcoationMBTilesFilePath";
private static final String HEALTH_MONITOR_REPORT_PATH = "HealthMonitorReportPath";
// Prevent instantiation. // Prevent instantiation.
private UserPreferences() { private UserPreferences() {
@ -668,4 +669,22 @@ public final class UserPreferences {
return Paths.get(UserMachinePreferences.getBaseTempDirectory(), getAppName()) return Paths.get(UserMachinePreferences.getBaseTempDirectory(), getAppName())
.toAbsolutePath().toString(); .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, "");
}
} }

View 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);
}
}
}

View File

@ -18,13 +18,17 @@
*/ */
package org.sleuthkit.autopsy.coreutils; package org.sleuthkit.autopsy.coreutils;
import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.StringTokenizer; import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
public class NetworkUtils { public class NetworkUtils {
private static final Logger logger = Logger.getLogger(NetworkUtils.class.getName());
private NetworkUtils() { private NetworkUtils() {
} }
@ -32,7 +36,7 @@ public class NetworkUtils {
* Set the host name variable. Sometimes the network can be finicky, so the * Set the host name variable. Sometimes the network can be finicky, so the
* answer returned by getHostName() could throw an exception or be null. * answer returned by getHostName() could throw an exception or be null.
* Have it read the environment variable if getHostName() is unsuccessful. * Have it read the environment variable if getHostName() is unsuccessful.
* *
* @return the local host name * @return the local host name
*/ */
public static String getLocalHostName() { public static String getLocalHostName() {
@ -49,16 +53,16 @@ public class NetworkUtils {
} }
return hostName; return hostName;
} }
/** /**
* Attempt to manually extract the domain from a URL. * Attempt to manually extract the domain from a URL.
* *
* @param url * @param url
* @return empty string if no domain could be found * @return empty string if no domain could be found
*/ */
private static String getBaseDomain(String url) { private static String getBaseDomain(String url) {
String host = null; String host = null;
//strip protocol //strip protocol
String cleanUrl = url.replaceFirst(".*:\\/\\/", ""); String cleanUrl = url.replaceFirst(".*:\\/\\/", "");
@ -70,42 +74,30 @@ public class NetworkUtils {
host = cleanUrl; host = cleanUrl;
} }
//get the domain part from host (last 2) String base = host;
StringTokenizer tok = new StringTokenizer(host, "."); try {
StringBuilder hostB = new StringBuilder(); base = DomainTokenizer.getInstance().getDomain(host);
int toks = tok.countTokens(); } catch (IOException ex) {
logger.log(Level.WARNING, "Unable to load resources for domain categorization.", ex);
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 = hostB.toString();
// verify there are no special characters in there // verify there are no special characters in there
if (base.matches(".*[~`!@#$%^&\\*\\(\\)\\+={}\\[\\];:\\?<>,/ ].*")) { if (base.matches(".*[~`!@#$%^&\\*\\(\\)\\+={}\\[\\];:\\?<>,/ ].*")) {
return ""; return "";
} }
//verify that the base domain actually has a '.', details JIRA-4609 //verify that the base domain actually has a '.', details JIRA-4609
if(!base.contains(".")) { if (!base.contains(".")) {
return ""; return "";
} }
return base; return base;
} }
/** /**
* Attempt to extract the domain from a URL. * Attempt to extract the domain from a URL. Will start by using the
* Will start by using the built-in URL class, and if that fails will * built-in URL class, and if that fails will try to extract it manually.
* try to extract it manually. *
*
* @param urlString The URL to extract the domain from * @param urlString The URL to extract the domain from
* @return empty string if no domain name was found * @return empty string if no domain name was found
*/ */
@ -113,20 +105,22 @@ public class NetworkUtils {
if (urlString == null) { if (urlString == null) {
return ""; return "";
} }
String result = ""; String urlHost = null;
try { try {
URL url = new URL(urlString); URL url = new URL(urlString);
result = url.getHost(); urlHost = url.getHost();
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
//do not log if not a valid URL - we will try to extract it ourselves //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 there is a valid url host, get base domain from that host
if (result == null || result.trim().isEmpty()) { // otherwise use urlString and parse the domain
return getBaseDomain(urlString); String result = (StringUtils.isNotBlank(urlHost))
} ? getBaseDomain(urlHost)
: getBaseDomain(urlString);
return result; return result;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -121,6 +121,10 @@ public final class IconsUtil {
imageFile = "web-account-type.png"; //NON-NLS imageFile = "web-account-type.png"; //NON-NLS
} else if (typeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) { } else if (typeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) {
imageFile = "web-form-address.png"; //NON-NLS 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 { } else {
imageFile = "artifact-icon.png"; //NON-NLS imageFile = "artifact-icon.png"; //NON-NLS
} }

View File

@ -130,7 +130,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) { if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) {
return null; return null;
} else { } 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) { if (StringUtils.isBlank(path) || accessedTime == null || accessedTime == 0) {
return null; return null;
} else { } 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)) { if (date == null || date == 0 || StringUtils.isBlank(path)) {
return null; return null;
} else { } 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 String path;
private final long date; private final long date;
private final BlackboardArtifact artifact;
/** /**
* Constructor for files with just a path and date. * Constructor for files with just a path and date.
* *
* @param artifact The relevant artifact.
* @param path File path. * @param path File path.
* @param date File access date\time in seconds with java epoch * @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.path = path;
this.date = date; this.date = date;
} }
@ -364,6 +367,12 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
return path; 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. * Constructor for files with just a path and date.
* *
* @param artifact The relevant artifact.
* @param path File path. * @param path File path.
* @param date File access date\time in seconds with java epoch. * @param date File access date\time in seconds with java epoch.
* @param webDomain The webdomain from which the file was downloaded. * @param webDomain The webdomain from which the file was downloaded.
*/ */
RecentDownloadDetails(String path, long date, String webDomain) { RecentDownloadDetails(BlackboardArtifact artifact, String path, long date, String webDomain) {
super(path, date); super(artifact, path, date);
this.webDomain = webDomain; this.webDomain = webDomain;
} }
@ -407,13 +417,14 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
* Constructor for recent download files which have a path, date and * Constructor for recent download files which have a path, date and
* domain value. * domain value.
* *
* @param artifact The relevant artifact.
* @param path File path. * @param path File path.
* @param date File crtime. * @param date File crtime.
* @param sender The sender of the message from which the file was * @param sender The sender of the message from which the file was
* attached. * attached.
*/ */
RecentAttachmentDetails(String path, long date, String sender) { RecentAttachmentDetails(BlackboardArtifact artifact, String path, long date, String sender) {
super(path, date); super(artifact, path, date);
this.sender = sender; this.sender = sender;
} }

View File

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

View File

@ -38,12 +38,11 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TimelineEvent; import org.sleuthkit.datamodel.TimelineEvent;
import org.sleuthkit.datamodel.TimelineEventType; 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.TimelineFilter.RootFilter;
import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.TimelineManager;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferences;
/** /**
@ -51,6 +50,23 @@ import org.sleuthkit.autopsy.core.UserPreferences;
*/ */
public class TimelineSummary implements DefaultUpdateGovernor { 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 long DAY_SECS = 24 * 60 * 60;
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS = new HashSet<>( private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS = new HashSet<>(
Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED)); Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED));
@ -64,23 +80,29 @@ public class TimelineSummary implements DefaultUpdateGovernor {
private final SleuthkitCaseProvider caseProvider; private final SleuthkitCaseProvider caseProvider;
private final Supplier<TimeZone> timeZoneProvider; private final Supplier<TimeZone> timeZoneProvider;
private final DataSourceFilterFunction filterFunction;
/** /**
* Default constructor. * Default constructor.
*/ */
public TimelineSummary() { 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 * Construct object with given SleuthkitCaseProvider
* *
* @param caseProvider SleuthkitCaseProvider provider, cannot be null. * @param caseProvider SleuthkitCaseProvider provider; cannot be null.
* @param timeZoneProvider The timezone 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.caseProvider = caseProvider;
this.timeZoneProvider = timeZoneProvider; this.timeZoneProvider = timeZoneProvider;
this.filterFunction = filterFunction;
} }
@Override @Override
@ -113,8 +135,9 @@ public class TimelineSummary implements DefaultUpdateGovernor {
* @return The retrieved data. * @return The retrieved data.
* @throws SleuthkitCaseProviderException * @throws SleuthkitCaseProviderException
* @throws TskCoreException * @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(); TimeZone timeZone = this.timeZoneProvider.get();
TimelineManager timelineManager = this.caseProvider.get().getTimelineManager(); TimelineManager timelineManager = this.caseProvider.get().getTimelineManager();
@ -144,7 +167,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
// get most recent days activity // get most recent days activity
List<DailyActivityAmount> mostRecentActivityAmt = getMostRecentActivityAmounts(dateCounts, minRecentDay, maxDay); 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. * belongs.
* @return A Map mapping days from epoch to the activity for that day. * @return A Map mapping days from epoch to the activity for that day.
* @throws TskCoreException * @throws TskCoreException
* @throws NoCurrentCaseException
*/ */
private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone) throws TskCoreException { private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone)
throws TskCoreException, NoCurrentCaseException {
DataSourcesFilter dataSourceFilter = new DataSourcesFilter(); RootFilter rootFilter = this.filterFunction.apply(dataSource);
dataSourceFilter.addSubFilter(new TimelineFilter.DataSourceFilter(dataSource.getName(), dataSource.getId()));
RootFilter dataSourceRootFilter = new RootFilter(
null,
null,
null,
null,
null,
dataSourceFilter,
null,
Collections.emptySet());
// get events for data source // get events for data source
long curRunTime = System.currentTimeMillis(); 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) // get counts of events per day (left is file system events, right is everything else)
Map<Long, DailyActivityAmount> dateCounts = new HashMap<>(); Map<Long, DailyActivityAmount> dateCounts = new HashMap<>();
@ -233,6 +246,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
private final Date minDate; private final Date minDate;
private final Date maxDate; private final Date maxDate;
private final List<DailyActivityAmount> histogramActivity; private final List<DailyActivityAmount> histogramActivity;
private final DataSource dataSource;
/** /**
* Main constructor. * Main constructor.
@ -240,12 +254,15 @@ public class TimelineSummary implements DefaultUpdateGovernor {
* @param minDate Earliest usage date recorded for the data source. * @param minDate Earliest usage date recorded for the data source.
* @param maxDate Latest 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 * @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.minDate = minDate;
this.maxDate = maxDate; this.maxDate = maxDate;
this.histogramActivity = (recentDaysActivity == null) ? Collections.emptyList() : Collections.unmodifiableList(recentDaysActivity); 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 * @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() { public List<DailyActivityAmount> getMostRecentDaysActivity() {
return histogramActivity; return histogramActivity;
} }
/**
* @return The data source that this data applies to.
*/
public DataSource getDataSource() {
return dataSource;
}
} }
/** /**

View File

@ -108,8 +108,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT"; private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT";
private static final String WINDOWS_PREFIX = "/WINDOWS"; 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<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.getDateAccessed().compareTo(b.getDateAccessed()); 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 * 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 // second priority for sorting is the last run date
// if non-0, this is the return value for the comparator // if non-0, this is the return value for the comparator
int lastRunCompare = nullableCompare( int lastRunCompare = nullableCompare(
a.getLastRun() == null ? null : a.getLastRun().getTime(), a.getLastAccessed() == null ? null : a.getLastAccessed().getTime(),
b.getLastRun() == null ? null : b.getLastRun().getTime()); b.getLastAccessed() == null ? null : b.getLastAccessed().getTime());
if (lastRunCompare != 0) { if (lastRunCompare != 0) {
return -lastRunCompare; return -lastRunCompare;
@ -219,14 +219,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return Collections.emptyList(); 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 no recent domains, return accordingly
if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) { if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) {
return Collections.emptyList(); return Collections.emptyList();
} }
final long mostRecentMs = mostRecentAndGroups.getLeft(); 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() return groups.entrySet().stream()
.map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs)) .map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs))
@ -243,24 +243,32 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* within DOMAIN_WINDOW_MS of mostRecentMs. * within DOMAIN_WINDOW_MS of mostRecentMs.
* *
* @param domain The domain. * @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. * @param mostRecentMs The most recent visit of any domain.
* *
* @return The TopDomainsResult or null if no visits to this domain within * @return The TopDomainsResult or null if no visits to this domain within
* 30 days of mostRecentMs. * 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 visitCount = 0;
Long thisMostRecentMs = null; 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. // 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; continue;
} }
// if visit is within window, increment the count and get most recent // if visit is within window, increment the count and get most recent
visitCount++; visitCount++;
if (thisMostRecentMs == null || visitMs > thisMostRecentMs) {
thisMostRecentMs = visitMs;
thisMostRecentArtifact = artifact;
}
thisMostRecentMs = getMax(thisMostRecentMs, visitMs); thisMostRecentMs = getMax(thisMostRecentMs, visitMs);
} }
@ -269,7 +277,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return null; return null;
} else { } else {
// create a top domain result with the domain, count, and most recent visit date // 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 * @return A tuple where the first value is the latest web history accessed
* date in milliseconds and the second value maps normalized (lowercase; * 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 TskCoreException
* @throws SleuthkitCaseProviderException * @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, List<BlackboardArtifact> artifacts = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_WEB_HISTORY,
dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0); dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0);
Long mostRecentMs = null; Long mostRecentMs = null;
Map<String, List<Long>> domainVisits = new HashMap<>(); Map<String, List<Pair<BlackboardArtifact, Long>>> domainVisits = new HashMap<>();
for (BlackboardArtifact art : artifacts) { for (BlackboardArtifact art : artifacts) {
Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED); Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED);
@ -312,13 +321,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
domain = domain.toLowerCase().trim(); domain = domain.toLowerCase().trim();
// add this visit date to the list of dates for the domain // 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) { if (domainVisitList == null) {
domainVisitList = new ArrayList<>(); domainVisitList = new ArrayList<>();
domainVisits.put(domain, domainVisitList); domainVisits.put(domain, domainVisitList);
} }
domainVisitList.add(artifactDateMs); domainVisitList.add(Pair.of(art, artifactDateMs));
} }
return Pair.of(mostRecentMs, domainVisits); return Pair.of(mostRecentMs, domainVisits);
@ -354,7 +363,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT); String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT);
Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED); Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED);
return (StringUtils.isNotBlank(searchString) && dateAccessed != null) return (StringUtils.isNotBlank(searchString) && dateAccessed != null)
? new TopWebSearchResult(searchString, dateAccessed) ? new TopWebSearchResult(searchString, dateAccessed, artifact)
: null; : null;
} }
@ -456,15 +465,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @return The most recent one with a non-null date. * @return The most recent one with a non-null date.
*/ */
private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) { private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) {
if (r2.getDateAccessed() == null) { if (r2.getLastAccessed()== null) {
return r1; return r1;
} }
if (r1.getDateAccessed() == null) { if (r1.getLastAccessed() == null) {
return r2; 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.getStringOrNull(artifact, TYPE_DEVICE_ID),
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME), DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE), DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE),
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL) DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL),
artifact
); );
}) })
// remove Root Hub identifier // remove Root Hub identifier
@ -524,7 +534,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE); String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE);
Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME); Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME);
return (StringUtils.isNotBlank(type) && date != null) return (StringUtils.isNotBlank(type) && date != null)
? new TopAccountResult(type, date) ? new TopAccountResult(type, date, artifact)
: null; : null;
} }
@ -552,7 +562,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
} }
return (StringUtils.isNotBlank(type) && latestDate != null) return (StringUtils.isNotBlank(type) && latestDate != null)
? new TopAccountResult(type, latestDate) ? new TopAccountResult(type, latestDate, artifact)
: null; : null;
} }
@ -690,7 +700,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
programName, programName,
path, path,
longCount, 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.getProgramPath() == null ? null : res.getProgramPath().toUpperCase()),
res -> res, res -> res,
(res1, res2) -> { (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( return new TopProgramsResult(
res1.getProgramName(), maxResult.getProgramName(),
res1.getProgramPath(), maxResult.getProgramPath(),
getMax(res1.getRunTimes(), res2.getRunTimes()), maxRunTimes,
getMax(res1.getLastRun(), res2.getLastRun())); maxDate,
maxResult.getArtifact());
})).values(); })).values();
List<TopProgramsResult> orderedResults = results.stream() 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, // if run times / last run information is available, the first item should have some value,
// and then the items should be limited accordingly. // and then the items should be limited accordingly.
if (isPositiveNum(topResult.getRunTimes()) 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()); return orderedResults.stream().limit(count).collect(Collectors.toList());
} }
} }
@ -812,13 +827,47 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return orderedResults; 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. * Object containing information about a web search artifact.
*/ */
public static class TopWebSearchResult { public static class TopWebSearchResult extends LastAccessedArtifact {
private final String searchString; private final String searchString;
private final Date dateAccessed;
private String translatedResult; private String translatedResult;
/** /**
@ -826,10 +875,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* *
* @param searchString The search string. * @param searchString The search string.
* @param dateAccessed The latest date searched. * @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.searchString = searchString;
this.dateAccessed = dateAccessed;
} }
/** /**
@ -854,22 +904,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public String getSearchString() { public String getSearchString() {
return searchString; return searchString;
} }
/**
* @return The date for the search.
*/
public Date getDateAccessed() {
return dateAccessed;
}
} }
/** /**
* A record of a device attached. * A record of a device attached.
*/ */
public static class TopDeviceAttachedResult { public static class TopDeviceAttachedResult extends LastAccessedArtifact {
private final String deviceId; private final String deviceId;
private final Date dateAccessed;
private final String deviceMake; private final String deviceMake;
private final String deviceModel; private final String deviceModel;
@ -880,10 +922,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @param dateAccessed The date last attached. * @param dateAccessed The date last attached.
* @param deviceMake The device make. * @param deviceMake The device make.
* @param deviceModel The device model. * @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.deviceId = deviceId;
this.dateAccessed = dateAccessed;
this.deviceMake = deviceMake; this.deviceMake = deviceMake;
this.deviceModel = deviceModel; this.deviceModel = deviceModel;
} }
@ -895,13 +938,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return deviceId; return deviceId;
} }
/**
* @return The date last attached.
*/
public Date getDateAccessed() {
return dateAccessed;
}
/** /**
* @return The device make. * @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 * A record of an account and the last time it was used determined by
* messages. * messages.
*/ */
public static class TopAccountResult { public static class TopAccountResult extends LastAccessedArtifact {
private final String accountType; private final String accountType;
private final Date lastAccess;
/** /**
* Main constructor. * Main constructor.
* *
* @param accountType The account type. * @param accountType The account type.
* @param lastAccess The date the account was last accessed. * @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.accountType = accountType;
this.lastAccess = lastAccess;
} }
/** /**
@ -943,23 +979,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public String getAccountType() { public String getAccountType() {
return accountType; 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. * 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 String domain;
private final Long visitTimes; private final Long visitTimes;
private final Date lastVisit;
/** /**
* Describes a top domain result. * Describes a top domain result.
@ -967,11 +995,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @param domain The domain. * @param domain The domain.
* @param visitTimes The number of times it was visited. * @param visitTimes The number of times it was visited.
* @param lastVisit The date of the last visit. * @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.domain = domain;
this.visitTimes = visitTimes; this.visitTimes = visitTimes;
this.lastVisit = lastVisit;
} }
/** /**
@ -987,24 +1016,16 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public Long getVisitTimes() { public Long getVisitTimes() {
return visitTimes; return visitTimes;
} }
/**
* @return The date of the last visit.
*/
public Date getLastVisit() {
return lastVisit;
}
} }
/** /**
* Describes a result of a program run on a datasource. * 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 programName;
private final String programPath; private final String programPath;
private final Long runTimes; private final Long runTimes;
private final Date lastRun;
/** /**
* Main constructor. * Main constructor.
@ -1012,12 +1033,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @param programName The name of the program. * @param programName The name of the program.
* @param programPath The path of the program. * @param programPath The path of the program.
* @param runTimes The number of runs. * @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.programName = programName;
this.programPath = programPath; this.programPath = programPath;
this.runTimes = runTimes; this.runTimes = runTimes;
this.lastRun = lastRun;
} }
/** /**
@ -1040,12 +1062,5 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public Long getRunTimes() { public Long getRunTimes() {
return runTimes; return runTimes;
} }
/**
* @return The last time the program was run or null if not present.
*/
public Date getLastRun() {
return lastRun;
}
} }
} }

View File

@ -19,20 +19,29 @@
package org.sleuthkit.autopsy.datasourcesummary.ui; package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.SwingWorker; 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.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; 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;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; 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.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.SwingWorkerSequentialExecutor; import org.sleuthkit.autopsy.datasourcesummary.uiutils.SwingWorkerSequentialExecutor;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor; 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.IngestManager.IngestJobEvent;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; 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. * 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 { abstract class BaseDataSourceSummaryPanel extends JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -64,6 +77,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
private final EventUpdateHandler updateHandler; private final EventUpdateHandler updateHandler;
private final List<UpdateGovernor> governors; private final List<UpdateGovernor> governors;
private Runnable notifyParentClose = null;
private DataSource dataSource; private DataSource dataSource;
/** /**
@ -77,7 +92,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* Checks to see if artifact is from a datasource. * Checks to see if artifact is from a datasource.
* *
* @param art The artifact. * @param art The artifact.
* @param ds The datasource. * @param ds The datasource.
* *
* @return True if in datasource; false if not or exception. * @return True if in datasource; false if not or exception.
*/ */
@ -219,7 +234,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* Main constructor. * Main constructor.
* *
* @param governors The items governing when this panel should receive * @param governors The items governing when this panel should receive
* updates. * updates.
*/ */
protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) { protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) {
this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors); this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors);
@ -227,6 +242,104 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
this.updateHandler.register(); 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. * Closes listeners and resources.
*/ */
@ -246,6 +359,24 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
onNewDataSource(this.dataSource); 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. * @return The current data source.
*/ */
@ -287,7 +418,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* argument and data fetch components and then submits them to run. * argument and data fetch components and then submits them to run.
* *
* @param dataFetchComponents The components to be run. * @param dataFetchComponents The components to be run.
* @param dataSource The data source argument. * @param dataSource The data source argument.
*/ */
protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) { protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) {
if (dataSource == null || !Case.isCaseOpen()) { if (dataSource == null || !Case.isCaseOpen()) {
@ -320,8 +451,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* argument and submits them to be executed. * argument and submits them to be executed.
* *
* @param dataFetchComponents The components to register. * @param dataFetchComponents The components to register.
* @param loadableComponents The components to set to a loading screen. * @param loadableComponents The components to set to a loading screen.
* @param dataSource The data source argument. * @param dataSource The data source argument.
*/ */
protected void onNewDataSource( protected void onNewDataSource(
List<DataFetchComponents<DataSource, ?>> dataFetchComponents, List<DataFetchComponents<DataSource, ?>> dataFetchComponents,
@ -371,12 +502,11 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* message indicating the unrun ingest module will be shown. Otherwise, the * message indicating the unrun ingest module will be shown. Otherwise, the
* default LoadableComponent.showDataFetchResult behavior will be used. * default LoadableComponent.showDataFetchResult behavior will be used.
* *
* @param component The component. * @param component The component.
* @param result The data result. * @param result The data result.
* @param factoryClass The fully qualified class name of the relevant * @param factoryClass The fully qualified class name of the relevant
* factory. * factory.
* @param moduleName The name of the ingest module (i.e. 'Keyword * @param moduleName The name of the ingest module (i.e. 'Keyword Search').
* Search').
*/ */
protected <T> void showResultWithModuleCheck(LoadableComponent<List<T>> component, DataFetchResult<List<T>> result, String factoryClass, String moduleName) { 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(); Predicate<List<T>> hasResults = (lst) -> lst != null && !lst.isEmpty();
@ -389,14 +519,13 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* message indicating the unrun ingest module will be shown. Otherwise, the * message indicating the unrun ingest module will be shown. Otherwise, the
* default LoadableComponent.showDataFetchResult behavior will be used. * default LoadableComponent.showDataFetchResult behavior will be used.
* *
* @param component The component. * @param component The component.
* @param result The data result. * @param result The data result.
* @param hasResults Given the data type, will provide whether or not the * @param hasResults Given the data type, will provide whether or not the
* data contains any actual results. * data contains any actual results.
* @param factoryClass The fully qualified class name of the relevant * @param factoryClass The fully qualified class name of the relevant
* factory. * factory.
* @param moduleName The name of the ingest module (i.e. 'Keyword * @param moduleName The name of the ingest module (i.e. 'Keyword Search').
* Search').
*/ */
protected <T> void showResultWithModuleCheck(LoadableComponent<T> component, DataFetchResult<T> result, protected <T> void showResultWithModuleCheck(LoadableComponent<T> component, DataFetchResult<T> result,
Predicate<T> hasResults, String factoryClass, String moduleName) { Predicate<T> hasResults, String factoryClass, String moduleName) {

View File

@ -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 PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
TimelinePanel.activityRangeLabel.text=Activity Range 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

View File

@ -3,6 +3,8 @@ AnalysisPanel_keyColumn_title=Name
AnalysisPanel_keywordSearchModuleName=Keyword Search AnalysisPanel_keywordSearchModuleName=Keyword Search
# {0} - module name # {0} - module name
BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source. 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 ContainerPanel_setFieldsForNonImageDataSource_na=N/A
CTL_DataSourceSummaryAction=Data Source Summary CTL_DataSourceSummaryAction=Data Source Summary
DataSourceSummaryDialog.closeButton.text=Close 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 PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
TimelinePanel.activityRangeLabel.text=Activity Range 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_noDataExists=No communication data exists
UserActivityPanel_tab_title=User Activity UserActivityPanel_tab_title=User Activity
UserActivityPanel_TopAccountTableModel_accountType_header=Account Type UserActivityPanel_TopAccountTableModel_accountType_header=Account Type

View File

@ -61,6 +61,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
Map<Long, Long> fileCountsMap = CaseDataSourcesSummary.getCountsOfFiles(); Map<Long, Long> fileCountsMap = CaseDataSourcesSummary.getCountsOfFiles();
dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap); dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap);
dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane(); dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane();
dataSourceSummaryTabbedPane.setParentCloseListener(() -> DataSourceSummaryDialog.this.dispose());
initComponents(); initComponents();
dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel); dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel);
dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> { dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> {
@ -169,7 +170,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
* source matches the dataSourceID it will select the first datasource. * source matches the dataSourceID it will select the first datasource.
* *
* @param dataSourceID the ID of the datasource to select, null will cause * @param dataSourceID the ID of the datasource to select, null will cause
* the first datasource to be selected * the first datasource to be selected
*/ */
void selectDataSource(Long dataSourceId) { void selectDataSource(Long dataSourceId) {
dataSourcesPanel.selectDataSource(dataSourceId); dataSourcesPanel.selectDataSource(dataSourceId);

View File

@ -47,7 +47,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
* Records of tab information (i.e. title, component, function to call on * Records of tab information (i.e. title, component, function to call on
* new data source). * new data source).
*/ */
private static class DataSourceTab { private class DataSourceTab {
private final String tabTitle; private final String tabTitle;
private final Component component; private final Component component;
@ -74,8 +74,12 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
* *
* @param tabTitle The title of the tab. * @param tabTitle The title of the tab.
* @param panel The component to be displayed in 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) { DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) {
panel.setParentCloseListener(() -> notifyParentClose());
this.tabTitle = tabTitle; this.tabTitle = tabTitle;
this.component = panel; this.component = panel;
this.onDataSource = panel::setDataSource; 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 TABBED_PANE = "tabbedPane";
private static final String NO_DATASOURCE_PANE = "noDataSourcePane"; private static final String NO_DATASOURCE_PANE = "noDataSourcePane";
private Runnable notifyParentClose = null;
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel(); private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
private final List<DataSourceTab> tabs = Arrays.asList( private final List<DataSourceTab> tabs = Arrays.asList(
@ -142,6 +147,24 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
postInit(); 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. * Method called right after initComponents during initialization.
*/ */

View File

@ -94,7 +94,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <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> </Constraint>
</Constraints> </Constraints>
@ -106,7 +106,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <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> </Constraint>
</Constraints> </Constraints>
@ -140,7 +140,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <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> </Constraint>
</Constraints> </Constraints>
</Component> </Component>
@ -156,7 +156,55 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="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> </Constraint>
</Constraints> </Constraints>
</Component> </Component>

View File

@ -21,13 +21,16 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails; 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.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
@ -77,6 +80,36 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
initalizeTables(); 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 @Override
protected void fetchInformation(DataSource dataSource) { protected void fetchInformation(DataSource dataSource) {
fetchInformation(dataFetchComponents, dataSource); fetchInformation(dataFetchComponents, dataSource);
@ -113,11 +146,13 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
List<ColumnModel<RecentFileDetails>> list = Arrays.asList( List<ColumnModel<RecentFileDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getPath()); return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250), }, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(), new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getDateAsString()); return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80)); }, 80));
ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(list); ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(list);
@ -126,6 +161,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
pane.setModel(tableModel); pane.setModel(tableModel);
pane.setColumnModel(JTablePanel.getTableColumnModel(list)); pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setKeyFunction((recentFile) -> recentFile.getPath()); pane.setKeyFunction((recentFile) -> recentFile.getPath());
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane); tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker
@ -148,15 +184,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
List<ColumnModel<RecentDownloadDetails>> list = Arrays.asList( List<ColumnModel<RecentDownloadDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getWebDomain()); return new DefaultCellModel(prog.getWebDomain())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 100), }, 100),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getPath()); return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250), }, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(), new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getDateAsString()); return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80)); }, 80));
ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(list); ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(list);
@ -165,6 +204,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
pane.setModel(tableModel); pane.setModel(tableModel);
pane.setKeyFunction((download) -> download.getPath()); pane.setKeyFunction((download) -> download.getPath());
pane.setColumnModel(JTablePanel.getTableColumnModel(list)); pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane); tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker
@ -187,15 +227,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
List<ColumnModel<RecentAttachmentDetails>> list = Arrays.asList( List<ColumnModel<RecentAttachmentDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getPath()); return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250), }, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(), new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getDateAsString()); return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80), }, 80),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getSender()); return new DefaultCellModel(prog.getSender())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 150)); }, 150));
ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(list); ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(list);
@ -204,6 +247,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
pane.setModel(tableModel); pane.setModel(tableModel);
pane.setKeyFunction((attachment) -> attachment.getPath()); pane.setKeyFunction((attachment) -> attachment.getPath());
pane.setColumnModel(JTablePanel.getTableColumnModel(list)); pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane); tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentAttachmentDetails>> worker 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 openDocsLabel = new javax.swing.JLabel();
javax.swing.JLabel downloadLabel = new javax.swing.JLabel(); javax.swing.JLabel downloadLabel = new javax.swing.JLabel();
javax.swing.JLabel attachmentLabel = 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()); setLayout(new java.awt.BorderLayout());
@ -263,7 +310,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
tablePanel.add(openedDocPane, gridBagConstraints); tablePanel.add(openedDocPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4; gridBagConstraints.gridy = 5;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0; gridBagConstraints.weightx = 1.0;
@ -272,7 +319,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
tablePanel.add(downloadsPane, gridBagConstraints); tablePanel.add(downloadsPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 6; gridBagConstraints.gridy = 8;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0; 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 org.openide.awt.Mnemonics.setLocalizedText(downloadLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.downloadLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3; gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0); gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(downloadLabel, gridBagConstraints); 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 org.openide.awt.Mnemonics.setLocalizedText(attachmentLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.attachmentLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 5; gridBagConstraints.gridy = 7;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0); gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(attachmentLabel, gridBagConstraints); 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); scrollPane.setViewportView(tablePanel);
add(scrollPane, java.awt.BorderLayout.CENTER); add(scrollPane, java.awt.BorderLayout.CENTER);

View File

@ -171,7 +171,7 @@
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalStrut"/> <AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalStrut"/>
</AuxValues> </AuxValues>
</Component> </Component>
<Container class="javax.swing.JPanel" name="sameIdPanel"> <Container class="javax.swing.JPanel" name="last30DaysPanel">
<Properties> <Properties>
<Property name="alignmentX" type="float" value="0.0"/> <Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <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"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </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, &quot;{key}&quot;)"/>
</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"> <Component class="javax.swing.Box$Filler" name="filler5">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">

View File

@ -26,8 +26,15 @@ import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.logging.Level;
import org.apache.commons.collections.CollectionUtils; 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.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;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData; 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.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel; 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.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
/** /**
* A tab shown in data source summary displaying information about a data * A tab shown in data source summary displaying information about a data
@ -54,7 +65,8 @@ import org.sleuthkit.datamodel.DataSource;
"TimlinePanel_last30DaysChart_fileEvts_title=File Events", "TimlinePanel_last30DaysChart_fileEvts_title=File Events",
"TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",}) "TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",})
public class TimelinePanel extends BaseDataSourceSummaryPanel { public class TimelinePanel extends BaseDataSourceSummaryPanel {
private static final Logger logger = Logger.getLogger(TimelinePanel.class.getName());
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy"); private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy");
private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d"); private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d");
@ -75,13 +87,14 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
private final LoadableLabel earliestLabel = new LoadableLabel(Bundle.TimelinePanel_earliestLabel_title()); private final LoadableLabel earliestLabel = new LoadableLabel(Bundle.TimelinePanel_earliestLabel_title());
private final LoadableLabel latestLabel = new LoadableLabel(Bundle.TimelinePanel_latestLabel_title()); private final LoadableLabel latestLabel = new LoadableLabel(Bundle.TimelinePanel_latestLabel_title());
private final BarChartPanel last30DaysChart = new BarChartPanel(Bundle.TimlinePanel_last30DaysChart_title(), "", ""); private final BarChartPanel last30DaysChart = new BarChartPanel(Bundle.TimlinePanel_last30DaysChart_title(), "", "");
private final TimelineDataSourceUtils timelineUtils = TimelineDataSourceUtils.getInstance();
// all loadable components on this tab // all loadable components on this tab
private final List<LoadableComponent<?>> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart); private final List<LoadableComponent<?>> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart);
// actions to load data for this tab // actions to load data for this tab
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents; private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
public TimelinePanel() { public TimelinePanel() {
this(new TimelineSummary()); this(new TimelineSummary());
} }
@ -96,7 +109,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
(dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT), (dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT),
(result) -> handleResult(result)) (result) -> handleResult(result))
); );
initComponents(); initComponents();
} }
@ -112,7 +125,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
private static String formatDate(Date date, DateFormat formatter) { private static String formatDate(Date date, DateFormat formatter) {
return date == null ? null : formatter.format(date); return date == null ? null : formatter.format(date);
} }
private static final Color FILE_EVT_COLOR = new Color(228, 22, 28); private static final Color FILE_EVT_COLOR = new Color(228, 22, 28);
private static final Color ARTIFACT_EVT_COLOR = new Color(21, 227, 100); private static final Color ARTIFACT_EVT_COLOR = new Color(21, 227, 100);
@ -132,24 +145,27 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
// Create a bar chart item for each recent days activity item // Create a bar chart item for each recent days activity item
List<BarChartItem> fileEvtCounts = new ArrayList<>(); List<BarChartItem> fileEvtCounts = new ArrayList<>();
List<BarChartItem> artifactEvtCounts = new ArrayList<>(); List<BarChartItem> artifactEvtCounts = new ArrayList<>();
for (int i = 0; i < recentDaysActivity.size(); i++) { for (int i = 0; i < recentDaysActivity.size(); i++) {
DailyActivityAmount curItem = recentDaysActivity.get(i); DailyActivityAmount curItem = recentDaysActivity.get(i);
long fileAmt = curItem.getFileActivityCount(); long fileAmt = curItem.getFileActivityCount();
long artifactAmt = curItem.getArtifactActivityCount() * 100; long artifactAmt = curItem.getArtifactActivityCount() * 100;
String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1) String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1)
? formatDate(curItem.getDay(), CHART_FORMAT) : ""; ? formatDate(curItem.getDay(), CHART_FORMAT) : "";
OrderedKey thisKey = new OrderedKey(formattedDate, i); OrderedKey thisKey = new OrderedKey(formattedDate, i);
fileEvtCounts.add(new BarChartItem(thisKey, fileAmt)); fileEvtCounts.add(new BarChartItem(thisKey, fileAmt));
artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt)); artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt));
} }
return Arrays.asList( return Arrays.asList(
new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts), new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts),
new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts)); 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 * Handles displaying the result for each displayable item in the
@ -163,18 +179,99 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT))); earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT)));
latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT))); latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT)));
last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity()))); 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 @Override
protected void fetchInformation(DataSource dataSource) { protected void fetchInformation(DataSource dataSource) {
fetchInformation(dataFetchComponents, dataSource); fetchInformation(dataFetchComponents, dataSource);
} }
@Override @Override
protected void onNewDataSource(DataSource dataSource) { protected void onNewDataSource(DataSource dataSource) {
onNewDataSource(dataFetchComponents, loadableComponents, dataSource); onNewDataSource(dataFetchComponents, loadableComponents, dataSource);
} }
@Override @Override
public void close() { public void close() {
ingestRunningLabel.unregister(); ingestRunningLabel.unregister();
@ -198,7 +295,8 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
javax.swing.JPanel earliestLabelPanel = earliestLabel; javax.swing.JPanel earliestLabelPanel = earliestLabel;
javax.swing.JPanel latestLabelPanel = latestLabel; 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.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)); javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
@ -233,12 +331,21 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
filler2.setAlignmentX(0.0F); filler2.setAlignmentX(0.0F);
mainContentPanel.add(filler2); mainContentPanel.add(filler2);
sameIdPanel.setAlignmentX(0.0F); last30DaysPanel.setAlignmentX(0.0F);
sameIdPanel.setMaximumSize(new java.awt.Dimension(600, 300)); last30DaysPanel.setMaximumSize(new java.awt.Dimension(600, 300));
sameIdPanel.setMinimumSize(new java.awt.Dimension(600, 300)); last30DaysPanel.setMinimumSize(new java.awt.Dimension(600, 300));
sameIdPanel.setPreferredSize(new java.awt.Dimension(600, 300)); last30DaysPanel.setPreferredSize(new java.awt.Dimension(600, 300));
sameIdPanel.setVerifyInputWhenFocusTarget(false); last30DaysPanel.setVerifyInputWhenFocusTarget(false);
mainContentPanel.add(sameIdPanel); 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); filler5.setAlignmentX(0.0F);
mainContentPanel.add(filler5); mainContentPanel.add(filler5);
@ -257,7 +364,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
); );
}// </editor-fold>//GEN-END:initComponents }// </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 // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton viewInTimelineBtn;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -135,6 +135,17 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler3"> <Component class="javax.swing.Box$Filler" name="filler3">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <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"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler4"> <Component class="javax.swing.Box$Filler" name="filler4">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <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"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler6"> <Component class="javax.swing.Box$Filler" name="filler6">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <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"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler8"> <Component class="javax.swing.Box$Filler" name="filler8">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <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"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
</SubComponents> </SubComponents>
</Container> </Container>
</SubComponents> </SubComponents>

View File

@ -29,12 +29,14 @@ import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary; 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.TopAccountResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; 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.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
@ -92,7 +94,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
Bundle.UserActivityPanel_TopProgramsTableModel_name_header(), Bundle.UserActivityPanel_TopProgramsTableModel_name_header(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getProgramName()) return new DefaultCellModel(prog.getProgramName())
.setTooltip(prog.getProgramPath()); .setTooltip(prog.getProgramPath())
.setPopupMenu(getPopup(prog));
}, },
250), 250),
// program folder column // program folder column
@ -103,7 +106,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
getShortFolderName( getShortFolderName(
prog.getProgramPath(), prog.getProgramPath(),
prog.getProgramName())) prog.getProgramName()))
.setTooltip(prog.getProgramPath()); .setTooltip(prog.getProgramPath())
.setPopupMenu(getPopup(prog));
}, },
150), 150),
// run count column // run count column
@ -111,13 +115,17 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
Bundle.UserActivityPanel_TopProgramsTableModel_count_header(), Bundle.UserActivityPanel_TopProgramsTableModel_count_header(),
(prog) -> { (prog) -> {
String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes()); String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes());
return new DefaultCellModel(runTimes); return new DefaultCellModel(runTimes)
.setPopupMenu(getPopup(prog));
}, },
80), 80),
// last run date column // last run date column
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopProgramsTableModel_lastrun_header(), Bundle.UserActivityPanel_TopProgramsTableModel_lastrun_header(),
(prog) -> new DefaultCellModel(getFormatted(prog.getLastRun())), (prog) -> {
return new DefaultCellModel(getFormatted(prog.getLastAccessed()))
.setPopupMenu(getPopup(prog));
},
150) 150)
)) ))
.setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName()); .setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName());
@ -127,20 +135,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// domain column // domain column
new ColumnModel<TopDomainsResult>( new ColumnModel<TopDomainsResult>(
Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(), Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(),
(recentDomain) -> new DefaultCellModel(recentDomain.getDomain()), (recentDomain) -> {
return new DefaultCellModel(recentDomain.getDomain())
.setPopupMenu(getPopup(recentDomain));
},
250), 250),
// count column // count column
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopDomainsTableModel_count_header(), Bundle.UserActivityPanel_TopDomainsTableModel_count_header(),
(recentDomain) -> { (recentDomain) -> {
String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes()); String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes());
return new DefaultCellModel(visitTimes); return new DefaultCellModel(visitTimes)
.setPopupMenu(getPopup(recentDomain));
}, },
100), 100),
// last accessed column // last accessed column
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(), Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(),
(recentDomain) -> new DefaultCellModel(getFormatted(recentDomain.getLastVisit())), (recentDomain) -> {
return new DefaultCellModel(getFormatted(recentDomain.getLastAccessed()))
.setPopupMenu(getPopup(recentDomain));
},
150) 150)
)) ))
.setKeyFunction((domain) -> domain.getDomain()); .setKeyFunction((domain) -> domain.getDomain());
@ -150,19 +165,28 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// search string column // search string column
new ColumnModel<TopWebSearchResult>( new ColumnModel<TopWebSearchResult>(
Bundle.UserActivityPanel_TopWebSearchTableModel_searchString_header(), Bundle.UserActivityPanel_TopWebSearchTableModel_searchString_header(),
(webSearch) -> new DefaultCellModel(webSearch.getSearchString()), (webSearch) -> {
return new DefaultCellModel(webSearch.getSearchString())
.setPopupMenu(getPopup(webSearch));
},
250 250
), ),
// last accessed // last accessed
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopWebSearchTableModel_dateAccessed_header(), Bundle.UserActivityPanel_TopWebSearchTableModel_dateAccessed_header(),
(webSearch) -> new DefaultCellModel(getFormatted(webSearch.getDateAccessed())), (webSearch) -> {
return new DefaultCellModel(getFormatted(webSearch.getLastAccessed()))
.setPopupMenu(getPopup(webSearch));
},
150 150
), ),
// translated value // translated value
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopWebSearchTableModel_translatedResult_header(), Bundle.UserActivityPanel_TopWebSearchTableModel_translatedResult_header(),
(webSearch) -> new DefaultCellModel(webSearch.getTranslatedResult()), (webSearch) -> {
return new DefaultCellModel(webSearch.getTranslatedResult())
.setPopupMenu(getPopup(webSearch));
},
250 250
) )
)) ))
@ -173,13 +197,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// device id column // device id column
new ColumnModel<TopDeviceAttachedResult>( new ColumnModel<TopDeviceAttachedResult>(
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(), Bundle.UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(),
(device) -> new DefaultCellModel(device.getDeviceId()), (device) -> {
return new DefaultCellModel(device.getDeviceId())
.setPopupMenu(getPopup(device));
},
250 250
), ),
// last accessed // last accessed
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(), Bundle.UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(),
(device) -> new DefaultCellModel(getFormatted(device.getDateAccessed())), (device) -> {
return new DefaultCellModel(getFormatted(device.getLastAccessed()))
.setPopupMenu(getPopup(device));
},
150 150
), ),
// make and model // make and model
@ -191,7 +221,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
String makeModelString = (make.isEmpty() || model.isEmpty()) String makeModelString = (make.isEmpty() || model.isEmpty())
? make + model ? make + model
: String.format("%s - %s", make, model); : String.format("%s - %s", make, model);
return new DefaultCellModel(makeModelString); return new DefaultCellModel(makeModelString)
.setPopupMenu(getPopup(device));
}, },
250 250
) )
@ -203,13 +234,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// account type column // account type column
new ColumnModel<TopAccountResult>( new ColumnModel<TopAccountResult>(
Bundle.UserActivityPanel_TopAccountTableModel_accountType_header(), Bundle.UserActivityPanel_TopAccountTableModel_accountType_header(),
(account) -> new DefaultCellModel(account.getAccountType()), (account) -> {
return new DefaultCellModel(account.getAccountType())
.setPopupMenu(getPopup(account));
},
250 250
), ),
// last accessed // last accessed
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopAccountTableModel_lastAccess_header(), Bundle.UserActivityPanel_TopAccountTableModel_lastAccess_header(),
(account) -> new DefaultCellModel(getFormatted(account.getLastAccess())), (account) -> {
return new DefaultCellModel(getFormatted(account.getLastAccessed()))
.setPopupMenu(getPopup(account));
},
150 150
) )
)) ))
@ -227,7 +264,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents; private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private final UserActivitySummary userActivityData; private final UserActivitySummary userActivityData;
/** /**
* Creates a new UserActivityPanel. * Creates a new UserActivityPanel.
*/ */
@ -239,7 +276,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
* Creates a new UserActivityPanel. * Creates a new UserActivityPanel.
* *
* @param userActivityData Class from which to obtain remaining user * @param userActivityData Class from which to obtain remaining user
* activity data. * activity data.
*/ */
public UserActivityPanel(UserActivitySummary userActivityData) { public UserActivityPanel(UserActivitySummary userActivityData) {
super(userActivityData); super(userActivityData);
@ -292,10 +329,23 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
initComponents(); 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. * Queries DataSourceTopProgramsSummary instance for short folder name.
* *
* @param path The path for the application. * @param path The path for the application.
* @param appName The application name. * @param appName The application name.
* *
* @return The underlying short folder name if one exists. * @return The underlying short folder name if one exists.
@ -335,22 +385,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel(); 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.JPanel topAccounts = topAccountsTable;
javax.swing.JLabel rightClickForMoreOptions5 = new javax.swing.JLabel();
setLayout(new java.awt.BorderLayout()); setLayout(new java.awt.BorderLayout());
@ -379,6 +434,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106)); topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106)); topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(topProgramsTablePanel); 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); contentPanel.add(filler3);
recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -391,6 +449,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106)); recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106)); recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(recentDomainsTablePanel); 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); contentPanel.add(filler4);
topWebSearchLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); topWebSearchLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -403,6 +464,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topWebSearches.setMinimumSize(new java.awt.Dimension(10, 106)); topWebSearches.setMinimumSize(new java.awt.Dimension(10, 106));
topWebSearches.setPreferredSize(new java.awt.Dimension(10, 106)); topWebSearches.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(topWebSearches); 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); contentPanel.add(filler6);
topDevicesAttachedLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); topDevicesAttachedLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -415,6 +479,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
recentDevicesAttached.setMinimumSize(new java.awt.Dimension(10, 106)); recentDevicesAttached.setMinimumSize(new java.awt.Dimension(10, 106));
recentDevicesAttached.setPreferredSize(new java.awt.Dimension(10, 106)); recentDevicesAttached.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(recentDevicesAttached); 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); contentPanel.add(filler8);
recentAccountsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); recentAccountsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -428,6 +495,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topAccounts.setPreferredSize(new java.awt.Dimension(10, 106)); topAccounts.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(topAccounts); 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); contentScrollPane.setViewportView(contentPanel);
add(contentScrollPane, java.awt.BorderLayout.CENTER); add(contentScrollPane, java.awt.BorderLayout.CENTER);

View File

@ -304,4 +304,5 @@ public class BarChartPanel extends AbstractLoadableComponent<List<BarChartPanel.
} }
} }
} }
} }

View File

@ -20,12 +20,22 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.Component; import java.awt.Component;
import java.awt.Insets; 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.BorderFactory;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; 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 * A Table cell renderer that renders a cell of a table based off of the
@ -49,7 +59,7 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
* Constructor for a HorizontalAlign enum. * Constructor for a HorizontalAlign enum.
* *
* @param jlabelAlignment The corresponding JLabel horizontal alignment * @param jlabelAlignment The corresponding JLabel horizontal alignment
* number. * number.
*/ */
HorizontalAlign(int jlabelAlignment) { HorizontalAlign(int jlabelAlignment) {
this.jlabelAlignment = jlabelAlignment; this.jlabelAlignment = jlabelAlignment;
@ -57,13 +67,60 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
/** /**
* @return The corresponding JLabel horizontal alignment (i.e. * @return The corresponding JLabel horizontal alignment (i.e.
* JLabel.LEFT). * JLabel.LEFT).
*/ */
int getJLabelAlignment() { int getJLabelAlignment() {
return this.jlabelAlignment; return this.jlabelAlignment;
} }
} }
/**
* 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. * Basic interface for a cell model.
*/ */
@ -88,6 +145,12 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
* @return The insets for the cell text. * @return The insets for the cell text.
*/ */
Insets getInsets(); 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 String tooltip;
private HorizontalAlign horizontalAlignment; private HorizontalAlign horizontalAlignment;
private Insets insets; private Insets insets;
private List<MenuItem> popupMenu;
private Supplier<List<MenuItem>> menuItemSupplier;
/** /**
* Main constructor. * Main constructor.
@ -166,6 +231,41 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
return this; 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 @Override
public String toString() { public String toString() {
return getText(); return getText();
@ -192,8 +292,8 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
* Customizes the jlabel to match the column model and cell model provided. * Customizes the jlabel to match the column model and cell model provided.
* *
* @param defaultCell The cell to customize that will be displayed in the * @param defaultCell The cell to customize that will be displayed in the
* jtable. * jtable.
* @param cellModel The cell model for this cell. * @param cellModel The cell model for this cell.
* *
* @return The provided defaultCell. * @return The provided defaultCell.
*/ */
@ -231,4 +331,42 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
return defaultCell; 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;
}
} }

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@ -40,6 +42,85 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRendere
*/ */
public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> { 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 * JTables don't allow displaying messages. So this LayerUI is used to
* display the contents of a child JLabel. Inspired by TableWaitLayerTest * display the contents of a child JLabel. Inspired by TableWaitLayerTest
@ -91,9 +172,9 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/** /**
* Constructor for a DataResultColumnModel. * Constructor for a DataResultColumnModel.
* *
* @param headerTitle The title for the column. * @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the * @param cellRenderer The method that generates a CellModel for the
* column based on the data. * column based on the data.
*/ */
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer) { public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer) {
this(headerTitle, cellRenderer, null); this(headerTitle, cellRenderer, null);
@ -102,10 +183,10 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/** /**
* Constructor for a DataResultColumnModel. * Constructor for a DataResultColumnModel.
* *
* @param headerTitle The title for the column. * @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the * @param cellRenderer The method that generates a CellModel for the
* column based on the data. * column based on the data.
* @param width The preferred width of the column. * @param width The preferred width of the column.
*/ */
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer, Integer width) { public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer, Integer width) {
this.headerTitle = headerTitle; this.headerTitle = headerTitle;
@ -122,7 +203,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/** /**
* @return The method that generates a CellModel for the column based on * @return The method that generates a CellModel for the column based on
* the data. * the data.
*/ */
public Function<T, CellModel> getCellRenderer() { public Function<T, CellModel> getCellRenderer() {
return cellRenderer; return cellRenderer;
@ -197,14 +278,18 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
*/ */
public static <T> JTablePanel<T> getJTablePanel(List<ColumnModel<T>> columns) { public static <T> JTablePanel<T> getJTablePanel(List<ColumnModel<T>> columns) {
ListTableModel<T> tableModel = getTableModel(columns); ListTableModel<T> tableModel = getTableModel(columns);
JTablePanel<T> resultTable = new JTablePanel<>(tableModel); JTablePanel<T> resultTable = new JTablePanel<>(tableModel)
return resultTable.setColumnModel(getTableColumnModel(columns)); .setColumnModel(getTableColumnModel(columns))
.setCellListener(CellModelTableCellRenderer.getMouseListener());
return resultTable;
} }
private JScrollPane tableScrollPane; private JScrollPane tableScrollPane;
private Overlay overlayLayer; private Overlay overlayLayer;
private ListTableModel<T> tableModel; private ListTableModel<T> tableModel;
private JTable table; private JTable table;
private CellMouseListener cellListener = null;
private Function<T, ? extends Object> keyFunction = (rowItem) -> rowItem; private Function<T, ? extends Object> keyFunction = (rowItem) -> rowItem;
/** /**
@ -222,6 +307,26 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
*/ */
public JTablePanel() { public JTablePanel() {
initComponents(); 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 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. * @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 * @return The function for determining the key for a data row. This key is
* used to maintain current selection in the table despite changing * used to maintain current selection in the table despite changing rows.
* rows.
*/ */
public Function<T, ? extends Object> getKeyFunction() { public Function<T, ? extends Object> getKeyFunction() {
return keyFunction; return keyFunction;
@ -295,7 +419,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
T prevValue = (tableRows != null && prevSelectedRow >= 0 && prevSelectedRow < tableRows.size()) T prevValue = (tableRows != null && prevSelectedRow >= 0 && prevSelectedRow < tableRows.size())
? this.tableModel.getDataRows().get(prevSelectedRow) ? this.tableModel.getDataRows().get(prevSelectedRow)
: null; : null;
Object prevKeyValue = (prevValue == null) ? null : this.keyFunction.apply(prevValue); Object prevKeyValue = (prevValue == null) ? null : this.keyFunction.apply(prevValue);
// set the list of data to be shown as either the data or an empty list // set the list of data to be shown as either the data or an empty list
@ -330,7 +454,6 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
private void initComponents() { private void initComponents() {
table = new JTable(); table = new JTable();
table.getTableHeader().setReorderingAllowed(false); table.getTableHeader().setReorderingAllowed(false);
overlayLayer = new Overlay(); overlayLayer = new Overlay();
tableScrollPane = new JScrollPane(table); tableScrollPane = new JScrollPane(table);
JLayer<JComponent> dualLayer = new JLayer<>(tableScrollPane, overlayLayer); JLayer<JComponent> dualLayer = new JLayer<>(tableScrollPane, overlayLayer);

View File

@ -22,6 +22,7 @@ import com.google.common.cache.CacheLoader;
import java.awt.Image; import java.awt.Image;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -38,17 +39,22 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.openide.util.ImageUtilities; import org.openide.util.ImageUtilities;
/** /**
* Loads a thumbnail for the given request. Thumbnail candidates are JPEG files * Loads a thumbnail for the given request. Thumbnail candidates types are defined below.
* that have either TSK_WEB_DOWNLOAD or TSK_WEB_CACHE artifacts that match the * These candidates types must be the source of either TSK_WEB_DOWNLOAD or
* domain name (see the DomainSearch getArtifacts() API). JPEG files are sorted * 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 * 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. * TSK_WEB_CACHE artifacts. The first suitable thumbnail is selected.
*/ */
public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbnailRequest, Image> { 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 UNSUPPORTED_IMAGE = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
private static final String JPG_EXTENSION = "jpg"; private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("jpg", "svg", "png", "webp", "ico", "gif");
private static final String JPG_MIME_TYPE = "image/jpeg"; 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; private final DomainSearchArtifactsCache artifactsCache;
/** /**
@ -75,8 +81,21 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
final DomainSearchArtifactsRequest webDownloadsRequest = new DomainSearchArtifactsRequest( final DomainSearchArtifactsRequest webDownloadsRequest = new DomainSearchArtifactsRequest(
caseDb, thumbnailRequest.getDomain(), TSK_WEB_DOWNLOAD); caseDb, thumbnailRequest.getDomain(), TSK_WEB_DOWNLOAD);
final List<BlackboardArtifact> webDownloads = artifactsCache.get(webDownloadsRequest); final List<BlackboardArtifact> webDownloads = artifactsCache.get(webDownloadsRequest);
final List<AbstractFile> webDownloadPictures = getJpegsFromWebDownload(caseDb, webDownloads); final List<AbstractFile> webDownloadPictures = getCandidatesFromWebDownloads(caseDb, webDownloads);
Collections.sort(webDownloadPictures, (file1, file2) -> Long.compare(file1.getCrtime(), file2.getCrtime())); 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--) { for (int i = webDownloadPictures.size() - 1; i >= 0; i--) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
@ -92,8 +111,20 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
final DomainSearchArtifactsRequest webCacheRequest = new DomainSearchArtifactsRequest( final DomainSearchArtifactsRequest webCacheRequest = new DomainSearchArtifactsRequest(
caseDb, thumbnailRequest.getDomain(), TSK_WEB_CACHE); caseDb, thumbnailRequest.getDomain(), TSK_WEB_CACHE);
final List<BlackboardArtifact> webCacheArtifacts = artifactsCache.get(webCacheRequest); final List<BlackboardArtifact> webCacheArtifacts = artifactsCache.get(webCacheRequest);
final List<AbstractFile> webCachePictures = getJpegsFromWebCache(caseDb, webCacheArtifacts); final List<AbstractFile> webCachePictures = getCandidatesFromWebCache(caseDb, webCacheArtifacts);
Collections.sort(webCachePictures, (file1, file2) -> Long.compare(file1.getSize(), file2.getSize())); 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--) { for (int i = webCachePictures.size() - 1; i >= 0; i--) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); 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 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. * associated with the artifacts.
* *
* @throws TskCoreException * @throws TskCoreException
*/ */
private List<AbstractFile> getJpegsFromWebDownload(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException { private List<AbstractFile> getCandidatesFromWebDownloads(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
final List<AbstractFile> jpegs = new ArrayList<>(); final List<AbstractFile> candidates = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) { for (BlackboardArtifact artifact : artifacts) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
} }
final Content sourceContent = caseDb.getContentById(artifact.getObjectID()); 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 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. * 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 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) { for (BlackboardArtifact artifact : artifacts) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
@ -150,24 +186,23 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID); final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID);
if (tskPathId != null) { if (tskPathId != null) {
final Content sourceContent = caseDb.getContentById(tskPathId.getValueLong()); 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 * @param files The list of source content files which are supported.
* add to.
* @param sourceContent The source content to check and possibly add. * @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)) { if ((sourceContent instanceof AbstractFile) && !(sourceContent instanceof DataSource)) {
final AbstractFile file = (AbstractFile) sourceContent; final AbstractFile file = (AbstractFile) sourceContent;
if (JPG_EXTENSION.equals(file.getNameExtension()) if (SUPPORTED_EXTENSIONS.contains(file.getNameExtension())
|| JPG_MIME_TYPE.equals(file.getMIMEType())) { || SUPPORTED_MIMETYPES.contains(file.getMIMEType())) {
files.add(file); files.add(file);
} }
} }

View File

@ -22,11 +22,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.util.Pair;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult; 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.Track;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; 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. * @param wasEntirelySuccessful True if no errors occurred while processing.
*/ */
abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks,
boolean wasEntirelySuccessful); List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful);
@Override @Override
public void process(GeoLocationParseResult<Waypoint> waypointResults) { public void process(GeoLocationParseResult<Waypoint> waypointResults) {
@ -93,36 +93,54 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex); logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex);
} }
} }
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(), 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<>(); final List<Set<MapWaypoint>> trackSets = new ArrayList<>();
for (List<Waypoint> t : waypointsAndTracks.getValue()) { for (List<Waypoint> t : geoDataSet.getTracks()) {
trackSets.add(MapWaypoint.getWaypoints(t)); trackSets.add(MapWaypoint.getWaypoints(t));
} }
final List<Set<MapWaypoint>> areaSets = new ArrayList<>();
for (List<Waypoint> t : geoDataSet.getAreas()) {
areaSets.add(MapWaypoint.getWaypoints(t));
}
handleFilteredWaypointSet( handleFilteredWaypointSet(
pointSet, trackSets, pointSet, trackSets, areaSets,
(trackResults == null || trackResults.isSuccessfullyParsed()) && waypointResults.isSuccessfullyParsed()); (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. * account the current filters and includes waypoints as approprate.
* *
* @param waypoints List of waypoints * @param waypoints List of waypoints
* @param tracks List of tracks * @param tracks List of tracks
* @param areas List of areas
* *
* @return A list of waypoints including the tracks based on the current * @return A GeoDataSet object containing a list of waypoints including the tracks and areas based on the current
* filters. 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<>(); final List<Waypoint> completeList = new ArrayList<>();
List<List<Waypoint>> filteredTracks = new ArrayList<>(); List<List<Waypoint>> filteredTracks = new ArrayList<>();
List<List<Waypoint>> filteredAreas = new ArrayList<>();
if (tracks != null) { if (tracks != null) {
Long timeRangeEnd; Long timeRangeEnd;
@ -149,7 +167,14 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
} else { } else {
completeList.addAll(waypoints); 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; 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;
}
}
} }

View File

@ -71,7 +71,8 @@ class GeoFilterPanel extends javax.swing.JPanel {
ARTIFACT_TYPE.TSK_GPS_SEARCH, ARTIFACT_TYPE.TSK_GPS_SEARCH,
ARTIFACT_TYPE.TSK_GPS_TRACK, ARTIFACT_TYPE.TSK_GPS_TRACK,
ARTIFACT_TYPE.TSK_GPS_TRACKPOINT, 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_LONGITUDE.getTypeID()
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS.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_WAYPOINTS.getTypeID()
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS.getTypeID()
+ " )" + " )"
+ " ) as innerTable"; + " ) as innerTable";
try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr); try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr);

View File

@ -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_GPS_ROUTE.getTypeID()
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.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_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); showRefreshPanel(true);
} }
@ -330,7 +331,7 @@ public final class GeolocationTopComponent extends TopComponent {
* *
* @param waypointList * @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() { SwingUtilities.invokeLater(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -348,6 +349,7 @@ public final class GeolocationTopComponent extends TopComponent {
mapPanel.clearWaypoints(); mapPanel.clearWaypoints();
mapPanel.setWaypoints(waypointList); mapPanel.setWaypoints(waypointList);
mapPanel.setTracks(tracks); mapPanel.setTracks(tracks);
mapPanel.setAreas(areas);
mapPanel.initializePainter(); mapPanel.initializePainter();
setWaypointLoading(false); setWaypointLoading(false);
geoFilterPanel.setEnabled(true); geoFilterPanel.setEnabled(true);
@ -505,8 +507,9 @@ public final class GeolocationTopComponent extends TopComponent {
} }
@Override @Override
void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, boolean wasEntirelySuccessful) { void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks,
addWaypointsToMap(mapWaypoints, tracks); List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful) {
addWaypointsToMap(mapWaypoints, tracks, areas);
// if there is an error, present to the user. // if there is an error, present to the user.
if (!wasEntirelySuccessful) { if (!wasEntirelySuccessful) {

View File

@ -23,6 +23,7 @@ import java.awt.BasicStroke;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
@ -30,6 +31,7 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter; import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent; 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 long serialVersionUID = 1L;
private static final Set<Integer> DOT_WAYPOINT_TYPES = new HashSet<>(); private static final Set<Integer> DOT_WAYPOINT_TYPES = new HashSet<>();
private static final int DOT_SIZE = 12; 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 boolean zoomChanging;
private KdTree<MapWaypoint> waypointTree; private KdTree<MapWaypoint> waypointTree;
private Set<MapWaypoint> waypointSet; private Set<MapWaypoint> waypointSet;
private List<Set<MapWaypoint>> tracks = new ArrayList<>(); private List<Set<MapWaypoint>> tracks = new ArrayList<>();
private List<Set<MapWaypoint>> areas = new ArrayList<>();
private Popup currentPopup; private Popup currentPopup;
private final PopupFactory popupFactory; private final PopupFactory popupFactory;
@ -108,12 +113,13 @@ final public class MapPanel extends javax.swing.JPanel {
private BufferedImage transparentWaypointImage; private BufferedImage transparentWaypointImage;
private MapWaypoint currentlySelectedWaypoint; private MapWaypoint currentlySelectedWaypoint;
private Set<MapWaypoint> currentlySelectedTrack; private Set<MapWaypoint> currentlySelectedSet;
static { static {
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()); 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_TRACK.getTypeID());
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_ROUTE.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()); waypointPainter.setRenderer(new MapWaypointRenderer());
ArrayList<Painter<JXMapViewer>> painters = new ArrayList<>(); ArrayList<Painter<JXMapViewer>> painters = new ArrayList<>();
painters.add(new MapAreaRenderer(areas));
painters.add(new MapTrackRenderer(tracks)); painters.add(new MapTrackRenderer(tracks));
painters.add(waypointPainter); painters.add(waypointPainter);
@ -342,6 +349,15 @@ final public class MapPanel extends javax.swing.JPanel {
void setTracks(List<Set<MapWaypoint>> tracks) { void setTracks(List<Set<MapWaypoint>> tracks) {
this.tracks = tracks; 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. * Set the current zoom level.
@ -361,7 +377,7 @@ final public class MapPanel extends javax.swing.JPanel {
void clearWaypoints() { void clearWaypoints() {
waypointTree = null; waypointTree = null;
currentlySelectedWaypoint = null; currentlySelectedWaypoint = null;
currentlySelectedTrack = null; currentlySelectedSet = null;
if (currentPopup != null) { if (currentPopup != null) {
currentPopup.hide(); currentPopup.hide();
} }
@ -514,6 +530,13 @@ final public class MapPanel extends javax.swing.JPanel {
DOT_SIZE, DOT_SIZE,
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 { } else {
rect = new Rectangle( rect = new Rectangle(
pointX - (whiteWaypointImage.getWidth() / 2), pointX - (whiteWaypointImage.getWidth() / 2),
@ -717,16 +740,24 @@ final public class MapPanel extends javax.swing.JPanel {
if (waypoints.size() > 0) { if (waypoints.size() > 0) {
MapWaypoint selection = waypoints.get(0); MapWaypoint selection = waypoints.get(0);
currentlySelectedWaypoint = selection; currentlySelectedWaypoint = selection;
currentlySelectedTrack = null; currentlySelectedSet = null;
for (Set<MapWaypoint> track : tracks) { for (Set<MapWaypoint> track : tracks) {
if (track.contains(selection)) { if (track.contains(selection)) {
currentlySelectedTrack = track; currentlySelectedSet = track;
break; break;
} }
} }
if (currentlySelectedSet == null) {
for (Set<MapWaypoint> area : areas) {
if (area.contains(selection)) {
currentlySelectedSet = area;
break;
}
}
}
} else { } else {
currentlySelectedWaypoint = null; currentlySelectedWaypoint = null;
currentlySelectedTrack = null; currentlySelectedSet = null;
} }
showDetailsPopup(); showDetailsPopup();
} }
@ -755,6 +786,7 @@ final public class MapPanel extends javax.swing.JPanel {
private class MapWaypointRenderer implements WaypointRenderer<MapWaypoint> { private class MapWaypointRenderer implements WaypointRenderer<MapWaypoint> {
private final Map<Color, BufferedImage> dotImageCache = new HashMap<>(); private final Map<Color, BufferedImage> dotImageCache = new HashMap<>();
private final Map<Color, BufferedImage> verySmallDotImageCache = new HashMap<>();
private final Map<Color, BufferedImage> waypointImageCache = 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) { private Color getColor(MapWaypoint waypoint) {
Color baseColor = waypoint.getColor(); Color baseColor = waypoint.getColor();
if (waypoint.equals(currentlySelectedWaypoint) if (waypoint.equals(currentlySelectedWaypoint)
|| (currentlySelectedTrack != null && currentlySelectedTrack.contains(waypoint))) { || (currentlySelectedSet != null && currentlySelectedSet.contains(waypoint))) {
// Highlight this waypoint since it is selected // Highlight this waypoint since it is selected
return Color.YELLOW; return Color.YELLOW;
} else { } else {
@ -778,11 +810,11 @@ final public class MapPanel extends javax.swing.JPanel {
* Creates a dot image with the specified color * Creates a dot image with the specified color
* *
* @param color the color of the new image * @param color the color of the new image
* @param s the size of the dot
* *
* @return the new dot image * @return the new dot image
*/ */
private BufferedImage createTrackDotImage(Color color) { private BufferedImage createTrackDotImage(Color color, int s) {
int s = DOT_SIZE;
BufferedImage ret = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB); BufferedImage ret = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = ret.createGraphics(); Graphics2D g = ret.createGraphics();
@ -831,7 +863,13 @@ final public class MapPanel extends javax.swing.JPanel {
if (DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) { if (DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) {
image = dotImageCache.computeIfAbsent(color, k -> { 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 // Center the dot on the GPS coordinate
y -= image.getHeight() / 2; y -= image.getHeight() / 2;
@ -903,4 +941,74 @@ final public class MapPanel extends javax.swing.JPanel {
g2d.dispose(); 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();
}
}
} }

View File

@ -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_TRACK.getTypeID(), Color.ORANGE);
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.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_METADATA_EXIF.getTypeID(), Color.MAGENTA);
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID(), new Color(0x8a2be2)); // Blue violet
} }
private final Waypoint dataModelWaypoint; private final Waypoint dataModelWaypoint;

View 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;
}
}
}

View File

@ -1,3 +1,5 @@
# {0} - area name
GEOArea_point_label_header=Area outline point for area: {0}
# {0} - track name # {0} - track name
GEOTrack_point_label_header=Trackpoint for track: {0} GEOTrack_point_label_header=Trackpoint for track: {0}
LastKnownWaypoint_Label=Last Known Location LastKnownWaypoint_Label=Last Known Location

View File

@ -22,6 +22,8 @@ package org.sleuthkit.autopsy.geolocation.datamodel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
@ -31,6 +33,8 @@ import org.sleuthkit.datamodel.TskCoreException;
* Class representing a series of waypoints that form a path. * Class representing a series of waypoints that form a path.
*/ */
public class GeoPath { public class GeoPath {
private static final Logger LOGGER = Logger.getLogger(GeoPath.class.getName());
private final List<Waypoint> waypointList; private final List<Waypoint> waypointList;
private final String pathName; private final String pathName;
private final BlackboardArtifact artifact; 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 skCase Currently open SleuthkitCase
* @param sourceList List of source to return tracks from, maybe null to * @param sourceList List of source to return tracks from, maybe null to
* return tracks from all sources * 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 * were found
* *
* @throws GeoLocationDataException * @throws GeoLocationDataException
@ -85,6 +89,7 @@ public class GeoPath {
tracks.add(new Track(artifact)); tracks.add(new Track(artifact));
} catch (GeoLocationDataException e) { } catch (GeoLocationDataException e) {
LOGGER.log(Level.WARNING, "Error loading track from artifact with ID " + artifact.getArtifactID(), e);
allParsedSuccessfully = false; allParsedSuccessfully = false;
} }
} }
@ -94,6 +99,41 @@ public class GeoPath {
} }
return new GeoLocationParseResult<Track>(tracks, allParsedSuccessfully); 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. * Path constructor.

View File

@ -22,20 +22,17 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil; import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException; import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException;
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints; import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints;
import org.sleuthkit.autopsy.coreutils.Logger;
/** /**
* A GPS track with which wraps the TSK_GPS_TRACK artifact. * A GPS track with which wraps the TSK_GPS_TRACK artifact.
*/ */
public final class Track extends GeoPath { public final class Track extends GeoPath {
private static final Logger LOGGER = Logger.getLogger(Track.class.getName());
private final Long startTimestamp; private final Long startTimestamp;
private final Long endTimeStamp; 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 { private GeoTrackPoints getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS); BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS);
if (attribute == null) { 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."); throw new GeoLocationDataException("No TSK_GEO_TRACKPOINTS attribute present in attribute map to parse.");
} }
try { try {
return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class); return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class);
} catch (InvalidJsonException ex) { } 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); throw new GeoLocationDataException("Unable to parse track points in TSK_GEO_TRACKPOINTS attribute", ex);
} }
} }

View File

@ -178,6 +178,28 @@ public final class WaypointBuilder {
return trackList; 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. * Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts.

View File

@ -7,6 +7,10 @@ HealthMonitorDashboard.createTimingControlPanel.skipOutliers=Do not plot outlier
HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled
HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics
HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display 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.noData=No data to display - monitor is not enabled
HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics
HealthMonitorDashboard.DateRange.oneDay=One day HealthMonitorDashboard.DateRange.oneDay=One day

View File

@ -65,7 +65,7 @@ public final class HealthMonitor implements PropertyChangeListener {
private final static Logger logger = Logger.getLogger(HealthMonitor.class.getName()); private final static Logger logger = Logger.getLogger(HealthMonitor.class.getName());
private final static String DATABASE_NAME = "HealthMonitor"; private final static String DATABASE_NAME = "HealthMonitor";
private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes 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 final static AtomicBoolean isEnabled = new AtomicBoolean(false);
private static HealthMonitor instance; private static HealthMonitor instance;
@ -77,6 +77,7 @@ public final class HealthMonitor implements PropertyChangeListener {
private BasicDataSource connectionPool = null; private BasicDataSource connectionPool = null;
private CaseDbConnectionInfo connectionSettingsInUse = null; private CaseDbConnectionInfo connectionSettingsInUse = null;
private String hostName; private String hostName;
private final String username;
private HealthMonitor() throws HealthMonitorException { private HealthMonitor() throws HealthMonitorException {
@ -96,6 +97,9 @@ public final class HealthMonitor implements PropertyChangeListener {
hostName = UUID.randomUUID().toString(); hostName = UUID.randomUUID().toString();
logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex); 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 // Read from the database to determine if the module is enabled
updateFromGlobalEnabledStatus(); updateFromGlobalEnabledStatus();
@ -153,8 +157,8 @@ public final class HealthMonitor implements PropertyChangeListener {
if (!databaseIsInitialized()) { if (!databaseIsInitialized()) {
initializeDatabaseSchema(); initializeDatabaseSchema();
} }
if (!CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) { if (getVersion().compareTo(CURRENT_DB_SCHEMA_VERSION) < 0) {
upgradeDatabaseSchema(); upgradeDatabaseSchema();
} }
@ -179,10 +183,15 @@ public final class HealthMonitor implements PropertyChangeListener {
if (conn == null) { if (conn == null) {
throw new HealthMonitorException("Error getting database connection"); throw new HealthMonitorException("Error getting database connection");
} }
ResultSet resultSet = null;
try (Statement statement = conn.createStatement()) { try (Statement statement = conn.createStatement()) {
conn.setAutoCommit(false); 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 // Upgrade from 1.0 to 1.1
// Changes: user_data table added // Changes: user_data table added
if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) { if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) {
@ -197,6 +206,19 @@ public final class HealthMonitor implements PropertyChangeListener {
+ "case_name text NOT NULL" + "case_name text NOT NULL"
+ ")"); + ")");
} }
// 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 // 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.getMajor() + "' WHERE name='SCHEMA_VERSION'");
@ -212,6 +234,13 @@ public final class HealthMonitor implements PropertyChangeListener {
} }
throw new HealthMonitorException("Error upgrading database", ex); throw new HealthMonitorException("Error upgrading database", ex);
} finally { } finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex2) {
logger.log(Level.SEVERE, "Error closing result set");
}
}
try { try {
conn.close(); conn.close();
} catch (SQLException ex) { } catch (SQLException ex) {
@ -499,7 +528,7 @@ public final class HealthMonitor implements PropertyChangeListener {
// Add metrics to the database // Add metrics to the database
String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; 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); try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql);
PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) { PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) {
@ -519,10 +548,11 @@ public final class HealthMonitor implements PropertyChangeListener {
for (UserData userInfo : userDataCopy) { for (UserData userInfo : userDataCopy) {
userStatement.setString(1, hostName); userStatement.setString(1, hostName);
userStatement.setLong(2, userInfo.getTimestamp()); userStatement.setString(2, username);
userStatement.setInt(3, userInfo.getEventType().getEventValue()); userStatement.setLong(3, userInfo.getTimestamp());
userStatement.setBoolean(4, userInfo.isExaminerNode()); userStatement.setInt(4, userInfo.getEventType().getEventValue());
userStatement.setString(5, userInfo.getCaseName()); userStatement.setBoolean(5, userInfo.isExaminerNode());
userStatement.setString(6, userInfo.getCaseName());
userStatement.execute(); userStatement.execute();
} }
@ -903,7 +933,8 @@ public final class HealthMonitor implements PropertyChangeListener {
+ "timestamp bigint NOT NULL," + "timestamp bigint NOT NULL,"
+ "event_type int NOT NULL," + "event_type int NOT NULL,"
+ "is_examiner BOOLEAN 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() + "')"); 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(); getInstance().writeCurrentStateToDatabase();
} }
} catch (HealthMonitorException ex) { } 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 * Class holding user metric data. Can be used for storing new events or
* retrieving events out of the database. * retrieving events out of the database.
*/ */
static class UserData { static class UserData implements Comparable<UserData> {
private final UserEvent eventType; private final UserEvent eventType;
private long timestamp; private long timestamp;
private final boolean isExaminer; private final boolean isExaminer;
private final String hostname; private final String hostname;
private String username;
private String caseName; private String caseName;
/** /**
@ -1359,6 +1391,7 @@ public final class HealthMonitor implements PropertyChangeListener {
this.timestamp = System.currentTimeMillis(); this.timestamp = System.currentTimeMillis();
this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode()); this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode());
this.hostname = ""; this.hostname = "";
this.username = "";
// If there's a case open, record the name // If there's a case open, record the name
try { try {
@ -1383,6 +1416,10 @@ public final class HealthMonitor implements PropertyChangeListener {
this.eventType = UserEvent.valueOf(resultSet.getInt("event_type")); this.eventType = UserEvent.valueOf(resultSet.getInt("event_type"));
this.isExaminer = resultSet.getBoolean("is_examiner"); this.isExaminer = resultSet.getBoolean("is_examiner");
this.caseName = resultSet.getString("case_name"); 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() { String getCaseName() {
return caseName; 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());
}
} }
/** /**

View File

@ -24,12 +24,17 @@ import java.awt.Dimension;
import java.util.Set; import java.util.Set;
import java.util.HashSet; import java.util.HashSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JDialog; import javax.swing.JDialog;
@ -40,13 +45,19 @@ import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import java.util.Map;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import java.awt.GridLayout; import java.awt.GridLayout;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths; 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.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.JFileChooser;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil;
@ -443,7 +454,11 @@ public class HealthMonitorDashboard {
* Create the panel with controls for the user panel * Create the panel with controls for the user panel
* @return the control 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() { private JPanel createUserControlPanel() {
JPanel userControlPanel = new JPanel(); JPanel userControlPanel = new JPanel();
@ -473,9 +488,132 @@ public class HealthMonitorDashboard {
userControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays())); userControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays()));
userControlPanel.add(userDateComboBox); 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; 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. * Update the user graphs.
* @throws HealthMonitorException * @throws HealthMonitorException

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

View File

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

View File

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

View 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
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.modules.ileappanalyzer; package org.sleuthkit.autopsy.modules.leappanalyzers;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; 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_EXECUTABLE = "ileapp.exe";//NON-NLS
private static final String ILEAPP_PATHS_FILE = "iLeapp_paths.txt"; //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 File iLeappExecutable;
private IngestJobContext context; private IngestJobContext context;
private ILeappFileProcessor iLeappFileProcessor; private LeappFileProcessor iLeappFileProcessor;
ILeappAnalyzerIngestModule() { ILeappAnalyzerIngestModule() {
// This constructor is intentionally empty. Nothing special is needed here. // This constructor is intentionally empty. Nothing special is needed here.
@ -94,7 +97,7 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
} }
try { try {
iLeappFileProcessor = new ILeappFileProcessor(); iLeappFileProcessor = new LeappFileProcessor(XMLFILE);
} catch (IOException | IngestModuleException | NoCurrentCaseException ex) { } catch (IOException | IngestModuleException | NoCurrentCaseException ex) {
throw new IngestModuleException(Bundle.ILeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex); throw new IngestModuleException(Bundle.ILeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex);
} }

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.NbBundle;
import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProvider;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.modules.ileappanalyzer; package org.sleuthkit.autopsy.modules.leappanalyzers;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -63,14 +63,14 @@ import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; 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 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> tsvFiles;
private final Map<String, String> tsvFileArtifacts; private final Map<String, String> tsvFileArtifacts;
@ -79,11 +79,12 @@ public final class ILeappFileProcessor {
Blackboard blkBoard; Blackboard blkBoard;
public ILeappFileProcessor() throws IOException, IngestModuleException, NoCurrentCaseException { public LeappFileProcessor(String xmlFile) throws IOException, IngestModuleException, NoCurrentCaseException {
this.tsvFiles = new HashMap<>(); this.tsvFiles = new HashMap<>();
this.tsvFileArtifacts = new HashMap<>(); this.tsvFileArtifacts = new HashMap<>();
this.tsvFileArtifactComments = new HashMap<>(); this.tsvFileArtifactComments = new HashMap<>();
this.tsvFileAttributes = new HashMap<>(); this.tsvFileAttributes = new HashMap<>();
this.xmlFile = xmlFile;
blkBoard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); blkBoard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
@ -93,22 +94,22 @@ public final class ILeappFileProcessor {
} }
@NbBundle.Messages({ @NbBundle.Messages({
"ILeappFileProcessor.error.running.iLeapp=Error running iLeapp, see log file.", "LeappFileProcessor.error.running.Leapp=Error running Leapp, see log file.",
"ILeappFileProcessor.error.creating.output.dir=Error creating iLeapp module output directory.", "LeappFileProcessor.error.creating.output.dir=Error creating Leapp module output directory.",
"ILeappFileProcessor.starting.iLeapp=Starting iLeapp", "LeappFileProcessor.starting.Leapp=Starting Leapp",
"ILeappFileProcessor.running.iLeapp=Running iLeapp", "LeappFileProcessor.running.Leapp=Running Leapp",
"ILeappFileProcessor.has.run=iLeapp", "LeappFileProcessor.has.run=Leapp",
"ILeappFileProcessor.iLeapp.cancelled=iLeapp run was canceled", "LeappFileProcessor.Leapp.cancelled=Leapp run was canceled",
"ILeappFileProcessor.completed=iLeapp Processing Completed", "LeappFileProcessor.completed=Leapp Processing Completed",
"ILeappFileProcessor.error.reading.iLeapp.directory=Error reading iLeapp Output Directory"}) "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 { try {
List<String> iLeappTsvOutputFiles = findTsvFiles(moduleOutputPath); List<String> LeappTsvOutputFiles = findTsvFiles(moduleOutputPath);
processiLeappFiles(iLeappTsvOutputFiles, iLeappFile); processLeappFiles(LeappTsvOutputFiles, LeappFile);
} catch (IOException | IngestModuleException ex) { } 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; return ProcessResult.ERROR;
} }
@ -118,10 +119,10 @@ public final class ILeappFileProcessor {
public ProcessResult processFileSystem(Content dataSource, Path moduleOutputPath) { public ProcessResult processFileSystem(Content dataSource, Path moduleOutputPath) {
try { try {
List<String> iLeappTsvOutputFiles = findTsvFiles(moduleOutputPath); List<String> LeappTsvOutputFiles = findTsvFiles(moduleOutputPath);
processiLeappFiles(iLeappTsvOutputFiles, dataSource); processLeappFiles(LeappTsvOutputFiles, dataSource);
} catch (IOException | IngestModuleException ex) { } 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; 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. * 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> allTsvFiles = new ArrayList<>();
List<String> foundTsvFiles = 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()) allTsvFiles = walk.map(x -> x.toString())
.filter(f -> f.toLowerCase().endsWith(".tsv")).collect(Collectors.toList()); .filter(f -> f.toLowerCase().endsWith(".tsv")).collect(Collectors.toList());
@ -148,7 +149,7 @@ public final class ILeappFileProcessor {
} }
} catch (IOException | UncheckedIOException e) { } 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; 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 LeappFilesToProcess List of files to process
* @param iLeappImageFile Abstract file to create artifact for * @param LeappImageFile Abstract file to create artifact for
* *
* @throws FileNotFoundException * @throws FileNotFoundException
* @throws IOException * @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<>(); List<BlackboardArtifact> bbartifacts = new ArrayList<>();
for (String iLeappFileName : iLeappFilesToProcess) { for (String LeappFileName : LeappFilesToProcess) {
String fileName = FilenameUtils.getName(iLeappFileName); String fileName = FilenameUtils.getName(LeappFileName);
File iLeappFile = new File(iLeappFileName); File LeappFile = new File(LeappFileName);
if (tsvFileAttributes.containsKey(fileName)) { if (tsvFileAttributes.containsKey(fileName)) {
List<List<String>> attrList = tsvFileAttributes.get(fileName); List<List<String>> attrList = tsvFileAttributes.get(fileName);
try { try {
BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName)); 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) { } catch (TskCoreException ex) {
throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), 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 LeappFilesToProcess List of files to process
* @param iLeappImageFile Abstract file to create artifact for * @param LeappImageFile Abstract file to create artifact for
* *
* @throws FileNotFoundException * @throws FileNotFoundException
* @throws IOException * @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<>(); List<BlackboardArtifact> bbartifacts = new ArrayList<>();
for (String iLeappFileName : iLeappFilesToProcess) { for (String LeappFileName : LeappFilesToProcess) {
String fileName = FilenameUtils.getName(iLeappFileName); String fileName = FilenameUtils.getName(LeappFileName);
File iLeappFile = new File(iLeappFileName); File LeappFile = new File(LeappFileName);
if (tsvFileAttributes.containsKey(fileName)) { if (tsvFileAttributes.containsKey(fileName)) {
List<List<String>> attrList = tsvFileAttributes.get(fileName); List<List<String>> attrList = tsvFileAttributes.get(fileName);
try { try {
BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName)); 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) { } catch (TskCoreException ex) {
throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), 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, List<BlackboardArtifact> bbartifacts, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException,
TskCoreException { TskCoreException {
try (BufferedReader reader = new BufferedReader(new FileReader(iLeappFile))) { try (BufferedReader reader = new BufferedReader(new FileReader(LeappFile))) {
String line = reader.readLine(); 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. // Check first line, if it is null then no heading so nothing to match to, close and go to next file.
if (line != null) { if (line != null) {
@ -236,6 +237,10 @@ public final class ILeappFileProcessor {
line = reader.readLine(); line = reader.readLine();
while (line != null) { while (line != null) {
Collection<BlackboardAttribute> bbattributes = processReadLine(line, columnNumberToProcess, fileName); 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)) { if (!bbattributes.isEmpty() && !blkBoard.artifactExists(dataSource, BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactType.getTypeID()), bbattributes)) {
BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes); BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes);
if (bbartifact != null) { if (bbartifact != null) {
@ -354,11 +359,11 @@ public final class ILeappFileProcessor {
} }
@NbBundle.Messages({ @NbBundle.Messages({
"ILeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.", "LeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.",
"ILeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.", "LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.",
"ILeappFileProcessor_cannotParseXml=Cannot Parse XML file.", "LeappFileProcessor_cannotParseXml=Cannot Parse XML file.",
"ILeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact", "LeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact",
"ILeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts." "LeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts."
}) })
/** /**
@ -367,18 +372,18 @@ public final class ILeappFileProcessor {
private void loadConfigFile() throws IngestModuleException { private void loadConfigFile() throws IngestModuleException {
Document xmlinput; Document xmlinput;
try { try {
String path = PlatformUtil.getUserConfigDirectory() + File.separator + XMLFILE; String path = PlatformUtil.getUserConfigDirectory() + File.separator + xmlFile;
File f = new File(path); File f = new File(path);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder(); DocumentBuilder db = dbf.newDocumentBuilder();
xmlinput = db.parse(f); xmlinput = db.parse(f);
} catch (IOException e) { } 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) { } 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) { } 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); getFileNode(xmlinput);
@ -466,7 +471,7 @@ public final class ILeappFileProcessor {
bbart.addAttributes(bbattributes); bbart.addAttributes(bbattributes);
return bbart; return bbart;
} catch (TskException ex) { } 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; return null;
} }
@ -490,7 +495,7 @@ public final class ILeappFileProcessor {
bbart.addAttributes(bbattributes); bbart.addAttributes(bbattributes);
return bbart; return bbart;
} catch (TskException ex) { } 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; return null;
} }
@ -509,17 +514,17 @@ public final class ILeappFileProcessor {
try { try {
Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(artifacts, MODULE_NAME); Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(artifacts, MODULE_NAME);
} catch (Blackboard.BlackboardException ex) { } 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 * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException
*/ */
private void configExtractor() throws IOException { private void configExtractor() throws IOException {
PlatformUtil.extractResourceToUserConfigDir(ILeappFileProcessor.class, XMLFILE, true); PlatformUtil.extractResourceToUserConfigDir(LeappFileProcessor.class, xmlFile, true);
} }
} }

View File

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

View File

@ -1793,6 +1793,7 @@ class TableReportGenerator {
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.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_SEARCH.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID()

View File

@ -384,6 +384,12 @@ public class HTMLReport implements TableReportModule {
case TSK_WEB_FORM_ADDRESS: case TSK_WEB_FORM_ADDRESS:
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS
break; 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: default:
logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS

View File

@ -27,6 +27,7 @@ ReportKML.genReport.reportName=KML Report
ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n
ReportKML.latLongEndPoint={0};{1};;{2} (End)\n ReportKML.latLongEndPoint={0};{1};;{2} (End)\n
Route_Details_Header=GPS Route Route_Details_Header=GPS Route
Waypoint_Area_Point_Display_String=GPS Area Outline Point
Waypoint_Bookmark_Display_String=GPS Bookmark Waypoint_Bookmark_Display_String=GPS Bookmark
Waypoint_EXIF_Display_String=EXIF Metadata With Location Waypoint_EXIF_Display_String=EXIF Metadata With Location
Waypoint_Last_Known_Display_String=GPS Last Known Location Waypoint_Last_Known_Display_String=GPS Last Known Location

View File

@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.geolocation.datamodel.Route;
import org.sleuthkit.autopsy.geolocation.datamodel.Track; 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.geolocation.datamodel.WaypointBuilder;
import org.sleuthkit.autopsy.report.GeneralReportSettings; import org.sleuthkit.autopsy.report.GeneralReportSettings;
import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportBranding;
@ -84,6 +85,7 @@ public final class KMLReport implements GeneralReportModule {
private Element gpsSearchesFolder; private Element gpsSearchesFolder;
private Element gpsTrackpointsFolder; private Element gpsTrackpointsFolder;
private Element gpsTracksFolder; private Element gpsTracksFolder;
private Element gpsAreasFolder;
private GeneralReportSettings settings; private GeneralReportSettings settings;
@ -154,7 +156,8 @@ public final class KMLReport implements GeneralReportModule {
"Waypoint_Track_Display_String=GPS Track", "Waypoint_Track_Display_String=GPS Track",
"Route_Details_Header=GPS Route", "Route_Details_Header=GPS Route",
"ReportBodyFile.ingestWarning.text=Ingest Warning message", "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) { public void generateReport(String baseReportDir, ReportProgressPanel progressPanel, List<Waypoint> waypointList) {
@ -216,6 +219,11 @@ public final class KMLReport implements GeneralReportModule {
result = ReportProgressPanel.ReportStatus.ERROR; result = ReportProgressPanel.ReportStatus.ERROR;
errorMessage = Bundle.KMLReport_partialFailure(); errorMessage = Bundle.KMLReport_partialFailure();
} }
entirelySuccessful = makeAreas(skCase);
if (!entirelySuccessful) {
result = ReportProgressPanel.ReportStatus.ERROR;
errorMessage = Bundle.KMLReport_partialFailure();
}
addLocationsToReport(skCase, baseReportDir); addLocationsToReport(skCase, baseReportDir);
} catch (GeoLocationDataException | IOException | TskCoreException ex) { } catch (GeoLocationDataException | IOException | TskCoreException ex) {
@ -326,6 +334,11 @@ public final class KMLReport implements GeneralReportModule {
CDATA cdataTrack = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS CDATA cdataTrack = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefTrack = new Element("href", ns).addContent(cdataTrack); //NON-NLS Element hrefTrack = new Element("href", ns).addContent(cdataTrack); //NON-NLS
gpsTracksFolder.addContent(new Element("Icon", ns).addContent(hrefTrack)); //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 gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS
gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS
@ -334,6 +347,7 @@ public final class KMLReport implements GeneralReportModule {
gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS
gpsTracksFolder.addContent(new Element("name", ns).addContent("GPS Tracks")); //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(gpsExifMetadataFolder);
document.addContent(gpsBookmarksFolder); document.addContent(gpsBookmarksFolder);
@ -342,6 +356,7 @@ public final class KMLReport implements GeneralReportModule {
document.addContent(gpsSearchesFolder); document.addContent(gpsSearchesFolder);
document.addContent(gpsTrackpointsFolder); document.addContent(gpsTrackpointsFolder);
document.addContent(gpsTracksFolder); document.addContent(gpsTracksFolder);
document.addContent(gpsAreasFolder);
return kmlDocument; return kmlDocument;
} }
@ -570,6 +585,62 @@ public final class KMLReport implements GeneralReportModule {
point.getTimestamp(), element, formattedCoordinates(point.getLatitude(), point.getLongitude()))); //NON-NLS point.getTimestamp(), element, formattedCoordinates(point.getLatitude(), point.getLongitude()))); //NON-NLS
} }
} }
/**
* 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. * Format a point time stamp (in seconds) to the report format.
@ -628,7 +699,7 @@ public final class KMLReport implements GeneralReportModule {
point.addContent(coordinates); point.addContent(coordinates);
return point; return point;
} }
/** /**
* Create a LineString for use in a Placemark. Note in this method, start * Create a LineString for use in a Placemark. Note in this method, start
@ -662,6 +733,35 @@ public final class KMLReport implements GeneralReportModule {
return lineString; 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 * Make a Placemark for use in displaying features. Takes a
* coordinate-bearing feature (Point, LineString, etc) and places it in the * coordinate-bearing feature (Point, LineString, etc) and places it in the

View File

@ -24,6 +24,7 @@ import javafx.application.Platform;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import org.joda.time.Interval;
import org.openide.awt.ActionID; import org.openide.awt.ActionID;
import org.openide.awt.ActionReference; import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences; 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 * 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) * various specific states (e.g., showing a specific artifact in the List View)
*/ */
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline")
@ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false) @ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false)
@ActionReferences(value = { @ActionReferences(value = {
@ActionReference(path = "Menu/Tools", position = 104) @ActionReference(path = "Menu/Tools", position = 104),
,
@ActionReference(path = "Toolbars/Case", position = 104)}) @ActionReference(path = "Toolbars/Case", position = 104)})
public final class OpenTimelineAction extends CallableSystemAction { public final class OpenTimelineAction extends CallableSystemAction {
@ -64,7 +62,6 @@ public final class OpenTimelineAction extends CallableSystemAction {
private final JButton toolbarButton = new JButton(getName(), private final JButton toolbarButton = new JButton(getName(),
new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS
public OpenTimelineAction() { public OpenTimelineAction() {
toolbarButton.addActionListener(actionEvent -> performAction()); toolbarButton.addActionListener(actionEvent -> performAction());
menuItem = super.getMenuPresenter(); menuItem = super.getMenuPresenter();
@ -74,9 +71,10 @@ public final class OpenTimelineAction extends CallableSystemAction {
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
/** /**
* We used to also check if Case.getCurrentOpenCase().hasData() was true. We * We used to also check if Case.getCurrentOpenCase().hasData() was
* disabled that check because if it is executed while a data source is * true. We disabled that check because if it is executed while a data
* being added, it blocks the edt. We still do that in ImageGallery. * source is being added, it blocks the edt. We still do that in
* ImageGallery.
*/ */
return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited(); return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited();
} }
@ -103,7 +101,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
@NbBundle.Messages({ @NbBundle.Messages({
"OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.", "OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.",
"OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."}) "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 { try {
Case currentCase = Case.getCurrentCaseThrows(); Case currentCase = Case.getCurrentCaseThrows();
if (currentCase.hasData() == false) { if (currentCase.hasData() == false) {
@ -112,6 +110,15 @@ public final class OpenTimelineAction extends CallableSystemAction {
return; return;
} }
TimeLineController controller = TimeLineModule.getController(); 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); controller.showTimeLine(file, artifact);
} catch (NoCurrentCaseException e) { } catch (NoCurrentCaseException e) {
//there is no case... Do nothing. //there is no case... Do nothing.
@ -123,7 +130,18 @@ public final class OpenTimelineAction extends CallableSystemAction {
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void showTimeline() throws TskCoreException { 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) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void showFileInTimeline(AbstractFile file) throws TskCoreException { 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) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException { public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException {
showTimeline(null, artifact); showTimeline(null, artifact, null);
} }
@Override @Override

View File

@ -62,7 +62,6 @@ import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; 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 static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
@ -348,7 +347,7 @@ public class TimeLineController {
/** /**
* Show the entire range of the timeline. * Show the entire range of the timeline.
*/ */
private boolean showFullRange() throws TskCoreException { boolean showFullRange() throws TskCoreException {
synchronized (filteredEvents) { synchronized (filteredEvents) {
return pushTimeRange(filteredEvents.getSpanningInterval()); return pushTimeRange(filteredEvents.getSpanningInterval());
} }
@ -359,7 +358,7 @@ public class TimeLineController {
* ViewInTimelineRequestedEvent in the List View. * ViewInTimelineRequestedEvent in the List View.
* *
* @param requestEvent Contains the ID of the requested events and the * @param requestEvent Contains the ID of the requested events and the
* timerange to show. * timerange to show.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void showInListView(ViewInTimelineRequestedEvent requestEvent) throws TskCoreException { private void showInListView(ViewInTimelineRequestedEvent requestEvent) throws TskCoreException {
@ -376,14 +375,13 @@ public class TimeLineController {
} }
} }
/** /**
* Shuts down the task executor in charge of handling case events. * Shuts down the task executor in charge of handling case events.
*/ */
void shutDownTimeLineListeners() { void shutDownTimeLineListeners() {
ThreadUtils.shutDownTaskExecutor(executor); ThreadUtils.shutDownTaskExecutor(executor);
} }
/** /**
* "Shut down" Timeline. Close the timeline window. * "Shut down" Timeline. Close the timeline window.
*/ */
@ -394,15 +392,13 @@ public class TimeLineController {
topComponent = null; topComponent = null;
} }
} }
/** /**
* Add the case and ingest listeners, prompt for rebuilding the database if * Add the case and ingest listeners, prompt for rebuilding the database if
* necessary, and show the timeline window. * necessary, and show the timeline window.
* *
* @param file The AbstractFile from which to choose an event to show in * @param file The AbstractFile from which to choose an event to show in the
* the List View. * List View.
* @param artifact The BlackboardArtifact to show in the List View. * @param artifact The BlackboardArtifact to show in the List View.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@ -421,9 +417,7 @@ public class TimeLineController {
try { try {
if (file == null && artifact == null) { if (file == null && artifact == null) {
SwingUtilities.invokeLater(TimeLineController.this::showWindow); SwingUtilities.invokeLater(TimeLineController.this::showWindow);
this.showFullRange();
} else { } else {
//prompt user to pick specific event and time range //prompt user to pick specific event and time range
ShowInTimelineDialog showInTimelineDilaog = (file == null) ShowInTimelineDialog showInTimelineDilaog = (file == null)
? new ShowInTimelineDialog(this, artifact) ? new ShowInTimelineDialog(this, artifact)
@ -453,7 +447,7 @@ public class TimeLineController {
* around the middle of the currently viewed time range. * around the middle of the currently viewed time range.
* *
* @param period The period of time to show around the current center of the * @param period The period of time to show around the current center of the
* view. * view.
*/ */
synchronized public void pushPeriod(ReadablePeriod period) throws TskCoreException { synchronized public void pushPeriod(ReadablePeriod period) throws TskCoreException {
synchronized (filteredEvents) { synchronized (filteredEvents) {
@ -496,8 +490,8 @@ public class TimeLineController {
*/ */
topComponent.requestActive(); topComponent.requestActive();
} }
synchronized public TimeLineTopComponent getTopComponent(){ synchronized public TimeLineTopComponent getTopComponent() {
return topComponent; return topComponent;
} }
@ -517,7 +511,7 @@ public class TimeLineController {
* @param timeRange The Interval to view. * @param timeRange The Interval to view.
* *
* @return True if the interval was changed. False if the interval was the * @return True if the interval was changed. False if the interval was the
* same as the existing one and no change happened. * same as the existing one and no change happened.
*/ */
synchronized public boolean pushTimeRange(Interval timeRange) throws TskCoreException { synchronized public boolean pushTimeRange(Interval timeRange) throws TskCoreException {
//clamp timerange to case //clamp timerange to case
@ -709,7 +703,7 @@ public class TimeLineController {
* Register the given object to receive events. * Register the given object to receive events.
* *
* @param listener The object to register. Must implement public methods * @param listener The object to register. Must implement public methods
* annotated with Subscribe. * annotated with Subscribe.
*/ */
synchronized public void registerForEvents(Object listener) { synchronized public void registerForEvents(Object listener) {
eventbus.register(listener); eventbus.register(listener);

View File

@ -300,7 +300,7 @@ public class UserActivitySummaryTest {
Assert.assertEquals(acceptedDevice, results.get(0).getDeviceModel()); Assert.assertEquals(acceptedDevice, results.get(0).getDeviceModel());
Assert.assertEquals("MAKE " + acceptedDevice, results.get(0).getDeviceMake()); Assert.assertEquals("MAKE " + acceptedDevice, results.get(0).getDeviceMake());
Assert.assertEquals("ID " + acceptedDevice, results.get(0).getDeviceId()); 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); List<TopDeviceAttachedResult> results = summary.getRecentDevices(dataSource, 10);
Assert.assertEquals(1, results.size()); 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("ID1".equalsIgnoreCase(results.get(0).getDeviceId()));
Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake())); Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake()));
Assert.assertTrue("MODEL1".equalsIgnoreCase(results.get(0).getDeviceModel())); 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.assertEquals("Expected two different search queries", 2, results.size());
Assert.assertTrue(query1.equalsIgnoreCase(results.get(0).getSearchString())); 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.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) private void webSearchTranslationTest(List<String> queries, boolean hasProvider, String translationSuffix)
@ -599,11 +599,11 @@ public class UserActivitySummaryTest {
Assert.assertEquals(2, domains.size()); Assert.assertEquals(2, domains.size());
Assert.assertTrue("Expected " + domain1 + " to be first domain", domain1.equalsIgnoreCase(domains.get(0).getDomain())); 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.assertEquals((Long) 2L, domains.get(0).getVisitTimes());
Assert.assertTrue("Expected " + domain3 + " to be second domain", domain3.equalsIgnoreCase(domains.get(1).getDomain())); 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()); Assert.assertEquals((Long) 1L, domains.get(1).getVisitTimes());
} }
@ -643,7 +643,7 @@ public class UserActivitySummaryTest {
Assert.assertEquals(1, domains.size()); Assert.assertEquals(1, domains.size());
Assert.assertTrue("Expected " + domain1 + " to be most recent domain", domain1.equalsIgnoreCase(domains.get(0).getDomain())); 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.assertEquals(2, domains.size());
Assert.assertTrue(domain1.equalsIgnoreCase(domains.get(1).getDomain())); 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.assertEquals((Long) 2L, domains.get(1).getVisitTimes());
Assert.assertTrue(domain2.equalsIgnoreCase(domains.get(0).getDomain())); 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()); Assert.assertEquals((Long) 3L, domains.get(0).getVisitTimes());
} }
@ -869,7 +869,8 @@ public class UserActivitySummaryTest {
// since this may be somewhat variable // since this may be somewhat variable
Assert.assertTrue(expectedItem.getAccountType().equalsIgnoreCase(receivedItem.getAccountType())); 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, getRecentAccountsOneArtTest(ds1, email1,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), email1));
BlackboardArtifact email2 = getEmailArtifact(2, ds1, null, DAY_SECONDS); BlackboardArtifact email2 = getEmailArtifact(2, ds1, null, DAY_SECONDS);
getRecentAccountsOneArtTest(ds1, email2, getRecentAccountsOneArtTest(ds1, email2,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), email2));
BlackboardArtifact email3 = getEmailArtifact(3, ds1, null, null); BlackboardArtifact email3 = getEmailArtifact(3, ds1, null, null);
getRecentAccountsOneArtTest(ds1, email3, null); getRecentAccountsOneArtTest(ds1, email3, null);
@ -911,19 +912,19 @@ public class UserActivitySummaryTest {
getRecentAccountsOneArtTest(ds1, email4, getRecentAccountsOneArtTest(ds1, email4,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), 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); BlackboardArtifact callog1 = getCallogArtifact(11, ds1, DAY_SECONDS, null);
getRecentAccountsOneArtTest(ds1, callog1, getRecentAccountsOneArtTest(ds1, callog1,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), callog1));
BlackboardArtifact callog2 = getCallogArtifact(12, ds1, null, DAY_SECONDS); BlackboardArtifact callog2 = getCallogArtifact(12, ds1, null, DAY_SECONDS);
getRecentAccountsOneArtTest(ds1, callog2, getRecentAccountsOneArtTest(ds1, callog2,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), callog2));
BlackboardArtifact callog3 = getCallogArtifact(13, ds1, null, null); BlackboardArtifact callog3 = getCallogArtifact(13, ds1, null, null);
getRecentAccountsOneArtTest(ds1, callog3, null); getRecentAccountsOneArtTest(ds1, callog3, null);
@ -932,7 +933,7 @@ public class UserActivitySummaryTest {
getRecentAccountsOneArtTest(ds1, callog4, getRecentAccountsOneArtTest(ds1, callog4,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
new Date(DAY_SECONDS * 2 * 1000))); new Date(DAY_SECONDS * 2 * 1000), callog4));
BlackboardArtifact message1 = getMessageArtifact(21, ds1, "Skype", null); BlackboardArtifact message1 = getMessageArtifact(21, ds1, "Skype", null);
getRecentAccountsOneArtTest(ds1, message1, null); getRecentAccountsOneArtTest(ds1, message1, null);
@ -944,7 +945,7 @@ public class UserActivitySummaryTest {
getRecentAccountsOneArtTest(ds1, message3, null); getRecentAccountsOneArtTest(ds1, message3, null);
BlackboardArtifact message4 = getMessageArtifact(24, ds1, "Skype", DAY_SECONDS); 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, getRecentAccountsTest(ds1, 10,
Arrays.asList(email1, email2, email3, callog1, callog2, message1a, message1b, message2a, message2b), Arrays.asList(email1, email2, email3, callog1, callog2, message1a, message1b, message2a, message2b),
Arrays.asList( Arrays.asList(
new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000)), new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000), message2b),
new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000)), new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000), message1b),
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), new Date((DAY_SECONDS + 22) * 1000)), 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)) 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("program1.exe".equalsIgnoreCase(results.get(0).getProgramName()));
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(0).getProgramPath())); Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(0).getProgramPath()));
Assert.assertEquals((Long) 31L, results.get(0).getRunTimes()); 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("program1.exe".equalsIgnoreCase(results.get(1).getProgramName()));
Assert.assertTrue("/Program Files/etc/".equalsIgnoreCase(results.get(1).getProgramPath())); Assert.assertTrue("/Program Files/etc/".equalsIgnoreCase(results.get(1).getProgramPath()));
Assert.assertEquals((Long) 21L, results.get(1).getRunTimes()); 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("program2.exe".equalsIgnoreCase(results.get(2).getProgramName()));
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(2).getProgramPath())); Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(2).getProgramPath()));
Assert.assertEquals((Long) 10L, results.get(2).getRunTimes()); 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) private void assertProgramOrder(DataSource ds1, List<BlackboardArtifact> artifacts, List<String> programNamesReturned)

View File

@ -16,6 +16,11 @@ DataSourceUsage_FlashDrive=Flash Drive
# {0} - OS name # {0} - OS name
DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0}) DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0})
DataSourceUsageAnalyzer.parentModuleName=Recent Activity 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.indexError.message=Failed to index artifact for keyword search.
Extract.noOpenCase.errMsg=No open case available. Extract.noOpenCase.errMsg=No open case available.
ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history

View File

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

View File

@ -81,6 +81,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
Extract sru = new ExtractSru(); Extract sru = new ExtractSru();
Extract prefetch = new ExtractPrefetch(); Extract prefetch = new ExtractPrefetch();
Extract webAccountType = new ExtractWebAccountType(); Extract webAccountType = new ExtractWebAccountType();
Extract messageDomainType = new DomainCategorizer();
extractors.add(chrome); extractors.add(chrome);
extractors.add(firefox); 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(recycleBin); // this needs to run after ExtractRegistry and ExtractOS
extractors.add(sru); extractors.add(sru);
extractors.add(prefetch); extractors.add(prefetch);
extractors.add(messageDomainType);
browserExtractors.add(chrome); browserExtractors.add(chrome);
browserExtractors.add(firefox); browserExtractors.add(firefox);

21
thirdparty/aLeapp/LICENSE vendored Normal file
View 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

Binary file not shown.

View File

@ -15,10 +15,7 @@ echo "Testing Autopsy..." && echo -en 'travis_fold:start:script.tests\\r'
echo "Free Space:" echo "Free Space:"
echo `df -h .` echo `df -h .`
if [ "${TRAVIS_OS_NAME}" = "osx" ]; then if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
# if os x, just run it
# ant -q test-no-regression
elif [ "${TRAVIS_OS_NAME}" = "linux" ]; then
# if linux use xvfb # if linux use xvfb
xvfb-run ant -q test-no-regression xvfb-run ant -q test-no-regression
fi fi