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) {