mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #6528 from sleuthkit/develop
Merge develop into Java 11 upgrade branch
This commit is contained in:
commit
a82d88a3a5
@ -5,6 +5,7 @@ jobs:
|
|||||||
- os: linux
|
- os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
- os: osx
|
- os: osx
|
||||||
|
osx_image: xcode12.2
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
|
@ -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"/>
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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, "");
|
||||||
|
}
|
||||||
}
|
}
|
197
Core/src/org/sleuthkit/autopsy/coreutils/DomainTokenizer.java
Normal file
197
Core/src/org/sleuthkit/autopsy/coreutils/DomainTokenizer.java
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2020 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.coreutils;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to get the domain from a url/domain provided removing the
|
||||||
|
* subdomain(s).
|
||||||
|
*/
|
||||||
|
class DomainTokenizer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a node in the trie. Children in the hashmap are identified by
|
||||||
|
* token. So for example, for a domain google.co.uk, the top level category
|
||||||
|
* would have an entry for "uk" linking to a domain category with "co".
|
||||||
|
*/
|
||||||
|
private static class DomainCategory extends HashMap<String, DomainCategory> {
|
||||||
|
|
||||||
|
private DomainCategory getOrAddChild(String childKey) {
|
||||||
|
DomainCategory cat = this.get(childKey);
|
||||||
|
if (cat == null) {
|
||||||
|
cat = new DomainCategory();
|
||||||
|
this.put(childKey, cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character for joining domain segments.
|
||||||
|
private static final String JOINER = ".";
|
||||||
|
// delimiter when used with regex
|
||||||
|
private static final String DELIMITER = "\\" + JOINER;
|
||||||
|
|
||||||
|
private static final String WILDCARD = "*";
|
||||||
|
private static final String EXCEPTION_PREFIX = "!";
|
||||||
|
|
||||||
|
// taken from https://publicsuffix.org/list/public_suffix_list.dat
|
||||||
|
// file containing line seperated suffixes
|
||||||
|
// rules for parsing can be found here: https://publicsuffix.org/list/
|
||||||
|
private static final String DOMAIN_LIST = "public_suffix_list.dat";
|
||||||
|
|
||||||
|
// token for comments
|
||||||
|
private static final String COMMENT_TOKEN = "//";
|
||||||
|
|
||||||
|
// singleton instance of this class.
|
||||||
|
private static DomainTokenizer categorizer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singleton instance of this class.
|
||||||
|
*
|
||||||
|
* @return The DomainCategorizer instance.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static DomainTokenizer getInstance() throws IOException {
|
||||||
|
if (categorizer == null) {
|
||||||
|
categorizer = load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return categorizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a DomainCategorizer instance using the public suffix list.
|
||||||
|
*
|
||||||
|
* @return The DomainCategorizer instance.
|
||||||
|
* @throws IOException If there is an error reading the file.
|
||||||
|
*/
|
||||||
|
private static DomainTokenizer load() throws IOException {
|
||||||
|
try (InputStream is = DomainTokenizer.class.getResourceAsStream(DOMAIN_LIST);
|
||||||
|
InputStreamReader isReader = new InputStreamReader(is, StandardCharsets.UTF_8);
|
||||||
|
BufferedReader reader = new BufferedReader(isReader)) {
|
||||||
|
|
||||||
|
DomainTokenizer categorizer = new DomainTokenizer();
|
||||||
|
while (reader.ready()) {
|
||||||
|
String line = reader.readLine();
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (!StringUtils.isBlank(trimmed) && !trimmed.startsWith(COMMENT_TOKEN)) {
|
||||||
|
categorizer.addDomainSuffix(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return categorizer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DomainTokenizer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// The top-level trie node.
|
||||||
|
private final DomainCategory trie = new DomainCategory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a domain suffix and adds components to the trie in reverse order
|
||||||
|
* (i.e. ".co.uk" becomes "uk" -> "co").
|
||||||
|
*
|
||||||
|
* @param domainSuffix The domain suffix.
|
||||||
|
*/
|
||||||
|
private void addDomainSuffix(String domainSuffix) {
|
||||||
|
if (StringUtils.isBlank(domainSuffix)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] tokens = domainSuffix.toLowerCase().trim().split(DELIMITER);
|
||||||
|
|
||||||
|
DomainCategory cat = trie;
|
||||||
|
for (int i = tokens.length - 1; i >= 0; i--) {
|
||||||
|
String token = tokens[i];
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cat = cat.getOrAddChild(tokens[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the domain by attempting to identify the host without the subdomain.
|
||||||
|
* If no domain can be determined, the domain is returned.
|
||||||
|
*
|
||||||
|
* @param domain The domain to query for.
|
||||||
|
* @return If provided argument is blank, null is returned. If no domain
|
||||||
|
* suffixes can be identified, the full host is returned. If a host and
|
||||||
|
* suffixes are identified, the domain (all suffixes with a prefix of the
|
||||||
|
* next token) are returned.
|
||||||
|
*/
|
||||||
|
String getDomain(String domain) {
|
||||||
|
if (StringUtils.isBlank(domain)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> tokens = Stream.of(domain.toLowerCase().split(DELIMITER))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
int idx = tokens.size() - 1;
|
||||||
|
DomainCategory cat = trie;
|
||||||
|
|
||||||
|
for (; idx >= 0; idx--) {
|
||||||
|
// an exception rule must be at the beginning of a suffix, and, in
|
||||||
|
// practice, indicates a domain that would otherwise be a further
|
||||||
|
// suffix with a wildcard rule per: https://publicsuffix.org/list/
|
||||||
|
if (cat.get(EXCEPTION_PREFIX + tokens.get(idx)) != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainCategory newCat = cat.get(tokens.get(idx));
|
||||||
|
|
||||||
|
// if no matching token can be found, look for wildcard token
|
||||||
|
if (newCat == null) {
|
||||||
|
// if no wildcard token can be found, the portion found
|
||||||
|
// so far is the suffix.
|
||||||
|
newCat = cat.get(WILDCARD);
|
||||||
|
if (newCat == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cat = newCat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if first suffix cannot be found, return the whole domain
|
||||||
|
if (idx == tokens.size() - 1) {
|
||||||
|
return domain;
|
||||||
|
} else {
|
||||||
|
int minIndex = Math.max(0, idx);
|
||||||
|
List<String> subList = tokens.subList(minIndex, tokens.size());
|
||||||
|
return String.join(JOINER, subList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,13 +18,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.coreutils;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
13494
Core/src/org/sleuthkit/autopsy/coreutils/public_suffix_list.dat
Normal file
13494
Core/src/org/sleuthkit/autopsy/coreutils/public_suffix_list.dat
Normal file
File diff suppressed because it is too large
Load Diff
@ -121,6 +121,10 @@ public final class IconsUtil {
|
|||||||
imageFile = "web-account-type.png"; //NON-NLS
|
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
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2020 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
|
||||||
|
|
||||||
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
|
import org.sleuthkit.autopsy.timeline.TimeLineModule;
|
||||||
|
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.FilterState;
|
||||||
|
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState;
|
||||||
|
import org.sleuthkit.datamodel.DataSource;
|
||||||
|
import org.sleuthkit.datamodel.TimelineFilter;
|
||||||
|
import org.sleuthkit.datamodel.TimelineFilter.DataSourceFilter;
|
||||||
|
import org.sleuthkit.datamodel.TimelineFilter.RootFilter;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for interacting with Timeline in relation to data sources.
|
||||||
|
*/
|
||||||
|
public class TimelineDataSourceUtils {
|
||||||
|
|
||||||
|
private static TimelineDataSourceUtils instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Singleton instance of this class.
|
||||||
|
*/
|
||||||
|
public static TimelineDataSourceUtils getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new TimelineDataSourceUtils();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main constructor. Should be instantiated through getInstance().
|
||||||
|
*/
|
||||||
|
private TimelineDataSourceUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a RootFilter based on the default filter state but only the
|
||||||
|
* specified dataSource is selected.
|
||||||
|
*
|
||||||
|
* @param dataSource The data source.
|
||||||
|
* @return The root filter representing a default filter with only this data
|
||||||
|
* source selected.
|
||||||
|
* @throws NoCurrentCaseException
|
||||||
|
* @throws TskCoreException
|
||||||
|
*/
|
||||||
|
public RootFilter getDataSourceFilter(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
|
||||||
|
RootFilterState filterState = getDataSourceFilterState(dataSource);
|
||||||
|
return filterState == null ? null : filterState.getActiveFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a TimeLineController based on the default filter state but only
|
||||||
|
* the specified dataSource is selected.
|
||||||
|
*
|
||||||
|
* @param dataSource The data source.
|
||||||
|
* @return The root filter state representing a default filter with only
|
||||||
|
* this data source selected.
|
||||||
|
* @throws NoCurrentCaseException
|
||||||
|
* @throws TskCoreException
|
||||||
|
*/
|
||||||
|
public RootFilterState getDataSourceFilterState(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
|
||||||
|
TimeLineController controller = TimeLineModule.getController();
|
||||||
|
RootFilterState dataSourceState = controller.getEventsModel().getDefaultEventFilterState().copyOf();
|
||||||
|
|
||||||
|
for (FilterState<? extends TimelineFilter.DataSourceFilter> filterState : dataSourceState.getDataSourcesFilterState().getSubFilterStates()) {
|
||||||
|
DataSourceFilter dsFilter = filterState.getFilter();
|
||||||
|
if (dsFilter != null) {
|
||||||
|
filterState.setSelected(dsFilter.getDataSourceID() == dataSource.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSourceState;
|
||||||
|
}
|
||||||
|
}
|
@ -38,12 +38,11 @@ import org.sleuthkit.datamodel.AbstractFile;
|
|||||||
import org.sleuthkit.datamodel.DataSource;
|
import org.sleuthkit.datamodel.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions2">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="6" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions3">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="9" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||||
</Constraint>
|
</Constraint>
|
||||||
</Constraints>
|
</Constraints>
|
||||||
</Component>
|
</Component>
|
||||||
|
@ -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);
|
||||||
|
@ -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, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="enabled" type="boolean" value="false"/>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="viewInTimelineBtnActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</Component>
|
||||||
<Component class="javax.swing.Box$Filler" name="filler5">
|
<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">
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
</Component>
|
||||||
<Component class="javax.swing.Box$Filler" name="filler3">
|
<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, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
</Component>
|
||||||
<Component class="javax.swing.Box$Filler" name="filler4">
|
<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, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
</Component>
|
||||||
<Component class="javax.swing.Box$Filler" name="filler6">
|
<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, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
</Component>
|
||||||
<Component class="javax.swing.Box$Filler" name="filler8">
|
<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, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
</Component>
|
||||||
</SubComponents>
|
</SubComponents>
|
||||||
</Container>
|
</Container>
|
||||||
</SubComponents>
|
</SubComponents>
|
||||||
|
@ -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);
|
||||||
|
@ -304,4 +304,5 @@ public class BarChartPanel extends AbstractLoadableComponent<List<BarChartPanel.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,22 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
|
|||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
171
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Area.java
Normal file
171
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Area.java
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2020 Basis Technology Corp.
|
||||||
|
* contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation.datamodel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
|
||||||
|
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException;
|
||||||
|
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoAreaPoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A GPS track with which wraps the TSK_GPS_AREA artifact.
|
||||||
|
*/
|
||||||
|
public final class Area extends GeoPath {
|
||||||
|
/**
|
||||||
|
* Construct a new Area for the given artifact.
|
||||||
|
*
|
||||||
|
* @param artifact
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
public Area(BlackboardArtifact artifact) throws GeoLocationDataException {
|
||||||
|
this(artifact, Waypoint.getAttributesFromArtifactAsMap(artifact));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an Area for the given artifact and attributeMap.
|
||||||
|
*
|
||||||
|
* @param artifact TSK_GPD_TRACK artifact
|
||||||
|
* @param attributeMap Map of the artifact attributes
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
private Area(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
|
||||||
|
super(artifact, getAreaName(attributeMap));
|
||||||
|
|
||||||
|
GeoAreaPoints points = getPointsList(attributeMap);
|
||||||
|
buildPath(points, artifact);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name of the area from the attributeMap. Track name is stored
|
||||||
|
* in the attribute TSK_NAME
|
||||||
|
*
|
||||||
|
* @param attributeMap
|
||||||
|
*
|
||||||
|
* @return Area name or empty string if none was available.
|
||||||
|
*/
|
||||||
|
private static String getAreaName(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) {
|
||||||
|
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
|
||||||
|
|
||||||
|
return attribute != null ? attribute.getValueString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the list of AreaWaypoints from the GeoTrackPoint list.
|
||||||
|
*
|
||||||
|
* @param points GeoAreaPoints object.
|
||||||
|
* @param artifact The artifact to which these points belong
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
@Messages({
|
||||||
|
"# {0} - area name",
|
||||||
|
"GEOArea_point_label_header=Area outline point for area: {0}"
|
||||||
|
})
|
||||||
|
private void buildPath(GeoAreaPoints points, BlackboardArtifact artifact) throws GeoLocationDataException {
|
||||||
|
for (GeoAreaPoints.AreaPoint point : points) {
|
||||||
|
addToPath(new AreaWaypoint(artifact, Bundle.GEOArea_point_label_header(getLabel()), point));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of GeoAreaPoints from the attributeMap. Creates the
|
||||||
|
* GeoAreaPoint list from the TSK_GEO_AREAPOINTS attribute.
|
||||||
|
*
|
||||||
|
* @param attributeMap Map of artifact attributes.
|
||||||
|
*
|
||||||
|
* @return GeoTrackPoint list empty list if the attribute was not found.
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
private GeoAreaPoints getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
|
||||||
|
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS);
|
||||||
|
if (attribute == null) {
|
||||||
|
throw new GeoLocationDataException("No TSK_GEO_AREAPOINTS attribute present in attribute map to parse.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoAreaPoints.class);
|
||||||
|
} catch (InvalidJsonException ex) {
|
||||||
|
throw new GeoLocationDataException("Unable to parse area points in TSK_GEO_AREAPOINTS attribute", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Waypoint subclass for the points of an area outline.
|
||||||
|
*/
|
||||||
|
final class AreaWaypoint extends Waypoint {
|
||||||
|
|
||||||
|
private final List<Waypoint.Property> propertyList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a AreaWaypoint.
|
||||||
|
*
|
||||||
|
* @param artifact the artifact to which this waypoint belongs
|
||||||
|
*
|
||||||
|
* @param pointLabel the label for the waypoint
|
||||||
|
*
|
||||||
|
* @param point GeoAreaPoint
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
AreaWaypoint(BlackboardArtifact artifact, String pointLabel, GeoAreaPoints.AreaPoint point) throws GeoLocationDataException {
|
||||||
|
super(artifact, pointLabel,
|
||||||
|
null,
|
||||||
|
point.getLatitude(),
|
||||||
|
point.getLongitude(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Area.this);
|
||||||
|
|
||||||
|
propertyList = createPropertyList(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded to return a property list that is generated from the
|
||||||
|
* GeoTrackPoint instead of an artifact.
|
||||||
|
*
|
||||||
|
* @return unmodifiable list of Waypoint.Property
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Waypoint.Property> getOtherProperties() {
|
||||||
|
return Collections.unmodifiableList(propertyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a propertyList specific to GeoAreaPoints.
|
||||||
|
*
|
||||||
|
* @param point GeoAreaPoint to get values from.
|
||||||
|
*
|
||||||
|
* @return A list of Waypoint.properies.
|
||||||
|
*/
|
||||||
|
private List<Waypoint.Property> createPropertyList(GeoAreaPoints.AreaPoint point) {
|
||||||
|
List<Waypoint.Property> list = new ArrayList<>();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
# {0} - area name
|
||||||
|
GEOArea_point_label_header=Area outline point for area: {0}
|
||||||
# {0} - track name
|
# {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
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
BIN
Core/src/org/sleuthkit/autopsy/images/domain-16.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/domain-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
BIN
Core/src/org/sleuthkit/autopsy/images/gps-area.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/gps-area.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 984 B |
@ -1,4 +0,0 @@
|
|||||||
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
|
||||||
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
|
||||||
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
|
||||||
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
|
@ -1,30 +0,0 @@
|
|||||||
ILeappAnalyzerIngestModule.completed=iLeapp Processing Completed
|
|
||||||
ILeappAnalyzerIngestModule.error.creating.output.dir=Error creating iLeapp module output directory.
|
|
||||||
ILeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize ILeappProcessFile
|
|
||||||
ILeappAnalyzerIngestModule.error.running.iLeapp=Error running iLeapp, see log file.
|
|
||||||
ILeappAnalyzerIngestModule.executable.not.found=iLeapp Executable Not Found.
|
|
||||||
ILeappAnalyzerIngestModule.has.run=iLeapp
|
|
||||||
ILeappAnalyzerIngestModule.iLeapp.cancelled=iLeapp run was canceled
|
|
||||||
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
|
||||||
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
|
||||||
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
|
||||||
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
|
||||||
ILeappAnalyzerIngestModule.report.name=iLeapp Html Report
|
|
||||||
ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows.
|
|
||||||
ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp
|
|
||||||
ILeappAnalyzerIngestModule.starting.iLeapp=Starting iLeapp
|
|
||||||
ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices.
|
|
||||||
ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP)
|
|
||||||
ILeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.
|
|
||||||
ILeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.
|
|
||||||
ILeappFileProcessor.completed=iLeapp Processing Completed
|
|
||||||
ILeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts.
|
|
||||||
ILeappFileProcessor.error.creating.output.dir=Error creating iLeapp module output directory.
|
|
||||||
ILeappFileProcessor.error.reading.iLeapp.directory=Error reading iLeapp Output Directory
|
|
||||||
ILeappFileProcessor.error.running.iLeapp=Error running iLeapp, see log file.
|
|
||||||
ILeappFileProcessor.has.run=iLeapp
|
|
||||||
ILeappFileProcessor.iLeapp.cancelled=iLeapp run was canceled
|
|
||||||
ILeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact
|
|
||||||
ILeappFileProcessor.running.iLeapp=Running iLeapp
|
|
||||||
ILeappFileProcessor.starting.iLeapp=Starting iLeapp
|
|
||||||
ILeappFileProcessor_cannotParseXml=Cannot Parse XML file.
|
|
@ -0,0 +1,492 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2020 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.modules.leappanalyzers;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.openide.modules.InstalledFileLocator;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import static org.sleuthkit.autopsy.casemodule.Case.getCurrentCase;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ExecUtil;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||||
|
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
|
||||||
|
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator;
|
||||||
|
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestMessage;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
|
||||||
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.LocalFilesDataSource;
|
||||||
|
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data source ingest module that runs aLeapp against logical iOS files.
|
||||||
|
*/
|
||||||
|
public class ALeappAnalyzerIngestModule implements DataSourceIngestModule {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ALeappAnalyzerIngestModule.class.getName());
|
||||||
|
private static final String MODULE_NAME = ALeappAnalyzerModuleFactory.getModuleName();
|
||||||
|
|
||||||
|
private static final String ALEAPP = "aLeapp"; //NON-NLS
|
||||||
|
private static final String ALEAPP_FS = "fs_"; //NON-NLS
|
||||||
|
private static final String ALEAPP_EXECUTABLE = "aleapp.exe";//NON-NLS
|
||||||
|
private static final String ALEAPP_PATHS_FILE = "aLeapp_paths.txt"; //NON-NLS
|
||||||
|
|
||||||
|
private static final String XMLFILE = "aleap-artifact-attribute-reference.xml"; //NON-NLS
|
||||||
|
|
||||||
|
|
||||||
|
private File aLeappExecutable;
|
||||||
|
|
||||||
|
private IngestJobContext context;
|
||||||
|
|
||||||
|
private LeappFileProcessor aLeappFileProcessor;
|
||||||
|
|
||||||
|
ALeappAnalyzerIngestModule() {
|
||||||
|
// This constructor is intentionally empty. Nothing special is needed here.
|
||||||
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"ALeappAnalyzerIngestModule.executable.not.found=aLeapp Executable Not Found.",
|
||||||
|
"ALeappAnalyzerIngestModule.requires.windows=aLeapp module requires windows.",
|
||||||
|
"ALeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize aLeappProcessFile"})
|
||||||
|
@Override
|
||||||
|
public void startUp(IngestJobContext context) throws IngestModuleException {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
if (false == PlatformUtil.isWindowsOS()) {
|
||||||
|
throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_requires_windows());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
aLeappFileProcessor = new LeappFileProcessor(XMLFILE);
|
||||||
|
} catch (IOException | IngestModuleException | NoCurrentCaseException ex) {
|
||||||
|
throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
aLeappExecutable = locateExecutable(ALEAPP_EXECUTABLE);
|
||||||
|
} catch (FileNotFoundException exception) {
|
||||||
|
logger.log(Level.WARNING, "aLeapp executable not found.", exception); //NON-NLS
|
||||||
|
throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_executable_not_found(), exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"ALeappAnalyzerIngestModule.error.running.aLeapp=Error running aLeapp, see log file.",
|
||||||
|
"ALeappAnalyzerIngestModule.error.creating.output.dir=Error creating aLeapp module output directory.",
|
||||||
|
"ALeappAnalyzerIngestModule.starting.aLeapp=Starting aLeapp",
|
||||||
|
"ALeappAnalyzerIngestModule.running.aLeapp=Running aLeapp",
|
||||||
|
"ALeappAnalyzerIngestModule.has.run=aLeapp",
|
||||||
|
"ALeappAnalyzerIngestModule.aLeapp.cancelled=aLeapp run was canceled",
|
||||||
|
"ALeappAnalyzerIngestModule.completed=aLeapp Processing Completed",
|
||||||
|
"ALeappAnalyzerIngestModule.report.name=aLeapp Html Report"})
|
||||||
|
@Override
|
||||||
|
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
|
||||||
|
|
||||||
|
Case currentCase = Case.getCurrentCase();
|
||||||
|
Path tempOutputPath = Paths.get(currentCase.getTempDirectory(), ALEAPP, ALEAPP_FS + dataSource.getId());
|
||||||
|
try {
|
||||||
|
Files.createDirectories(tempOutputPath);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error creating aLeapp output directory %s", tempOutputPath.toString()), ex);
|
||||||
|
return ProcessResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> aLeappPathsToProcess = new ArrayList<>();
|
||||||
|
ProcessBuilder aLeappCommand = buildaLeappListCommand(tempOutputPath);
|
||||||
|
try {
|
||||||
|
int result = ExecUtil.execute(aLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
|
||||||
|
if (result != 0) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
|
||||||
|
return ProcessResult.ERROR;
|
||||||
|
}
|
||||||
|
aLeappPathsToProcess = loadIleappPathFile(tempOutputPath);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program getting file paths to search"), ex);
|
||||||
|
return ProcessResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusHelper.progress(Bundle.ALeappAnalyzerIngestModule_starting_aLeapp(), 0);
|
||||||
|
|
||||||
|
List<AbstractFile> aLeappFilesToProcess = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!(context.getDataSource() instanceof LocalFilesDataSource)) {
|
||||||
|
extractFilesFromImage(dataSource, aLeappPathsToProcess, tempOutputPath);
|
||||||
|
statusHelper.switchToDeterminate(aLeappFilesToProcess.size());
|
||||||
|
processALeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString());
|
||||||
|
} else {
|
||||||
|
aLeappFilesToProcess = findaLeappFilesToProcess(dataSource);
|
||||||
|
statusHelper.switchToDeterminate(aLeappFilesToProcess.size());
|
||||||
|
|
||||||
|
Integer filesProcessedCount = 0;
|
||||||
|
for (AbstractFile aLeappFile : aLeappFilesToProcess) {
|
||||||
|
processALeappFile(dataSource, currentCase, statusHelper, filesProcessedCount, aLeappFile);
|
||||||
|
filesProcessedCount++;
|
||||||
|
}
|
||||||
|
// Process the logical image as a fs in aLeapp to make sure this is not a logical fs that was added
|
||||||
|
extractFilesFromImage(dataSource, aLeappPathsToProcess, tempOutputPath);
|
||||||
|
processALeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
|
||||||
|
Bundle.ALeappAnalyzerIngestModule_has_run(),
|
||||||
|
Bundle.ALeappAnalyzerIngestModule_completed());
|
||||||
|
IngestServices.getInstance().postMessage(message);
|
||||||
|
return ProcessResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a file from a logical image using the aLeapp program
|
||||||
|
* @param dataSource datasource to process
|
||||||
|
* @param currentCase current case that is being worked on
|
||||||
|
* @param statusHelper show progress and update what is being processed
|
||||||
|
* @param filesProcessedCount number of files that have been processed
|
||||||
|
* @param aLeappFile the abstract file to process
|
||||||
|
*/
|
||||||
|
private void processALeappFile(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, int filesProcessedCount,
|
||||||
|
AbstractFile aLeappFile) {
|
||||||
|
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
|
||||||
|
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ALEAPP, currentTime);
|
||||||
|
try {
|
||||||
|
Files.createDirectories(moduleOutputPath);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error creating aLeapp output directory %s", moduleOutputPath.toString()), ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusHelper.progress(NbBundle.getMessage(this.getClass(), "ALeappAnalyzerIngestModule.processing.file", aLeappFile.getName()), filesProcessedCount);
|
||||||
|
ProcessBuilder aLeappCommand = buildaLeappCommand(moduleOutputPath, aLeappFile.getLocalAbsPath(), aLeappFile.getNameExtension());
|
||||||
|
try {
|
||||||
|
int result = ExecUtil.execute(aLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
|
||||||
|
if (result != 0) {
|
||||||
|
logger.log(Level.WARNING, String.format("Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addILeappReportToReports(moduleOutputPath, currentCase);
|
||||||
|
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program against file %s", aLeappFile.getLocalAbsPath()), ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
|
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessResult fileProcessorResult = aLeappFileProcessor.processFiles(dataSource, moduleOutputPath, aLeappFile);
|
||||||
|
|
||||||
|
if (fileProcessorResult == ProcessResult.ERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a image/directory using the aLeapp program
|
||||||
|
* @param dataSource datasource to process
|
||||||
|
* @param currentCase current case being procesed
|
||||||
|
* @param statusHelper show progress and update what is being processed
|
||||||
|
* @param directoryToProcess directory to run aLeapp against
|
||||||
|
*/
|
||||||
|
private void processALeappFs(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, String directoryToProcess) {
|
||||||
|
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
|
||||||
|
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ALEAPP, currentTime);
|
||||||
|
try {
|
||||||
|
Files.createDirectories(moduleOutputPath);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error creating aLeapp output directory %s", moduleOutputPath.toString()), ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusHelper.progress(NbBundle.getMessage(this.getClass(), "ALeappAnalyzerIngestModule.processing.filesystem"));
|
||||||
|
ProcessBuilder aLeappCommand = buildaLeappCommand(moduleOutputPath, directoryToProcess, "fs");
|
||||||
|
try {
|
||||||
|
int result = ExecUtil.execute(aLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
|
||||||
|
if (result != 0) {
|
||||||
|
logger.log(Level.WARNING, String.format("Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addILeappReportToReports(moduleOutputPath, currentCase);
|
||||||
|
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error when trying to execute aLeapp program against file system"), ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
|
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessResult fileProcessorResult = aLeappFileProcessor.processFileSystem(dataSource, moduleOutputPath);
|
||||||
|
|
||||||
|
if (fileProcessorResult == ProcessResult.ERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the files that will be processed by the aLeapp program
|
||||||
|
*
|
||||||
|
* @param dataSource
|
||||||
|
*
|
||||||
|
* @return List of abstract files to process.
|
||||||
|
*/
|
||||||
|
private List<AbstractFile> findaLeappFilesToProcess(Content dataSource) {
|
||||||
|
|
||||||
|
List<AbstractFile> aLeappFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
FileManager fileManager = getCurrentCase().getServices().getFileManager();
|
||||||
|
|
||||||
|
// findFiles use the SQL wildcard % in the file name
|
||||||
|
try {
|
||||||
|
aLeappFiles = fileManager.findFiles(dataSource, "%", "/"); //NON-NLS
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.WARNING, "No files found to process"); //NON-NLS
|
||||||
|
return aLeappFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AbstractFile> aLeappFilesToProcess = new ArrayList<>();
|
||||||
|
for (AbstractFile aLeappFile : aLeappFiles) {
|
||||||
|
if (((aLeappFile.getLocalAbsPath() != null)
|
||||||
|
&& (!aLeappFile.getNameExtension().isEmpty() && (!aLeappFile.isVirtual())))
|
||||||
|
&& ((aLeappFile.getName().toLowerCase().contains(".zip") || (aLeappFile.getName().toLowerCase().contains(".tar")))
|
||||||
|
|| aLeappFile.getName().toLowerCase().contains(".tgz"))) {
|
||||||
|
aLeappFilesToProcess.add(aLeappFile);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aLeappFilesToProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the aLeapp command to run
|
||||||
|
*
|
||||||
|
* @param moduleOutputPath output path for the aLeapp program.
|
||||||
|
* @param sourceFilePath where the source files to process reside.
|
||||||
|
* @param aLeappFileSystemType the filesystem type to process
|
||||||
|
*
|
||||||
|
* @return the command to execute
|
||||||
|
*/
|
||||||
|
private ProcessBuilder buildaLeappCommand(Path moduleOutputPath, String sourceFilePath, String aLeappFileSystemType) {
|
||||||
|
|
||||||
|
ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
|
||||||
|
"\"" + aLeappExecutable + "\"", //NON-NLS
|
||||||
|
"-t", aLeappFileSystemType, //NON-NLS
|
||||||
|
"-i", sourceFilePath, //NON-NLS
|
||||||
|
"-o", moduleOutputPath.toString()
|
||||||
|
);
|
||||||
|
processBuilder.redirectError(moduleOutputPath.resolve("aLeapp_err.txt").toFile()); //NON-NLS
|
||||||
|
processBuilder.redirectOutput(moduleOutputPath.resolve("aLeapp_out.txt").toFile()); //NON-NLS
|
||||||
|
return processBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessBuilder buildaLeappListCommand(Path moduleOutputPath) {
|
||||||
|
|
||||||
|
ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
|
||||||
|
"\"" + aLeappExecutable + "\"", //NON-NLS
|
||||||
|
"-p"
|
||||||
|
);
|
||||||
|
processBuilder.redirectError(moduleOutputPath.resolve("aLeapp_paths_error.txt").toFile()); //NON-NLS
|
||||||
|
processBuilder.redirectOutput(moduleOutputPath.resolve("aLeapp_paths.txt").toFile()); //NON-NLS
|
||||||
|
return processBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
|
||||||
|
/*
|
||||||
|
* Add an environment variable to force aLeapp to run with
|
||||||
|
* the same permissions Autopsy uses.
|
||||||
|
*/
|
||||||
|
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
|
||||||
|
return processBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File locateExecutable(String executableName) throws FileNotFoundException {
|
||||||
|
String executableToFindName = Paths.get(ALEAPP, executableName).toString();
|
||||||
|
|
||||||
|
File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, ALeappAnalyzerIngestModule.class.getPackage().getName(), false);
|
||||||
|
if (null == exeFile || exeFile.canExecute() == false) {
|
||||||
|
throw new FileNotFoundException(executableName + " executable not found.");
|
||||||
|
}
|
||||||
|
return exeFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index.html file in the aLeapp output directory so it can be
|
||||||
|
* added to reports
|
||||||
|
*/
|
||||||
|
private void addILeappReportToReports(Path aLeappOutputDir, Case currentCase) {
|
||||||
|
List<String> allIndexFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
try (Stream<Path> walk = Files.walk(aLeappOutputDir)) {
|
||||||
|
|
||||||
|
allIndexFiles = walk.map(x -> x.toString())
|
||||||
|
.filter(f -> f.toLowerCase().endsWith("index.html")).collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!allIndexFiles.isEmpty()) {
|
||||||
|
// Check for existance of directory that holds report data if does not exist then report contains no data
|
||||||
|
String filePath = FilenameUtils.getFullPathNoEndSeparator(allIndexFiles.get(0));
|
||||||
|
File dataFilesDir = new File(Paths.get(filePath, "_TSV Exports").toString());
|
||||||
|
if (dataFilesDir.exists()) {
|
||||||
|
currentCase.addReport(allIndexFiles.get(0), MODULE_NAME, Bundle.ALeappAnalyzerIngestModule_report_name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException | UncheckedIOException | TskCoreException ex) {
|
||||||
|
// catch the error and continue on as report is not added
|
||||||
|
logger.log(Level.WARNING, String.format("Error finding index file in path %s", aLeappOutputDir.toString()), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reads the aLeapp paths file to get the paths that we want to extract
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private List<String> loadIleappPathFile(Path moduleOutputPath) throws FileNotFoundException, IOException {
|
||||||
|
List<String> aLeappPathsToProcess = new ArrayList<>();
|
||||||
|
|
||||||
|
Path filePath = Paths.get(moduleOutputPath.toString(), ALEAPP_PATHS_FILE);
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toString()))) {
|
||||||
|
String line = reader.readLine();
|
||||||
|
while (line != null) {
|
||||||
|
if (line.contains("path list generation") || line.length() < 2) {
|
||||||
|
line = reader.readLine();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
aLeappPathsToProcess.add(line.trim());
|
||||||
|
line = reader.readLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aLeappPathsToProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extractFilesFromImage(Content dataSource, List<String> aLeappPathsToProcess, Path moduleOutputPath) {
|
||||||
|
FileManager fileManager = getCurrentCase().getServices().getFileManager();
|
||||||
|
|
||||||
|
for (String fullFilePath : aLeappPathsToProcess) {
|
||||||
|
|
||||||
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
|
logger.log(Level.INFO, "aLeapp Analyser ingest module run was canceled"); //NON-NLS
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ffp = fullFilePath.replaceAll("\\*", "%");
|
||||||
|
ffp = FilenameUtils.normalize(ffp, true);
|
||||||
|
String fileName = FilenameUtils.getName(ffp);
|
||||||
|
String filePath = FilenameUtils.getPath(ffp);
|
||||||
|
|
||||||
|
List<AbstractFile> aLeappFiles = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
if (filePath.isEmpty()) {
|
||||||
|
aLeappFiles = fileManager.findFiles(dataSource, fileName); //NON-NLS
|
||||||
|
} else {
|
||||||
|
aLeappFiles = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS
|
||||||
|
}
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.WARNING, "No files found to process"); //NON-NLS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AbstractFile aLeappFile : aLeappFiles) {
|
||||||
|
Path parentPath = Paths.get(moduleOutputPath.toString(), aLeappFile.getParentPath());
|
||||||
|
File fileParentPath = new File(parentPath.toString());
|
||||||
|
|
||||||
|
extractFileToOutput(dataSource, aLeappFile, fileParentPath, parentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extractFileToOutput(Content dataSource, AbstractFile aLeappFile, File fileParentPath, Path parentPath) {
|
||||||
|
if (fileParentPath.exists()) {
|
||||||
|
if (!aLeappFile.isDir()) {
|
||||||
|
writeaLeappFile(dataSource, aLeappFile, fileParentPath.toString());
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(Paths.get(parentPath.toString(), aLeappFile.getName()));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.INFO, String.format("Error creating aLeapp output directory %s", parentPath.toString()), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(parentPath);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.INFO, String.format("Error creating aLeapp output directory %s", parentPath.toString()), ex);
|
||||||
|
}
|
||||||
|
if (!aLeappFile.isDir()) {
|
||||||
|
writeaLeappFile(dataSource, aLeappFile, fileParentPath.toString());
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(Paths.get(parentPath.toString(), aLeappFile.getName()));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.INFO, String.format("Error creating aLeapp output directory %s", parentPath.toString()), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeaLeappFile(Content dataSource, AbstractFile aLeappFile, String parentPath) {
|
||||||
|
String fileName = aLeappFile.getName().replace(":", "-");
|
||||||
|
if (!fileName.matches(".") && !fileName.matches("..") && !fileName.toLowerCase().endsWith("-slack")) {
|
||||||
|
Path filePath = Paths.get(parentPath, fileName);
|
||||||
|
File localFile = new File(filePath.toString());
|
||||||
|
try {
|
||||||
|
ContentUtils.writeToFile(aLeappFile, localFile, context::dataSourceIngestIsCancelled);
|
||||||
|
} catch (ReadContentInputStream.ReadContentInputStreamException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).",
|
||||||
|
aLeappFile.getName(), aLeappFile.getId()), ex); //NON-NLS
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("Error writing file local file '%s' (id=%d).",
|
||||||
|
filePath.toString(), aLeappFile.getId()), ex); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2020 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.modules.leappanalyzers;
|
||||||
|
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.openide.util.lookup.ServiceProvider;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Version;
|
||||||
|
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestModuleFactory;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory that creates data source ingest modules that will run aLeapp
|
||||||
|
* against logical files and saves the output to module output.
|
||||||
|
*/
|
||||||
|
@ServiceProvider(service = IngestModuleFactory.class)
|
||||||
|
public class ALeappAnalyzerModuleFactory extends IngestModuleFactoryAdapter {
|
||||||
|
|
||||||
|
@NbBundle.Messages({"ALeappAnalyzerModuleFactory_moduleName=Android Analyzer (aLEAPP)"})
|
||||||
|
static String getModuleName() {
|
||||||
|
return Bundle.ALeappAnalyzerModuleFactory_moduleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModuleDisplayName() {
|
||||||
|
return getModuleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({"ALeappAnalyzerModuleFactory_moduleDesc=Uses aLEAPP to analyze logical acquisitions of Android devices."})
|
||||||
|
@Override
|
||||||
|
public String getModuleDescription() {
|
||||||
|
return Bundle.ALeappAnalyzerModuleFactory_moduleDesc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModuleVersionNumber() {
|
||||||
|
return Version.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDataSourceIngestModuleFactory() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings ingestJobOptions) {
|
||||||
|
return new ALeappAnalyzerIngestModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||||
|
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||||
|
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||||
|
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
||||||
|
ALeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||||
|
ALeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||||
|
ALeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||||
|
ALeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
@ -0,0 +1,47 @@
|
|||||||
|
ALeappAnalyzerIngestModule.aLeapp.cancelled=aLeapp run was canceled
|
||||||
|
ALeappAnalyzerIngestModule.completed=aLeapp Processing Completed
|
||||||
|
ALeappAnalyzerIngestModule.error.creating.output.dir=Error creating aLeapp module output directory.
|
||||||
|
ALeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize aLeappProcessFile
|
||||||
|
ALeappAnalyzerIngestModule.error.running.aLeapp=Error running aLeapp, see log file.
|
||||||
|
ALeappAnalyzerIngestModule.executable.not.found=aLeapp Executable Not Found.
|
||||||
|
ALeappAnalyzerIngestModule.has.run=aLeapp
|
||||||
|
ALeappAnalyzerIngestModule.report.name=aLeapp Html Report
|
||||||
|
ALeappAnalyzerIngestModule.requires.windows=aLeapp module requires windows.
|
||||||
|
ALeappAnalyzerIngestModule.running.aLeapp=Running aLeapp
|
||||||
|
ALeappAnalyzerIngestModule.starting.aLeapp=Starting aLeapp
|
||||||
|
ALeappAnalyzerModuleFactory_moduleDesc=Uses aLEAPP to analyze logical acquisitions of Android devices.
|
||||||
|
ALeappAnalyzerModuleFactory_moduleName=Android Analyzer (aLEAPP)
|
||||||
|
ILeappAnalyzerIngestModule.completed=iLeapp Processing Completed
|
||||||
|
ILeappAnalyzerIngestModule.error.creating.output.dir=Error creating iLeapp module output directory.
|
||||||
|
ILeappAnalyzerIngestModule.error.ileapp.file.processor.init=Failure to initialize ILeappProcessFile
|
||||||
|
ILeappAnalyzerIngestModule.error.running.iLeapp=Error running iLeapp, see log file.
|
||||||
|
ILeappAnalyzerIngestModule.executable.not.found=iLeapp Executable Not Found.
|
||||||
|
ILeappAnalyzerIngestModule.has.run=iLeapp
|
||||||
|
ILeappAnalyzerIngestModule.iLeapp.cancelled=iLeapp run was canceled
|
||||||
|
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||||
|
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||||
|
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||||
|
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
||||||
|
ALeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
|
||||||
|
ALeappAnalyzerIngestModule.processing.file=Processing file {0}
|
||||||
|
ALeappAnalyzerIngestModule.parsing.file=Parsing file {0}
|
||||||
|
ALeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
|
||||||
|
ILeappAnalyzerIngestModule.report.name=iLeapp Html Report
|
||||||
|
ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows.
|
||||||
|
ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp
|
||||||
|
ILeappAnalyzerIngestModule.starting.iLeapp=Starting iLeapp
|
||||||
|
ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices.
|
||||||
|
ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP)
|
||||||
|
LeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.
|
||||||
|
LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.
|
||||||
|
LeappFileProcessor.completed=Leapp Processing Completed
|
||||||
|
LeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts.
|
||||||
|
LeappFileProcessor.error.creating.output.dir=Error creating Leapp module output directory.
|
||||||
|
LeappFileProcessor.error.reading.Leapp.directory=Error reading Leapp Output Directory
|
||||||
|
LeappFileProcessor.error.running.Leapp=Error running Leapp, see log file.
|
||||||
|
LeappFileProcessor.has.run=Leapp
|
||||||
|
LeappFileProcessor.Leapp.cancelled=Leapp run was canceled
|
||||||
|
LeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact
|
||||||
|
LeappFileProcessor.running.Leapp=Running Leapp
|
||||||
|
LeappFileProcessor.starting.Leapp=Starting Leapp
|
||||||
|
LeappFileProcessor_cannotParseXml=Cannot Parse XML file.
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* 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);
|
||||||
}
|
}
|
@ -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;
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,307 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!---
|
||||||
|
This file contains the parameters for how to map aLeapp plugin output to attributes inside Autopsy for the aleapp Analyser module.
|
||||||
|
|
||||||
|
Each FileName node corresponds to a tab seperated values (tsv) file that is produced from iLeapp.
|
||||||
|
|
||||||
|
A FileName will have an associated TSK artifact assigned to it.
|
||||||
|
|
||||||
|
Each TSK artifact may have multiple attributes that correspond to the columns of the output from the iLeapp program tsv file.
|
||||||
|
|
||||||
|
|
||||||
|
FileName:
|
||||||
|
filename: The aLeapp TSV file that you want to process.
|
||||||
|
description: A description of the tsv file name, this is defined in the iLeapp plugin for each tsv file.
|
||||||
|
|
||||||
|
ArtifactName:
|
||||||
|
artifactname: The artifact that is to be created for the data in the tsv file.
|
||||||
|
comment: This will be the data that will be added to the TSK_COMMENT attribute for each artifact. If the artifact
|
||||||
|
does not need/require a comment then make the value null, a null comment will be ignored.
|
||||||
|
|
||||||
|
AttributeName:
|
||||||
|
attributeName: The TSK attribute that the data corresponds to in the TSV file. If the data has no corresponding TSK attribute then
|
||||||
|
make the value null, this will make sure the data in this column is ignored.
|
||||||
|
columnName: This is the column name that is defined in the tsv file and what the attributeName corresponds to.
|
||||||
|
required: whether the attribute is required or not (yes or no)
|
||||||
|
|
||||||
|
|
||||||
|
-->
|
||||||
|
<aLeap_Files_To_Process>
|
||||||
|
|
||||||
|
<FileName filename="accounts ce 0.tsv" description="Accounts_ce">
|
||||||
|
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="accounts ce 0">
|
||||||
|
<AttributeName attributename="TSK_USER_ID" columnName="Name" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PROG_NAME" columnName=" Type" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PASSWORD" columnName=" Password" required="yes" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="authtokens 0.tsv" description="Authtokens">
|
||||||
|
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="Authtokens">
|
||||||
|
<AttributeName attributename="null" columnName="ID" required="no" />
|
||||||
|
<AttributeName attributename="TSK_USER_ID" columnName=" Name" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PROG_NAME" columnName=" Account Type" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName="Authtoken Type" required="no" />
|
||||||
|
<AttributeName attributename="TSK_PASSWORD" columnName=" Authtoken" required="yes" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="accounts de 0.tsv" description="Accounts_de">
|
||||||
|
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="accounts de 0">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last password entry" required="no" />
|
||||||
|
<AttributeName attributename="TSK_USER_ID" columnName="Name" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PROG_NAME" columnName="Type" required="yes" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser Bookmarks.tsv" description="Browser Bookmarks">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_BOOKMARK" comment="Browser Bookmarks">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED " columnName="Added Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_URL" columnName=" URL" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName=" Name" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName=" Parent" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" Type" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser cookies.tsv" description="Browser Cookies">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_COOKIE" comment="Browser Cookies">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESS" columnName="Last Access Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DOMAIN" columnName="Host" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_NAME" columnName="Name" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_VALUE" columnName="Value" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DATETIME_END" columnName="Expiration Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PATH" columnName="Path" required="yes" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser History.tsv" description="Browser History">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Browser History">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName="Title" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Hidden" required="no"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser keyword search terms.tsv" description="Browser keyword Search Terms">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_SEARCH_QUERY" comment="Browser Keyword Search Terms">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_TEXT" columnName="Term" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser login data.tsv" description="Browser Login Data">
|
||||||
|
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="Browser Login">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Time" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_USER_NAME" columnName="Username" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PASSWORD" columnName="Password" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="Origin URL" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName="Blacklisted by User" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser offline pages.tsv" description="Browser Offline Pages">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Browser Offline Pages">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Creation Time" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Access Time" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_URL" columnName=" Online URL" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName=" File Path" required="no" />
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName=" Title" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" Access Count" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" File Size" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser search terms.tsv" description="Browser Search Terms">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_SEARCH_QUERY" comment="Browser Search Terms">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_TEXT" columnName="Search Term" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Title" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Browser top sites.tsv" description="Browser Top Sites">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Browser Top Sites">
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="URL" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName="Rank" required="no" />
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName="Title" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName="Redirects" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Call Logs.tsv" description="Call logs">
|
||||||
|
<ArtifactName artifactname="TSK_CALLLOG" comment="null">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_START" columnName="Call Date" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="Phone Account Address" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="Partner" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_DIRECTION" columnName="Type" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Duration in Secs" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Partner Location" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Country ISO" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Data" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Mime Type" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Transcription" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Deleted" required="no"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Chrome Bookmarks.tsv" description="Chrome Bookmarks">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_BOOKMARK" comment="Chrome Bookmarks">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED " columnName="Added Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_URL" columnName=" URL" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName=" Name" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName=" Parent" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" Type" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Chrome cookies.tsv" description="Chrome Cookies">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_COOKIE" comment="Chrome Cookies">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESS" columnName="Last Access Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DOMAIN" columnName="Host" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_NAME" columnName="Name" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_VALUE" columnName="Value" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DATETIME_END" columnName="Expiration Date" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PATH" columnName="Path" required="yes" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Chrome History.tsv" description="Chrome History">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Chrome History">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName="Title" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Hidden" required="no"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Chrome login data.tsv" description="Chrome Login Data">
|
||||||
|
<ArtifactName artifactname="TSK_SERVICE_ACCOUNT" comment="Chrome Login">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Created Time" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_USER_NAME" columnName="Username" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PASSWORD" columnName="Password" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="Origin URL" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName="Blacklisted by User" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Chrome offline pages.tsv" description="Chrome Offline Pages">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Chrome Offline Pages">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_CREATED" columnName="Creation Time" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Access Time" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_URL" columnName=" Online URL" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName=" File Path" required="no" />
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName=" Title" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" Access Count" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" File Size" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Chrome search terms.tsv" description="Chrome Search Terms">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_SEARCH_QUERY" comment="Chrome Search Terms">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Last Visit Time" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_TEXT" columnName="Search Term" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="URL" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Title" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Visit Count" required="no"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="Chrome top sites.tsv" description="Chrome Top Sites">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="Chrome Top Sites">
|
||||||
|
<AttributeName attributename="TSK_URL" columnName="URL" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName="Rank" required="no" />
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName="Title" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName="Redirects" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="google play searches.tsv" description="Google Play Searches">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_SEARCH" comment="Google Play Search">
|
||||||
|
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Timestamp" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PROG_NAME" columnName="Display" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_TEXT" columnName="query" required="yes" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="google quick search box.tsv" description="Google quick search box">
|
||||||
|
<ArtifactName artifactname="TSK_WEB_SEARCH" comment="Google Quick Search Search">
|
||||||
|
<AttributeName attributename="TSK_DATETIME" columnName="File Timestamp" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName="Type" required="no" />
|
||||||
|
<AttributeName attributename="TSK_TEXT" columnName="Queries Response" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName="Source File" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="installed apps library.tsv" description="Installed Apps (Library)">
|
||||||
|
<ArtifactName artifactname="TSK_INSTALLED_PROG" comment="Installed Apps (Library)">
|
||||||
|
<AttributeName attributename="TSK_DATETIME" columnName="Purchase Time" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_USER_NAME" columnName="Account" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_PROG_NAME" columnName="Doc ID" required="yes"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="installed apps - GMS.tsv" description="Installed Apps">
|
||||||
|
<ArtifactName artifactname="TSK_INSTALLED_PROG" comment="Installed Apps GSM">
|
||||||
|
<AttributeName attributename="TSK_PROG_NAME" columnName="Bundle ID" required="yes" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="installed apps vending.tsv" description="Installed Apps (Vending)">
|
||||||
|
<ArtifactName artifactname="TSK_INSTALLED_PROG" comment="Installed Apps (VEnding)">
|
||||||
|
<AttributeName attributename="TSK_DATETIME" columnName="First Download" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PROG_NAME" columnName="Package Name" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_TITLE" columnName=" Title" required="yes" />
|
||||||
|
<AttributeName attributename="null" columnName="Install Reason" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" Auto Update?" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<FileName filename="mms messages.tsv" description="MMS messages">
|
||||||
|
<ArtifactName artifactname="TSK_MESSAGE" comment="MMS messages">
|
||||||
|
<AttributeName attributename="TSK_DATETIME" columnName="Date" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="MSG ID" required="no"/>
|
||||||
|
<AttributeName attributename="TSK_THREAD_ID" columnName="Thread ID" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_DATETIME_SENT" columnName="Date sent" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_READ_STATUS" columnName="Read" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="From" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="To" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Cc" required="no"/>
|
||||||
|
<AttributeName attributename="null" columnName="Bcc" required="no"/>
|
||||||
|
<AttributeName attributename="TSK_TEXT" columnName="Body" required="yes"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
<!-- <FileName filename="partner settings.tsv" description="Partner Settings">
|
||||||
|
<ArtifactName artifactname="TSK_" comment="null">
|
||||||
|
<AttributeName attributename="null" columnName="Name" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName="Value ) # Dont remove the comma" required="no" />
|
||||||
|
<AttributeName attributename="null" columnName=" that is required to make this a tuple as there is only 1 eleme" required="no" />
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FileName filename="sms messages.tsv" description="SMS messages">
|
||||||
|
<ArtifactName artifactname="TSK_MESSAGE" comment="SMS messages">
|
||||||
|
<AttributeName attributename="TSK_DATETIME" columnName="Date" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="MSG ID" required="no"/>
|
||||||
|
<AttributeName attributename="TSK_THREAD_ID" columnName="Thread ID" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Address" required="yes" />
|
||||||
|
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="Contact ID" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_DATETIME_SENT" columnName="Date sent" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_READ_STATUS" columnName="Read" required="yes"/>
|
||||||
|
<AttributeName attributename="TSK_TEXT" columnName="Body" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Service Center" required="yes"/>
|
||||||
|
<AttributeName attributename="null" columnName="Error Code" required="no"/>
|
||||||
|
</ArtifactName>
|
||||||
|
</FileName>
|
||||||
|
|
||||||
|
</aLeap_Files_To_Process>
|
@ -1793,6 +1793,7 @@ class TableReportGenerator {
|
|||||||
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()
|
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -0,0 +1,488 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2020 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.recentactivity;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
|
||||||
|
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||||
|
import org.sleuthkit.autopsy.ingest.IngestModule;
|
||||||
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes a URL to determine if the url host is one of a certain kind of category
|
||||||
|
* (i.e. webmail, disposable mail). If found, a web category artifact is
|
||||||
|
* created.
|
||||||
|
*
|
||||||
|
* CSV entries describing these domain types are compiled from sources.
|
||||||
|
* webmail: https://github.com/mailcheck/mailcheck/wiki/List-of-Popular-Domains
|
||||||
|
* disposable mail: https://www.npmjs.com/package/disposable-email-domains
|
||||||
|
*/
|
||||||
|
@Messages({
|
||||||
|
"DomainCategorizer_moduleName_text=DomainCategorizer",
|
||||||
|
"DomainCategorizer_Progress_Message_Domain_Types=Finding Domain Types",
|
||||||
|
"DomainCategorizer_parentModuleName=Recent Activity"
|
||||||
|
})
|
||||||
|
class DomainCategorizer extends Extract {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The domain type (i.e. webmail, disposable mail).
|
||||||
|
*/
|
||||||
|
@Messages({
|
||||||
|
"DomainType_disposableMail_displayName=Disposable Email",
|
||||||
|
"DomainType_webmail_displayName=Web Email"
|
||||||
|
})
|
||||||
|
private enum DomainType {
|
||||||
|
DISPOSABLE_EMAIL("Disposable Email", Bundle.DomainType_disposableMail_displayName()),
|
||||||
|
WEBMAIL("Web Email", Bundle.DomainType_webmail_displayName());
|
||||||
|
|
||||||
|
private final String csvId;
|
||||||
|
private final String attrDisplayName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main constructor.
|
||||||
|
*
|
||||||
|
* @param csvId The identifier within the csv for this type.
|
||||||
|
* @param attrDisplayName The display name in the artifact for this
|
||||||
|
* domain category.
|
||||||
|
*/
|
||||||
|
private DomainType(String csvId, String attrDisplayName) {
|
||||||
|
this.csvId = csvId;
|
||||||
|
this.attrDisplayName = attrDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The identifier within the csv for this type.
|
||||||
|
*/
|
||||||
|
String getCsvId() {
|
||||||
|
return csvId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The display name in the artifact for this domain category.
|
||||||
|
*/
|
||||||
|
String getAttrDisplayName() {
|
||||||
|
return attrDisplayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node in the trie indicating a domain suffix token. For instance, the
|
||||||
|
* csv entry: "hotmail.com,Web Email" would get parsed to a node, "com" having
|
||||||
|
* a child of "hotmail". That child node, as a leaf, would have a webmail
|
||||||
|
* message type.
|
||||||
|
*/
|
||||||
|
private static class DomainTypeTrieNode {
|
||||||
|
|
||||||
|
private final Map<String, DomainTypeTrieNode> children = new HashMap<>();
|
||||||
|
private DomainType domainType = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the child node of the given key. If that child key does not
|
||||||
|
* exist, a child node of that key is created and returned.
|
||||||
|
*
|
||||||
|
* @param childKey The key for the child (i.e. "com").
|
||||||
|
* @return The retrieved or newly created child node.
|
||||||
|
*/
|
||||||
|
DomainTypeTrieNode getOrAddChild(String childKey) {
|
||||||
|
DomainTypeTrieNode child = children.get(childKey);
|
||||||
|
if (child == null) {
|
||||||
|
child = new DomainTypeTrieNode();
|
||||||
|
children.put(childKey, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the child node of the given key or returns null if child
|
||||||
|
* does not exist.
|
||||||
|
*
|
||||||
|
* @param childKey The key for the child node (i.e. "com").
|
||||||
|
* @return The child node or null if it does not exist.
|
||||||
|
*/
|
||||||
|
DomainTypeTrieNode getChild(String childKey) {
|
||||||
|
return children.get(childKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If this is a leaf node, the type of domain for this node.
|
||||||
|
*/
|
||||||
|
DomainType getDomainType() {
|
||||||
|
return domainType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a leaf node, this sets the domain type for this node.
|
||||||
|
*
|
||||||
|
* @param domainType The domain type for this leaf node.
|
||||||
|
*/
|
||||||
|
void setDomainType(DomainType domainType) {
|
||||||
|
this.domainType = domainType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the trie of suffixes from the csv resource file.
|
||||||
|
*
|
||||||
|
* @return The root trie node.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private static DomainTypeTrieNode loadTrie() throws IOException {
|
||||||
|
try (InputStream is = DomainCategorizer.class.getResourceAsStream(DOMAIN_TYPE_CSV);
|
||||||
|
InputStreamReader isReader = new InputStreamReader(is, StandardCharsets.UTF_8);
|
||||||
|
BufferedReader reader = new BufferedReader(isReader)) {
|
||||||
|
|
||||||
|
DomainTypeTrieNode trie = new DomainTypeTrieNode();
|
||||||
|
int lineNum = 1;
|
||||||
|
while (reader.ready()) {
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (!StringUtils.isBlank(line)) {
|
||||||
|
addItem(trie, line.trim(), lineNum);
|
||||||
|
lineNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a trie node based on the csv line.
|
||||||
|
*
|
||||||
|
* @param trie The root trie node.
|
||||||
|
* @param line The line to be parsed.
|
||||||
|
* @param lineNumber The line number of this csv line.
|
||||||
|
*/
|
||||||
|
private static void addItem(DomainTypeTrieNode trie, String line, int lineNumber) {
|
||||||
|
// make sure this isn't a blank line.
|
||||||
|
if (StringUtils.isBlank(line)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] csvItems = line.split(CSV_DELIMITER);
|
||||||
|
// line should be a key value pair
|
||||||
|
if (csvItems.length < 2) {
|
||||||
|
logger.log(Level.WARNING, String.format("Unable to properly parse line of \"%s\" at line %d", line, lineNumber));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the domain type from the value, and return if can't be determined.
|
||||||
|
String domainTypeStr = csvItems[1].trim();
|
||||||
|
|
||||||
|
DomainType domainType = (StringUtils.isNotBlank(domainTypeStr))
|
||||||
|
? Stream.of(DomainType.values())
|
||||||
|
.filter((m) -> m.getCsvId().equalsIgnoreCase(domainTypeStr))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (domainType == null) {
|
||||||
|
logger.log(Level.WARNING, String.format("Could not determine domain type for this line: \"%s\" at line %d", line, lineNumber));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gather the domainSuffix and parse into domain trie tokens
|
||||||
|
String domainSuffix = csvItems[0];
|
||||||
|
if (StringUtils.isBlank(domainSuffix)) {
|
||||||
|
logger.log(Level.WARNING, String.format("Could not determine domain suffix for this line: \"%s\" at line %d", line, lineNumber));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] domainTokens = domainSuffix.trim().toLowerCase().split(DELIMITER);
|
||||||
|
|
||||||
|
// add into the trie
|
||||||
|
DomainTypeTrieNode node = trie;
|
||||||
|
for (int i = domainTokens.length - 1; i >= 0; i--) {
|
||||||
|
String token = domainTokens[i];
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = node.getOrAddChild(domainTokens[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setDomainType(domainType);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character for joining domain segments.
|
||||||
|
private static final String JOINER = ".";
|
||||||
|
// delimiter when used with regex for domains
|
||||||
|
private static final String DELIMITER = "\\" + JOINER;
|
||||||
|
|
||||||
|
// csv delimiter
|
||||||
|
private static final String CSV_DELIMITER = ",";
|
||||||
|
|
||||||
|
private static final String DOMAIN_TYPE_CSV = "default_domain_categories.csv"; //NON-NLS
|
||||||
|
|
||||||
|
// The url regex is based on the regex provided in https://tools.ietf.org/html/rfc3986#appendix-B
|
||||||
|
// but expanded to be a little more flexible, and also properly parses user info and port in a url
|
||||||
|
// this item has optional colon since some urls were coming through without the colon
|
||||||
|
private static final String URL_REGEX_SCHEME = "(((?<scheme>[^:\\/?#]+):?)?\\/\\/)";
|
||||||
|
|
||||||
|
private static final String URL_REGEX_USERINFO = "((?<userinfo>[^\\/?#@]*)@)";
|
||||||
|
private static final String URL_REGEX_HOST = "(?<host>[^\\/\\.?#:]*\\.[^\\/?#:]*)";
|
||||||
|
private static final String URL_REGEX_PORT = "(:(?<port>[0-9]{1,5}))";
|
||||||
|
private static final String URL_REGEX_AUTHORITY = String.format("(%s?%s?%s?\\/?)", URL_REGEX_USERINFO, URL_REGEX_HOST, URL_REGEX_PORT);
|
||||||
|
|
||||||
|
private static final String URL_REGEX_PATH = "(?<path>([^?#]*)(\\?([^#]*))?(#(.*))?)";
|
||||||
|
|
||||||
|
private static final String URL_REGEX_STR = String.format("^\\s*%s?%s?%s?", URL_REGEX_SCHEME, URL_REGEX_AUTHORITY, URL_REGEX_PATH);
|
||||||
|
private static final Pattern URL_REGEX = Pattern.compile(URL_REGEX_STR);
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DomainCategorizer.class.getName());
|
||||||
|
|
||||||
|
// the root node for the trie containing suffixes for domain categories.
|
||||||
|
private DomainTypeTrieNode rootTrie = null;
|
||||||
|
|
||||||
|
private Content dataSource;
|
||||||
|
private IngestJobContext context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main constructor.
|
||||||
|
*/
|
||||||
|
DomainCategorizer() {
|
||||||
|
moduleName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to determine the host from the url string. If none can be
|
||||||
|
* determined, returns null.
|
||||||
|
*
|
||||||
|
* @param urlString The url string.
|
||||||
|
* @return The host or null if cannot be determined.
|
||||||
|
*/
|
||||||
|
private String getHost(String urlString) {
|
||||||
|
String host = null;
|
||||||
|
try {
|
||||||
|
// try first using the built-in url class to determine the host.
|
||||||
|
URL url = new URL(urlString);
|
||||||
|
if (url != null) {
|
||||||
|
host = url.getHost();
|
||||||
|
}
|
||||||
|
} catch (MalformedURLException ignore) {
|
||||||
|
// ignore this and go to fallback regex
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the built-in url parsing doesn't work, then use more flexible regex.
|
||||||
|
if (StringUtils.isBlank(host)) {
|
||||||
|
Matcher m = URL_REGEX.matcher(urlString);
|
||||||
|
if (m.find()) {
|
||||||
|
host = m.group("host");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the host is a known type of domain. If so, returns the
|
||||||
|
* portion of the host suffix that signifies the domain type (i.e.
|
||||||
|
* "hotmail.com" or "mail.google.com") and the domain type.
|
||||||
|
*
|
||||||
|
* @param host The host.
|
||||||
|
* @return A pair of the host suffix and domain type for that suffix if
|
||||||
|
* found. Otherwise, returns null.
|
||||||
|
*/
|
||||||
|
private Pair<String, DomainType> findHostSuffix(String host) {
|
||||||
|
// if no host, return none.
|
||||||
|
if (StringUtils.isBlank(host)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the tokens splitting on delimiter
|
||||||
|
List<String> tokens = Stream.of(host.toLowerCase().split(DELIMITER))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
DomainTypeTrieNode node = rootTrie;
|
||||||
|
// the root node is null indicating we won't be able to do a lookup.
|
||||||
|
if (node == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate through tokens in reverse order
|
||||||
|
int idx = tokens.size() - 1;
|
||||||
|
for (; idx >= 0; idx--) {
|
||||||
|
node = node.getChild(tokens.get(idx));
|
||||||
|
// if we hit a leaf node or we have no matching child node, continue.
|
||||||
|
if (node == null || node.getDomainType() != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainType domainType = node != null ? node.getDomainType() : null;
|
||||||
|
|
||||||
|
if (domainType == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// if there is a domain type, we have a result. Concatenate the
|
||||||
|
// appropriate domain tokens and return.
|
||||||
|
int minIndex = Math.max(0, idx);
|
||||||
|
List<String> subList = tokens.subList(minIndex, tokens.size());
|
||||||
|
String hostSuffix = String.join(JOINER, subList);
|
||||||
|
return Pair.of(hostSuffix, domainType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goes through web history artifacts and attempts to determine any hosts of
|
||||||
|
* a domain type. If any are found, a TSK_WEB_CATEGORIZATION artifact is
|
||||||
|
* created (at most one per host suffix).
|
||||||
|
*/
|
||||||
|
private void findDomainTypes() {
|
||||||
|
if (this.rootTrie == null) {
|
||||||
|
logger.log(Level.SEVERE, "Not analyzing domain types. No root trie loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int artifactsAnalyzed = 0;
|
||||||
|
int domainTypeInstancesFound = 0;
|
||||||
|
|
||||||
|
// only one suffix per ingest is captured so this tracks the suffixes seen.
|
||||||
|
Set<String> domainSuffixesSeen = new HashSet<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Collection<BlackboardArtifact> listArtifacts = currentCase.getSleuthkitCase().getBlackboard().getArtifacts(
|
||||||
|
Arrays.asList(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_HISTORY)),
|
||||||
|
Arrays.asList(dataSource.getId()));
|
||||||
|
|
||||||
|
logger.log(Level.INFO, "Processing {0} blackboard artifacts.", listArtifacts.size()); //NON-NLS
|
||||||
|
|
||||||
|
for (BlackboardArtifact artifact : listArtifacts) {
|
||||||
|
// make sure we haven't cancelled
|
||||||
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
|
break; //User cancelled the process.
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure there is attached file
|
||||||
|
AbstractFile file = tskCase.getAbstractFileById(artifact.getObjectID());
|
||||||
|
if (file == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the url string from the artifact
|
||||||
|
BlackboardAttribute urlAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||||
|
if (urlAttr == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String urlString = urlAttr.getValueString();
|
||||||
|
|
||||||
|
// atempt to get the host from the url provided.
|
||||||
|
String host = getHost(urlString);
|
||||||
|
if (StringUtils.isBlank(host)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we reached this point, we are at least analyzing this item
|
||||||
|
artifactsAnalyzed++;
|
||||||
|
|
||||||
|
// attempt to get the domain type for the host using the suffix trie
|
||||||
|
Pair<String, DomainType> domainEntryFound = findHostSuffix(host);
|
||||||
|
if (domainEntryFound == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got this far, we found a domain type, but it may not be unique
|
||||||
|
domainTypeInstancesFound++;
|
||||||
|
|
||||||
|
String hostSuffix = domainEntryFound.getLeft();
|
||||||
|
DomainType domainType = domainEntryFound.getRight();
|
||||||
|
if (StringUtils.isBlank(hostSuffix) || domainType == null || domainSuffixesSeen.contains(hostSuffix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got this far, this is a unique suffix. Add to the set, so we don't create
|
||||||
|
// multiple of same suffix and add an artifact.
|
||||||
|
domainSuffixesSeen.add(hostSuffix);
|
||||||
|
|
||||||
|
String moduleName = Bundle.DomainCategorizer_parentModuleName();
|
||||||
|
|
||||||
|
Collection<BlackboardAttribute> bbattributes = Arrays.asList(
|
||||||
|
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, moduleName, NetworkUtils.extractDomain(host)),
|
||||||
|
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_HOST, moduleName, host),
|
||||||
|
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, moduleName, domainType.getAttrDisplayName())
|
||||||
|
);
|
||||||
|
postArtifact(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_CATEGORIZATION, file, bbattributes));
|
||||||
|
}
|
||||||
|
} catch (TskCoreException e) {
|
||||||
|
logger.log(Level.SEVERE, "Encountered error retrieving artifacts for messaging domains", e); //NON-NLS
|
||||||
|
} finally {
|
||||||
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
|
logger.info("Operation terminated by user."); //NON-NLS
|
||||||
|
}
|
||||||
|
logger.log(Level.INFO, String.format("Extracted %s distinct messaging domain(s) from the blackboard. "
|
||||||
|
+ "Of the %s artifact(s) with valid hosts, %s url(s) contained messaging domain suffix.",
|
||||||
|
domainSuffixesSeen.size(), artifactsAnalyzed, domainTypeInstancesFound));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
progressBar.progress(Bundle.Progress_Message_Find_Search_Query());
|
||||||
|
this.findDomainTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void configExtractor() throws IngestModule.IngestModuleException {
|
||||||
|
try {
|
||||||
|
this.rootTrie = loadTrie();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IngestModule.IngestModuleException("Unable to load domain type csv for domain category analysis", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void complete() {
|
||||||
|
logger.info("Search Engine URL Query Analyzer has completed."); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
@ -81,6 +81,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
|
|||||||
Extract sru = new ExtractSru();
|
Extract 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);
|
||||||
|
File diff suppressed because it is too large
Load Diff
21
thirdparty/aLeapp/LICENSE
vendored
Normal file
21
thirdparty/aLeapp/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Brigs
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
BIN
thirdparty/aLeapp/aleapp.exe
vendored
Normal file
BIN
thirdparty/aLeapp/aleapp.exe
vendored
Normal file
Binary file not shown.
@ -15,10 +15,7 @@ echo "Testing Autopsy..." && echo -en 'travis_fold:start:script.tests\\r'
|
|||||||
echo "Free Space:"
|
echo "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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user