diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
index b3a384ce48..f8119b88da 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
@@ -93,3 +93,7 @@ MediaPlayerPanel.playButton.text=\u25ba
MediaPlayerPanel.infoLabel.text=No Errors
MediaPlayerPanel.VolumeIcon.text=Volume
MediaPlayerPanel.playBackSpeedLabel.text=Speed:
+ContextViewer.jSourceGoToResultButton.text=Go to Result
+ContextViewer.jSourceNameLabel.text=jSourceNameLabel
+ContextViewer.jSourceTextLabel.text=jLabel2
+ContextViewer.jSourceLabel.text=Source
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
index 2d9df0ae33..ceec760a69 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
@@ -10,6 +10,17 @@ AnnotationsContentViewer.title=Annotations
AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.
ApplicationContentViewer.title=Application
ApplicationContentViewer.toolTip=Displays file contents.
+ContextViewer.attachmentSource=Attached to:
+ContextViewer.downloadedOn=On
+ContextViewer.downloadSource=Downloaded from:
+ContextViewer.downloadURL=URL
+ContextViewer.email=Email
+ContextViewer.message=Message
+ContextViewer.messageFrom=From
+ContextViewer.messageOn=On
+ContextViewer.messageTo=From
+ContextViewer.title=Context Viewer
+ContextViewer.toolTip=Displays context for selected file.
FXVideoPanel.pauseButton.infoLabel.playbackErr=Unable to play video.
FXVideoPanel.progress.bufferingCancelled=media buffering was canceled
FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted
@@ -164,6 +175,10 @@ MediaPlayerPanel.playButton.text=\u25ba
MediaPlayerPanel.infoLabel.text=No Errors
MediaPlayerPanel.VolumeIcon.text=Volume
MediaPlayerPanel.playBackSpeedLabel.text=Speed:
+ContextViewer.jSourceGoToResultButton.text=Go to Result
+ContextViewer.jSourceNameLabel.text=jSourceNameLabel
+ContextViewer.jSourceTextLabel.text=jLabel2
+ContextViewer.jSourceLabel.text=Source
# {0} - tableName
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
# {0} - tableName
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContextViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/ContextViewer.form
new file mode 100644
index 0000000000..2546ea406a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ContextViewer.form
@@ -0,0 +1,103 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContextViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ContextViewer.java
new file mode 100644
index 0000000000..41a399be52
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ContextViewer.java
@@ -0,0 +1,440 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.contentviewers;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import org.apache.commons.lang.StringUtils;
+import org.openide.nodes.Node;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Displays additional context for the selected file, such as its source, and
+ * usage, if known.
+ *
+ */
+@ServiceProvider(service = DataContentViewer.class, position = 7)
+public final class ContextViewer extends javax.swing.JPanel implements DataContentViewer {
+
+ private static final long serialVersionUID = 1L;
+ private static final Logger logger = Logger.getLogger(ContextViewer.class.getName());
+ private static final int ARTIFACT_STR_MAX_LEN = 1024;
+ private static final int ATTRIBUTE_STR_MAX_LEN = 200;
+
+ // defines a list of artifacts that provide context for a file
+ private static final List SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
+
+ static {
+ SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
+ }
+
+ private BlackboardArtifact sourceContextArtifact;
+
+ /**
+ * Creates new form ContextViewer
+ */
+ public ContextViewer() {
+
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JButton jSourceGoToResultButton = new javax.swing.JButton();
+ javax.swing.JLabel jSourceLabel = new javax.swing.JLabel();
+ jSourceNameLabel = new javax.swing.JLabel();
+ jSourceTextLabel = new javax.swing.JLabel();
+
+ org.openide.awt.Mnemonics.setLocalizedText(jSourceGoToResultButton, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceGoToResultButton.text")); // NOI18N
+ jSourceGoToResultButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ jSourceGoToResultButtonActionPerformed(evt);
+ }
+ });
+
+ jSourceLabel.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(jSourceLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(jSourceNameLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceNameLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(jSourceTextLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceTextLabel.text")); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jSourceLabel)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(6, 6, 6)
+ .addComponent(jSourceNameLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jSourceTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE)))
+ .addGap(36, 36, 36))
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(jSourceGoToResultButton)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jSourceLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(jSourceNameLabel)
+ .addComponent(jSourceTextLabel))
+ .addGap(18, 18, 18)
+ .addComponent(jSourceGoToResultButton)
+ .addGap(0, 203, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+ private void jSourceGoToResultButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jSourceGoToResultButtonActionPerformed
+
+ final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
+
+ // Navigate to the source context artifact.
+ if (sourceContextArtifact != null) {
+ dtc.viewArtifact(sourceContextArtifact);
+ }
+
+ }//GEN-LAST:event_jSourceGoToResultButtonActionPerformed
+
+ @Override
+ public void setNode(Node selectedNode) {
+ if ((selectedNode == null) || (!isSupported(selectedNode))) {
+ resetComponent();
+ return;
+ }
+
+ AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class);
+ try {
+ populateSourceContextData(file);
+ } catch (NoCurrentCaseException | TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Exception displaying context for file %s", file.getName()), ex); //NON-NLS
+ }
+ }
+
+ @NbBundle.Messages({
+ "ContextViewer.title=Context Viewer",
+ "ContextViewer.toolTip=Displays context for selected file."
+ })
+
+ @Override
+ public String getTitle() {
+ return Bundle.ContextViewer_title();
+ }
+
+ @Override
+ public String getToolTip() {
+ return Bundle.ContextViewer_toolTip();
+ }
+
+ @Override
+ public DataContentViewer createInstance() {
+ return new ContextViewer();
+ }
+
+ @Override
+ public Component getComponent() {
+ return this;
+ }
+
+ @Override
+ public void resetComponent() {
+ setSourceName("");
+ setSourceText("");
+ }
+
+ @Override
+ public boolean isSupported(Node node) {
+
+ // check if the node has an abstract file and the file has any context defining artifacts.
+ if (node.getLookup().lookup(AbstractFile.class) != null) {
+ AbstractFile abstractFile = node.getLookup().lookup(AbstractFile.class);
+ for (BlackboardArtifact.ARTIFACT_TYPE artifactType : SOURCE_CONTEXT_ARTIFACTS) {
+ List artifactsList;
+ try {
+ artifactsList = abstractFile.getArtifacts(artifactType);
+ if (!artifactsList.isEmpty()) {
+ return true;
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Exception while looking up context artifacts for file %s", abstractFile), ex); //NON-NLS
+ }
+ }
+
+ }
+
+ return false;
+ }
+
+ @Override
+ public int isPreferred(Node node) {
+ // this is a low preference viewer.
+ return 1;
+ }
+
+ /**
+ * Looks for context providing artifacts for the given file and populates
+ * the source context.
+ *
+ * @param sourceFile File for which to show the context.
+ *
+ * @throws NoCurrentCaseException
+ * @throws TskCoreException
+ */
+ private void populateSourceContextData(AbstractFile sourceFile) throws NoCurrentCaseException, TskCoreException {
+
+ SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+
+ // Check for all context artifacts
+ boolean foundASource = false;
+ for (BlackboardArtifact.ARTIFACT_TYPE artifactType : SOURCE_CONTEXT_ARTIFACTS) {
+ List artifactsList = tskCase.getBlackboardArtifacts(artifactType, sourceFile.getId());
+
+ foundASource = !artifactsList.isEmpty();
+ for (BlackboardArtifact contextArtifact : artifactsList) {
+ addSourceEntry(contextArtifact);
+ }
+ }
+ if (foundASource == false) {
+ setSourceName("Unknown");
+ showSourceText(false);
+ }
+ }
+
+
+
+ /**
+ * Adds a source context entry for the selected file based on the given context
+ * providing artifact.
+ *
+ * @param artifact Artifact that may provide context.
+ *
+ * @throws NoCurrentCaseException
+ * @throws TskCoreException
+ */
+ private void addSourceEntry(BlackboardArtifact artifact) throws TskCoreException {
+ if (BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT.getTypeID() == artifact.getArtifactTypeID()) {
+ BlackboardAttribute associatedArtifactAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
+ if (associatedArtifactAttribute != null) {
+ long artifactId = associatedArtifactAttribute.getValueLong();
+ BlackboardArtifact associatedArtifact = artifact.getSleuthkitCase().getBlackboardArtifact(artifactId);
+
+ //save the artifact for "Go to Result" button
+ sourceContextArtifact = associatedArtifact;
+
+ setSourceFields(associatedArtifact);
+ }
+ }
+ }
+
+ /**
+ * Sets the source label and text fields based on the given associated
+ * artifact.
+ *
+ * @param associatedArtifact - associated artifact
+ *
+ * @throws TskCoreException
+ */
+ @NbBundle.Messages({
+ "ContextViewer.attachmentSource=Attached to: ",
+ "ContextViewer.downloadSource=Downloaded from: "
+ })
+ private void setSourceFields(BlackboardArtifact associatedArtifact) throws TskCoreException {
+ if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
+ || BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) {
+
+ setSourceName(Bundle.ContextViewer_attachmentSource());
+ setSourceText(msgArtifactToAbbreviatedString(associatedArtifact));
+
+ } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID()
+ || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) {
+
+ setSourceName(Bundle.ContextViewer_downloadSource());
+ setSourceText(webDownloadArtifactToString(associatedArtifact));
+ }
+ }
+
+ /**
+ * Sets the source label string.
+ *
+ * @param nameLabel String value for source label.
+ */
+ private void setSourceName(String nameLabel) {
+ jSourceNameLabel.setText(nameLabel);
+ }
+
+ /**
+ * Sets the source text string.
+ *
+ * @param nameLabel String value for source text.
+ */
+ private void setSourceText(String text) {
+ jSourceTextLabel.setText(text);
+ showSourceText(true);
+ }
+
+ private void showSourceText(boolean isVisible) {
+ jSourceTextLabel.setVisible(isVisible);
+ }
+
+ /**
+ * Returns a display string with download source URL from the given
+ * artifact.
+ *
+ * @param artifact artifact to get download source URL from.
+ *
+ * @return Display string with download URL and date/time.
+ *
+ * @throws TskCoreException
+ */
+ @NbBundle.Messages({
+ "ContextViewer.downloadURL=URL",
+ "ContextViewer.downloadedOn=On"
+ })
+ private String webDownloadArtifactToString(BlackboardArtifact artifact) throws TskCoreException {
+ StringBuilder sb = new StringBuilder(ARTIFACT_STR_MAX_LEN);
+ Map attributesMap = getAttributesMap(artifact);
+
+ if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifact.getArtifactTypeID()
+ || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == artifact.getArtifactTypeID()) {
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL, attributesMap, Bundle.ContextViewer_downloadURL());
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, attributesMap, Bundle.ContextViewer_downloadedOn());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a abbreviated display string for a message artifact.
+ *
+ * @param artifact artifact to get download source URL from.
+ *
+ * @return Display string for message artifact.
+ *
+ * @throws TskCoreException
+ */
+ @NbBundle.Messages({
+ "ContextViewer.message=Message",
+ "ContextViewer.email=Email",
+ "ContextViewer.messageFrom=From",
+ "ContextViewer.messageTo=From",
+ "ContextViewer.messageOn=On",
+ })
+ private String msgArtifactToAbbreviatedString(BlackboardArtifact artifact) throws TskCoreException {
+
+ StringBuilder sb = new StringBuilder(ARTIFACT_STR_MAX_LEN);
+ Map attributesMap = getAttributesMap(artifact);
+
+ if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == artifact.getArtifactTypeID()) {
+ sb.append(Bundle.ContextViewer_message()).append(' ');
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, attributesMap, Bundle.ContextViewer_messageFrom());
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, attributesMap, Bundle.ContextViewer_messageTo());
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, attributesMap, Bundle.ContextViewer_messageOn());
+ } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == artifact.getArtifactTypeID()) {
+ sb.append(Bundle.ContextViewer_email()).append(' ');
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM, attributesMap, Bundle.ContextViewer_messageFrom());
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO, attributesMap, Bundle.ContextViewer_messageTo());
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT, attributesMap, Bundle.ContextViewer_messageOn());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Looks up specified attribute in the given map and, if found, appends its
+ * value to the given string builder.
+ *
+ * @param sb String builder to append to.
+ * @param attribType Attribute type to look for.
+ * @param attributesMap Attributes map.
+ * @param prependStr Optional string that is prepended before the attribute
+ * value.
+ */
+ private void appendAttributeString(StringBuilder sb, BlackboardAttribute.ATTRIBUTE_TYPE attribType,
+ Map attributesMap, String prependStr) {
+
+ BlackboardAttribute attribute = attributesMap.get(attribType);
+ if (attribute != null) {
+ String attrVal = attribute.getDisplayString();
+ if (!StringUtils.isEmpty(attrVal)) {
+ if (!StringUtils.isEmpty(prependStr)) {
+ sb.append(prependStr).append(' ');
+ }
+ sb.append(StringUtils.abbreviate(attrVal, ATTRIBUTE_STR_MAX_LEN)).append(' ');
+ }
+ }
+ }
+
+ /**
+ * Gets all attributes for the given artifact, and returns a map of
+ * attributes keyed by attribute type.
+ *
+ * @param artifact Artifact for which to get the attributes.
+ *
+ * @return Map of attribute type and value.
+ *
+ * @throws TskCoreException
+ */
+ private Map getAttributesMap(BlackboardArtifact artifact) throws TskCoreException {
+ Map attributeMap = new HashMap<>();
+
+ List attributeList = artifact.getAttributes();
+ for (BlackboardAttribute attribute : attributeList) {
+ BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID());
+ attributeMap.put(type, attribute);
+ }
+
+ return attributeMap;
+ }
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel jSourceNameLabel;
+ private javax.swing.JLabel jSourceTextLabel;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java
index 126299f120..796386a5b3 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java
@@ -191,12 +191,14 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
}
try {
- List sourceArtifacts = file.getArtifacts(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
- if (!sourceArtifacts.isEmpty()) {
- BlackboardArtifact artifact = sourceArtifacts.get(0);
- BlackboardAttribute urlAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL));
- if (urlAttr != null) {
- addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.downloadSource"), urlAttr.getValueString());
+ List associatedObjectArtifacts = file.getArtifacts(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
+ if (!associatedObjectArtifacts.isEmpty()) {
+ BlackboardArtifact artifact = associatedObjectArtifacts.get(0);
+ BlackboardAttribute associatedArtifactAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
+ if (associatedArtifactAttribute != null) {
+ long artifactId = associatedArtifactAttribute.getValueLong();
+ BlackboardArtifact associatedArtifact = artifact.getSleuthkitCase().getBlackboardArtifact(artifactId);
+ addDownloadSourceRow(sb, associatedArtifact);
}
}
} catch (TskCoreException ex) {
@@ -292,6 +294,26 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
this.setCursor(null);
}
+ /**
+ * Adds a row for download source from the given associated artifact,
+ * if the associated artifacts specifies a source.
+ *
+ * @param sb string builder.
+ * @param associatedArtifact
+ *
+ * @throws TskCoreException if there is an error
+ */
+ private void addDownloadSourceRow(StringBuilder sb, BlackboardArtifact associatedArtifact ) throws TskCoreException {
+ if (associatedArtifact != null &&
+ ((associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()) ||
+ (associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID())) ) {
+ BlackboardAttribute urlAttr = associatedArtifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL));
+ if (urlAttr != null) {
+ addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.downloadSource"), urlAttr.getValueString());
+ }
+ }
+ }
+
/**
* Add the acquisition details to the results (if applicable)
*
diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java
index bc3c04259b..726c86f170 100755
--- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java
@@ -52,7 +52,11 @@ final class XRYFileReader implements AutoCloseable {
//Assume UTF_16LE
private static final Charset CHARSET = StandardCharsets.UTF_16LE;
- //Assume all XRY reports have the type on the 3rd line.
+ //Assume the header begins with 'xry export'.
+ private static final String START_OF_HEADER = "xry export";
+
+ //Assume all XRY reports have the type on the 3rd line
+ //relative to the start of the header.
private static final int LINE_WITH_REPORT_TYPE = 3;
//Assume all headers are 5 lines in length.
@@ -91,8 +95,12 @@ final class XRYFileReader implements AutoCloseable {
reader = Files.newBufferedReader(xryFile, CHARSET);
xryFilePath = xryFile;
- //Advance the reader to the start of the first XRY entity.
- for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) {
+ //Advance the reader to the start of the header.
+ advanceToHeader(reader);
+
+ //Advance the reader past the header to the start
+ //of the first XRY entity.
+ for (int i = 1; i < HEADER_LENGTH_IN_LINES; i++) {
reader.readLine();
}
@@ -298,8 +306,11 @@ final class XRYFileReader implements AutoCloseable {
*/
private static Optional getType(Path file) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(file, CHARSET)) {
+ //Header may not start at the beginning of the file.
+ advanceToHeader(reader);
+
//Advance the reader to the line before the report type.
- for (int i = 0; i < LINE_WITH_REPORT_TYPE - 1; i++) {
+ for (int i = 1; i < LINE_WITH_REPORT_TYPE - 1; i++) {
reader.readLine();
}
@@ -310,4 +321,51 @@ final class XRYFileReader implements AutoCloseable {
return Optional.empty();
}
}
+
+ /**
+ * Advances the reader to the start of the header. The XRY Export header may
+ * not be the first n lines of the file. It may be preceded by new lines or
+ * white space.
+ *
+ * This function will consume the first line of the header, which will be
+ * 'XRY Export'.
+ *
+ * @param reader BufferedReader pointing to the xry file
+ * @throws IOException if an I/O error occurs
+ */
+ private static void advanceToHeader(BufferedReader reader) throws IOException {
+ String line;
+ if((line = reader.readLine()) == null) {
+ return;
+ }
+
+ String normalizedLine = line.trim().toLowerCase();
+ if (normalizedLine.equals(START_OF_HEADER)) {
+ return;
+ }
+
+ /**
+ * The first line may have 0xFFFE BOM prepended to it, which will cause
+ * the equality check to fail. This bit a logic will try to remove those
+ * bytes and attempt another check.
+ */
+ byte[] normalizedBytes = normalizedLine.getBytes(CHARSET);
+ if (normalizedBytes.length > 2) {
+ normalizedLine = new String(normalizedBytes, 2,
+ normalizedBytes.length - 2, CHARSET);
+ if (normalizedLine.equals(START_OF_HEADER)) {
+ return;
+ }
+ }
+
+ /**
+ * All other lines will need to match completely.
+ */
+ while ((line = reader.readLine()) != null) {
+ normalizedLine = line.trim().toLowerCase();
+ if (normalizedLine.equals(START_OF_HEADER)) {
+ return;
+ }
+ }
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java
index 52276b6046..2f4247f3e6 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java
@@ -68,7 +68,7 @@ final class CheckBoxJList extends JLis
CheckBoxJList() {
initalize();
}
-
+
/**
* Do all of the UI initialization.
*/
@@ -103,6 +103,7 @@ final class CheckBoxJList extends JLis
setBackground(list.getBackground());
setSelected(value.isChecked());
setText(value.getDisplayName());
+ setEnabled(list.isEnabled());
return this;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form
index 112b734f1e..75fb1d4c62 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form
@@ -37,10 +37,6 @@
-
-
-
-
@@ -56,10 +52,6 @@
-
-
-
-
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java
index 37dea163f5..ff3fd5b4a4 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java
@@ -61,6 +61,14 @@ final class CheckBoxListPanel extends javax.swing.JPanel {
void clearList() {
model.removeAllElements();
}
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ checkboxList.setEnabled(enabled);
+ checkButton.setEnabled(enabled);
+ uncheckButton.setEnabled(enabled);
+ checkboxList.setEnabled(enabled);
+ }
/**
* Returns a list of all of the selected elements.
@@ -126,8 +134,8 @@ final class CheckBoxListPanel extends javax.swing.JPanel {
java.awt.GridBagConstraints gridBagConstraints;
titleLabel = new javax.swing.JLabel();
- javax.swing.JButton uncheckButton = new javax.swing.JButton();
- javax.swing.JButton checkButton = new javax.swing.JButton();
+ uncheckButton = new javax.swing.JButton();
+ checkButton = new javax.swing.JButton();
scrollPane = new javax.swing.JScrollPane();
setLayout(new java.awt.GridBagLayout());
@@ -186,8 +194,10 @@ final class CheckBoxListPanel extends javax.swing.JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton checkButton;
private javax.swing.JScrollPane scrollPane;
private javax.swing.JLabel titleLabel;
+ private javax.swing.JButton uncheckButton;
// End of variables declaration//GEN-END:variables
/**
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form
index 79a852b4d4..1b80568453 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form
@@ -115,10 +115,6 @@
-
-
-
-
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java
index 9e009d1245..fe20a9bceb 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java
@@ -74,6 +74,20 @@ class GeoFilterPanel extends javax.swing.JPanel {
add(checkboxPanel, gridBagConstraints);
}
+ @Override
+ public void setEnabled(boolean enabled) {
+ applyButton.setEnabled(enabled);
+ mostRecentButton.setEnabled(enabled);
+ allButton.setEnabled(enabled);
+ showWaypointsWOTSCheckBox.setEnabled(enabled && mostRecentButton.isSelected());
+ checkboxPanel.setEnabled(enabled);
+ daysLabel.setEnabled(enabled);
+ daysSpinner.setEnabled(enabled);
+ }
+
+ /**
+ * Update the data source list with the current data sources
+ */
void updateDataSourceList() {
try {
initCheckboxList();
@@ -155,7 +169,7 @@ class GeoFilterPanel extends javax.swing.JPanel {
mostRecentButton = new javax.swing.JRadioButton();
showWaypointsWOTSCheckBox = new javax.swing.JCheckBox();
daysSpinner = new javax.swing.JSpinner(numberModel);
- javax.swing.JLabel daysLabel = new javax.swing.JLabel();
+ daysLabel = new javax.swing.JLabel();
javax.swing.JPanel buttonPanel = new javax.swing.JPanel();
applyButton = new javax.swing.JButton();
javax.swing.JLabel optionsLabel = new javax.swing.JLabel();
@@ -272,6 +286,7 @@ class GeoFilterPanel extends javax.swing.JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JRadioButton allButton;
private javax.swing.JButton applyButton;
+ private javax.swing.JLabel daysLabel;
private javax.swing.JSpinner daysSpinner;
private javax.swing.JRadioButton mostRecentButton;
private javax.swing.JCheckBox showWaypointsWOTSCheckBox;
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java
index a653979bb6..1bf0aaabbf 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java
@@ -22,6 +22,7 @@ import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@@ -171,6 +172,7 @@ public final class GeolocationTopComponent extends TopComponent {
logger.log(Level.SEVERE, ex.getMessage(), ex);
return; // Doen't set the waypoints.
}
+ mapPanel.setWaypoints(new ArrayList<>());
updateWaypoints();
}
@@ -213,36 +215,12 @@ public final class GeolocationTopComponent extends TopComponent {
JOptionPane.INFORMATION_MESSAGE);
return;
}
-
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- Case currentCase = Case.getCurrentCase();
- try {
- WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAllWaypoints(), filters.getMostRecentNumDays(), filters.showWaypointsWithoutTimeStamp(), new WaypointFilterQueryCallBack() {
- @Override
- public void process(List waypoints) {
- // If the list is empty, tell the user and do not change
- // the visible waypoints.
- if (waypoints == null || waypoints.isEmpty()) {
- JOptionPane.showMessageDialog(GeolocationTopComponent.this,
- Bundle.GeoTopComponent_no_waypoints_returned_Title(),
- Bundle.GeoTopComponent_no_waypoints_returned_mgs(),
- JOptionPane.INFORMATION_MESSAGE);
-
- return;
- }
- mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints));
- }
- });
- } catch (GeoLocationDataException ex) {
- logger.log(Level.SEVERE, "Failed to filter waypoints.", ex);
- JOptionPane.showMessageDialog(GeolocationTopComponent.this,
- Bundle.GeoTopComponent_filter_exception_Title(),
- Bundle.GeoTopComponent_filter_exception_msg(),
- JOptionPane.ERROR_MESSAGE);
- }
- }
- });
+
+ mapPanel.setWaypointLoading(true);
+ geoFilterPanel.setEnabled(false);
+
+ Thread thread = new Thread(new WaypointRunner(filters));
+ thread.start();
}
/**
@@ -269,4 +247,76 @@ public final class GeolocationTopComponent extends TopComponent {
private org.sleuthkit.autopsy.geolocation.HidingPane filterPane;
private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel;
// End of variables declaration//GEN-END:variables
+
+ /**
+ * A runnable class for getting waypoints based on the current filters.
+ */
+ private class WaypointRunner implements Runnable {
+
+ private final GeoFilter filters;
+
+ /**
+ * Constructs the Waypoint Runner
+ *
+ * @param filters
+ */
+ WaypointRunner(GeoFilter filters) {
+ this.filters = filters;
+ }
+
+ @Override
+ public void run() {
+ Case currentCase = Case.getCurrentCase();
+ try {
+ WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(),
+ filters.getDataSources(),
+ filters.showAllWaypoints(),
+ filters.getMostRecentNumDays(),
+ filters.showWaypointsWithoutTimeStamp(),
+ new WaypointCallBack());
+
+ } catch (GeoLocationDataException ex) {
+ logger.log(Level.SEVERE, "Failed to filter waypoints.", ex);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ JOptionPane.showMessageDialog(GeolocationTopComponent.this,
+ Bundle.GeoTopComponent_filter_exception_Title(),
+ Bundle.GeoTopComponent_filter_exception_msg(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ });
+ }
+ }
+
+ }
+
+ /**
+ * Callback for getting waypoints.
+ */
+ private class WaypointCallBack implements WaypointFilterQueryCallBack {
+
+ @Override
+ public void process(List waypoints) {
+ // Make sure that the waypoints are added to the map panel in
+ // the correct thread.
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // If the list is empty, tell the user and do not change
+ // the visible waypoints.
+ if (waypoints == null || waypoints.isEmpty()) {
+ JOptionPane.showMessageDialog(GeolocationTopComponent.this,
+ Bundle.GeoTopComponent_no_waypoints_returned_Title(),
+ Bundle.GeoTopComponent_no_waypoints_returned_mgs(),
+ JOptionPane.INFORMATION_MESSAGE);
+
+ return;
+ }
+ mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints));
+ geoFilterPanel.setEnabled(true);
+ }
+ });
+ }
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form
index 454008c062..5158982822 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form
@@ -98,7 +98,7 @@
-
+
@@ -107,8 +107,19 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java
index fb5cdc23e5..03ec22cf2b 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java
@@ -178,6 +178,17 @@ final public class MapPanel extends javax.swing.JPanel {
mapViewer.setOverlayPainter(waypointPainter);
}
+
+ /**
+ * Show or hide the waypoint loading progress bar.
+ *
+ * @param loading
+ */
+ void setWaypointLoading(boolean loading) {
+ progressBar.setEnabled(true);
+ progressBar.setVisible(loading);
+ progressBar.setString("Loading Waypoints");
+ }
/**
* Setup the zoom slider based on the current tileFactory.
@@ -265,6 +276,7 @@ final public class MapPanel extends javax.swing.JPanel {
}
mapViewer.repaint();
+ setWaypointLoading(false);
}
/**
@@ -486,6 +498,7 @@ final public class MapPanel extends javax.swing.JPanel {
zoomSlider = new javax.swing.JSlider();
infoPanel = new javax.swing.JPanel();
cordLabel = new javax.swing.JLabel();
+ progressBar = new javax.swing.JProgressBar();
setFocusable(false);
setLayout(new java.awt.BorderLayout());
@@ -553,10 +566,24 @@ final public class MapPanel extends javax.swing.JPanel {
add(mapViewer, java.awt.BorderLayout.CENTER);
- infoPanel.setLayout(new java.awt.BorderLayout());
+ infoPanel.setLayout(new java.awt.GridBagLayout());
org.openide.awt.Mnemonics.setLocalizedText(cordLabel, org.openide.util.NbBundle.getMessage(MapPanel.class, "MapPanel.cordLabel.text")); // NOI18N
- infoPanel.add(cordLabel, java.awt.BorderLayout.EAST);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
+ infoPanel.add(cordLabel, gridBagConstraints);
+
+ progressBar.setIndeterminate(true);
+ progressBar.setStringPainted(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ infoPanel.add(progressBar, gridBagConstraints);
add(infoPanel, java.awt.BorderLayout.SOUTH);
}// //GEN-END:initComponents
@@ -594,6 +621,7 @@ final public class MapPanel extends javax.swing.JPanel {
private javax.swing.JLabel cordLabel;
private javax.swing.JPanel infoPanel;
private org.jxmapviewer.JXMapViewer mapViewer;
+ private javax.swing.JProgressBar progressBar;
private javax.swing.JPanel zoomPanel;
private javax.swing.JSlider zoomSlider;
// End of variables declaration//GEN-END:variables
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java
index c9fad6c1bc..d359332f84 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java
@@ -85,7 +85,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
private static final int LOG2TIMELINE_WORKERS = 2;
private static final long TERMINATION_CHECK_INTERVAL = 5;
private static final TimeUnit TERMINATION_CHECK_INTERVAL_UNITS = TimeUnit.SECONDS;
-
+
private File log2TimeLineExecutable;
private File psortExecutable;
@@ -103,8 +103,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
@NbBundle.Messages({
"PlasoIngestModule.executable.not.found=Plaso Executable Not Found.",
- "PlasoIngestModule.requires.windows=Plaso module requires windows.",
- "PlasoIngestModule.dataSource.not.an.image=Datasource is not an Image."})
+ "PlasoIngestModule.requires.windows=Plaso module requires windows."})
@Override
public void startUp(IngestJobContext context) throws IngestModuleException {
this.context = context;
@@ -121,11 +120,6 @@ public class PlasoIngestModule implements DataSourceIngestModule {
throw new IngestModuleException(Bundle.PlasoIngestModule_executable_not_found(), exception);
}
- Content dataSource = context.getDataSource();
- if (!(dataSource instanceof Image)) {
- throw new IngestModuleException(Bundle.PlasoIngestModule_dataSource_not_an_image());
- }
- image = (Image) dataSource;
}
@NbBundle.Messages({
@@ -138,80 +132,90 @@ public class PlasoIngestModule implements DataSourceIngestModule {
"PlasoIngestModule.psort.cancelled=psort run was canceled",
"PlasoIngestModule.bad.imageFile=Cannot find image file name and path",
"PlasoIngestModule.completed=Plaso Processing Completed",
- "PlasoIngestModule.has.run=Plaso Plugin has been run.",
- "PlasoIngestModule.psort.fail=Plaso returned an error when sorting events. Results are not complete."})
+ "PlasoIngestModule.has.run=Plaso",
+ "PlasoIngestModule.psort.fail=Plaso returned an error when sorting events. Results are not complete.",
+ "PlasoIngestModule.dataSource.not.an.image=Skipping non-disk image datasource"})
@Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
- assert dataSource.equals(image);
- statusHelper.switchToDeterminate(100);
- currentCase = Case.getCurrentCase();
- fileManager = currentCase.getServices().getFileManager();
+ if (!(dataSource instanceof Image)) {
+ IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
+ Bundle.PlasoIngestModule_has_run(),
+ Bundle.PlasoIngestModule_dataSource_not_an_image());
+ IngestServices.getInstance().postMessage(message);
+ return ProcessResult.OK;
+ } else {
+ image = (Image) dataSource;
- String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
- Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime);
- try {
- Files.createDirectories(moduleOutputPath);
- } catch (IOException ex) {
- logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS
- return ProcessResult.ERROR;
- }
+ statusHelper.switchToDeterminate(100);
+ currentCase = Case.getCurrentCase();
+ fileManager = currentCase.getServices().getFileManager();
- // Run log2timeline
- logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS
- statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0);
- ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image);
- try {
- Process log2TimeLineProcess = log2TimeLineCommand.start();
- try (BufferedReader log2TimeLineOutpout = new BufferedReader(new InputStreamReader(log2TimeLineProcess.getInputStream()))) {
- L2TStatusProcessor statusReader = new L2TStatusProcessor(log2TimeLineOutpout, statusHelper, moduleOutputPath);
- new Thread(statusReader, "log2timeline status reader").start(); //NON-NLS
- ExecUtil.waitForTermination(LOG2TIMELINE_EXECUTABLE, log2TimeLineProcess, TERMINATION_CHECK_INTERVAL, TERMINATION_CHECK_INTERVAL_UNITS, new DataSourceIngestModuleProcessTerminator(context));
- statusReader.cancel();
- }
-
- if (context.dataSourceIngestIsCancelled()) {
- logger.log(Level.INFO, "Log2timeline run was canceled"); //NON-NLS
- return ProcessResult.OK;
- }
- if (Files.notExists(moduleOutputPath.resolve(PLASO))) {
- logger.log(Level.WARNING, "Error running log2timeline: there was no storage file."); //NON-NLS
+ String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
+ Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime);
+ try {
+ Files.createDirectories(moduleOutputPath);
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS
return ProcessResult.ERROR;
}
- // sort the output
- statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33);
- ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath);
- int result = ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context));
- if (result != 0) {
- logger.log(Level.SEVERE, String.format("Error running Psort, error code returned %d", result)); //NON-NLS
- MessageNotifyUtil.Notify.error(MODULE_NAME, Bundle.PlasoIngestModule_psort_fail());
- return ProcessResult.ERROR;
- }
+ // Run log2timeline
+ logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS
+ statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0);
+ ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image);
+ try {
+ Process log2TimeLineProcess = log2TimeLineCommand.start();
+ try (BufferedReader log2TimeLineOutpout = new BufferedReader(new InputStreamReader(log2TimeLineProcess.getInputStream()))) {
+ L2TStatusProcessor statusReader = new L2TStatusProcessor(log2TimeLineOutpout, statusHelper, moduleOutputPath);
+ new Thread(statusReader, "log2timeline status reader").start(); //NON-NLS
+ ExecUtil.waitForTermination(LOG2TIMELINE_EXECUTABLE, log2TimeLineProcess, TERMINATION_CHECK_INTERVAL, TERMINATION_CHECK_INTERVAL_UNITS, new DataSourceIngestModuleProcessTerminator(context));
+ statusReader.cancel();
+ }
- if (context.dataSourceIngestIsCancelled()) {
- logger.log(Level.INFO, "psort run was canceled"); //NON-NLS
- return ProcessResult.OK;
- }
- Path plasoFile = moduleOutputPath.resolve("plasodb.db3"); //NON-NLS
- if (Files.notExists(plasoFile)) {
- logger.log(Level.SEVERE, "Error running Psort: there was no sqlite db file."); //NON-NLS
+ if (context.dataSourceIngestIsCancelled()) {
+ logger.log(Level.INFO, "Log2timeline run was canceled"); //NON-NLS
+ return ProcessResult.OK;
+ }
+ if (Files.notExists(moduleOutputPath.resolve(PLASO))) {
+ logger.log(Level.WARNING, "Error running log2timeline: there was no storage file."); //NON-NLS
+ return ProcessResult.ERROR;
+ }
+
+ // sort the output
+ statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33);
+ ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath);
+ int result = ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context));
+ if (result != 0) {
+ logger.log(Level.SEVERE, String.format("Error running Psort, error code returned %d", result)); //NON-NLS
+ MessageNotifyUtil.Notify.error(MODULE_NAME, Bundle.PlasoIngestModule_psort_fail());
+ return ProcessResult.ERROR;
+ }
+
+ if (context.dataSourceIngestIsCancelled()) {
+ logger.log(Level.INFO, "psort run was canceled"); //NON-NLS
+ return ProcessResult.OK;
+ }
+ Path plasoFile = moduleOutputPath.resolve("plasodb.db3"); //NON-NLS
+ if (Files.notExists(plasoFile)) {
+ logger.log(Level.SEVERE, "Error running Psort: there was no sqlite db file."); //NON-NLS
+ return ProcessResult.ERROR;
+ }
+
+ // parse the output and make artifacts
+ createPlasoArtifacts(plasoFile.toString(), statusHelper);
+
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "Error running Plaso.", ex);//NON-NLS
return ProcessResult.ERROR;
}
- // parse the output and make artifacts
- createPlasoArtifacts(plasoFile.toString(), statusHelper);
-
- } catch (IOException ex) {
- logger.log(Level.SEVERE, "Error running Plaso.", ex);//NON-NLS
- return ProcessResult.ERROR;
+ IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
+ Bundle.PlasoIngestModule_has_run(),
+ Bundle.PlasoIngestModule_completed());
+ IngestServices.getInstance().postMessage(message);
+ return ProcessResult.OK;
}
-
- IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
- Bundle.PlasoIngestModule_has_run(),
- Bundle.PlasoIngestModule_completed());
- IngestServices.getInstance().postMessage(message);
- return ProcessResult.OK;
}
private ProcessBuilder buildLog2TimeLineCommand(Path moduleOutputPath, Image image) {
@@ -240,8 +244,10 @@ public class PlasoIngestModule implements DataSourceIngestModule {
static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
- /* Add an environment variable to force log2timeline/psort to run with
- * the same permissions Autopsy uses. */
+ /*
+ * Add an environment variable to force log2timeline/psort to run with
+ * the same permissions Autopsy uses.
+ */
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
return processBuilder;
}
@@ -277,31 +283,30 @@ public class PlasoIngestModule implements DataSourceIngestModule {
"PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ",
"# {0} - file that events are from",
"PlasoIngestModule.artifact.progress=Adding events to case: {0}",
- "PlasoIngestModule.info.empty.database=Plaso database was empty.",
- })
+ "PlasoIngestModule.info.empty.database=Plaso database was empty.",})
private void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper) {
Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
String sqlStatement = "SELECT substr(filename,1) AS filename, "
- + " strftime('%s', datetime) AS epoch_date, "
- + " description, "
- + " source, "
- + " type, "
- + " sourcetype "
- + " FROM log2timeline "
- + " WHERE source NOT IN ('FILE', "
- + " 'WEBHIST') " // bad dates and duplicates with what we have.
- + " AND sourcetype NOT IN ('UNKNOWN', "
- + " 'PE Import Time');"; // lots of bad dates //NON-NLS
+ + " strftime('%s', datetime) AS epoch_date, "
+ + " description, "
+ + " source, "
+ + " type, "
+ + " sourcetype "
+ + " FROM log2timeline "
+ + " WHERE source NOT IN ('FILE', "
+ + " 'WEBHIST') " // bad dates and duplicates with what we have.
+ + " AND sourcetype NOT IN ('UNKNOWN', "
+ + " 'PE Import Time');"; // lots of bad dates //NON-NLS
try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + plasoDb); //NON-NLS
ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) {
-
+
boolean dbHasData = false;
-
+
while (resultSet.next()) {
dbHasData = true;
-
+
if (context.dataSourceIngestIsCancelled()) {
logger.log(Level.INFO, "Cancelled Plaso Artifact Creation."); //NON-NLS
return;
@@ -314,20 +319,20 @@ public class PlasoIngestModule implements DataSourceIngestModule {
logger.log(Level.INFO, "File {0} from Plaso output not found in case. Associating it with the data source instead.", currentFileName);//NON-NLS
resolvedFile = image;
}
-
+
String description = resultSet.getString("description");
TimelineEventType eventType = findEventSubtype(currentFileName, resultSet);
-
+
// If the description is empty use the event type display name
// as the description.
- if ( description == null || description.isEmpty() ) {
+ if (description == null || description.isEmpty()) {
if (eventType != TimelineEventType.OTHER) {
description = eventType.getDisplayName();
} else {
continue;
}
}
-
+
Collection bbattributes = Arrays.asList(
new BlackboardAttribute(
TSK_DATETIME, MODULE_NAME,
@@ -338,14 +343,16 @@ public class PlasoIngestModule implements DataSourceIngestModule {
new BlackboardAttribute(
TSK_TL_EVENT_TYPE, MODULE_NAME,
eventType.getTypeID()));
-
+
try {
BlackboardArtifact bbart = resolvedFile.newArtifact(TSK_TL_EVENT);
bbart.addAttributes(bbattributes);
try {
- /* Post the artifact which will index the artifact for
+ /*
+ * Post the artifact which will index the artifact for
* keyword search, and fire an event to notify UI of
- * this new artifact */
+ * this new artifact
+ */
blackboard.postArtifact(bbart, MODULE_NAME);
} catch (BlackboardException ex) {
logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS
@@ -354,12 +361,12 @@ public class PlasoIngestModule implements DataSourceIngestModule {
logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS
}
}
-
+
// Check if there is data the db
- if( !dbHasData ) {
+ if (!dbHasData) {
logger.log(Level.INFO, String.format("PlasoDB was empty: %s", plasoDb));
MessageNotifyUtil.Notify.info(MODULE_NAME, Bundle.PlasoIngestModule_info_empty_database());
- }
+ }
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS
}
@@ -377,8 +384,8 @@ public class PlasoIngestModule implements DataSourceIngestModule {
// check the cached file
//TODO: would we reduce 'cache misses' if we retrieved the events sorted by file? Is that overhead worth it?
if (previousFile != null
- && previousFile.getName().equalsIgnoreCase(fileName)
- && previousFile.getParentPath().equalsIgnoreCase(filePath)) {
+ && previousFile.getName().equalsIgnoreCase(fileName)
+ && previousFile.getParentPath().equalsIgnoreCase(filePath)) {
return previousFile;
}
@@ -416,7 +423,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
switch (row.getString("source")) {
case "WEBHIST": //These shouldn't actually be present, but keeping the logic just in case...
if (fileName.toLowerCase().contains(COOKIE)
- || row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS
+ || row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS
return TimelineEventType.WEB_COOKIE;
} else {
diff --git a/InternalPythonModules/android/skype.py b/InternalPythonModules/android/skype.py
index d8b79ac7fe..721fc92421 100644
--- a/InternalPythonModules/android/skype.py
+++ b/InternalPythonModules/android/skype.py
@@ -46,6 +46,8 @@ from org.sleuthkit.datamodel import Account
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
+from org.sleuthkit.datamodel.blackboardutils import FileAttachment
+from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
from TskMessagesParser import TskMessagesParser
from TskContactsParser import TskContactsParser
from TskCallLogsParser import TskCallLogsParser
@@ -137,7 +139,7 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer):
)
self.parse_contacts(skype_db, helper)
self.parse_calllogs(skype_db, helper)
- self.parse_messages(skype_db, helper)
+ self.parse_messages(skype_db, helper, current_case)
except NoCurrentCaseException as ex:
self._logger.log(Level.WARNING, "No case currently open.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
@@ -209,23 +211,30 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer):
"Failed to post call log artifact to the blackboard", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
- def parse_messages(self, skype_db, helper):
+ def parse_messages(self, skype_db, helper, current_case):
#Query for messages and iterate row by row adding
#each message artifact
try:
messages_parser = SkypeMessagesParser(skype_db)
while messages_parser.next():
- helper.addMessage(
- messages_parser.get_message_type(),
- messages_parser.get_message_direction(),
- messages_parser.get_phone_number_from(),
- messages_parser.get_phone_number_to(),
- messages_parser.get_message_date_time(),
- messages_parser.get_message_read_status(),
- messages_parser.get_message_subject(),
- messages_parser.get_message_text(),
- messages_parser.get_thread_id()
- )
+ message_artifact = helper.addMessage(
+ messages_parser.get_message_type(),
+ messages_parser.get_message_direction(),
+ messages_parser.get_phone_number_from(),
+ messages_parser.get_phone_number_to(),
+ messages_parser.get_message_date_time(),
+ messages_parser.get_message_read_status(),
+ messages_parser.get_message_subject(),
+ messages_parser.get_message_text(),
+ messages_parser.get_thread_id()
+ )
+
+ if (messages_parser.get_file_attachment() is not None):
+ file_attachments = ArrayList()
+ file_attachments.add(FileAttachment(current_case.getSleuthkitCase(), skype_db.getDBFile().getDataSource(), messages_parser.get_file_attachment()))
+ message_attachments = MessageAttachments(file_attachments, [])
+ helper.addAttachments(message_artifact, message_attachments)
+
messages_parser.close()
except SQLException as ex:
#Error parsing Skype db
@@ -425,12 +434,6 @@ class SkypeMessagesParser(TskMessagesParser):
content = self.result_set.getString("content")
if content is not None:
- file_path = self.result_set.getString("device_gallery_path")
-
- #if a file name and file path are associated with a message, append it
- if file_path is not None:
- return general.appendAttachmentList(content, [file_path])
-
return content
return super(SkypeMessagesParser, self).get_message_text()
@@ -440,3 +443,11 @@ class SkypeMessagesParser(TskMessagesParser):
if group_ids is not None:
return self.result_set.getString("conversation_id")
return super(SkypeMessagesParser, self).get_thread_id()
+
+
+ def get_file_attachment(self):
+ if (self.result_set.getString("device_gallery_path") is None):
+ return None
+ else:
+ return self.result_set.getString("device_gallery_path")
+
diff --git a/InternalPythonModules/android/whatsapp.py b/InternalPythonModules/android/whatsapp.py
index 438784f3e2..a91c21375b 100644
--- a/InternalPythonModules/android/whatsapp.py
+++ b/InternalPythonModules/android/whatsapp.py
@@ -45,6 +45,9 @@ from org.sleuthkit.datamodel import Account
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
+from org.sleuthkit.datamodel.blackboardutils import FileAttachment
+from org.sleuthkit.datamodel.blackboardutils import URLAttachment
+from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
from TskMessagesParser import TskMessagesParser
from TskContactsParser import TskContactsParser
from TskCallLogsParser import TskCallLogsParser
@@ -151,7 +154,7 @@ class WhatsAppAnalyzer(general.AndroidComponentAnalyzer):
current_case.getSleuthkitCase(), self._PARSER_NAME,
calllog_and_message_db.getDBFile(), Account.Type.WHATSAPP)
self.parse_calllogs(calllog_and_message_db, helper)
- self.parse_messages(dataSource, calllog_and_message_db, helper)
+ self.parse_messages(dataSource, calllog_and_message_db, helper, current_case)
except NoCurrentCaseException as ex:
#If there is no current case, bail out immediately.
@@ -227,24 +230,32 @@ class WhatsAppAnalyzer(general.AndroidComponentAnalyzer):
"Error posting calllog artifact to the blackboard.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
- def parse_messages(self, dataSource, messages_db, helper):
+ def parse_messages(self, dataSource, messages_db, helper, current_case):
try:
messages_db.attachDatabase(dataSource, "wa.db",
messages_db.getDBFile().getParentPath(), "wadb")
messages_parser = WhatsAppMessagesParser(messages_db)
while messages_parser.next():
- helper.addMessage(
- messages_parser.get_message_type(),
- messages_parser.get_message_direction(),
- messages_parser.get_phone_number_from(),
- messages_parser.get_phone_number_to(),
- messages_parser.get_message_date_time(),
- messages_parser.get_message_read_status(),
- messages_parser.get_message_subject(),
- messages_parser.get_message_text(),
- messages_parser.get_thread_id()
- )
+ message_artifact = helper.addMessage(
+ messages_parser.get_message_type(),
+ messages_parser.get_message_direction(),
+ messages_parser.get_phone_number_from(),
+ messages_parser.get_phone_number_to(),
+ messages_parser.get_message_date_time(),
+ messages_parser.get_message_read_status(),
+ messages_parser.get_message_subject(),
+ messages_parser.get_message_text(),
+ messages_parser.get_thread_id()
+ )
+
+ # add attachments, if any
+ if (messages_parser.get_url_attachment() is not None):
+ url_attachments = ArrayList()
+ url_attachments.add(URLAttachment(messages_parser.get_url_attachment()))
+ message_attachments = MessageAttachments([], url_attachments)
+ helper.addAttachments(message_artifact, message_attachments)
+
messages_parser.close()
except SQLException as ex:
self._logger.log(Level.WARNING, "Error querying the whatsapp database for contacts.", ex)
@@ -502,9 +513,6 @@ class WhatsAppMessagesParser(TskMessagesParser):
message = self.result_set.getString("content")
if message is None:
message = super(WhatsAppMessagesParser, self).get_message_text()
- attachment = self.result_set.getString("attachment")
- if attachment is not None:
- return general.appendAttachmentList(message, [attachment])
return message
def get_thread_id(self):
@@ -512,3 +520,14 @@ class WhatsAppMessagesParser(TskMessagesParser):
if group is not None:
return self.result_set.getString("id")
return super(WhatsAppMessagesParser, self).get_thread_id()
+
+
+ def get_url_attachment(self):
+ attachment = self.result_set.getString("attachment")
+ if (attachment is None):
+ return None
+ elif (str(attachment).startswith("http:") or str(attachment).startswith("https:") ):
+ return attachment
+ else:
+ return None
+
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java
index f75132c610..49ac9911fe 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java
@@ -554,22 +554,24 @@ class Chrome extends Extract {
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
- BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes);
- if (bbart != null) {
- bbartifacts.add(bbart);
- }
-
- // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it..
- try {
- for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(fullPath), FilenameUtils.getPath(fullPath))) {
- BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
- downloadSourceArt.addAttributes(createDownloadSourceAttributes(result.get("url").toString()));
-
- bbartifacts.add(downloadSourceArt);
- break;
+ BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes);
+ if (webDownloadArtifact != null) {
+ bbartifacts.add(webDownloadArtifact);
+
+ // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
+ try {
+ for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(fullPath), FilenameUtils.getPath(fullPath))) {
+ BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
+ associatedObjectArtifact.addAttribute(
+ new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
+ RecentActivityExtracterModuleFactory.getModuleName(), webDownloadArtifact.getArtifactID()));
+
+ bbartifacts.add(associatedObjectArtifact);
+ break;
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Error creating associated object artifact for file '%s'", fullPath), ex); //NON-NLS
}
- } catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'", fullPath), ex); //NON-NLS
}
}
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java
index efaf8934b2..4e1b5c3931 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java
@@ -380,12 +380,12 @@ final class ChromeCacheExtractor {
* Extracts the files if needed and adds as derived files, creates artifacts
*
* @param cacheEntryAddress cache entry address
- * @param sourceArtifacts any source artifacts created are added to this collection
+ * @param associatedObjectArtifacts any associated object artifacts created are added to this collection
* @param webCacheArtifacts any web cache artifacts created are added to this collection
*
* @return Optional derived file, is a derived file is added for the given entry
*/
- private List processCacheEntry(CacheAddress cacheEntryAddress, Collection sourceArtifacts, Collection webCacheArtifacts ) throws TskCoreException, IngestModuleException {
+ private List processCacheEntry(CacheAddress cacheEntryAddress, Collection associatedObjectArtifacts, Collection webCacheArtifacts ) throws TskCoreException, IngestModuleException {
List derivedFiles = new ArrayList<>();
@@ -437,10 +437,6 @@ final class ChromeCacheExtractor {
moduleName,
cacheEntry.getHTTPHeaders());
- Collection sourceArtifactAttributes = new ArrayList<>();
- sourceArtifactAttributes.add(urlAttr);
- sourceArtifactAttributes.add(createTimeAttr);
-
Collection webCacheAttributes = new ArrayList<>();
webCacheAttributes.add(urlAttr);
webCacheAttributes.add(createTimeAttr);
@@ -450,12 +446,7 @@ final class ChromeCacheExtractor {
// add artifacts to the f_XXX file
if (dataSegment.isInExternalFile() ) {
try {
- BlackboardArtifact sourceArtifact = cachedFileAbstractFile.get().newArtifact(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
- if (sourceArtifact != null) {
- sourceArtifact.addAttributes(sourceArtifactAttributes);
- sourceArtifacts.add(sourceArtifact);
- }
-
+
BlackboardArtifact webCacheArtifact = cacheEntryFile.get().getAbstractFile().newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE);
if (webCacheArtifact != null) {
webCacheArtifact.addAttributes(webCacheAttributes);
@@ -469,6 +460,14 @@ final class ChromeCacheExtractor {
moduleName, cachedFileAbstractFile.get().getId()));
webCacheArtifacts.add(webCacheArtifact);
+
+ BlackboardArtifact associatedObjectArtifact = cachedFileAbstractFile.get().newArtifact(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
+ if (associatedObjectArtifact != null) {
+ associatedObjectArtifact.addAttribute(
+ new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
+ moduleName, webCacheArtifact.getArtifactID()));
+ associatedObjectArtifacts.add(associatedObjectArtifact);
+ }
}
if (isBrotliCompressed) {
@@ -497,12 +496,7 @@ final class ChromeCacheExtractor {
"",
TskData.EncodingType.NONE);
- BlackboardArtifact sourceArtifact = derivedFile.newArtifact(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
- if (sourceArtifact != null) {
- sourceArtifact.addAttributes(sourceArtifactAttributes);
- sourceArtifacts.add(sourceArtifact);
- }
-
+
BlackboardArtifact webCacheArtifact = cacheEntryFile.get().getAbstractFile().newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE);
if (webCacheArtifact != null) {
webCacheArtifact.addAttributes(webCacheAttributes);
@@ -516,6 +510,14 @@ final class ChromeCacheExtractor {
moduleName, derivedFile.getId()));
webCacheArtifacts.add(webCacheArtifact);
+
+ BlackboardArtifact associatedObjectArtifact = derivedFile.newArtifact(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
+ if (associatedObjectArtifact != null) {
+ associatedObjectArtifact.addAttribute(
+ new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
+ moduleName, webCacheArtifact.getArtifactID()));
+ associatedObjectArtifacts.add(associatedObjectArtifact);
+ }
}
if (isBrotliCompressed) {
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java
index 704826047f..c11e4fde22 100755
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java
@@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.xml.sax.SAXException;
@@ -637,15 +638,17 @@ final class ExtractSafari extends Extract {
time = date.getDate().getTime();
}
- BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD);
- bbart.addAttributes(this.createDownloadAttributes(path, pathID, url, time, NetworkUtils.extractDomain(url), getName()));
- bbartifacts.add(bbart);
+ BlackboardArtifact webDownloadArtifact = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD);
+ webDownloadArtifact.addAttributes(this.createDownloadAttributes(path, pathID, url, time, NetworkUtils.extractDomain(url), getName()));
+ bbartifacts.add(webDownloadArtifact);
- // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it.
+ // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(path), FilenameUtils.getPath(path))) {
- BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
- downloadSourceArt.addAttributes(createDownloadSourceAttributes(url));
- bbartifacts.add(downloadSourceArt);
+ BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
+ associatedObjectArtifact.addAttribute(
+ new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
+ RecentActivityExtracterModuleFactory.getModuleName(), webDownloadArtifact.getArtifactID()));
+ bbartifacts.add(associatedObjectArtifact);
break;
}
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java
index 250c55fa6e..5062806133 100755
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java
@@ -28,7 +28,6 @@ import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
-import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
@@ -36,13 +35,11 @@ import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
import org.sleuthkit.datamodel.BlackboardAttribute;
-import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN;
-import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION;
+import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID;
-import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
@@ -94,7 +91,7 @@ final class ExtractZoneIdentifier extends Extract {
return;
}
- Collection sourceArtifacts = new ArrayList<>();
+ Collection associatedObjectArtifacts = new ArrayList<>();
Collection downloadArtifacts = new ArrayList<>();
for (AbstractFile zoneFile : zoneFiles) {
@@ -104,7 +101,7 @@ final class ExtractZoneIdentifier extends Extract {
}
try {
- processZoneFile(context, dataSource, zoneFile, sourceArtifacts, downloadArtifacts, knownPathIDs);
+ processZoneFile(context, dataSource, zoneFile, associatedObjectArtifacts, downloadArtifacts, knownPathIDs);
} catch (TskCoreException ex) {
addErrorMessage(Bundle.ExtractZone_process_errMsg());
String message = String.format("Failed to process zone identifier file %s", zoneFile.getName()); //NON-NLS
@@ -112,23 +109,23 @@ final class ExtractZoneIdentifier extends Extract {
}
}
- postArtifacts(sourceArtifacts);
+ postArtifacts(associatedObjectArtifacts);
postArtifacts(downloadArtifacts);
}
/**
* Process a single Zone Identifier file.
*
- * @param context IngetJobContext
- * @param dataSource Content
- * @param zoneFile Zone Indentifier file
- * @param sourceArtifacts List for TSK_DOWNLOAD_SOURCE artifacts
- * @param downloadArtifacts List for TSK_WEB_DOWNLOAD aritfacts
- *
+ * @param context IngetJobContext
+ * @param dataSource Content
+ * @param zoneFile Zone Indentifier file
+ * @param associatedObjectArtifacts List for TSK_ASSOCIATED_OBJECT artifacts
+ * @param downloadArtifacts List for TSK_WEB_DOWNLOAD artifacts
+ *
* @throws TskCoreException
*/
private void processZoneFile(IngestJobContext context, Content dataSource,
- AbstractFile zoneFile, Collection sourceArtifacts,
+ AbstractFile zoneFile, Collection associatedObjectArtifacts,
Collection downloadArtifacts,
Set knownPathIDs) throws TskCoreException {
@@ -155,16 +152,16 @@ final class ExtractZoneIdentifier extends Extract {
BlackboardArtifact downloadBba = createDownloadArtifact(zoneFile, zoneInfo);
if (downloadBba != null) {
downloadArtifacts.add(downloadBba);
+ // create a TSK_ASSOCIATED_OBJECT for the downloaded file, associating it with the TSK_WEB_DOWNLOAD artifact.
+ if (downloadFile.getArtifactsCount(TSK_ASSOCIATED_OBJECT) == 0) {
+ BlackboardArtifact associatedObjectBba = createAssociatedObjectArtifact(downloadFile, downloadBba);
+ if (associatedObjectBba != null) {
+ associatedObjectArtifacts.add(associatedObjectBba);
+ }
+ }
}
}
- // check if download has a child TSK_DOWNLOAD_SOURCE artifact, if not create one
- if (downloadFile.getArtifactsCount(TSK_DOWNLOAD_SOURCE) == 0) {
- BlackboardArtifact sourceBba = createDownloadSourceArtifact(downloadFile, zoneInfo);
- if (sourceBba != null) {
- sourceArtifacts.add(sourceBba);
- }
- }
}
}
@@ -203,33 +200,26 @@ final class ExtractZoneIdentifier extends Extract {
}
/**
- * Create a Download Source Artifact for the given ZoneIdentifierInfo
+ * Create a Associated Object Artifact for the given ZoneIdentifierInfo
* object.
*
* @param downloadFile AbstractFile representing the file downloaded, not
- * the zone identifier file.
- * @param zoneInfo Zone identifier file wrapper object
+ * the zone identifier file.
+ * @param downloadBba TSK_WEB_DOWNLOAD artifact to associate with.
*
- * @return TSK_DOWNLOAD_SOURCE object for given parameters
+ * @return TSK_ASSOCIATED_OBJECT artifact.
*/
- private BlackboardArtifact createDownloadSourceArtifact(AbstractFile downloadFile, ZoneIdentifierInfo zoneInfo) {
+ private BlackboardArtifact createAssociatedObjectArtifact(AbstractFile downloadFile, BlackboardArtifact downloadBba) {
Collection bbattributes = new ArrayList<>();
bbattributes.addAll(Arrays.asList(
- new BlackboardAttribute(TSK_URL,
- RecentActivityExtracterModuleFactory.getModuleName(),
- StringUtils.defaultString(zoneInfo.getURL(), "")),
-
- new BlackboardAttribute(TSK_DOMAIN,
- RecentActivityExtracterModuleFactory.getModuleName(),
- (zoneInfo.getURL() != null) ? NetworkUtils.extractDomain(zoneInfo.getURL()) : ""),
-
- new BlackboardAttribute(TSK_LOCATION,
- RecentActivityExtracterModuleFactory.getModuleName(),
- StringUtils.defaultString(zoneInfo.getZoneIdAsString(), "")))); //NON-NLS
+ new BlackboardAttribute(TSK_ASSOCIATED_ARTIFACT,
+ RecentActivityExtracterModuleFactory.getModuleName(),
+ downloadBba.getArtifactID())
+ ));
- return createArtifactWithAttributes(TSK_DOWNLOAD_SOURCE, downloadFile, bbattributes);
+ return createArtifactWithAttributes(TSK_ASSOCIATED_OBJECT, downloadFile, bbattributes);
}
/**
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
index dd4e70ee06..7fddeb0aa8 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
@@ -544,22 +544,25 @@ class Firefox extends Extract {
domain)); //NON-NLS
}
- BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
- if (bbart != null) {
- bbartifacts.add(bbart);
- }
-
- // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it.
- try {
- for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) {
- BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
- downloadSourceArt.addAttributes(createDownloadSourceAttributes(source));
- bbartifacts.add(downloadSourceArt);
- break;
+ BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
+ if (webDownloadArtifact != null) {
+ bbartifacts.add(webDownloadArtifact);
+
+ // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
+ try {
+ for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) {
+ BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
+ associatedObjectArtifact.addAttribute(
+ new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
+ RecentActivityExtracterModuleFactory.getModuleName(), webDownloadArtifact.getArtifactID()));
+
+ bbartifacts.add(associatedObjectArtifact);
+ break;
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Error creating associated object artifact for file '%s'",
+ downloadedFilePath), ex); //NON-NLS
}
- } catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'",
- downloadedFilePath), ex); //NON-NLS
}
}
if (errors > 0) {
@@ -681,22 +684,24 @@ class Firefox extends Extract {
RecentActivityExtracterModuleFactory.getModuleName(), domain)); //NON-NLS
}
- BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
- if (bbart != null) {
- bbartifacts.add(bbart);
- }
+ BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
+ if (webDownloadArtifact != null) {
+ bbartifacts.add(webDownloadArtifact);
- // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it.
- try {
- for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) {
- BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
- downloadSourceArt.addAttributes(createDownloadSourceAttributes(url));
- bbartifacts.add(downloadSourceArt);
- break;
+ // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
+ try {
+ for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) {
+ BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
+ associatedObjectArtifact.addAttribute(
+ new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
+ RecentActivityExtracterModuleFactory.getModuleName(), webDownloadArtifact.getArtifactID()));
+ bbartifacts.add(associatedObjectArtifact);
+ break;
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Error creating associated object artifact for file '%s'",
+ downloadedFilePath), ex); //NON-NLS
}
- } catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'",
- downloadedFilePath), ex); //NON-NLS
}
}
if (errors > 0) {