From 1bcb510a1db3409526949eb52a77f1ba0c87500a Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Fri, 7 Jun 2019 10:09:10 -0400 Subject: [PATCH 01/33] Commit to save while I go work on something else --- .../relationships/Bundle.properties | 10 + .../relationships/Bundle.properties-MERGED | 17 +- .../relationships/ContactDetailsPane.form | 39 +- .../relationships/ContactDetailsPane.java | 44 +- .../ContactsChildNodeFactory.java | 2 +- .../relationships/MessageNode.java | 26 +- .../relationships/MessageViewer.form | 189 +++++++ .../relationships/MessageViewer.java | 481 ++++++++++++++++++ .../MessagesChildNodeFactory.java | 78 ++- .../relationships/MessagesPanel.form | 51 ++ ...MessagesViewer.java => MessagesPanel.java} | 131 ++--- .../relationships/MessagesViewer.form | 42 -- .../relationships/OutlineViewPanel.form | 5 - .../relationships/OutlineViewPanel.java | 2 - .../relationships/RelationshipBrowser.java | 11 +- .../relationships/ThreadChildNodeFactory.java | 200 ++++++++ .../directorytree/DataResultFilterNode.java | 6 + .../keywordsearch/Bundle.properties-MERGED | 2 +- .../recentactivity/Bundle.properties-MERGED | 2 +- 19 files changed, 1103 insertions(+), 235 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form create mode 100755 Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java create mode 100755 Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form rename Core/src/org/sleuthkit/autopsy/communications/relationships/{MessagesViewer.java => MessagesPanel.java} (59%) delete mode 100755 Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form create mode 100755 Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties index 4970a5958e..4adb9e4b8c 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties @@ -11,5 +11,15 @@ SummaryViewer.emailDataLabel.text=emails SummaryViewer.attachmentsDataLabel.text=attachments SummaryViewer.messagesLabel.text=Messages: SummaryViewer.callLogsLabel.text=Call Logs: +ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages +ThreadPane.backButton.text=<--- SummaryViewer.caseReferencesPanel.border.title=Other Occurrences SummaryViewer.fileReferencesPanel.border.title=File References in Current Case +MessageViewer.threadsLabel.text=Threads +MessageViewer.threadNameLabel.text= +MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: +MessageViewer.messagesLabel.text=Messages +MessageViewer.backButton.AccessibleContext.accessibleDescription= +MessageViewer.backButton.text= +MessageViewer.showMessagesButton.text= +MessageViewer.viewMessageLabel.text=View Messages: diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index b31598dbc6..7eed716c86 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -25,6 +25,9 @@ MessageViewer_columnHeader_Subject=Subject MessageViewer_columnHeader_To=To MessageViewer_no_messages= MessageViewer_tabTitle=Messages +MessageViewer_viewMessage_all=All +MessageViewer_viewMessage_selected=Selected +MessageViewer_viewMessage_unthreaded=Unthreaded SummaryViewer.countsPanel.border.title=Counts SummaryViewer.emailLabel.text=Emails: SummaryViewer.contactsLabel.text=Contacts: @@ -37,10 +40,20 @@ SummaryViewer.emailDataLabel.text=emails SummaryViewer.attachmentsDataLabel.text=attachments SummaryViewer.messagesLabel.text=Messages: SummaryViewer.callLogsLabel.text=Call Logs: -SummaryViewer.caseReferencesPanel.border.title=Other Occurrences -SummaryViewer.fileReferencesPanel.border.title=File References in Current Case SummaryViewer_CaseRefNameColumn_Title=Case Name SummaryViewer_CentralRepository_Message= SummaryViewer_Creation_Date_Title=Creation Date SummaryViewer_FileRefNameColumn_Title=Path SummaryViewer_TabTitle=Summary +ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages +ThreadPane.backButton.text=<--- +SummaryViewer.caseReferencesPanel.border.title=Other Occurrences +SummaryViewer.fileReferencesPanel.border.title=File References in Current Case +MessageViewer.threadsLabel.text=Threads +MessageViewer.threadNameLabel.text= +MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: +MessageViewer.messagesLabel.text=Messages +MessageViewer.backButton.AccessibleContext.accessibleDescription= +MessageViewer.backButton.text= +MessageViewer.showMessagesButton.text= +MessageViewer.viewMessageLabel.text=View Messages: diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form index 2ae2165358..671fac333e 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form @@ -17,34 +17,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -55,11 +28,21 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java index 82d4668e43..ae456e0abb 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java @@ -73,38 +73,36 @@ public final class ContactDetailsPane extends javax.swing.JPanel implements Expl @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; messageContentViewer1 = new org.sleuthkit.autopsy.contentviewers.MessageContentViewer(); nameLabel = new javax.swing.JLabel(); propertySheet = new org.openide.explorer.propertysheet.PropertySheet(); + setLayout(new java.awt.GridBagLayout()); + nameLabel.setFont(new java.awt.Font("Tahoma", 0, 24)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(ContactDetailsPane.class, "ContactDetailsPane.nameLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(16, 15, 15, 15); + add(nameLabel, gridBagConstraints); propertySheet.setDescriptionAreaVisible(false); - - 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) - .addComponent(propertySheet, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addComponent(nameLabel) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(nameLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(propertySheet, javax.swing.GroupLayout.DEFAULT_SIZE, 283, Short.MAX_VALUE) - .addContainerGap()) - ); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 15, 16, 15); + add(propertySheet, gridBagConstraints); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java index 681a572b96..07f98c613b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java @@ -36,7 +36,7 @@ import org.sleuthkit.datamodel.TskCoreException; * ChildFactory for ContactNodes. */ final class ContactsChildNodeFactory extends ChildFactory{ - private static final Logger logger = Logger.getLogger(MessagesChildNodeFactory.class.getName()); + private static final Logger logger = Logger.getLogger(ContactsChildNodeFactory.class.getName()); private SelectionInfo selectionInfo; diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index e6ac3f507f..4a3f88ad8d 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.communications.relationships; import java.util.List; import java.util.TimeZone; import java.util.logging.Level; +import javax.swing.Action; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Sheet; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; @@ -48,16 +49,26 @@ import org.sleuthkit.autopsy.communications.Utils; /** * Wraps a BlackboardArtifact as an AbstractNode for use in an OutlookView */ -final class MessageNode extends BlackboardArtifactNode { +class MessageNode extends BlackboardArtifactNode { + public static final String UNTHREADED_ID = ""; + private static final Logger logger = Logger.getLogger(MessageNode.class.getName()); + + private final String threadID; + + private final Action preferredAction; - MessageNode(BlackboardArtifact artifact) { + MessageNode(BlackboardArtifact artifact, String threadID, Action preferredAction) { super(artifact); + + this.preferredAction = preferredAction; final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); // NON-NLS String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); // NON-NLS setDisplayName(removeEndIgnoreCase.isEmpty() ? stripEnd : removeEndIgnoreCase); + + this.threadID = threadID; } @Messages({ @@ -112,6 +123,8 @@ final class MessageNode extends BlackboardArtifactNode { } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS } + + sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",threadID == null ? "" : threadID)); //NON-NLS break; case TSK_MESSAGE: @@ -183,4 +196,13 @@ final class MessageNode extends BlackboardArtifactNode { public String getSourceName() { return getDisplayName(); } + + String getThreadID() { + return threadID; + } + + @Override + public Action getPreferredAction() { + return preferredAction; + } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form new file mode 100755 index 0000000000..a6d0ddfa45 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form @@ -0,0 +1,189 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java new file mode 100755 index 0000000000..3da19c0d83 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java @@ -0,0 +1,481 @@ +/* + * 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.communications.relationships; + +import java.awt.CardLayout; +import java.awt.Component; +import java.awt.KeyboardFocusManager; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import static javax.swing.SwingUtilities.isDescendingFrom; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import static org.openide.explorer.ExplorerUtils.createLookup; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Node.Property; +import org.openide.nodes.Node.PropertySet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; + + +/** + * + * + */ +public class MessageViewer extends JPanel implements RelationshipsViewer{ + + private static final Logger logger = Logger.getLogger(MessageViewer.class.getName()); + + private final ModifiableProxyLookup proxyLookup; + private final PropertyChangeListener focusPropertyListener; + private final ThreadChildNodeFactory rootMessageFactory; + private final MessagesChildNodeFactory threadMessageNodeFactory; + + private SelectionInfo currentSelectionInfo = null; + + OutlineViewPanel currentPanel; + + @Messages({ + "MessageViewer_tabTitle=Messages", + "MessageViewer_columnHeader_From=From", + "MessageViewer_columnHeader_To=To", + "MessageViewer_columnHeader_Date=Date", + "MessageViewer_columnHeader_Subject=Subject", + "MessageViewer_columnHeader_Attms=Attachments", + "MessageViewer_no_messages=", + "MessageViewer_viewMessage_all=All", + "MessageViewer_viewMessage_selected=Selected", + "MessageViewer_viewMessage_unthreaded=Unthreaded", + }) + + + + /** + * Creates new form MessageViewer2 + */ + public MessageViewer() { + + initComponents(); + currentPanel = rootTablePane; + proxyLookup = new ModifiableProxyLookup(createLookup(rootTablePane.getExplorerManager(), getActionMap())); + rootMessageFactory = new ThreadChildNodeFactory(new ShowThreadMessagesAction()); + threadMessageNodeFactory = new MessagesChildNodeFactory(); + + // See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed + // explaination of focusPropertyListener + focusPropertyListener = (final PropertyChangeEvent focusEvent) -> { + if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) { + final Component newFocusOwner = (Component) focusEvent.getNewValue(); + + if (newFocusOwner == null) { + return; + } + if (isDescendingFrom(newFocusOwner, rootTablePane)) { + proxyLookup.setNewLookups(createLookup(rootTablePane.getExplorerManager(), getActionMap())); + } else if (isDescendingFrom(newFocusOwner, MessageViewer.this)) { + proxyLookup.setNewLookups(createLookup(currentPanel.getExplorerManager(), getActionMap())); + } + } + }; + + + rootTablePane.getExplorerManager().setRootContext( + new TableFilterNode( + new DataResultFilterNode( + new AbstractNode( + Children.create(rootMessageFactory, true)), + rootTablePane.getExplorerManager()), + true)); + + Outline outline = rootTablePane.getOutlineView().getOutline(); + rootTablePane.getOutlineView().setPropertyColumns( + "From", Bundle.MessageViewer_columnHeader_From(), + "To", Bundle.MessageViewer_columnHeader_To(), + "Date", Bundle.MessageViewer_columnHeader_Date(), + "Subject", Bundle.MessageViewer_columnHeader_Subject(), + "Attms", Bundle.MessageViewer_columnHeader_Attms() + ); + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel("Type"); + + + rootTablePane.getExplorerManager().addPropertyChangeListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] nodes = rootTablePane.getExplorerManager().getSelectedNodes(); + + switch((VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem()) { + case ALL: + showMessagesButton.setEnabled((nodes != null && nodes.length > 0)); + break; + case SELECTED: + showMessagesButton.setEnabled((nodes != null && nodes.length == 1)); + break; + } + } + }); + + threadMessagesPanel.setChildFactory(threadMessageNodeFactory); + + this.viewMessageComboBox.addItem(VIEW_MESSAGE_TYPE.SELECTED); + this.viewMessageComboBox.addItem(VIEW_MESSAGE_TYPE.ALL); + } + + @Override + public String getDisplayName() { + return Bundle.MessageViewer_tabTitle(); + } + + @Override + public JPanel getPanel() { + return this; + } + + @Override + public void setSelectionInfo(SelectionInfo info) { + currentSelectionInfo = info; + + currentPanel = rootTablePane; + + CardLayout layout = (CardLayout) this.getLayout(); + layout.show(this, "threads"); + + rootMessageFactory.refresh(info); + } + + @Override + public Lookup getLookup() { + return proxyLookup; + } + + @Override + public void addNotify() { + super.addNotify(); + //add listener that maintains correct selection in the Global Actions Context + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .addPropertyChangeListener("focusOwner", focusPropertyListener); + } + + @Override + public void removeNotify() { + super.removeNotify(); + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .removePropertyChangeListener("focusOwner", focusPropertyListener); + } + + private void showMessages() { + VIEW_MESSAGE_TYPE viewType = (VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem(); + switch((VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem()){ + case ALL: + threadMessageNodeFactory.refresh(currentSelectionInfo, null); + threadNameLabel.setText("All Messages"); + + ((CardLayout)getLayout()).show(this, "messages"); + break; + case SELECTED: + showSelectedThread(); + break; + } + } + + @SuppressWarnings("rawtypes") + private void showSelectedThread() { + final Node[] nodes = rootTablePane.getExplorerManager().getSelectedNodes(); + + if(nodes == null) { + return; + } + + if(nodes.length == 0 || nodes.length > 1) { + return; + } + + ArrayList threadIDList = new ArrayList<>(); + String subject = ""; + + PropertySet[] propertySets = nodes[0].getPropertySets(); + for(PropertySet pset: propertySets) { + Property[] properties = pset.getProperties(); + for(Property prop: properties) { + if(prop.getName().equalsIgnoreCase("threadid")){ + try { + String threadID = prop.getValue().toString(); + if(!threadIDList.contains(threadID)){ + threadIDList.add(threadID); + } + } catch (IllegalAccessException | InvocationTargetException ex) { + logger.log(Level.WARNING, String.format("Unable to get threadid for node: %s", nodes[0].getDisplayName()), ex); + } + } else if(prop.getName().equalsIgnoreCase("subject")) { + try { + subject = prop.getValue().toString(); + } catch (IllegalAccessException | InvocationTargetException ex) { + logger.log(Level.WARNING, String.format("Unable to get subject for node: %s", nodes[0].getDisplayName()), ex); + subject = ""; + } + } + } + + } + + if(!threadIDList.isEmpty()) { + threadMessageNodeFactory.refresh(currentSelectionInfo, threadIDList); + + if(!subject.isEmpty()){ + threadNameLabel.setText(subject); + } + // Come back and put involk later for safety. + CardLayout layout = (CardLayout)getLayout(); + layout.show(this, "messages"); + } + } + + /** + * 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() { + java.awt.GridBagConstraints gridBagConstraints; + + rootMessagesPane = new javax.swing.JPanel(); + rootTablePane = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); + threadsLabel = new javax.swing.JLabel(); + viewMessageComboBox = new javax.swing.JComboBox<>(); + viewMessageLabel = new javax.swing.JLabel(); + showMessagesButton = new javax.swing.JButton(); + messagePanel = new javax.swing.JPanel(); + threadMessagesPanel = new MessagesPanel(); + backButton = new javax.swing.JButton(); + messagesLabel = new javax.swing.JLabel(); + showingMessagesLabel = new javax.swing.JLabel(); + threadNameLabel = new javax.swing.JLabel(); + + setLayout(new java.awt.CardLayout()); + + rootMessagesPane.setOpaque(false); + rootMessagesPane.setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 15, 15, 15); + rootMessagesPane.add(rootTablePane, gridBagConstraints); + + threadsLabel.setFont(new java.awt.Font("Tahoma", 0, 24)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(threadsLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.threadsLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(15, 15, 0, 0); + rootMessagesPane.add(threadsLabel, gridBagConstraints); + + viewMessageComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + viewMessageComboBoxActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 4; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 0); + rootMessagesPane.add(viewMessageComboBox, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(viewMessageLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.viewMessageLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; + gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 5); + rootMessagesPane.add(viewMessageLabel, gridBagConstraints); + + showMessagesButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/timeline/images/btn_step_forward.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(showMessagesButton, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.showMessagesButton.text")); // NOI18N + showMessagesButton.setBorder(null); + showMessagesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + showMessagesButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(15, 5, 0, 15); + rootMessagesPane.add(showMessagesButton, gridBagConstraints); + + add(rootMessagesPane, "threads"); + + messagePanel.setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + gridBagConstraints.gridwidth = 5; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15); + messagePanel.add(threadMessagesPanel, gridBagConstraints); + + backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/timeline/images/btn_step_back.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.backButton.text")); // NOI18N + backButton.setBorder(null); + backButton.setBorderPainted(false); + backButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + backButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(16, 15, 0, 0); + messagePanel.add(backButton, gridBagConstraints); + backButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.backButton.AccessibleContext.accessibleDescription")); // NOI18N + + messagesLabel.setFont(new java.awt.Font("Tahoma", 0, 24)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(messagesLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.messagesLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 15, 0, 0); + messagePanel.add(messagesLabel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(showingMessagesLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.showingMessagesLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 15, 5, 0); + messagePanel.add(showingMessagesLabel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(threadNameLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.threadNameLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 15); + messagePanel.add(threadNameLabel, gridBagConstraints); + + add(messagePanel, "messages"); + }// //GEN-END:initComponents + + private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed + CardLayout layout = (CardLayout) this.getLayout(); + layout.show(this, "threads"); + }//GEN-LAST:event_backButtonActionPerformed + + private void showMessagesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showMessagesButtonActionPerformed + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + showMessages(); + } + }); + }//GEN-LAST:event_showMessagesButtonActionPerformed + + private void viewMessageComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewMessageComboBoxActionPerformed + boolean accountSelected = (currentSelectionInfo != null && currentSelectionInfo.getAccounts() != null && !currentSelectionInfo.getAccounts().isEmpty()); + switch((VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem()) { + case ALL: + showMessagesButton.setEnabled(accountSelected); + break; + case SELECTED: + Node[] selected = rootTablePane.getExplorerManager().getSelectedNodes(); + showMessagesButton.setEnabled(selected != null && selected.length == 1); + break; + } + }//GEN-LAST:event_viewMessageComboBoxActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton backButton; + private javax.swing.JPanel messagePanel; + private javax.swing.JLabel messagesLabel; + private javax.swing.JPanel rootMessagesPane; + private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel rootTablePane; + private javax.swing.JButton showMessagesButton; + private javax.swing.JLabel showingMessagesLabel; + private org.sleuthkit.autopsy.communications.relationships.MessagesPanel threadMessagesPanel; + private javax.swing.JLabel threadNameLabel; + private javax.swing.JLabel threadsLabel; + private javax.swing.JComboBox viewMessageComboBox; + private javax.swing.JLabel viewMessageLabel; + // End of variables declaration//GEN-END:variables + + class ShowThreadMessagesAction extends AbstractAction { + @Override + public void actionPerformed(ActionEvent e) { + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + showSelectedThread(); + } + }); + } + } + + private enum VIEW_MESSAGE_TYPE{ + ALL(Bundle.MessageViewer_viewMessage_all()), + SELECTED(Bundle.MessageViewer_viewMessage_selected()); + + private final String displayLabel; + VIEW_MESSAGE_TYPE(String label) { + displayLabel = label; + } + + @Override + public String toString() { + return displayLabel; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java index bb986c3920..9dd6fd45e8 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java @@ -27,29 +27,30 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** - * ChildFactory that creates createKeys and nodes from a given selectionInfo for - * only emails, call logs and messages. + * * */ -final class MessagesChildNodeFactory extends ChildFactory { +public class MessagesChildNodeFactory extends ChildFactory{ private static final Logger logger = Logger.getLogger(MessagesChildNodeFactory.class.getName()); private SelectionInfo selectionInfo; - - /** - * Construct a new MessageChildNodeFactory from the currently selectionInfo - * - * @param selectionInfo SelectionInfo object for the currently selected - * accounts - */ - MessagesChildNodeFactory(SelectionInfo selectionInfo) { + + private List threadIDs; + + MessagesChildNodeFactory(SelectionInfo selectionInfo, List threadIDs) { this.selectionInfo = selectionInfo; + this.threadIDs = threadIDs; + } + + MessagesChildNodeFactory() { + this(null, null); } /** @@ -61,18 +62,23 @@ final class MessagesChildNodeFactory extends ChildFactory { this.selectionInfo = selectionInfo; refresh(true); } + + public void refresh(List threadIDs) { + this.threadIDs = threadIDs; + refresh(true); + } + + public void refresh(SelectionInfo selectionInfo, List threadIDs) { + this.threadIDs = threadIDs; + this.selectionInfo = selectionInfo; + refresh(true); - /** - * Creates a list of Keys (BlackboardArtifact) for only messages for the - * currently selected accounts - * - * @param list List of BlackboardArtifact to populate - * - * @return True on success - */ + } + @Override protected boolean createKeys(List list) { CommunicationsManager communicationManager; + try { communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); } catch (NoCurrentCaseException | TskCoreException ex) { @@ -87,19 +93,34 @@ final class MessagesChildNodeFactory extends ChildFactory { final Set relationshipSources; try { + relationshipSources = communicationManager.getRelationshipSources(selectionInfo.getAccountDevicesInstances(), selectionInfo.getCommunicationsFilter()); - - relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { - + for(Content content: relationshipSources) { + if( !(content instanceof BlackboardArtifact)){ + continue; + } + BlackboardArtifact bba = (BlackboardArtifact) content; BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); - if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG - || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG - || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { + if (fromID != BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG + && fromID != BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG + && fromID != BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { + continue; + } + + String artifactThreadID = MessageNode.UNTHREADED_ID; + BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); + + if(attribute != null) { + artifactThreadID = attribute.getValueString(); + } + + if(threadIDs == null || threadIDs.contains(artifactThreadID)) { list.add(bba); } - }); + + } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to get relationship sources.", ex); //NON-NLS @@ -107,9 +128,10 @@ final class MessagesChildNodeFactory extends ChildFactory { return true; } - + @Override protected Node createNodeForKey(BlackboardArtifact key) { - return new MessageNode(key); + return new MessageNode(key, null, null); } + } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form new file mode 100755 index 0000000000..50c8190984 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form @@ -0,0 +1,51 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java similarity index 59% rename from Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java rename to Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java index 8bda078161..20ce895e8e 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java @@ -6,7 +6,7 @@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. - * You may obt ain a copy of the License at + * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -22,56 +22,37 @@ import java.awt.Component; import java.awt.KeyboardFocusManager; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; import static javax.swing.SwingUtilities.isDescendingFrom; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import static org.openide.explorer.ExplorerUtils.createLookup; import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; -import org.openide.nodes.NodeAdapter; -import org.openide.nodes.NodeMemberEvent; import org.openide.util.Lookup; -import org.openide.util.NbBundle.Messages; -import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** - * Visualation for the messages of the currently selected accounts. + * + * */ -@ServiceProvider(service = RelationshipsViewer.class) -public final class MessagesViewer extends JPanel implements RelationshipsViewer { +public class MessagesPanel extends javax.swing.JPanel implements Lookup.Provider { private final Outline outline; private final ModifiableProxyLookup proxyLookup; private final PropertyChangeListener focusPropertyListener; - private final MessagesChildNodeFactory nodeFactory; - - @Messages({ - "MessageViewer_tabTitle=Messages", - "MessageViewer_columnHeader_From=From", - "MessageViewer_columnHeader_To=To", - "MessageViewer_columnHeader_Date=Date", - "MessageViewer_columnHeader_Subject=Subject", - "MessageViewer_columnHeader_Attms=Attachments", - "MessageViewer_no_messages=" - }) - + /** - * Visualation for the messages of the currently selected accounts. + * Creates new form ThreadMessagesPanel */ - public MessagesViewer() { + public MessagesPanel() { initComponents(); - outlineViewPanel.hideOutlineView(Bundle.MessageViewer_no_messages()); - proxyLookup = new ModifiableProxyLookup(createLookup(outlineViewPanel.getExplorerManager(), getActionMap())); - nodeFactory = new MessagesChildNodeFactory(null); // See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed // explaination of focusPropertyListener @@ -82,10 +63,10 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer if (newFocusOwner == null) { return; } - if (isDescendingFrom(newFocusOwner, contentViewer)) { + if (isDescendingFrom(newFocusOwner, messageContentViewer)) { //if the focus owner is within the MessageContentViewer (the attachments table) - proxyLookup.setNewLookups(createLookup(((MessageDataContent) contentViewer).getExplorerManager(), getActionMap())); - } else if (isDescendingFrom(newFocusOwner, MessagesViewer.this)) { + proxyLookup.setNewLookups(createLookup(((MessageDataContent) messageContentViewer).getExplorerManager(), getActionMap())); + } else if (isDescendingFrom(newFocusOwner, MessagesPanel.this)) { //... or if it is within the Results table. proxyLookup.setNewLookups(createLookup(outlineViewPanel.getExplorerManager(), getActionMap())); @@ -109,52 +90,21 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer final Node[] nodes = outlineViewPanel.getExplorerManager().getSelectedNodes(); if (nodes != null && nodes.length == 1) { - contentViewer.setNode(nodes[0]); + messageContentViewer.setNode(nodes[0]); } else { - contentViewer.setNode(null); + messageContentViewer.setNode(null); } } }); - outlineViewPanel.getExplorerManager().setRootContext( - new TableFilterNode( - new DataResultFilterNode( - new AbstractNode( - Children.create(nodeFactory, true)), - outlineViewPanel.getExplorerManager()), - true)); - - // When a new set of nodes are added to the OutlineView the childrenAdded - // seems to be fired before the childrenRemoved. - outlineViewPanel.getExplorerManager().getRootContext().addNodeListener(new NodeAdapter() { - @Override - public void childrenAdded(NodeMemberEvent nme) { - updateOutlineViewPanel(); - } - - @Override - public void childrenRemoved(NodeMemberEvent nme) { - updateOutlineViewPanel(); - } - }); } - - @Override - public String getDisplayName() { - return Bundle.MessageViewer_tabTitle(); + + public MessagesPanel(ChildFactory nodeFactory) { + this(); + setChildFactory(nodeFactory); } - - @Override - public JPanel getPanel() { - return this; - } - - @Override - public void setSelectionInfo(SelectionInfo info) { - nodeFactory.refresh(info); - } - + @Override public Lookup getLookup() { return proxyLookup; @@ -174,16 +124,16 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer KeyboardFocusManager.getCurrentKeyboardFocusManager() .removePropertyChangeListener("focusOwner", focusPropertyListener); } - - private void updateOutlineViewPanel() { - int nodeCount = outlineViewPanel.getExplorerManager().getRootContext().getChildren().getNodesCount(); - if(nodeCount == 0) { - outlineViewPanel.hideOutlineView(Bundle.MessageViewer_no_messages()); - } else { - outlineViewPanel.showOutlineView(); - } - } + final void setChildFactory(ChildFactory nodeFactory) { + outlineViewPanel.getExplorerManager().setRootContext( + new TableFilterNode( + new DataResultFilterNode( + new AbstractNode( + Children.create(nodeFactory, true)), + outlineViewPanel.getExplorerManager()),true)); + } + /** * 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 @@ -193,28 +143,23 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer // //GEN-BEGIN:initComponents private void initComponents() { - contentViewer = new MessageDataContent(); + jSplitPane1 = new javax.swing.JSplitPane(); outlineViewPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); + messageContentViewer = new MessageDataContent(); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(outlineViewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(outlineViewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 390, Short.MAX_VALUE)) - ); + setLayout(new java.awt.BorderLayout()); + + jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); + jSplitPane1.setLeftComponent(outlineViewPanel); + jSplitPane1.setRightComponent(messageContentViewer); + + add(jSplitPane1, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer; + private javax.swing.JSplitPane jSplitPane1; + private org.sleuthkit.autopsy.contentviewers.MessageContentViewer messageContentViewer; private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form deleted file mode 100755 index 34126bbf8c..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form +++ /dev/null @@ -1,42 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form index 5efb16c2b1..58240dec57 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form @@ -20,11 +20,6 @@
- - - - - diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java index 4e04db4f53..81f05b8534 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java @@ -113,8 +113,6 @@ public class OutlineViewPanel extends javax.swing.JPanel implements ExplorerMana messageLabel = new javax.swing.JLabel(); setLayout(new java.awt.CardLayout(5, 5)); - - outlineView.setPreferredSize(new java.awt.Dimension(300, 400)); add(outlineView, "outlineCard"); messagePanel.setLayout(new java.awt.BorderLayout()); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java index 03729540df..b13eef621b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java @@ -31,7 +31,7 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider private SelectionInfo currentSelection; - private final MessagesViewer messagesViewer; + private final MessageViewer messagesViewer; private final ContactsViewer contactsViewer; private final SummaryViewer summaryViewer; private final MediaViewer mediaViewer; @@ -42,21 +42,18 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider * Creates new form RelationshipBrowser */ public RelationshipBrowser() { - messagesViewer = new MessagesViewer(); + initComponents(); + messagesViewer = new MessageViewer(); contactsViewer = new ContactsViewer(); summaryViewer = new SummaryViewer(); mediaViewer = new MediaViewer(); proxyLookup = new ModifiableProxyLookup(messagesViewer.getLookup()); - - initComponents(); - + tabPane.add(summaryViewer.getDisplayName(), summaryViewer); tabPane.add(messagesViewer.getDisplayName(), messagesViewer); tabPane.add(contactsViewer.getDisplayName(), contactsViewer); tabPane.add(mediaViewer.getDisplayName(), mediaViewer); - - } /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java new file mode 100755 index 0000000000..4cbdd87f53 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java @@ -0,0 +1,200 @@ +/* + * 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.communications.relationships; + +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javax.swing.Action; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * ChildFactory that creates createKeys and nodes from a given selectionInfo for + * only emails, call logs and messages. + * + */ +final class ThreadChildNodeFactory extends ChildFactory { + + private static final Logger logger = Logger.getLogger(ThreadChildNodeFactory.class.getName()); + + private SelectionInfo selectionInfo; + + private final Action preferredAction; + + /** + * Construct a new ThreadChildNodeFactory from the currently selectionInfo + * + * @param preferredAction SelectionInfo object for the currently selected + * accounts + */ + + ThreadChildNodeFactory(Action preferredAction) { + this.preferredAction = preferredAction; + } + + /** + * Updates the current instance of selectionInfo and calls the refresh method. + * + * @param selectionInfo New instance of the currently selected accounts + */ + public void refresh(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + refresh(true); + } + + /** + * Creates a list of Keys (BlackboardArtifact) for only messages for the + * currently selected accounts. + * + * @param list List of BlackboardArtifact to populate + * + * @return True on success + */ + @Override + protected boolean createKeys(List list) { + CommunicationsManager communicationManager; + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get communications manager from case.", ex); //NON-NLS + return false; + } + + if(selectionInfo == null) { + return true; + } + + final Set relationshipSources; + + try { + relationshipSources = communicationManager.getRelationshipSources(selectionInfo.getAccountDevicesInstances(), selectionInfo.getCommunicationsFilter()); + + createRootMessageKeys(list, relationshipSources) ; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get relationship sources.", ex); //NON-NLS + } + + return true; + } + + /** + * Adds only BlackboardArtifact objects to the list where are the earliest + * message in a message thread (based on threadID). If there are "unthreaded" + * messages (messages that do not have a threadID) one representitive artifact + * will be added to the list and dealt with a node creation time. + * + * @param list + * @param relationshipSources + * @return + * @throws TskCoreException + */ + private boolean createRootMessageKeys(List list, Set relationshipSources) throws TskCoreException{ + HashMap rootMessageMap = new HashMap<>(); + for(Content content: relationshipSources) { + if(content instanceof BlackboardArtifact) { + BlackboardArtifact bba = (BlackboardArtifact) content; + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); + + if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { + + String threadID = MessageNode.UNTHREADED_ID; + BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); + + if(attribute != null) { + threadID = attribute.getValueString(); + } + + BlackboardArtifact tableArtifact = rootMessageMap.get(threadID); + if(tableArtifact == null) { + rootMessageMap.put(threadID, bba); + } else { + BlackboardAttribute tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + + if(tableAttribute.getValueLong() > attribute.getValueLong()) { + rootMessageMap.put(threadID, bba); + } + } + } + } + } + + for(BlackboardArtifact bba: rootMessageMap.values()) { + list.add(bba); + } + + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact bba) { + BlackboardAttribute attribute = null; + try { + attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); + } catch (TskCoreException tskCoreException) { + } + + if (attribute != null) { + return new MessageNode(bba, attribute.getValueString(), preferredAction); + } else { + // Only one of these should occur. + return new UnthreadedNode(preferredAction); + } + } + + final class UnthreadedNode extends AbstractNode { + private final Action preferredAction; + + UnthreadedNode(Action preferredAction) { + super(Children.LEAF); + setDisplayName("Unthreaded"); + this.preferredAction = preferredAction; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",MessageNode.UNTHREADED_ID)); + + return sheet; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 0b3a6f698f..0961642519 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -489,6 +489,12 @@ public class DataResultFilterNode extends FilterNode { @Override public AbstractAction visit(BlackboardArtifactNode ban) { + + Action preferredAction = ban.getPreferredAction(); + if(preferredAction != null && preferredAction instanceof AbstractAction) { + return (AbstractAction) preferredAction; + } + BlackboardArtifact artifact = ban.getArtifact(); try { if ((artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index c3a2ee4faf..2ba6856d7e 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -36,7 +36,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 17d744eee1..7da2ebca7b 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -64,7 +64,7 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome From de13b8c6e41a10d40b550f6d4cb51da14f4deefe Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 12 Jun 2019 10:11:44 -0400 Subject: [PATCH 02/33] Added comments and cleaned up factory code. --- .../MessagesChildNodeFactory.java | 15 ++++------ .../relationships/ThreadChildNodeFactory.java | 28 ++++++++++++------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java index 9dd6fd45e8..d185b1a374 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java @@ -57,17 +57,9 @@ public class MessagesChildNodeFactory extends ChildFactory{ * Updates the current instance of selectionInfo and calls the refresh method. * * @param selectionInfo New instance of the currently selected accounts + * @param threadIDs A list of threadIDs to filter the keys by, null will + * return all keys for the selected accounts. */ - public void refresh(SelectionInfo selectionInfo) { - this.selectionInfo = selectionInfo; - refresh(true); - } - - public void refresh(List threadIDs) { - this.threadIDs = threadIDs; - refresh(true); - } - public void refresh(SelectionInfo selectionInfo, List threadIDs) { this.threadIDs = threadIDs; this.selectionInfo = selectionInfo; @@ -109,6 +101,9 @@ public class MessagesChildNodeFactory extends ChildFactory{ continue; } + // We want all artifacts that do not have "threadIDs" to appear as one thread in the UI + // To achive this assign any artifact that does not have a threadID + // the "UNTHREADED_ID" String artifactThreadID = MessageNode.UNTHREADED_ID; BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java index 4cbdd87f53..679a4e62dc 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java @@ -113,9 +113,9 @@ final class ThreadChildNodeFactory extends ChildFactory { * messages (messages that do not have a threadID) one representitive artifact * will be added to the list and dealt with a node creation time. * - * @param list - * @param relationshipSources - * @return + * @param list List to populate with BlackboardArtifact keys + * @param relationshipSources Set of Content objects + * @return True on success * @throws TskCoreException */ private boolean createRootMessageKeys(List list, Set relationshipSources) throws TskCoreException{ @@ -129,6 +129,9 @@ final class ThreadChildNodeFactory extends ChildFactory { || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { + // We want all artifacts that do not have "threadIDs" to appear as one thread in the UI + // To achive this assign any artifact that does not have a threadID + // the "UNTHREADED_ID" String threadID = MessageNode.UNTHREADED_ID; BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); @@ -143,7 +146,7 @@ final class ThreadChildNodeFactory extends ChildFactory { BlackboardAttribute tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); - if(tableAttribute.getValueLong() > attribute.getValueLong()) { + if(tableAttribute != null && attribute != null && tableAttribute.getValueLong() > attribute.getValueLong()) { rootMessageMap.put(threadID, bba); } } @@ -163,24 +166,28 @@ final class ThreadChildNodeFactory extends ChildFactory { BlackboardAttribute attribute = null; try { attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); - } catch (TskCoreException tskCoreException) { + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to get threadID for artifact: %s", bba.getName()), ex); } if (attribute != null) { return new MessageNode(bba, attribute.getValueString(), preferredAction); } else { // Only one of these should occur. - return new UnthreadedNode(preferredAction); + return new UnthreadedNode(); } } + /** + * An this node represents the "unthreaded" thread. + */ final class UnthreadedNode extends AbstractNode { - private final Action preferredAction; - - UnthreadedNode(Action preferredAction) { + /** + * Construct an instance of UnthreadNode. + */ + UnthreadedNode() { super(Children.LEAF); setDisplayName("Unthreaded"); - this.preferredAction = preferredAction; } @Override @@ -192,6 +199,7 @@ final class ThreadChildNodeFactory extends ChildFactory { sheet.put(sheetSet); } + // Give this node a threadID of "UNTHEADED_ID" sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",MessageNode.UNTHREADED_ID)); return sheet; From 49772123f8678725778af5b3f528a1fd7341fc0c Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 12 Jun 2019 16:22:41 -0400 Subject: [PATCH 03/33] Fixed the MessagePanel spliter bar --- .../relationships/MessagesPanel.form | 2 +- .../relationships/MessagesPanel.java | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form index 50c8190984..5521c04bda 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.form @@ -16,7 +16,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java index 20ce895e8e..f2a161c148 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java @@ -98,6 +98,9 @@ public class MessagesPanel extends javax.swing.JPanel implements Lookup.Provider } }); + splitPane.setResizeWeight(0.5); + splitPane.setDividerLocation(0.5); + } public MessagesPanel(ChildFactory nodeFactory) { @@ -143,23 +146,23 @@ public class MessagesPanel extends javax.swing.JPanel implements Lookup.Provider // //GEN-BEGIN:initComponents private void initComponents() { - jSplitPane1 = new javax.swing.JSplitPane(); + splitPane = new javax.swing.JSplitPane(); outlineViewPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); messageContentViewer = new MessageDataContent(); setLayout(new java.awt.BorderLayout()); - jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); - jSplitPane1.setLeftComponent(outlineViewPanel); - jSplitPane1.setRightComponent(messageContentViewer); + splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); + splitPane.setLeftComponent(outlineViewPanel); + splitPane.setRightComponent(messageContentViewer); - add(jSplitPane1, java.awt.BorderLayout.CENTER); + add(splitPane, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JSplitPane jSplitPane1; private org.sleuthkit.autopsy.contentviewers.MessageContentViewer messageContentViewer; private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel; + private javax.swing.JSplitPane splitPane; // End of variables declaration//GEN-END:variables } From 4da9bc79d4639202c60fb7c74d3578ad17f4c195 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 12 Jun 2019 16:36:59 -0400 Subject: [PATCH 04/33] Added support for andriod text message --- .../autopsy/communications/relationships/MessageNode.java | 4 ++-- InternalPythonModules/android/textmessage.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index a7743dc3af..8a5af271d1 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -86,6 +86,8 @@ class MessageNode extends BlackboardArtifactNode { } sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS + + sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",threadID == null ? UNTHREADED_ID : threadID)); //NON-NLS final BlackboardArtifact artifact = getArtifact(); @@ -107,8 +109,6 @@ class MessageNode extends BlackboardArtifactNode { } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS } - - sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",threadID == null ? "" : threadID)); //NON-NLS break; case TSK_MESSAGE: diff --git a/InternalPythonModules/android/textmessage.py b/InternalPythonModules/android/textmessage.py index 5d7f9db638..ef8fa498c8 100644 --- a/InternalPythonModules/android/textmessage.py +++ b/InternalPythonModules/android/textmessage.py @@ -99,13 +99,14 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer): resultSet = None try: resultSet = statement.executeQuery( - "SELECT address, date, read, type, subject, body FROM sms;") + "SELECT address, date, read, type, subject, body, thread_id FROM sms;") while resultSet.next(): address = resultSet.getString("address") # may be phone number, or other addresses date = Long.valueOf(resultSet.getString("date")) / 1000 read = resultSet.getInt("read") # may be unread = 0, read = 1 subject = resultSet.getString("subject") # message subject body = resultSet.getString("body") # message body + thread_id = "{0}_{1}".format(abstractFile.getId(), resultSet.getInt("thread_id")) attributes = ArrayList() artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); #create Message artifact and then add attributes from result set. if resultSet.getString("type") == "1": @@ -119,6 +120,7 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer): attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT, general.MODULE_NAME, subject)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, general.MODULE_NAME, body)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, general.MODULE_NAME, "SMS Message")) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID, general.MODULE_NAME, thread_id)) artifact.addAttributes(attributes) From d60052f281cd36ec0ec9d3dfe5a7f10d85a0c2c8 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 13 Jun 2019 14:31:12 -0400 Subject: [PATCH 05/33] modifying message view based on talks with Brian --- .../autopsy/communications/images/nail.png | Bin 0 -> 202 bytes .../autopsy/communications/images/screw.png | Bin 0 -> 217 bytes .../communications/images/threaded.png | Bin 0 -> 263 bytes .../communications/images/unthreaded.png | Bin 0 -> 199 bytes .../relationships/Bundle.properties | 8 +- .../relationships/Bundle.properties-MERGED | 11 +- .../relationships/MessageNode.java | 6 +- .../relationships/MessageViewer.form | 80 ++---- .../relationships/MessageViewer.java | 269 ++++++------------ .../relationships/MessagesPanel.java | 2 +- .../relationships/OutlineViewPanel.java | 24 ++ .../relationships/ThreadChildNodeFactory.java | 2 +- .../relationships/ThreadNode.java | 46 +++ 13 files changed, 194 insertions(+), 254 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/communications/images/nail.png create mode 100755 Core/src/org/sleuthkit/autopsy/communications/images/screw.png create mode 100755 Core/src/org/sleuthkit/autopsy/communications/images/threaded.png create mode 100755 Core/src/org/sleuthkit/autopsy/communications/images/unthreaded.png create mode 100755 Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/nail.png b/Core/src/org/sleuthkit/autopsy/communications/images/nail.png new file mode 100755 index 0000000000000000000000000000000000000000..f10f365a10aabd7eae9c30ced2905b0958c86e82 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|oCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#F&nqAn9x_2jM+dT+02lL66gHf+|;}hAeVu`xhOTUBsE2$JhLQ2!QIn0AVn{g z9VqVL>EaloaX$Ia|Nr*PAmCh8WyPFlQ2fk?qnR_9qd!Z8cgL3noJNP{w(uyNJG^Rs mXQPY3s>Fhf19F#{U#T#x&=hkFowMpS$P!OiKbLh*2~7a=I5)Nc literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/screw.png b/Core/src/org/sleuthkit/autopsy/communications/images/screw.png new file mode 100755 index 0000000000000000000000000000000000000000..ec8d434e258ad02545a0e7eefa3f166975b57cdf GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|oCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#F&nqAsP&xLi%oz+vY8S|xv6<2KrRD=b5UwyNotBhd1gt5g1e`0K#E=} zJ5b!;)5S4F<@Wt~$( F698u;KpOx6 literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/threaded.png b/Core/src/org/sleuthkit/autopsy/communications/images/threaded.png new file mode 100755 index 0000000000000000000000000000000000000000..449f6bad6cb1b286f2916044d10129b06ad9150b GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#F&noqzrI;c?`Ki;O^-gkfN8$ z4ivBTba4!^IK6lBK~4rm9+vPEr;XmYaBmlxKTYA^x41)!PCk5o950j?^UZb;il4^d zpxLUadPJg@fni4RuXqNAz2*1iJ(^UC&c#%FpXAIcRH}ab+veB?#;mo8jZxWVo$3>$ zKdf#p==#LB>(s%x?K=hDGM#PDID2ajOYR&~dH(J*{h6nh$^Z>wVDNPHb6Mw<&;$U^ C)KudD literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/unthreaded.png b/Core/src/org/sleuthkit/autopsy/communications/images/unthreaded.png new file mode 100755 index 0000000000000000000000000000000000000000..e66695ea888f26e1f25c6d04e801d29e5b467bff GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#F&nqAp#CG>O};=O+02lL66gHf+|;}hAeVu`xhOTUBsE2$JhLQ2!QIn0AVn{g z9Vl+=>Eak-ar*5ALtX|$9+rchUqTZkx_&<9Xe;4kuYb+F!RqWPt@>|{X<;rms=qT8 mynk!oQ0%9@;aCFCF$Np+cI~Y MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: -MessageViewer.messagesLabel.text=Messages MessageViewer.backButton.AccessibleContext.accessibleDescription= -MessageViewer.backButton.text= -MessageViewer.showMessagesButton.text= -MessageViewer.viewMessageLabel.text=View Messages: +MessageViewer.backButton.text=Threads +MessageViewer.showAllButton.text=All Messages diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index 1efa1db775..9b01fc653b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -19,7 +19,8 @@ MessageNode_Node_Property_Subject=Subject MessageNode_Node_Property_To=To MessageNode_Node_Property_Type=Type MessageViewer_columnHeader_Attms=Attachments -MessageViewer_columnHeader_Date=Date +MessageViewer_columnHeader_Date=Data +MessageViewer_columnHeader_EarlyDate=Earliest Message MessageViewer_columnHeader_From=From MessageViewer_columnHeader_Subject=Subject MessageViewer_columnHeader_To=To @@ -50,11 +51,9 @@ ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages ThreadPane.backButton.text=<--- SummaryViewer.caseReferencesPanel.border.title=Other Occurrences SummaryViewer.fileReferencesPanel.border.title=File References in Current Case -MessageViewer.threadsLabel.text=Threads +MessageViewer.threadsLabel.text=Select a Thread to View MessageViewer.threadNameLabel.text= MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: -MessageViewer.messagesLabel.text=Messages MessageViewer.backButton.AccessibleContext.accessibleDescription= -MessageViewer.backButton.text= -MessageViewer.showMessagesButton.text= -MessageViewer.viewMessageLabel.text=View Messages: +MessageViewer.backButton.text=Threads +MessageViewer.showAllButton.text=All Messages diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index a7743dc3af..6a39c246d0 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -25,7 +25,6 @@ import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -41,6 +40,7 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBU import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.communications.Utils; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; /** * Wraps a BlackboardArtifact as an AbstractNode for use in an OutlookView @@ -86,8 +86,8 @@ class MessageNode extends BlackboardArtifactNode { } sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS - - final BlackboardArtifact artifact = getArtifact(); + + BlackboardArtifact artifact = this.getArtifact(); BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); if (null != fromID) { diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form index a6d0ddfa45..cbff1d5e95 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form @@ -28,71 +28,44 @@ - - - - - - - - - - - + - - - - - - - - - - - - - - + - + + + + - + - + - - - - - - - + + + - - - - + @@ -113,22 +86,18 @@ - + - + - - - - @@ -140,22 +109,7 @@ - - - - - - - - - - - - - - - - + @@ -167,7 +121,7 @@ - + @@ -179,7 +133,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java index 3da19c0d83..32f0496c81 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java @@ -24,11 +24,13 @@ import java.awt.KeyboardFocusManager; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JPanel; +import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import static javax.swing.SwingUtilities.isDescendingFrom; import org.netbeans.swing.outline.DefaultOutlineModel; @@ -40,6 +42,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Node.Property; import org.openide.nodes.Node.PropertySet; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; @@ -47,12 +50,11 @@ import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; - /** * * */ -public class MessageViewer extends JPanel implements RelationshipsViewer{ +public class MessageViewer extends JPanel implements RelationshipsViewer { private static final Logger logger = Logger.getLogger(MessageViewer.class.getName()); @@ -64,27 +66,25 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ private SelectionInfo currentSelectionInfo = null; OutlineViewPanel currentPanel; - + @Messages({ "MessageViewer_tabTitle=Messages", "MessageViewer_columnHeader_From=From", + "MessageViewer_columnHeader_Date=Data", "MessageViewer_columnHeader_To=To", - "MessageViewer_columnHeader_Date=Date", + "MessageViewer_columnHeader_EarlyDate=Earliest Message", "MessageViewer_columnHeader_Subject=Subject", "MessageViewer_columnHeader_Attms=Attachments", "MessageViewer_no_messages=", "MessageViewer_viewMessage_all=All", "MessageViewer_viewMessage_selected=Selected", - "MessageViewer_viewMessage_unthreaded=Unthreaded", - }) - - + "MessageViewer_viewMessage_unthreaded=Unthreaded",}) /** * Creates new form MessageViewer2 */ public MessageViewer() { - + initComponents(); currentPanel = rootTablePane; proxyLookup = new ModifiableProxyLookup(createLookup(rootTablePane.getExplorerManager(), getActionMap())); @@ -108,7 +108,6 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ } }; - rootTablePane.getExplorerManager().setRootContext( new TableFilterNode( new DataResultFilterNode( @@ -119,40 +118,27 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ Outline outline = rootTablePane.getOutlineView().getOutline(); rootTablePane.getOutlineView().setPropertyColumns( - "From", Bundle.MessageViewer_columnHeader_From(), - "To", Bundle.MessageViewer_columnHeader_To(), - "Date", Bundle.MessageViewer_columnHeader_Date(), - "Subject", Bundle.MessageViewer_columnHeader_Subject(), - "Attms", Bundle.MessageViewer_columnHeader_Attms() + "Date", Bundle.MessageViewer_columnHeader_EarlyDate(), + "Subject", Bundle.MessageViewer_columnHeader_Subject() ); outline.setRootVisible(false); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel("Type"); - + outline.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); rootTablePane.getExplorerManager().addPropertyChangeListener((PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { - final Node[] nodes = rootTablePane.getExplorerManager().getSelectedNodes(); - - switch((VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem()) { - case ALL: - showMessagesButton.setEnabled((nodes != null && nodes.length > 0)); - break; - case SELECTED: - showMessagesButton.setEnabled((nodes != null && nodes.length == 1)); - break; - } + showSelectedThread(); } }); - + threadMessagesPanel.setChildFactory(threadMessageNodeFactory); - - this.viewMessageComboBox.addItem(VIEW_MESSAGE_TYPE.SELECTED); - this.viewMessageComboBox.addItem(VIEW_MESSAGE_TYPE.ALL); + + rootTablePane.setTableColumnsWidth(10, 20, 70); } @Override public String getDisplayName() { - return Bundle.MessageViewer_tabTitle(); + return Bundle.MessageViewer_tabTitle(); } @Override @@ -191,31 +177,16 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ KeyboardFocusManager.getCurrentKeyboardFocusManager() .removePropertyChangeListener("focusOwner", focusPropertyListener); } - - private void showMessages() { - VIEW_MESSAGE_TYPE viewType = (VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem(); - switch((VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem()){ - case ALL: - threadMessageNodeFactory.refresh(currentSelectionInfo, null); - threadNameLabel.setText("All Messages"); - - ((CardLayout)getLayout()).show(this, "messages"); - break; - case SELECTED: - showSelectedThread(); - break; - } - } @SuppressWarnings("rawtypes") private void showSelectedThread() { final Node[] nodes = rootTablePane.getExplorerManager().getSelectedNodes(); - if(nodes == null) { + if (nodes == null) { return; } - if(nodes.length == 0 || nodes.length > 1) { + if (nodes.length == 0 || nodes.length > 1) { return; } @@ -223,19 +194,19 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ String subject = ""; PropertySet[] propertySets = nodes[0].getPropertySets(); - for(PropertySet pset: propertySets) { + for (PropertySet pset : propertySets) { Property[] properties = pset.getProperties(); - for(Property prop: properties) { - if(prop.getName().equalsIgnoreCase("threadid")){ + for (Property prop : properties) { + if (prop.getName().equalsIgnoreCase("threadid")) { try { String threadID = prop.getValue().toString(); - if(!threadIDList.contains(threadID)){ + if (!threadIDList.contains(threadID)) { threadIDList.add(threadID); } } catch (IllegalAccessException | InvocationTargetException ex) { logger.log(Level.WARNING, String.format("Unable to get threadid for node: %s", nodes[0].getDisplayName()), ex); } - } else if(prop.getName().equalsIgnoreCase("subject")) { + } else if (prop.getName().equalsIgnoreCase("subject")) { try { subject = prop.getValue().toString(); } catch (IllegalAccessException | InvocationTargetException ex) { @@ -247,17 +218,34 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ } - if(!threadIDList.isEmpty()) { + if (!threadIDList.isEmpty()) { threadMessageNodeFactory.refresh(currentSelectionInfo, threadIDList); - - if(!subject.isEmpty()){ + + if (!subject.isEmpty()) { threadNameLabel.setText(subject); } - // Come back and put involk later for safety. - CardLayout layout = (CardLayout)getLayout(); - layout.show(this, "messages"); + + showMessagesPane(); } } + + private void showThreadsPane() { + switchCard("threads"); + } + + private void showMessagesPane() { + switchCard("messages"); + } + + private void switchCard(String cardName) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + CardLayout layout = (CardLayout)getLayout(); + layout.show(MessageViewer.this, cardName); + } + }); + } /** * This method is called from within the constructor to initialize the form. @@ -270,15 +258,12 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ java.awt.GridBagConstraints gridBagConstraints; rootMessagesPane = new javax.swing.JPanel(); - rootTablePane = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); threadsLabel = new javax.swing.JLabel(); - viewMessageComboBox = new javax.swing.JComboBox<>(); - viewMessageLabel = new javax.swing.JLabel(); - showMessagesButton = new javax.swing.JButton(); + showAllButton = new javax.swing.JButton(); + rootTablePane = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); messagePanel = new javax.swing.JPanel(); threadMessagesPanel = new MessagesPanel(); backButton = new javax.swing.JButton(); - messagesLabel = new javax.swing.JLabel(); showingMessagesLabel = new javax.swing.JLabel(); threadNameLabel = new javax.swing.JLabel(); @@ -286,64 +271,41 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ rootMessagesPane.setOpaque(false); rootMessagesPane.setLayout(new java.awt.GridBagLayout()); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 4; - gridBagConstraints.gridwidth = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new java.awt.Insets(5, 15, 15, 15); - rootMessagesPane.add(rootTablePane, gridBagConstraints); - threadsLabel.setFont(new java.awt.Font("Tahoma", 0, 24)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(threadsLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.threadsLabel.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; - gridBagConstraints.gridheight = 3; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; - gridBagConstraints.insets = new java.awt.Insets(15, 15, 0, 0); + gridBagConstraints.insets = new java.awt.Insets(15, 15, 9, 0); rootMessagesPane.add(threadsLabel, gridBagConstraints); - viewMessageComboBox.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(showAllButton, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.showAllButton.text")); // NOI18N + showAllButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - viewMessageComboBoxActionPerformed(evt); + showAllButtonActionPerformed(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 2; - gridBagConstraints.gridy = 0; - gridBagConstraints.gridheight = 4; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 0); - rootMessagesPane.add(viewMessageComboBox, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(0, 15, 15, 0); + rootMessagesPane.add(showAllButton, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(viewMessageLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.viewMessageLabel.text")); // NOI18N + rootTablePane.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 0; - gridBagConstraints.gridheight = 2; - gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; - gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 5); - rootMessagesPane.add(viewMessageLabel, gridBagConstraints); - - showMessagesButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/timeline/images/btn_step_forward.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(showMessagesButton, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.showMessagesButton.text")); // NOI18N - showMessagesButton.setBorder(null); - showMessagesButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - showMessagesButtonActionPerformed(evt); - } - }); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 3; - gridBagConstraints.gridy = 0; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(15, 5, 0, 15); - rootMessagesPane.add(showMessagesButton, gridBagConstraints); + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 15, 9, 15); + rootMessagesPane.add(rootTablePane, gridBagConstraints); add(rootMessagesPane, "threads"); @@ -351,7 +313,7 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 3; - gridBagConstraints.gridwidth = 5; + gridBagConstraints.gridwidth = 3; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; @@ -359,102 +321,74 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15); messagePanel.add(threadMessagesPanel, gridBagConstraints); - backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/timeline/images/btn_step_back.png"))); // NOI18N + backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/timeline/images/arrow-180.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.backButton.text")); // NOI18N - backButton.setBorder(null); - backButton.setBorderPainted(false); backButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { backButtonActionPerformed(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; + gridBagConstraints.gridx = 2; gridBagConstraints.gridy = 0; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(16, 15, 0, 0); + gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 9, 15); messagePanel.add(backButton, gridBagConstraints); backButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.backButton.AccessibleContext.accessibleDescription")); // NOI18N - messagesLabel.setFont(new java.awt.Font("Tahoma", 0, 24)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(messagesLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.messagesLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridwidth = 2; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(5, 15, 0, 0); - messagePanel.add(messagesLabel, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(showingMessagesLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.showingMessagesLabel.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 2; - gridBagConstraints.gridwidth = 3; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(5, 15, 5, 0); + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(9, 15, 5, 0); messagePanel.add(showingMessagesLabel, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(threadNameLabel, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.threadNameLabel.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 3; - gridBagConstraints.gridy = 2; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 15); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(9, 5, 5, 15); messagePanel.add(threadNameLabel, gridBagConstraints); add(messagePanel, "messages"); }// //GEN-END:initComponents private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed - CardLayout layout = (CardLayout) this.getLayout(); - layout.show(this, "threads"); + try { + rootTablePane.getExplorerManager().setSelectedNodes(new Node[0]); + } catch (PropertyVetoException ex) { + Exceptions.printStackTrace(ex); + } + showThreadsPane(); }//GEN-LAST:event_backButtonActionPerformed - private void showMessagesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showMessagesButtonActionPerformed - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - showMessages(); - } - }); - }//GEN-LAST:event_showMessagesButtonActionPerformed - - private void viewMessageComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewMessageComboBoxActionPerformed - boolean accountSelected = (currentSelectionInfo != null && currentSelectionInfo.getAccounts() != null && !currentSelectionInfo.getAccounts().isEmpty()); - switch((VIEW_MESSAGE_TYPE)viewMessageComboBox.getSelectedItem()) { - case ALL: - showMessagesButton.setEnabled(accountSelected); - break; - case SELECTED: - Node[] selected = rootTablePane.getExplorerManager().getSelectedNodes(); - showMessagesButton.setEnabled(selected != null && selected.length == 1); - break; - } - }//GEN-LAST:event_viewMessageComboBoxActionPerformed + private void showAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showAllButtonActionPerformed + threadMessageNodeFactory.refresh(currentSelectionInfo, null); + threadNameLabel.setText("All Messages"); + showMessagesPane(); + }//GEN-LAST:event_showAllButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton backButton; private javax.swing.JPanel messagePanel; - private javax.swing.JLabel messagesLabel; private javax.swing.JPanel rootMessagesPane; private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel rootTablePane; - private javax.swing.JButton showMessagesButton; + private javax.swing.JButton showAllButton; private javax.swing.JLabel showingMessagesLabel; private org.sleuthkit.autopsy.communications.relationships.MessagesPanel threadMessagesPanel; private javax.swing.JLabel threadNameLabel; private javax.swing.JLabel threadsLabel; - private javax.swing.JComboBox viewMessageComboBox; - private javax.swing.JLabel viewMessageLabel; // End of variables declaration//GEN-END:variables class ShowThreadMessagesAction extends AbstractAction { + @Override public void actionPerformed(ActionEvent e) { - + SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -463,19 +397,4 @@ public class MessageViewer extends JPanel implements RelationshipsViewer{ }); } } - - private enum VIEW_MESSAGE_TYPE{ - ALL(Bundle.MessageViewer_viewMessage_all()), - SELECTED(Bundle.MessageViewer_viewMessage_selected()); - - private final String displayLabel; - VIEW_MESSAGE_TYPE(String label) { - displayLabel = label; - } - - @Override - public String toString() { - return displayLabel; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java index f2a161c148..366d6ee43b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java @@ -100,7 +100,7 @@ public class MessagesPanel extends javax.swing.JPanel implements Lookup.Provider splitPane.setResizeWeight(0.5); splitPane.setDividerLocation(0.5); - + outlineViewPanel.setTableColumnsWidth(5,10,10,15,50,10); } public MessagesPanel(ChildFactory nodeFactory) { diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java index 81f05b8534..31d9a983f2 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java @@ -19,7 +19,9 @@ package org.sleuthkit.autopsy.communications.relationships; import java.awt.CardLayout; +import javax.swing.JTable; import javax.swing.SwingUtilities; +import javax.swing.table.TableColumn; import org.openide.explorer.ExplorerManager; import static org.openide.explorer.ExplorerUtils.createLookup; import org.openide.explorer.view.OutlineView; @@ -98,6 +100,28 @@ public class OutlineViewPanel extends javax.swing.JPanel implements ExplorerMana super.setEnabled(enabled); outlineView.setEnabled(enabled); } + + /** + * Sets the width of the columns of the OutlineView based on the passed in + * list of percentages. There should be on double value for each column + * in the OutlineView. + * + * @param percentages A series of double percentages values representing + * what percent of the total width of the table each + * column should have. + */ + public void setTableColumnsWidth(double... percentages) { + JTable table = outlineView.getOutline(); + double total = 0; + for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) { + total += percentages[i]; + } + + for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) { + TableColumn column = table.getColumnModel().getColumn(i); + column.setPreferredWidth((int) (table.getPreferredSize().width * (percentages[i] / total))); + } + } /** * This method is called from within the constructor to initialize the form. diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java index 679a4e62dc..eaa975a1d9 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java @@ -171,7 +171,7 @@ final class ThreadChildNodeFactory extends ChildFactory { } if (attribute != null) { - return new MessageNode(bba, attribute.getValueString(), preferredAction); + return new ThreadNode(bba, attribute.getValueString()); } else { // Only one of these should occur. return new UnthreadedNode(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java new file mode 100755 index 0000000000..25cd3acf3b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java @@ -0,0 +1,46 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.communications.relationships; + +import javax.swing.Action; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * + * @author kelly + */ +final class ThreadNode extends AbstractNode{ + + final private MessageNode messageNode; + + ThreadNode(BlackboardArtifact artifact, String threadID) { + super(Children.LEAF); + messageNode = new MessageNode(artifact, threadID, null); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/threaded.png" ); + } + + @Override + protected Sheet createSheet() { + return messageNode.createSheet(); + } + + String getThreadID() { + return messageNode.getThreadID(); + } + + @Override + public Action getPreferredAction() { + return messageNode.getPreferredAction(); + } + + @Override + public String getDisplayName() { + return messageNode.getDisplayName(); + } +} From b80c7d69c7fd6a99376ac6207629dc90bff3a541 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 13 Jun 2019 15:36:05 -0400 Subject: [PATCH 06/33] 5184 display name for FileKnown status not enum --- .../centralrepository/contentviewer/OccurrencePanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java index d8b18d5f82..36877a124d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java @@ -175,7 +175,7 @@ final class OccurrencePanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text()); addItemToBag(gridY, 0, 0, 0, knownStatusLabel); javax.swing.JLabel knownStatusValue = new javax.swing.JLabel(); - knownStatusValue.setText(knownStatus.toString()); + knownStatusValue.setText(knownStatus.getName()); if (knownStatus == TskData.FileKnown.BAD) { knownStatusValue.setForeground(Color.RED); } From 0f21a8225604a60244bc7c602b0a7fc52176ac98 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 13 Jun 2019 15:46:12 -0400 Subject: [PATCH 07/33] Added resetComponent call to setSelectionInfo --- .../autopsy/communications/relationships/MediaViewer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java index cbb71263e2..03412a0263 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java @@ -130,9 +130,7 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM logger.log(Level.WARNING, "Unable to update selection." , ex); } - if(artifactList.size() == 0) { - thumbnailViewer.resetComponent(); - } + thumbnailViewer.resetComponent(); thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode(new AttachmentsChildren(artifactList)), tableEM), true, this.getClass().getName())); } From bf73912a5a7031db6e028ac08d1340ca5993bdc0 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Fri, 14 Jun 2019 10:25:29 -0400 Subject: [PATCH 08/33] Added unthreaded icon --- .../communications/relationships/ThreadChildNodeFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java index eaa975a1d9..1fad288bba 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java @@ -188,6 +188,7 @@ final class ThreadChildNodeFactory extends ChildFactory { UnthreadedNode() { super(Children.LEAF); setDisplayName("Unthreaded"); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/unthreaded.png" ); } @Override From c86f732b6f84108cffb30ec5bda564d4aa97dd6b Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 17 Jun 2019 12:58:08 -0400 Subject: [PATCH 09/33] Added code for getting the list of accounts dynamically --- .../autopsy/communications/FiltersPanel.java | 55 +++++++++++++++---- .../autopsy/datamodel/accounts/Accounts.java | 3 +- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 5ba6b9b89a..993d6db047 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -45,6 +45,7 @@ import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingWorker; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; @@ -63,6 +64,7 @@ import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; import org.sleuthkit.datamodel.CommunicationsFilter.MostRecentFilter; +import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.DataSource; import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG; import static org.sleuthkit.datamodel.Relationship.Type.CONTACT; @@ -248,29 +250,60 @@ final public class FiltersPanel extends JPanel { //TODO: something like this commented code could be used to show only //the account types that are found: - //final CommunicationsManager communicationsManager = Case.getCurrentOpenCase().getSleuthkitCase().getCommunicationsManager(); +// final CommunicationsManager communicationsManager = Case.getCurrentOpenCase().getSleuthkitCase().getCommunicationsManager(); //List accountTypesInUse = communicationsManager.getAccountTypesInUse(); //accountTypesInUSe.forEach(...) - Account.Type.PREDEFINED_ACCOUNT_TYPES.forEach(type -> { - if (type.equals(Account.Type.CREDIT_CARD)) { - //don't show a check box for credit cards - } else { - accountTypeMap.computeIfAbsent(type, t -> { - + + + + + List accountTypesInUse = null; + + try { + final CommunicationsManager communicationsManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager(); + accountTypesInUse = communicationsManager.getAccountTypesInUse(); + + for(Account.Type type: accountTypesInUse) { + if(!accountTypeMap.containsKey(type) && !type.equals(Account.Type.CREDIT_CARD)){ CheckBoxIconPanel panel = new CheckBoxIconPanel( type.getDisplayName(), new ImageIcon(FiltersPanel.class.getResource(Utils.getIconFilePath(type)))); + panel.setSelected(true); panel.addItemListener(validationListener); accountTypeListPane.add(panel); - if (t.equals(Account.Type.DEVICE)) { + if (type.equals(Account.Type.DEVICE)) { //Deveice type filter is enabled based on whether we are in table or graph view. panel.setEnabled(deviceAccountTypeEnabled); } - return panel.getCheckBox(); - }); + + accountTypeMap.put(type, panel.getCheckBox()); + } } - }); + +// Account.Type.PREDEFINED_ACCOUNT_TYPES.forEach(type -> { +// if (type.equals(Account.Type.CREDIT_CARD)) { +// //don't show a check box for credit cards +// } else { +// accountTypeMap.computeIfAbsent(type, t -> { +// +// CheckBoxIconPanel panel = new CheckBoxIconPanel( +// type.getDisplayName(), +// new ImageIcon(FiltersPanel.class.getResource(Utils.getIconFilePath(type)))); +// panel.setSelected(true); +// panel.addItemListener(validationListener); +// accountTypeListPane.add(panel); +// if (t.equals(Account.Type.DEVICE)) { +// //Deveice type filter is enabled based on whether we are in table or graph view. +// panel.setEnabled(deviceAccountTypeEnabled); +// } +// return panel.getCheckBox(); +// }); +// } +// }); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 4cd6e330e0..c3259807c6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -1845,7 +1845,8 @@ final public class Accounts implements AutopsyVisitableItem { return ICON_BASE_PATH + "WhatsApp.png"; } else { //there could be a default icon instead... - throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName()); + return ICON_BASE_PATH + "warning_triangle.png"; +// throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName()); } } } From 4000be94365e917d6186764d7868f56d0327e2d1 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 17 Jun 2019 13:37:46 -0400 Subject: [PATCH 10/33] 2955 don't allow user to index nsrl hash sets --- .../hashdatabase/Bundle.properties-MERGED | 9 ++ .../hashdatabase/HashLookupSettingsPanel.java | 96 +++++++++++++------ 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED index 2d105bc3a9..9a2f654602 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED @@ -26,15 +26,24 @@ HashDbSearchAction.noOpenCase.errMsg=No open case available. HashDbSearchPanel.noOpenCase.errMsg=No open case available. HashLookupSettingsPanel.centralRepo=Central Repository HashLookupSettingsPanel.editable=Editable +# {0} - nsrlUrl +HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, instead of indexing the NSRL please download an already indexed version available here: {0} +HashLookupSettingsPanel.indexNsrl.title=NSRL will not be indexed HashLookupSettingsPanel.notApplicable=N/A HashLookupSettingsPanel.promptMessage.deleteHashDb=This will make the hash database unavailable for lookup. Do you want to proceed?\n\nNote: The hash database can still be re-imported later. HashLookupSettingsPanel.promptTitle.deleteHashDb=Delete Hash Database from Configuration HashLookupSettingsPanel.readOnly=Read only # {0} - hash lookup name HashLookupSettingsPanel.removeDatabaseFailure.message=Failed to remove hash lookup: {0} +# {0} - nsrlUrlAddress +# {1} - nsrlHashSets +HashLookupSettingsPanel.removeUnindexedNsrl.text=instead of indexing the NSRL please download an already indexed version available here: {0}\n\nHash set(s):{1} +HashLookupSettingsPanel.removeUnindexedNsrl.title=Unindexed NSRL(s) will be removed HashLookupSettingsPanel.saveFail.message=Couldn't save hash set settings. HashLookupSettingsPanel.saveFail.title=Save Fail HashLookupSettingsPanel.Title=Global Hash Lookup Settings +HashLookupSettingsPanel.unindexedNsrl.base=The following hash set appears to be an unindexed version of the NSRL it will be removed, +HashLookupSettingsPanel.unindexedNsrls.base=The following hash sets appear to be unindexed versions of the NSRL they will be removed, HashLookupSettingsPanel.updateStatusError=Error reading status ImportCentralRepoDbProgressDialog.errorParsingFile.message=Error parsing hash set file ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index e6f399d38b..984f70c7ce 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -61,6 +61,8 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { + private static final String NSRL_URL = "https://sourceforge.net/projects/autopsy/files/NSRL/"; + private static final String NSRL_NAME_STRING = "nsrl"; private static final String NO_SELECTION_TEXT = NbBundle .getMessage(HashLookupSettingsPanel.class, "HashDbConfigPanel.noSelectionText"); private static final String ERROR_GETTING_PATH_TEXT = NbBundle @@ -320,10 +322,8 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan @Override public void run() { //If unindexed ones are found, show a popup box that will either index them, or remove them. - if (unindexed.size() == 1) { - showInvalidIndex(false, unindexed); - } else if (unindexed.size() > 1) { - showInvalidIndex(true, unindexed); + if (unindexed.size() >= 1) { + showInvalidIndex(unindexed); } } }); @@ -394,16 +394,44 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan * @param plural Whether or not there are multiple unindexed databases * @param unindexed The list of unindexed databases. Can be of size 1. */ - private void showInvalidIndex(boolean plural, List unindexed) { + @NbBundle.Messages({"# {0} - nsrlUrlAddress", + "# {1} - nsrlHashSets", + "HashLookupSettingsPanel.removeUnindexedNsrl.text=instead of indexing the NSRL please download an already indexed version available here: {0}\n\nHash set(s):{1}", + "HashLookupSettingsPanel.unindexedNsrl.base=The following hash set appears to be an unindexed version of the NSRL it will be removed, ", + "HashLookupSettingsPanel.unindexedNsrls.base=The following hash sets appear to be unindexed versions of the NSRL they will be removed, ", + "HashLookupSettingsPanel.removeUnindexedNsrl.title=Unindexed NSRL(s) will be removed"}) + private void showInvalidIndex(List unindexed) { String total = ""; - String message; - for (HashDb hdb : unindexed) { - total += "\n" + hdb.getHashSetName(); + String nsrlTotal = ""; + + List nsrlHashsets = new ArrayList<>(); + for (SleuthkitHashSet hdb : unindexed) { + //check if this is the NSRL if so point users toward already indexed versions + if (hdb.getHashSetName().toLowerCase().contains(NSRL_NAME_STRING)) { + nsrlHashsets.add(hdb); + nsrlTotal += "\n" + hdb.getHashSetName(); + } else { + total += "\n" + hdb.getHashSetName(); + } } - if (plural) { + if (!nsrlHashsets.isEmpty()) { + String message; + if (nsrlHashsets.size() > 1) { + message = Bundle.HashLookupSettingsPanel_unindexedNsrls_base() + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL, nsrlTotal); + } else { + message = Bundle.HashLookupSettingsPanel_unindexedNsrl_base() + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL, nsrlTotal); + } + JOptionPane.showMessageDialog(this, message, Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_title(), JOptionPane.INFORMATION_MESSAGE); + for (SleuthkitHashSet hdb : nsrlHashsets) { + unindexed.remove(hdb); + } + removeThese(nsrlHashsets); + } + String message = NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.dbNotIndexedMsg", total); + if (unindexed.isEmpty()) { + return; + } else if (unindexed.size() > 1) { message = NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.dbsNotIndexedMsg", total); - } else { - message = NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.dbNotIndexedMsg", total); } int res = JOptionPane.showConfirmDialog(this, message, NbBundle.getMessage(this.getClass(), @@ -947,7 +975,9 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } }//GEN-LAST:event_sendIngestMessagesCheckBoxActionPerformed - + @NbBundle.Messages({"# {0} - nsrlUrl", + "HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, instead of indexing the NSRL please download an already indexed version available here: {0}", + "HashLookupSettingsPanel.indexNsrl.title=NSRL will not be indexed"}) private void indexButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_indexButtonActionPerformed final HashDb hashDatabase = ((HashSetTable) hashSetTable).getSelection(); assert hashDatabase != null; @@ -956,28 +986,32 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan // Add a listener for the INDEXING_DONE event. This listener will update // the UI. SleuthkitHashSet hashDb = (SleuthkitHashSet) hashDatabase; - hashDb.addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(SleuthkitHashSet.Event.INDEXING_DONE.toString())) { - HashDb selectedHashDb = ((HashSetTable) hashSetTable).getSelection(); - if (selectedHashDb != null && hashDb != null && hashDb.equals(selectedHashDb)) { - updateComponents(); + if (hashDb.getHashSetName().toLowerCase().contains(NSRL_NAME_STRING)) { + JOptionPane.showMessageDialog(this, Bundle.HashLookupSettingsPanel_indexNsrl_text(NSRL_URL), Bundle.HashLookupSettingsPanel_indexNsrl_title(), JOptionPane.INFORMATION_MESSAGE); + } else { + hashDb.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(SleuthkitHashSet.Event.INDEXING_DONE.toString())) { + HashDb selectedHashDb = ((HashSetTable) hashSetTable).getSelection(); + if (selectedHashDb != null && hashDb != null && hashDb.equals(selectedHashDb)) { + updateComponents(); + } + hashSetTableModel.refreshDisplay(); } - hashSetTableModel.refreshDisplay(); } - } - }); + }); - // Display a modal dialog box to kick off the indexing on a worker thread - // and try to persuade the user to wait for the indexing task to finish. - // TODO: If the user waits, this defeats the purpose of doing the indexing on a worker thread. - // But if the user cancels the dialog, other operations on the database - // may be attempted when it is not in a suitable state. - ModalNoButtons indexDialog = new ModalNoButtons(this, new Frame(), hashDb); - indexDialog.setLocationRelativeTo(null); - indexDialog.setVisible(true); - indexDialog.setModal(true); + // Display a modal dialog box to kick off the indexing on a worker thread + // and try to persuade the user to wait for the indexing task to finish. + // TODO: If the user waits, this defeats the purpose of doing the indexing on a worker thread. + // But if the user cancels the dialog, other operations on the database + // may be attempted when it is not in a suitable state. + ModalNoButtons indexDialog = new ModalNoButtons(this, new Frame(), hashDb); + indexDialog.setLocationRelativeTo(null); + indexDialog.setVisible(true); + indexDialog.setModal(true); + } }//GEN-LAST:event_indexButtonActionPerformed private void importDatabaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importDatabaseButtonActionPerformed From 98feb3137a575207f61d5f4a682d15526f3a892c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 17 Jun 2019 14:36:45 -0400 Subject: [PATCH 11/33] 2955 remove nsrl from list if they attempt to index, adjust messages --- .../hashdatabase/Bundle.properties-MERGED | 12 ++++---- .../hashdatabase/HashLookupSettingsPanel.java | 30 ++++++++++++------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED index 9a2f654602..0b470ce6b1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED @@ -26,8 +26,7 @@ HashDbSearchAction.noOpenCase.errMsg=No open case available. HashDbSearchPanel.noOpenCase.errMsg=No open case available. HashLookupSettingsPanel.centralRepo=Central Repository HashLookupSettingsPanel.editable=Editable -# {0} - nsrlUrl -HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, instead of indexing the NSRL please download an already indexed version available here: {0} +HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, it will be removed from the list.\n HashLookupSettingsPanel.indexNsrl.title=NSRL will not be indexed HashLookupSettingsPanel.notApplicable=N/A HashLookupSettingsPanel.promptMessage.deleteHashDb=This will make the hash database unavailable for lookup. Do you want to proceed?\n\nNote: The hash database can still be re-imported later. @@ -36,14 +35,15 @@ HashLookupSettingsPanel.readOnly=Read only # {0} - hash lookup name HashLookupSettingsPanel.removeDatabaseFailure.message=Failed to remove hash lookup: {0} # {0} - nsrlUrlAddress -# {1} - nsrlHashSets -HashLookupSettingsPanel.removeUnindexedNsrl.text=instead of indexing the NSRL please download an already indexed version available here: {0}\n\nHash set(s):{1} +HashLookupSettingsPanel.removeUnindexedNsrl.text=Instead of indexing the NSRL, please download an already indexed version available here:\n{0} HashLookupSettingsPanel.removeUnindexedNsrl.title=Unindexed NSRL(s) will be removed HashLookupSettingsPanel.saveFail.message=Couldn't save hash set settings. HashLookupSettingsPanel.saveFail.title=Save Fail HashLookupSettingsPanel.Title=Global Hash Lookup Settings -HashLookupSettingsPanel.unindexedNsrl.base=The following hash set appears to be an unindexed version of the NSRL it will be removed, -HashLookupSettingsPanel.unindexedNsrls.base=The following hash sets appear to be unindexed versions of the NSRL they will be removed, +# {0} - nsrlHashSet +HashLookupSettingsPanel.unindexedNsrl.base=The following hash set appears to be an unindexed version of the NSRL, it will be removed from the list.\nHash set:{0}\n +# {0} - nsrlHashSets +HashLookupSettingsPanel.unindexedNsrls.base=The following hash sets appear to be unindexed versions of the NSRL, they will be removed from the list.\nHash sets:{0}\n HashLookupSettingsPanel.updateStatusError=Error reading status ImportCentralRepoDbProgressDialog.errorParsingFile.message=Error parsing hash set file ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index 984f70c7ce..f259456cae 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,6 @@ import java.awt.Frame; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -69,6 +68,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan .getMessage(HashLookupSettingsPanel.class, "HashDbConfigPanel.errorGettingPathText"); private static final String ERROR_GETTING_INDEX_STATUS_TEXT = NbBundle .getMessage(HashLookupSettingsPanel.class, "HashDbConfigPanel.errorGettingIndexStatusText"); + private static final Logger logger = Logger.getLogger(HashLookupSettingsPanel.class.getName()); private final HashDbManager hashSetManager = HashDbManager.getInstance(); private final HashSetTableModel hashSetTableModel = new HashSetTableModel(); private final List newReferenceSetIDs = new ArrayList<>(); @@ -395,10 +395,11 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan * @param unindexed The list of unindexed databases. Can be of size 1. */ @NbBundle.Messages({"# {0} - nsrlUrlAddress", - "# {1} - nsrlHashSets", - "HashLookupSettingsPanel.removeUnindexedNsrl.text=instead of indexing the NSRL please download an already indexed version available here: {0}\n\nHash set(s):{1}", - "HashLookupSettingsPanel.unindexedNsrl.base=The following hash set appears to be an unindexed version of the NSRL it will be removed, ", - "HashLookupSettingsPanel.unindexedNsrls.base=The following hash sets appear to be unindexed versions of the NSRL they will be removed, ", + "HashLookupSettingsPanel.removeUnindexedNsrl.text=Instead of indexing the NSRL, please download an already indexed version available here:\n{0}", + "# {0} - nsrlHashSet", + "HashLookupSettingsPanel.unindexedNsrl.base=The following hash set appears to be an unindexed version of the NSRL, it will be removed from the list.\nHash set:{0}\n", + "# {0} - nsrlHashSets", + "HashLookupSettingsPanel.unindexedNsrls.base=The following hash sets appear to be unindexed versions of the NSRL, they will be removed from the list.\nHash sets:{0}\n", "HashLookupSettingsPanel.removeUnindexedNsrl.title=Unindexed NSRL(s) will be removed"}) private void showInvalidIndex(List unindexed) { String total = ""; @@ -417,9 +418,9 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan if (!nsrlHashsets.isEmpty()) { String message; if (nsrlHashsets.size() > 1) { - message = Bundle.HashLookupSettingsPanel_unindexedNsrls_base() + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL, nsrlTotal); + message = Bundle.HashLookupSettingsPanel_unindexedNsrls_base(nsrlTotal) + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL); } else { - message = Bundle.HashLookupSettingsPanel_unindexedNsrl_base() + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL, nsrlTotal); + message = Bundle.HashLookupSettingsPanel_unindexedNsrl_base(nsrlTotal) + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL); } JOptionPane.showMessageDialog(this, message, Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_title(), JOptionPane.INFORMATION_MESSAGE); for (SleuthkitHashSet hdb : nsrlHashsets) { @@ -975,8 +976,8 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } }//GEN-LAST:event_sendIngestMessagesCheckBoxActionPerformed - @NbBundle.Messages({"# {0} - nsrlUrl", - "HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, instead of indexing the NSRL please download an already indexed version available here: {0}", + + @NbBundle.Messages({"HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, it will be removed from the list.\n", "HashLookupSettingsPanel.indexNsrl.title=NSRL will not be indexed"}) private void indexButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_indexButtonActionPerformed final HashDb hashDatabase = ((HashSetTable) hashSetTable).getSelection(); @@ -987,7 +988,14 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan // the UI. SleuthkitHashSet hashDb = (SleuthkitHashSet) hashDatabase; if (hashDb.getHashSetName().toLowerCase().contains(NSRL_NAME_STRING)) { - JOptionPane.showMessageDialog(this, Bundle.HashLookupSettingsPanel_indexNsrl_text(NSRL_URL), Bundle.HashLookupSettingsPanel_indexNsrl_title(), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(this, Bundle.HashLookupSettingsPanel_indexNsrl_text() + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL), Bundle.HashLookupSettingsPanel_indexNsrl_title(), JOptionPane.INFORMATION_MESSAGE); + try { + hashSetManager.removeHashDatabaseNoSave(hashDatabase); + hashSetTableModel.refreshModel(); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } catch (HashDbManager.HashDbManagerException ex) { + logger.log(Level.WARNING, "Unable to remove unindexed NSRL from hash set list", ex); + } } else { hashDb.addPropertyChangeListener(new PropertyChangeListener() { @Override From 42291954b4dcbe0cfeb782a3f333f5d2dc49efb4 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 17 Jun 2019 15:28:23 -0400 Subject: [PATCH 12/33] Handled some of the codecy issues and fixed a bug --- .../autopsy/communications/relationships/MessageViewer.java | 2 ++ .../communications/relationships/ThreadChildNodeFactory.java | 4 +++- .../autopsy/communications/relationships/ThreadNode.java | 4 ++-- .../sleuthkit/autopsy/directorytree/DataResultFilterNode.java | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java index 32f0496c81..6e69841350 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java @@ -223,6 +223,8 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { if (!subject.isEmpty()) { threadNameLabel.setText(subject); + } else { + threadNameLabel.setText(Bundle.MessageViewer_viewMessage_unthreaded()); } showMessagesPane(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java index 1fad288bba..3293ea59fd 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java @@ -143,9 +143,11 @@ final class ThreadChildNodeFactory extends ChildFactory { if(tableArtifact == null) { rootMessageMap.put(threadID, bba); } else { + // Get the date of the message BlackboardAttribute tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + // put the earliest message into the table if(tableAttribute != null && attribute != null && tableAttribute.getValueLong() > attribute.getValueLong()) { rootMessageMap.put(threadID, bba); } @@ -171,7 +173,7 @@ final class ThreadChildNodeFactory extends ChildFactory { } if (attribute != null) { - return new ThreadNode(bba, attribute.getValueString()); + return new ThreadNode(bba, attribute.getValueString(), preferredAction); } else { // Only one of these should occur. return new UnthreadedNode(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java index 25cd3acf3b..a3730df69c 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java @@ -19,9 +19,9 @@ final class ThreadNode extends AbstractNode{ final private MessageNode messageNode; - ThreadNode(BlackboardArtifact artifact, String threadID) { + ThreadNode(BlackboardArtifact artifact, String threadID, Action preferredAction) { super(Children.LEAF); - messageNode = new MessageNode(artifact, threadID, null); + messageNode = new MessageNode(artifact, threadID, preferredAction); this.setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/threaded.png" ); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index e6f8fd7cf0..3c206049d6 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -492,7 +492,7 @@ public class DataResultFilterNode extends FilterNode { public AbstractAction visit(BlackboardArtifactNode ban) { Action preferredAction = ban.getPreferredAction(); - if(preferredAction != null && preferredAction instanceof AbstractAction) { + if(preferredAction instanceof AbstractAction) { return (AbstractAction) preferredAction; } From 91aef7be0da6a13a730610ad114f8227ceb0f0d7 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 17 Jun 2019 16:10:52 -0400 Subject: [PATCH 13/33] 2955 address codacy complaint about not using isEmpty --- .../autopsy/modules/hashdatabase/HashLookupSettingsPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index f259456cae..67a020488d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -322,7 +322,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan @Override public void run() { //If unindexed ones are found, show a popup box that will either index them, or remove them. - if (unindexed.size() >= 1) { + if (!unindexed.isEmpty()) { showInvalidIndex(unindexed); } } From cdd5c21787db057759fe4c29b131827b92763a80 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 18 Jun 2019 09:02:19 -0400 Subject: [PATCH 14/33] Fixed more codacy issues and scaled threads button image. --- .../relationships/MessageViewer.form | 3 - .../relationships/MessageViewer.java | 43 +++++++++++++- .../relationships/ThreadChildNodeFactory.java | 57 ++++++++++--------- 3 files changed, 73 insertions(+), 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form index cbff1d5e95..37aa8bdfd5 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.form @@ -92,9 +92,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java index 6e69841350..b7a42088d0 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java @@ -20,8 +20,12 @@ package org.sleuthkit.autopsy.communications.relationships; import java.awt.CardLayout; import java.awt.Component; +import java.awt.Graphics2D; +import java.awt.Image; import java.awt.KeyboardFocusManager; +import java.awt.RenderingHints; import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; @@ -29,6 +33,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.logging.Level; import javax.swing.AbstractAction; +import javax.swing.ImageIcon; import javax.swing.JPanel; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; @@ -134,6 +139,9 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { threadMessagesPanel.setChildFactory(threadMessageNodeFactory); rootTablePane.setTableColumnsWidth(10, 20, 70); + + Image image = getScaledImage((new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/timeline/images/arrow-180.png"))).getImage(), 16, 16); + backButton.setIcon(new ImageIcon(image) ); } @Override @@ -231,14 +239,25 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { } } + /** + * Make the threads pane visible. + */ private void showThreadsPane() { switchCard("threads"); } + /** + * Make the message pane visible. + */ private void showMessagesPane() { switchCard("messages"); } + /** + * Changes the visible panel (card). + * + * @param cardName Name of card to show + */ private void switchCard(String cardName) { SwingUtilities.invokeLater(new Runnable() { @Override @@ -248,6 +267,26 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { } }); } + + /** + * Scales the given image to the given width and height. + * + * @param srcImg Image to scale + * @param w Image width + * @param h Image height + * + * @return Scaled version of srcImg + */ + private Image getScaledImage(Image srcImg, int w, int h){ + BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = resizedImg.createGraphics(); + + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.drawImage(srcImg, 0, 0, w, h, null); + g2.dispose(); + + return resizedImg; + } /** * This method is called from within the constructor to initialize the form. @@ -323,7 +362,6 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15); messagePanel.add(threadMessagesPanel, gridBagConstraints); - backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/timeline/images/arrow-180.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(MessageViewer.class, "MessageViewer.backButton.text")); // NOI18N backButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -386,6 +424,9 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { private javax.swing.JLabel threadsLabel; // End of variables declaration//GEN-END:variables + /** + * The preferred action of the table nodes. + */ class ShowThreadMessagesAction extends AbstractAction { @Override diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java index 3293ea59fd..8c4f8b40de 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.communications.relationships; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.logging.Level; import javax.swing.Action; @@ -119,38 +120,42 @@ final class ThreadChildNodeFactory extends ChildFactory { * @throws TskCoreException */ private boolean createRootMessageKeys(List list, Set relationshipSources) throws TskCoreException{ - HashMap rootMessageMap = new HashMap<>(); + Map rootMessageMap = new HashMap<>(); for(Content content: relationshipSources) { - if(content instanceof BlackboardArtifact) { - BlackboardArtifact bba = (BlackboardArtifact) content; - BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); + if(!(content instanceof BlackboardArtifact)) { + continue; + } + + BlackboardArtifact bba = (BlackboardArtifact) content; + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); - if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG - || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG - || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { + if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { - // We want all artifacts that do not have "threadIDs" to appear as one thread in the UI - // To achive this assign any artifact that does not have a threadID - // the "UNTHREADED_ID" - String threadID = MessageNode.UNTHREADED_ID; - BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); + // We want all artifacts that do not have "threadIDs" to appear as one thread in the UI + // To achive this assign any artifact that does not have a threadID + // the "UNTHREADED_ID" + String threadID = MessageNode.UNTHREADED_ID; + BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); - if(attribute != null) { - threadID = attribute.getValueString(); - } + if(attribute != null) { + threadID = attribute.getValueString(); + } - BlackboardArtifact tableArtifact = rootMessageMap.get(threadID); - if(tableArtifact == null) { - rootMessageMap.put(threadID, bba); - } else { - // Get the date of the message - BlackboardAttribute tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); - attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + BlackboardArtifact tableArtifact = rootMessageMap.get(threadID); + if(tableArtifact == null) { + rootMessageMap.put(threadID, bba); + } else { + // Get the date of the message + BlackboardAttribute tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); - // put the earliest message into the table - if(tableAttribute != null && attribute != null && tableAttribute.getValueLong() > attribute.getValueLong()) { - rootMessageMap.put(threadID, bba); - } + // put the earliest message into the table + if(tableAttribute != null + && attribute != null + && tableAttribute.getValueLong() > attribute.getValueLong()) { + rootMessageMap.put(threadID, bba); } } } From 7699c53b318ecad1b422d118527e332d47d2ebce Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 18 Jun 2019 11:26:14 -0400 Subject: [PATCH 15/33] Added annotation to suppress codacy complaints --- .../autopsy/communications/relationships/MessageViewer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java index b7a42088d0..86b343cfac 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java @@ -56,9 +56,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** - * + * The main panel for the messages tab of the RelationshipViewer * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class MessageViewer extends JPanel implements RelationshipsViewer { private static final Logger logger = Logger.getLogger(MessageViewer.class.getName()); @@ -86,7 +87,7 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { "MessageViewer_viewMessage_unthreaded=Unthreaded",}) /** - * Creates new form MessageViewer2 + * Creates new form MessageViewer */ public MessageViewer() { From 6cd7263256fe2fde94b3879e7f59ad9c7450dbe8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 18 Jun 2019 15:41:01 -0400 Subject: [PATCH 16/33] Fixed export image tags bug, now works for all images that can be manipulated in Autopsy --- .../contentviewers/MediaViewImagePanel.java | 10 +- .../imagetagging/ImageTagsUtil.java | 235 ++++++++++++++++++ .../imagetagging/ImageTagsUtility.java | 141 ----------- .../sleuthkit/autopsy/report/ReportHTML.java | 13 +- 4 files changed, 247 insertions(+), 152 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java delete mode 100755 Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtility.java diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index fbca1e3ab8..746c16c4c0 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -25,7 +25,6 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -78,7 +77,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.SerializationException; -import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsUtility; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsUtil; import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagControls; import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion; import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagCreator; @@ -876,13 +875,14 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan .map(cvTag -> cvTag.getDetails()).collect(Collectors.toList()); //Apply tags to image and write to file - BufferedImage pngImage = ImageTagsUtility.writeTags(file, regions, "png"); + BufferedImage taggedImage = ImageTagsUtil.getImageWithTags(file, regions); Path output = Paths.get(exportChooser.getSelectedFile().getPath(), FilenameUtils.getBaseName(file.getName()) + "-with_tags.png"); //NON-NLS - ImageIO.write(pngImage, "png", output.toFile()); + ImageIO.write(taggedImage, "png", output.toFile()); JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport()); - } catch (TskCoreException | NoCurrentCaseException | IOException ex) { + } catch (Exception ex) { //Runtime exceptions may spill out of ImageTagsUtil from JavaFX. + //This ensures we (devs and users) have something when it doesn't work. LOGGER.log(Level.WARNING, "Unable to export tagged image to disk", ex); //NON-NLS JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_unsuccessfulExport()); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java new file mode 100755 index 0000000000..1faf44adf0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java @@ -0,0 +1,235 @@ +/* + * 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.imagetagging; + +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import javafx.concurrent.Task; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; +import javax.imageio.ImageIO; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.MatOfByte; +import org.opencv.core.Point; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.highgui.Highgui; +import org.opencv.imgproc.Imgproc; +import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ReadContentInputStream; + +/** + * Utility for drawing rectangles on image files. + */ +public final class ImageTagsUtil { + + /** + * Creates an image with tags applied. + * + * @param file Source image. + * @param tagRegions Tags to apply. + * @return Tagged image. + * + * @throws IOException + * @throws InterruptedException Calling thread was interrupted + * @throws ExecutionException Error while reading image from AbstractFile + */ + public static BufferedImage getImageWithTags(AbstractFile file, + Collection tagRegions) throws IOException, InterruptedException, ExecutionException { + + //The raw image in OpenCV terms + Mat sourceImage = getImageMatFromFile(file); + //Image with tags in OpenCV terms + MatOfByte taggedMatrix = getTaggedImageMatrix(sourceImage, tagRegions); + + try (ByteArrayInputStream taggedStream = new ByteArrayInputStream(taggedMatrix.toArray())) { + BufferedImage taggedImage = ImageIO.read(taggedStream); + return taggedImage; + } finally { + sourceImage.release(); + taggedMatrix.release(); + } + } + + /** + * Get the image from file. + * + * @param file + * @return + * @throws IOException + * @throws InterruptedException + * @throws ExecutionException + */ + private static BufferedImage getImageFromFile(AbstractFile file) throws IOException, InterruptedException, ExecutionException { + if (ImageUtils.isGIF(file)) { + //Grab the first frame. + try (BufferedInputStream bufferedReadContentStream = + new BufferedInputStream(new ReadContentInputStream(file))) { + return ImageIO.read(bufferedReadContentStream); + } + } else { + //Otherwise, read the full image. + Task readImageTask = ImageUtils.newReadImageTask(file); + readImageTask.run(); + Image fxResult = readImageTask.get(); + return SwingFXUtils.fromFXImage(fxResult, null); + } + } + + /** + * Reads the image and converts it into an OpenCV equivalent. + * + * @param file Image to read + * @return raw image bytes + * + * @throws IOException + * @throws InterruptedException Calling thread was interrupted. + * @throws ExecutionException Error while reading image from AbstractFile + */ + private static Mat getImageMatFromFile(AbstractFile file) throws InterruptedException, ExecutionException, IOException { + //Get image from file + BufferedImage buffImage = getImageFromFile(file); + + //Convert it to OpenCV Mat. + try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) { + ImageIO.write(buffImage, "png", outStream); + + byte[] imageBytes = outStream.toByteArray(); + MatOfByte rawSourceBytes = new MatOfByte(imageBytes); + Mat sourceImage = Highgui.imdecode(rawSourceBytes, Highgui.IMREAD_COLOR); + rawSourceBytes.release(); + + return sourceImage; + } + } + + /** + * Adds tags to an image matrix. + * + * @param sourceImage + * @param tagRegions + * @param outputEncoding + * @return + */ + private static MatOfByte getTaggedImageMatrix(Mat sourceImage, Collection tagRegions) { + + //Apply all tags to source image + for (ImageTagRegion region : tagRegions) { + Point topLeft = new Point(region.getX(), region.getY()); + Point bottomRight = new Point(topLeft.x + region.getWidth(), + topLeft.y + region.getHeight()); + //Red + Scalar rectangleBorderColor = new Scalar(0, 0, 255); + + int rectangleBorderWidth = (int) Math.rint(region.getStrokeThickness()); + + Core.rectangle(sourceImage, topLeft, bottomRight, + rectangleBorderColor, rectangleBorderWidth); + } + + MatOfByte taggedMatrix = new MatOfByte(); + Highgui.imencode(".png", sourceImage, taggedMatrix); + + return taggedMatrix; + } + + /** + * Creates a thumbnail with tags applied. + * + * @param file Input file to apply tags & produce thumbnail from + * @param tagRegions Tags to apply + * @param iconSize Size of the output thumbnail + * @return BufferedImage Thumbnail image + * + * @throws InterruptedException Calling thread was interrupted. + * @throws ExecutionException Error while reading image from file. + */ + public static BufferedImage getThumbnailWithTags(AbstractFile file, Collection tagRegions, + IconSize iconSize) throws IOException, InterruptedException, ExecutionException { + + //Raw image + Mat sourceImage = getImageMatFromFile(file); + //Full size image with tags + MatOfByte taggedMatrix = getTaggedImageMatrix(sourceImage, tagRegions); + //Resized to produce thumbnail + MatOfByte thumbnailMatrix = getResizedMatrix(taggedMatrix, iconSize); + + try (ByteArrayInputStream thumbnailStream = new ByteArrayInputStream(thumbnailMatrix.toArray())) { + BufferedImage thumbnailImage = ImageIO.read(thumbnailStream); + return thumbnailImage; + } finally { + sourceImage.release(); + taggedMatrix.release(); + thumbnailMatrix.release(); + } + } + + /** + * Resizes the image matrix. + * + * @param taggedMatrix Image to resize. + * @param size Size of thumbnail. + * + * @return A new resized image matrix. + */ + private static MatOfByte getResizedMatrix(MatOfByte taggedMatrix, IconSize size) { + Size resizeDimensions = new Size(size.getSize(), size.getSize()); + Mat taggedImage = Highgui.imdecode(taggedMatrix, Highgui.IMREAD_COLOR); + + Mat thumbnailImage = new Mat(); + Imgproc.resize(taggedImage, thumbnailImage, resizeDimensions); + + MatOfByte thumbnailMatrix = new MatOfByte(); + Highgui.imencode(".png", thumbnailImage, thumbnailMatrix); + + thumbnailImage.release(); + taggedImage.release(); + + return thumbnailMatrix; + } + + private ImageTagsUtil() { + } + + /** + * Sizes for thumbnails + */ + public enum IconSize { + SMALL(50), + MEDIUM(100), + LARGE(200); + + private final int SIZE; + + IconSize(int size) { + this.SIZE = size; + } + + public int getSize() { + return SIZE; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtility.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtility.java deleted file mode 100755 index 3c5fccc5e6..0000000000 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtility.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.imagetagging; - -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Collection; -import javax.imageio.ImageIO; -import org.opencv.core.Core; -import org.opencv.core.Mat; -import org.opencv.core.MatOfByte; -import org.opencv.core.MatOfInt; -import org.opencv.core.Point; -import org.opencv.core.Scalar; -import org.opencv.core.Size; -import org.opencv.highgui.Highgui; -import org.opencv.imgproc.Imgproc; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Utility class for handling content viewer tags on images. - */ -public final class ImageTagsUtility { - - /** - * Sizes for thumbnails - */ - public enum IconSize { - SMALL(50), - MEDIUM(100), - LARGE(200); - - private final int SIZE; - - IconSize(int size) { - this.SIZE = size; - } - - public int getSize() { - return SIZE; - } - } - - /** - * Embeds the tag regions into an image. - * - * @param file Base Image - * @param tagRegions Tag regions to be saved into the image - * @param outputEncoding Format of image (jpg, png, etc). See OpenCV for - * supported formats. Do not include a "." - * @return Output image as a BufferedImage - * - * @throws TskCoreException Cannot read from abstract file - * @throws IOException Could not create buffered image from OpenCV result - */ - public static BufferedImage writeTags(AbstractFile file, Collection tagRegions, - String outputEncoding) throws TskCoreException, IOException { - byte[] imageInMemory = new byte[(int) file.getSize()]; - file.read(imageInMemory, 0, file.getSize()); - Mat originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_UNCHANGED); - - tagRegions.forEach((region) -> { - Core.rectangle( - originalImage, //Matrix obj of the image - new Point(region.getX(), region.getY()), //p1 - new Point(region.getX() + region.getWidth(), region.getY() + region.getHeight()), //p2 - new Scalar(0, 0, 255), //Scalar object for color - (int) Math.rint(region.getStrokeThickness()) - ); - }); - - MatOfByte matOfByte = new MatOfByte(); - MatOfInt params = new MatOfInt(Highgui.IMWRITE_JPEG_QUALITY, 100); - Highgui.imencode("." + outputEncoding, originalImage, matOfByte, params); - - try (ByteArrayInputStream imageStream = new ByteArrayInputStream(matOfByte.toArray())) { - BufferedImage result = ImageIO.read(imageStream); - originalImage.release(); - matOfByte.release(); - return result; - } - } - - /** - * Creates a thumbnail version of the image with tags applied. - * - * @param file Input file to apply tags & produce thumbnail from - * @param tagRegions Tags to apply - * @param iconSize Size of the output thumbnail - * @param outputEncoding Format of thumbnail (jpg, png, etc). See OpenCV for - * supported formats. Do not include a "." - * @return BufferedImage representing the thumbnail - * - * @throws TskCoreException Could not read from file - * @throws IOException Could not create buffered image from OpenCV result - */ - public static BufferedImage makeThumbnail(AbstractFile file, Collection tagRegions, - IconSize iconSize, String outputEncoding) throws TskCoreException, IOException { - try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - BufferedImage result = writeTags(file, tagRegions, outputEncoding); - ImageIO.write(result, outputEncoding, baos); - Mat markedUpImage = Highgui.imdecode(new MatOfByte(baos.toByteArray()), Highgui.IMREAD_UNCHANGED); - Mat thumbnail = new Mat(); - Size resize = new Size(iconSize.getSize(), iconSize.getSize()); - - Imgproc.resize(markedUpImage, thumbnail, resize); - MatOfByte matOfByte = new MatOfByte(); - Highgui.imencode("." + outputEncoding, thumbnail, matOfByte); - - try (ByteArrayInputStream thumbnailStream = new ByteArrayInputStream(matOfByte.toArray())) { - BufferedImage thumbnailImage = ImageIO.read(thumbnailStream); - thumbnail.release(); - matOfByte.release(); - markedUpImage.release(); - return thumbnailImage; - } - } - } - - private ImageTagsUtility() { - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index be1ec45965..e7bb2f313d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -45,6 +45,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.imageio.ImageIO; import javax.swing.JPanel; @@ -59,7 +60,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag; import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion; -import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsUtility; +import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsUtil; import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; @@ -805,10 +806,10 @@ class ReportHTML implements TableReportModule { if(!imageTags.isEmpty()) { //Write the tags to the fullsize and thumbnail images - BufferedImage fullImageWithTags = ImageTagsUtility.writeTags(file, imageTags, "png"); + BufferedImage fullImageWithTags = ImageTagsUtil.getImageWithTags(file, imageTags); - BufferedImage thumbnailImageWithTags = ImageTagsUtility.makeThumbnail(file, - imageTags, ImageTagsUtility.IconSize.MEDIUM, "png"); + BufferedImage thumbnailWithTags = ImageTagsUtil.getThumbnailWithTags(file, + imageTags, ImageTagsUtil.IconSize.MEDIUM); String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName()); @@ -819,7 +820,7 @@ class ReportHTML implements TableReportModule { File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile(); //Save images - ImageIO.write(thumbnailImageWithTags, "png", thumbnailImageWithTagsFile); + ImageIO.write(thumbnailWithTags, "png", thumbnailImageWithTagsFile); ImageIO.write(fullImageWithTags, "png", fullImageWithTagsFile); thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName(); @@ -828,7 +829,7 @@ class ReportHTML implements TableReportModule { } } catch (TskCoreException ex) { logger.log(Level.WARNING, "Could not get tags for file.", ex); //NON-NLS - } catch (IOException ex) { + } catch (IOException | InterruptedException | ExecutionException ex) { logger.log(Level.WARNING, "Could make marked up thumbnail.", ex); //NON-NLS } From bd92051d0c4edf9e715a25327e77bae3999de702 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 18 Jun 2019 15:56:57 -0400 Subject: [PATCH 17/33] Completed final changes --- .../autopsy/communications/FiltersPanel.form | 6 +- .../autopsy/communications/FiltersPanel.java | 188 +++++++++++------- .../autopsy/datamodel/accounts/Accounts.java | 2 +- InternalPythonModules/android/wwfmessage.py | 11 +- 4 files changed, 132 insertions(+), 75 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form index 2a656008c2..1aba67e76c 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form @@ -66,7 +66,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -464,7 +464,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 993d6db047..9548639d9e 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -45,7 +45,6 @@ import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingWorker; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; @@ -54,6 +53,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.ingest.IngestManager; +import static org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent.COMPLETED; import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.Account; @@ -98,6 +98,7 @@ final public class FiltersPanel extends JPanel { * Listens to ingest events to enable refresh button */ private final PropertyChangeListener ingestListener; + private final PropertyChangeListener ingestJobListener; /** * Flag that indicates the UI is not up-sto-date with respect to the case DB @@ -125,6 +126,11 @@ final public class FiltersPanel extends JPanel { @NbBundle.Messages({"refreshText=Refresh Results", "applyText=Apply"}) public FiltersPanel() { initComponents(); + + CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(Account.Type.DEVICE, true); + accountTypeMap.put(Account.Type.DEVICE, panel.getCheckBox()); + accountTypeListPane.add(panel); + deviceRequiredLabel.setVisible(false); accountTypeRequiredLabel.setVisible(false); startDatePicker.setDate(LocalDate.now().minusWeeks(3)); @@ -158,17 +164,27 @@ final public class FiltersPanel extends JPanel { ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue(); if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() - && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { - updateFilters(false); + && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() + && updateFilters(true)) { + + needsRefresh = true; + validateFilters(); + } + } + }; + + this.ingestJobListener = pce -> { + String eventType = pce.getPropertyName(); + if (eventType.equals(COMPLETED.toString()) && + updateFilters(true)) { + needsRefresh = true; validateFilters(); - } } }; applyFiltersButton.addActionListener(e -> applyFilters()); refreshButton.addActionListener(e -> applyFilters()); - } /** @@ -221,19 +237,26 @@ final public class FiltersPanel extends JPanel { /** * Updates the filter widgets to reflect he data sources/types in the case. */ - private void updateFilters(boolean initialState) { - updateAccountTypeFilter(); - updateDeviceFilter(initialState); + private boolean updateFilters(boolean initialState) { + boolean newAccountType = updateAccountTypeFilter(initialState); + boolean newDeviceFilter = updateDeviceFilter(initialState); + + // both or either are true, return true; + return newAccountType || newDeviceFilter; } @Override public void addNotify() { super.addNotify(); IngestManager.getInstance().addIngestModuleEventListener(ingestListener); + IngestManager.getInstance().addIngestJobEventListener(ingestJobListener); Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { //clear the device filter widget when the case changes. devicesMap.clear(); devicesListPane.removeAll(); + + accountTypeMap.clear(); + accountTypeListPane.removeAll(); }); } @@ -241,95 +264,107 @@ final public class FiltersPanel extends JPanel { public void removeNotify() { super.removeNotify(); IngestManager.getInstance().removeIngestModuleEventListener(ingestListener); + IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener); } /** * Populate the Account Types filter widgets + * + * @param selected the initial value for the account type checkbox + * + * @return True, if a new accountType was found */ - private void updateAccountTypeFilter() { - - //TODO: something like this commented code could be used to show only - //the account types that are found: -// final CommunicationsManager communicationsManager = Case.getCurrentOpenCase().getSleuthkitCase().getCommunicationsManager(); - //List accountTypesInUse = communicationsManager.getAccountTypesInUse(); - //accountTypesInUSe.forEach(...) - - - - - List accountTypesInUse = null; - + private boolean updateAccountTypeFilter(boolean selected) { + boolean newOneFound = false; try { - final CommunicationsManager communicationsManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager(); - accountTypesInUse = communicationsManager.getAccountTypesInUse(); + final CommunicationsManager communicationsManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + List accountTypesInUse = communicationsManager.getAccountTypesInUse(); - for(Account.Type type: accountTypesInUse) { - if(!accountTypeMap.containsKey(type) && !type.equals(Account.Type.CREDIT_CARD)){ - CheckBoxIconPanel panel = new CheckBoxIconPanel( - type.getDisplayName(), - new ImageIcon(FiltersPanel.class.getResource(Utils.getIconFilePath(type)))); - - panel.setSelected(true); - panel.addItemListener(validationListener); - accountTypeListPane.add(panel); - if (type.equals(Account.Type.DEVICE)) { - //Deveice type filter is enabled based on whether we are in table or graph view. - panel.setEnabled(deviceAccountTypeEnabled); - } - + for (Account.Type type : accountTypesInUse) { + + if (!accountTypeMap.containsKey(type) && !type.equals(Account.Type.CREDIT_CARD)) { + CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(type, selected); accountTypeMap.put(type, panel.getCheckBox()); + accountTypeListPane.add(panel); + + newOneFound = true; } } - -// Account.Type.PREDEFINED_ACCOUNT_TYPES.forEach(type -> { -// if (type.equals(Account.Type.CREDIT_CARD)) { -// //don't show a check box for credit cards -// } else { -// accountTypeMap.computeIfAbsent(type, t -> { -// -// CheckBoxIconPanel panel = new CheckBoxIconPanel( -// type.getDisplayName(), -// new ImageIcon(FiltersPanel.class.getResource(Utils.getIconFilePath(type)))); -// panel.setSelected(true); -// panel.addItemListener(validationListener); -// accountTypeListPane.add(panel); -// if (t.equals(Account.Type.DEVICE)) { -// //Deveice type filter is enabled based on whether we are in table or graph view. -// panel.setEnabled(deviceAccountTypeEnabled); -// } -// return panel.getCheckBox(); -// }); -// } -// }); + } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); + logger.log(Level.WARNING, "Unable to update to update Account Types Filter", ex); + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "A case is required to update the account types filter.", ex); } + + if (newOneFound) { + accountTypeListPane.revalidate(); + } + + return newOneFound; + } + + /** + * Helper function to create a new instance of the CheckBoxIconPanel base on + * the Account.Type and initalState (check box state). + * + * @param type Account.Type to display on the panel + * @param initalState initial check box state + * + * @return instance of the CheckBoxIconPanel + */ + private CheckBoxIconPanel createAccoutTypeCheckBoxPanel(Account.Type type, boolean initalState) { + CheckBoxIconPanel panel = new CheckBoxIconPanel( + type.getDisplayName(), + new ImageIcon(FiltersPanel.class.getResource(Utils.getIconFilePath(type)))); + + panel.setSelected(initalState); + panel.addItemListener(validationListener); + if (type.equals(Account.Type.DEVICE)) { + //Deveice type filter is enabled based on whether we are in table or graph view. + panel.setEnabled(deviceAccountTypeEnabled); + } + + return panel; } /** * Populate the devices filter widgets * - * @param initialState + * @param selected Sets the initial state of device check box + * + * @return true if a new device was found */ - private void updateDeviceFilter(boolean initialState) { + private boolean updateDeviceFilter(boolean selected) { + boolean newOneFound = false; try { final SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase(); for (DataSource dataSource : sleuthkitCase.getDataSources()) { String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); - //store the device id in the map, but display a datasource name in the UI. - devicesMap.computeIfAbsent(dataSource.getDeviceId(), ds -> { - final JCheckBox jCheckBox = new JCheckBox(dsName, initialState); - jCheckBox.addItemListener(validationListener); - devicesListPane.add(jCheckBox); - return jCheckBox; - }); + if(devicesMap.containsKey(dataSource.getDeviceId())) { + continue; + } + + final JCheckBox jCheckBox = new JCheckBox(dsName, selected); + jCheckBox.addItemListener(validationListener); + devicesListPane.add(jCheckBox); + devicesMap.put(dataSource.getDeviceId(), jCheckBox); + + newOneFound = true; + } } catch (NoCurrentCaseException ex) { logger.log(Level.INFO, "Filter update cancelled. Case is closed."); } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "There was a error loading the datasources for the case.", tskCoreException); } + + if(newOneFound) { + devicesListPane.revalidate(); + } + + return newOneFound; } /** @@ -352,7 +387,7 @@ final public class FiltersPanel extends JPanel { } /** - * Sets the state of the device filter checkboxes + * Sets the state of the device filter check boxes * * @param deviceFilter Selected devices */ @@ -399,6 +434,12 @@ final public class FiltersPanel extends JPanel { endDatePicker.setEnabled(state.isEnabled()); } + /** + * Sets the state of the most recent UI controls based on the current values + * in MostRecentFilter. + * + * @param filter The MostRecentFilter state to be set + */ private void setMostRecentFilter(MostRecentFilter filter) { int limit = filter.getLimit(); if(limit > 0) { @@ -457,6 +498,7 @@ final public class FiltersPanel extends JPanel { gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 5); topPane.add(applyFiltersButton, gridBagConstraints); needsRefreshLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.needsRefreshLabel.text")); // NOI18N @@ -730,7 +772,7 @@ final public class FiltersPanel extends JPanel { accountTypesScrollPane.setPreferredSize(new java.awt.Dimension(2, 200)); - accountTypeListPane.setLayout(new javax.swing.BoxLayout(accountTypeListPane, javax.swing.BoxLayout.Y_AXIS)); + accountTypeListPane.setLayout(new javax.swing.BoxLayout(accountTypeListPane, javax.swing.BoxLayout.PAGE_AXIS)); accountTypesScrollPane.setViewportView(accountTypeListPane); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -773,6 +815,7 @@ final public class FiltersPanel extends JPanel { gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); add(scrollPane, gridBagConstraints); }// //GEN-END:initComponents @@ -841,6 +884,11 @@ final public class FiltersPanel extends JPanel { endCheckBox.isSelected() ? endDatePicker.getDate().atStartOfDay(zone).toEpochSecond() : 0); } + /** + * Get a MostRecentFilter that based on the current state of the ui controls. + * + * @return A new instance of MostRecentFilter + */ private MostRecentFilter getMostRecentFilter() { String value = (String)limitComboBox.getSelectedItem(); if(value.trim().equalsIgnoreCase("all")){ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index c3259807c6..a75bca2972 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -1845,7 +1845,7 @@ final public class Accounts implements AutopsyVisitableItem { return ICON_BASE_PATH + "WhatsApp.png"; } else { //there could be a default icon instead... - return ICON_BASE_PATH + "warning_triangle.png"; + return ICON_BASE_PATH + "face.png"; // throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName()); } } diff --git a/InternalPythonModules/android/wwfmessage.py b/InternalPythonModules/android/wwfmessage.py index f2f0e213ec..faf62bb4e2 100644 --- a/InternalPythonModules/android/wwfmessage.py +++ b/InternalPythonModules/android/wwfmessage.py @@ -41,6 +41,8 @@ from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException from org.sleuthkit.datamodel import Account from org.sleuthkit.datamodel import Relationship +from org.sleuthkit.autopsy.ingest import IngestServices +from org.sleuthkit.autopsy.ingest import ModuleDataEvent import traceback import general @@ -78,7 +80,8 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): def __findWWFMessagesInDB(self, databasePath, abstractFile, dataSource): if not databasePath: return - + + bbartifacts = list() try: Class.forName("org.sqlite.JDBC"); # load JDBC driver connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath) @@ -115,6 +118,7 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MSG_ID, general.MODULE_NAME, game_id)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, general.MODULE_NAME, message)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, general.MODULE_NAME, "Words With Friends Message")) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID, general.MODULE_NAME, user_id)) artifact.addAttributes(attributes) @@ -124,6 +128,7 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): # create relationship between accounts Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [wwfAccountInstance], artifact,Relationship.Type.MESSAGE, created_at); + bbartifacts.append(artifact) try: # index the artifact for keyword search blackboard = Case.getCurrentCase().getServices().getBlackboard() @@ -140,6 +145,10 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): self._logger.log(Level.SEVERE, "Error parsing WWF messages to the blackboard", ex) self._logger.log(Level.SEVERE, traceback.format_exc()) finally: + if bbartifacts: + + IngestServices.getInstance().fireModuleDataEvent(ModuleDataEvent(general.MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE, bbartifacts)) + try: if resultSet is not None: resultSet.close() From 040e6910236c47caca94b49bf5cb613dd6f5c38f Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 18 Jun 2019 16:26:42 -0400 Subject: [PATCH 18/33] Codacy --- .../autopsy/contentviewers/imagetagging/ImageTagsUtil.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java index 1faf44adf0..150027b210 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java @@ -66,8 +66,7 @@ public final class ImageTagsUtil { MatOfByte taggedMatrix = getTaggedImageMatrix(sourceImage, tagRegions); try (ByteArrayInputStream taggedStream = new ByteArrayInputStream(taggedMatrix.toArray())) { - BufferedImage taggedImage = ImageIO.read(taggedStream); - return taggedImage; + return ImageIO.read(taggedStream); } finally { sourceImage.release(); taggedMatrix.release(); @@ -178,8 +177,7 @@ public final class ImageTagsUtil { MatOfByte thumbnailMatrix = getResizedMatrix(taggedMatrix, iconSize); try (ByteArrayInputStream thumbnailStream = new ByteArrayInputStream(thumbnailMatrix.toArray())) { - BufferedImage thumbnailImage = ImageIO.read(thumbnailStream); - return thumbnailImage; + return ImageIO.read(thumbnailStream); } finally { sourceImage.release(); taggedMatrix.release(); From 007a45aa470acf114724334a2669287a26214e52 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 18 Jun 2019 19:30:53 -0400 Subject: [PATCH 19/33] Give the Timeline top compnent the required ctor --- .../timeline/TimeLineTopComponent.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index 8ff938e60b..e61ad0937f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2014-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -91,6 +91,7 @@ import org.sleuthkit.datamodel.TskCoreException; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider { + private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(TimeLineTopComponent.class.getName()); @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @@ -102,7 +103,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private final ExplorerManager explorerManager = new ExplorerManager(); - private final TimeLineController controller; + private TimeLineController controller; /** * Lookup that will be exposed through the (Global Actions Context) @@ -253,11 +254,12 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer } /** - * Constructor - * - * @param controller The TimeLineController for this topcomponent. + * Constructs a "static" version of the top component for the Timeline + * feature which has only Swing components, no controller, and no listeners. + * This constructor conforms to the NetBeans window system requirement that + * all top components have a public, no argument constructor. */ - public TimeLineTopComponent(TimeLineController controller) { + public TimeLineTopComponent() { initComponents(); associateLookup(proxyLookup); setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent")); @@ -266,7 +268,6 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ExternalViewerShortcutAction.EXTERNAL_VIEWER_SHORTCUT, "useExternalViewer"); //NON-NLS getActionMap().put("useExternalViewer", ExternalViewerShortcutAction.getInstance()); //NON-NLS - this.controller = controller; //create linked result and content views contentViewerPanel = new DataContentExplorerPanel(); @@ -278,6 +279,17 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer dataResultPanel.open(); //get the explorermanager contentViewerPanel.initialize(); + } + + /** + * Constructs a fully functional top component for the Timeline feature. + * + * @param controller The TimeLineController for this top component. + */ + public TimeLineTopComponent(TimeLineController controller) { + this(); + + this.controller = controller; Platform.runLater(this::initFXComponents); From 7434b9037dbdb93d1eb4eac78e8d387aab48bb2f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 18 Jun 2019 19:33:52 -0400 Subject: [PATCH 20/33] Give the Timeline top compnent the required ctor --- .../org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index e61ad0937f..137ea4d78a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -254,7 +254,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer } /** - * Constructs a "static" version of the top component for the Timeline + * Constructs a "shell" version of the top component for the Timeline * feature which has only Swing components, no controller, and no listeners. * This constructor conforms to the NetBeans window system requirement that * all top components have a public, no argument constructor. @@ -288,7 +288,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer */ public TimeLineTopComponent(TimeLineController controller) { this(); - + this.controller = controller; Platform.runLater(this::initFXComponents); From 9c859442c6431a2c960c117f8843fc3551218966 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 19 Jun 2019 07:33:57 -0400 Subject: [PATCH 21/33] removed the check if filters were updated before enabling the refresh button --- .../sleuthkit/autopsy/communications/FiltersPanel.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 9548639d9e..28914af76e 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -101,7 +101,7 @@ final public class FiltersPanel extends JPanel { private final PropertyChangeListener ingestJobListener; /** - * Flag that indicates the UI is not up-sto-date with respect to the case DB + * Flag that indicates the UI is not up-to-date with respect to the case DB * and it should be refreshed (by reapplying the filters). */ private boolean needsRefresh; @@ -164,9 +164,9 @@ final public class FiltersPanel extends JPanel { ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue(); if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() - && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() - && updateFilters(true)) { - + && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) + { + updateFilters(true); needsRefresh = true; validateFilters(); } From d51bb75c35378d783810330cd8c90cb19a4eb2b8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 19 Jun 2019 09:44:18 -0400 Subject: [PATCH 22/33] Made png string constants into class variables so they are not sprinkled around the code --- .../contentviewers/imagetagging/ImageTagsUtil.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java index 150027b210..5afd3e363e 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtil.java @@ -45,6 +45,12 @@ import org.sleuthkit.datamodel.ReadContentInputStream; * Utility for drawing rectangles on image files. */ public final class ImageTagsUtil { + + //String constant for writing PNG in ImageIO + private final static String AWT_PNG = "png"; + + //String constant for encoding PNG in OpenCV + private final static String OPENCV_PNG = ".png"; /** * Creates an image with tags applied. @@ -114,7 +120,7 @@ public final class ImageTagsUtil { //Convert it to OpenCV Mat. try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) { - ImageIO.write(buffImage, "png", outStream); + ImageIO.write(buffImage, AWT_PNG, outStream); byte[] imageBytes = outStream.toByteArray(); MatOfByte rawSourceBytes = new MatOfByte(imageBytes); @@ -150,7 +156,7 @@ public final class ImageTagsUtil { } MatOfByte taggedMatrix = new MatOfByte(); - Highgui.imencode(".png", sourceImage, taggedMatrix); + Highgui.imencode(OPENCV_PNG, sourceImage, taggedMatrix); return taggedMatrix; } @@ -201,7 +207,7 @@ public final class ImageTagsUtil { Imgproc.resize(taggedImage, thumbnailImage, resizeDimensions); MatOfByte thumbnailMatrix = new MatOfByte(); - Highgui.imencode(".png", thumbnailImage, thumbnailMatrix); + Highgui.imencode(OPENCV_PNG, thumbnailImage, thumbnailMatrix); thumbnailImage.release(); taggedImage.release(); From 337b4c202bb55b207066bf5d0cb0891e17d0c189 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 19 Jun 2019 10:29:53 -0400 Subject: [PATCH 23/33] 2955 disable only for windows --- .../hashdatabase/HashLookupSettingsPanel.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index 67a020488d..c2251d0e56 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; @@ -408,7 +409,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan List nsrlHashsets = new ArrayList<>(); for (SleuthkitHashSet hdb : unindexed) { //check if this is the NSRL if so point users toward already indexed versions - if (hdb.getHashSetName().toLowerCase().contains(NSRL_NAME_STRING)) { + if (isWindows() && hdb.getHashSetName().toLowerCase().contains(NSRL_NAME_STRING)) { nsrlHashsets.add(hdb); nsrlTotal += "\n" + hdb.getHashSetName(); } else { @@ -977,6 +978,15 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan } }//GEN-LAST:event_sendIngestMessagesCheckBoxActionPerformed + /** + * Check if the current OS is windows + * + * @return true if running on windows, false otherwise + */ + private boolean isWindows() { + return PlatformUtil.getOSName().toLowerCase().startsWith("Windows"); + } + @NbBundle.Messages({"HashLookupSettingsPanel.indexNsrl.text=This hash set appears to be the NSRL, it will be removed from the list.\n", "HashLookupSettingsPanel.indexNsrl.title=NSRL will not be indexed"}) private void indexButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_indexButtonActionPerformed @@ -987,7 +997,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan // Add a listener for the INDEXING_DONE event. This listener will update // the UI. SleuthkitHashSet hashDb = (SleuthkitHashSet) hashDatabase; - if (hashDb.getHashSetName().toLowerCase().contains(NSRL_NAME_STRING)) { + if (isWindows() && hashDb.getHashSetName().toLowerCase().contains(NSRL_NAME_STRING)) { JOptionPane.showMessageDialog(this, Bundle.HashLookupSettingsPanel_indexNsrl_text() + Bundle.HashLookupSettingsPanel_removeUnindexedNsrl_text(NSRL_URL), Bundle.HashLookupSettingsPanel_indexNsrl_title(), JOptionPane.INFORMATION_MESSAGE); try { hashSetManager.removeHashDatabaseNoSave(hashDatabase); From 3658fa3b0870457af67a2696e4b881dc4a65233d Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 19 Jun 2019 10:39:29 -0400 Subject: [PATCH 24/33] narrowed the data_added events that trigger the refresh\update of the account types & devices --- .../org/sleuthkit/autopsy/communications/FiltersPanel.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 28914af76e..76c2f189ad 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -163,8 +163,10 @@ final public class FiltersPanel extends JPanel { // Indicate that a refresh may be needed, unless the data added is Keyword or Hashset hits ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue(); if (null != eventData - && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() - && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) + && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID())) { updateFilters(true); needsRefresh = true; From 3589b78f65ce850504f56cc09ca03f2e4051eb66 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 19 Jun 2019 11:44:32 -0400 Subject: [PATCH 25/33] Set root node before setting up paging support. --- .../autopsy/corecomponents/DataResultViewerTable.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index df2cbf5a49..e1feecf807 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -302,6 +302,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { if (rootNode != null) { + this.rootNode = rootNode; + /** * Check to see if we have previously created a paging support * class for this node. @@ -364,8 +366,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * case database round trips. */ if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { - this.rootNode = rootNode; - this.getExplorerManager().setRootContext(this.rootNode); setupTable(); } else { From ccd01682b9609c9d459c9f3a444c0ca77a1d44ff Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 19 Jun 2019 13:15:03 -0400 Subject: [PATCH 26/33] Addressed review comments and fixed ClassCastException --- .../relationships/MessageViewer.java | 11 +++------ .../MessagesChildNodeFactory.java | 3 ++- .../relationships/MessagesPanel.java | 5 ++-- .../relationships/ThreadNode.java | 24 +++++++++++++++---- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java index 86b343cfac..09f5d8e87f 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java @@ -51,9 +51,7 @@ import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; -import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** * The main panel for the messages tab of the RelationshipViewer @@ -115,12 +113,9 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { }; rootTablePane.getExplorerManager().setRootContext( - new TableFilterNode( - new DataResultFilterNode( - new AbstractNode( - Children.create(rootMessageFactory, true)), - rootTablePane.getExplorerManager()), - true)); + new AbstractNode(Children.create(rootMessageFactory, true))); + + rootTablePane.getOutlineView().setPopupAllowed(false); Outline outline = rootTablePane.getOutlineView().getOutline(); rootTablePane.getOutlineView().setPropertyColumns( diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java index d185b1a374..726fba4341 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java @@ -33,7 +33,8 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** - * + * A ChildFactory subclass for creating MessageNodes from a set of + * BlackboardArtifact objects. * */ public class MessagesChildNodeFactory extends ChildFactory{ diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java index 366d6ee43b..44f412ca30 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesPanel.java @@ -38,7 +38,8 @@ import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** * - * + * General Purpose class for panels that need OutlineView of message nodes at + * the top with a MessageContentViewer at the bottom. */ public class MessagesPanel extends javax.swing.JPanel implements Lookup.Provider { @@ -47,7 +48,7 @@ public class MessagesPanel extends javax.swing.JPanel implements Lookup.Provider private final PropertyChangeListener focusPropertyListener; /** - * Creates new form ThreadMessagesPanel + * Creates new form MessagesPanel */ public MessagesPanel() { initComponents(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java index a3730df69c..f398b94757 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * 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.communications.relationships; @@ -12,8 +25,9 @@ import org.openide.nodes.Sheet; import org.sleuthkit.datamodel.BlackboardArtifact; /** - * - * @author kelly + * An AbstractNode subclass which wraps a MessagNode object. Doing this allows + * for the reuse of the createSheet and other function from MessageNode, but + * also some customizing of how a ThreadNode is shown. */ final class ThreadNode extends AbstractNode{ From d05a440065f37e07f029391d5e26f3518d628256 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Wed, 19 Jun 2019 13:17:29 -0400 Subject: [PATCH 27/33] Updated docs. --- CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java index 7cfad05bc8..0027203cd7 100644 --- a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java +++ b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java @@ -41,6 +41,9 @@ public final class OpenCvLoader { * Return whether or not the OpenCV library has been loaded. * * @return - true if the opencv library is loaded or false if it is not + * @throws UnsatisfiedLinkError - A COPY of the exception that prevented OpenCV from loading. + * Note that the stack trace in the exception can be confusing because it refers to a + * past invocation. */ public static boolean isOpenCvLoaded() throws UnsatisfiedLinkError { if (!OPEN_CV_LOADED) { From 3da37814db712a9e7327ed5401584a6043aa9d2e Mon Sep 17 00:00:00 2001 From: Joe Ho Date: Wed, 19 Jun 2019 13:45:27 -0400 Subject: [PATCH 28/33] Use logical-imager-config.json --- .../autopsy/logicalimager/configuration/ConfigVisualPanel1.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java index 7ee034017e..1b4bee1d36 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java @@ -135,7 +135,7 @@ final class ConfigVisualPanel1 extends JPanel { fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); FileFilter filter = new FileNameExtensionFilter(Bundle.ConfigVisualPanel1_fileNameExtensionFilter(), new String[] {"json"}); // NON-NLS fileChooser.setFileFilter(filter); - fileChooser.setSelectedFile(new File("config.json")); // default + fileChooser.setSelectedFile(new File("logical-imager-config.json")); // default fileChooser.setMultiSelectionEnabled(false); if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { String path = fileChooser.getSelectedFile().getPath(); From a66f8eeca5e49df62df0545bb825cb05dadca544 Mon Sep 17 00:00:00 2001 From: Joe Ho Date: Wed, 19 Jun 2019 13:52:18 -0400 Subject: [PATCH 29/33] Update tsk_logical_imager.exe --- .../configuration/tsk_logical_imager.exe | Bin 1409024 -> 1409024 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/tsk_logical_imager.exe b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/tsk_logical_imager.exe index a444a782f2d6b9b1b2b3c6bc02974fdf1f7982ec..5aff397048396efc88af7453670a8fc6da7c9a8f 100644 GIT binary patch delta 126159 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,L8&6n{A|945j%^@K4P)8)HY{EU|*2 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 d^1FVdX&2@AuDj9?x<1dYnBXz9rx7EH~k zl;Xz@20`3LKr{B=PomOr^@N-5@$+!Vi6lEor^7@ypAlMxzch+wv*>H(3?52Mdev9-ByG7dRV_Gc^rDXl#}P^E-lz-ZXG3= zbZ&iaE4HXGzxhZH7|CigmZJBKVdbhI;-bxLY+IH}Zj4`M5qdh~nG*ljSih|B5sby@ z^)w#m&T}f$*#dkRn@zz5c`CnCiQX3EQEUt~_2tpbi?;dlp=^WhTZsEQGgrMqt$PpV!)owqq6HenRIPE zI-6_xMIAjYbr@@-Z}hV4wX!;TR8dPInU$kSB`tycgg0$1X{p1Hd(xYdmT1v>az;ba7-7UTaLIy2~wtShT)TOGm^(!vuxwvp>fix=BN0V^$2StHuI(o&iEQTj^DX10rFud)=a)V-~< z12p@HAR*0Gu9bsBrE|7ZQ>_jc!xO8ls_*P@fQdv)dHqgwEv2ur^kewN&F*%hRO2sNg|8b+4s4XH#@(zs1VfT*`gGQk%`wn;x*- zRM;B5*fFsA)#7^DG|LLcR_ambEsq%+O|>stin0aN?V{xal5KU#Qj)RxH2yLQv5j_Kwp8F3eCXL_ z%Q7~RCSS1}W-zHr;}a#z0mFr?acO2?LtHESON>D zd6`LF6H$NBF_QE z!9lZ?`IeItp6L7~56w!qEZ}K*$oHwGntQuE|5Iu5)lhM#eorlJG3t&zwKR9mcE!Z# zBP4NYIdtr})r+b;v-DzIU;4~qwXmJE?geJue9C-bdBxhc`K_;XFpn|^Vf_bKUpTQ!F1u1r7QEMz|WQ;{G5Zn{Ig{y z_w4#Bm&3mu%pI z^TZJP^M|D@i=*p5pfrr6Yy{C%HQTa}r+wGoW?SZS7C}>TFi5@VaE_%o|MHdo$*~M! z@hPpPvh0tPIH?w!opM|%?OgtAE-VTEiq@&pHzi9d&Kgi5CIw&}XvCyu{Ph=F!lW&% zo!)>$$ddGd7RiS}Yo;XKVs$7=mfEt(v{#lUu?l)+MHJ}D9_jGj-b$gw}X@{*>rL>lTX%|d~*yrt_{{4V-PWw23w z_mTcZ%5U;Z0sL1wc@+Q`Yf%BopOvB^1*A#*-4p#?0qH2?zaG<0U#S}FLN;HiRkziT zb2)^^hx!!`Pn3H-Sg_DnE-pp0 zJhY&=)P`r?rOe_|d7ge(FIWP~>>!OODIH+`RK{Ow%$#YAKiWs9U;a{awt|`kNIv}b z8X6EF?PnFpuas1qpSVRmOF?M>y(%Tu$G%gxv{ZmGAH7MSlv`#!sAB~wPOEkmEt2Tm zm!4OU)?4GR7}UKhw4kEY-N_eZM8XAZHM5CTl1gZAE(^KGBq~=)+GDL?P|lAo(`zBb z927|%BA?1q4X2}*e#c!-T`NnS&;@^0mg=zLlwDa0<+e*yuL|;ScF~B`Rlih4x}Tf< zrEdzAsxa18e;g)NW@rxghSD9@UVqb2s>S*BWqM#^B*N2meNYq0SgQ&&MePPr*QQcB zt3>r8(c8&1E)sKT5bck|n24bCNU1NgP~&FO6ZQv{ZZ7${EZu_{w&Ks6oXlLP&|zxd zT*_eesa^}IAx79AEu=uUkMtJOV%DBow3PDtgs0{*x9UbNVcE7hy*0EcYCyfX>odR#12C zBHGD;!lI-B{K0109fcv#p9Vx@U^Jq?qNP%7pZ+ph@{!Rxyq7eZ-(O4Pdr2*L+FH8Y zOKQN6t)+aurFJj|2KPpZST3s27vuEl8XDObiL9d2eWfh4#?gLKb(X4s?<{t{pL}Z7%2i{adV8+lGUZ3 zG3eEmbZeAUBsOtjE{Dtq*q4?Ga06hA0OtV?0%Tmpb^?4xF!uu76ksdBKLY#-kSV|t zfPV!@0L>p63OW;N(ZX|FsGoda5S1|eg z1TJpk7=cThI`INKoBZVh=Qa7O1y)VGP2h}QM!p9q7Q{o7I4lI$Ongn?(@BtHN z2)xb2p9NlRV(TK{6cZN|INrno0#7t?6@iDDxP!pGfMd}tg9OpR6eJ1U)Wj(QhnRSi zz*S7VSKt5>9~Zcwi7yG9$HZ(guxw&kU?&@E{WpiWNjRlSmS{ zjfwXP+{nb|1g>S`Hv(5Mah_$s#ZBxXaA}LtuSEoQHu+%!GZXg{`122=C8i6U@dNV@ z12A3?4^2U$z}HNiB=BhyrwDw&#JdFEX5!NVuQu^rfm2NUT;O;Ue-L=0i9MDB4>NJ_ z@?5c)1}4!(2s)TJUf`xCP82xA#ES*4V&ZKA2blPPzy(cwTHrh;zALb7;^zWqeK%^K zCGZ=8V-sbM6(G`0L4d$7&67eKfzO%zegYph@kD`lnK(({btc{>@M0666FAYtHwB(z z;tYXfOf0Vij_qd>-h$|2;wl2SFmVTg!%RF;;9wK47PyRw*9lz2#JdFcF!6JNttQSA z_-B?;d*@ZaA50t@EQseO(M#aFCLSyBB@-_f__&FW3%u9F4+Y+2;&g$RoA|T9NhW5i zfv20epul5I94zpltXQK&O$E`-6m$@{jfn>d+{nc70@pHeqQDhQoFs5@6R#84+r+yB zb~f>0ftiWZ1^$fV5eEL})q2K6>8XVU(8Vd(g!7Q!RH+BQw4CCnN>%yk<@)id5{{nt zmg%SBVA`;5ls!!vU8o;sVMM}7+pxhoIkvyr<>Ux^L9!KXZP*LRG=DmbkeQTk26pf$ z>N5j|Ol4v-B@fn;JZDP2I3rY@DHUN&sr^hTuy6#Dvpw6dIZQtm)7rKf8{q-WWXa~* z(nv3u)(Yi@bOy@xUX6ls%x~qI|0%C&VJMdtY4t|L(BR^SI=VWH^fp}_x65mG7e_Ev zo&^JI8g-k6k`$!LvygacS~E)uf`j1xER+%_oe%NSP5yQ5vGT z(IXMmBsLc@1MI3TXw@9a$2XsyEW1QB!^!Z3&VJ>?<4zThK8ay4mU_;WHsToNJWpzc zp6WJFTEq_N-{*&DR$#klYzug4pGP}7NV|??PRn;@{37SeW6j;&I=J|J;fq$OtvfCm`|(cNZyWCF~P^*9@$&tD3In8oTxmPwl!3f6apbdwFC z+AF1*tR0Nxr+`dt&$TpI^Nh*nPQ)Uy2k(+3flmZL5 z%w`;7cTm)3NoUn4-xe75J~Utp&ZY7pM)TOpkKI1vXGgiYpj`rE67T3f`_=$^PN&@ z{(6$`whMJ-i)q4cG{jo^up12#Kz^z44*Z%xLsO;vI8)9}l?oK7IT0nkQUWDDdkmIc zPDX2NRG}EmiO!AaR4O{spPcvLoVrjyyGJ_0(E2m>NvGI2y@@V0V(=}b?3a>R0SY(( zk3rN()a&CHS48`jHbD?r;;8 zKaJyLBbt30BeyK=I4y1C#|BZ<8RQV7Z#*L{XWGjFsBEHhU22*pJz|?E@SL;|&ViF6 z*g_r8BM77qB3MVWE+8nO7rH2|~19c zvxk2CnzV#jkAub-I^UIg-jMFWGhF9yDTcFIwD*=Yp8s7)uXtO!z~FEGbw^sx)1&p} zcf|zmK|c4f)ODm*_oW(eu+0^L8(p}M1-?I79$>I-qRJ1ju5F?252TGn>P4a5ALhfV zY&eTw-dDv+ikz+OM1ee(Q0a$g!ijpi=W|W*NFaphE>N^AM}rOz>=qSTjY7%34NW=$`x51 zp=vg1GY-d3ZPEzVhuUUH#B*BHy9_j+PBUIf&GJ@jh4YsyoK~X*Pywcs?UhuYA8$=% zGNr!|`&A|ix}FNX#xdSkkA5w!VYzQKLslGXk#C<}`1a?ooP?SCP1!EW z^+hU`=ayZz9WuoD8bl4hNCT=5x3epP9hPtzl3Cz{C7cySCo0IYOrgqOr7v(Qmi{Icu0Ex)F?Azc!j3^@JSf|oAHT3LOt)daH=TZLqzm{XOfB08 zh(Z&Z27)z)rhk)4`t=ntY8WxX3WF_Zu`>7@nqk6=(TlErld8gjsAgeXs7wJ_l7DP5 zyI%5lR4$=u`S8TnenGaB9FessN#5T%n)2qRJmx>SlU?3Q$XOS}_iz;~)}=18N6=i1 z5f69pY~{=}>Y8a(5NXiWEJ+q#f-J1#bLk5LkC~0kqF>2ElGAfW)d%uH0lGFbB$v|XtCFKGy7U` zX|tmo$2L$|CwVSBu1B2YR{Xv{<;^9xL?iajB~Q;CQZkoA!op?72zKsCS-Iqz+Sd|j z(M0E=RLfZ|uAMh1j}g@0S+1zfGi76Gt&sIFWfSO{v+T|O&@-K7_`MoXH5a)xTYKKO;2@op+IoXdwpW;i!Lfk^jo0#UOiRHHq{ha%o&fJu4y)WoYBp#h|;RxDa|2 zrisPmQPv73bpr0fVsdBgqM59--qlZD$N24Hx>`csjB6m;T~aQIF27e&Zo?Aw^8PaJ z`<(Tm0Wvn;{S;G5uEviQqV1*R&y3O9(()l(>vRp2cfjm)EraGi=c}ibks}%0%m0*> zA2N%6qMY28@z?pur-D3EYwKf_IhGbykV|QPCRIgGt00$U*dD%CL{Ius$x8A8T#cqz zlILR`9a&lK%D;Kht;%v2v65GjJL4)esfxUe^D7=yE=cacmQj3=9Lk6;2gyOWLsNt0 zA|BH`kQpXs=NdnALS6RNSI{7nsaCLD0Y?0gU^xJb+~Q!lj2I=sa%E|nVDf8$QU z)#WPumb*T*I#O%)PhK;f2SG?DAxyMux}9~-WJzE>va|LHmWv)Htc-hh-WpR)I7e^i zrLQ%R*)9sKDR;#fOspwKp}$|(l!N(KH!4|6Zsz{Q&CD@(wVzlV#M*`nqd#iN)e9Vh zBx4HJshZ7W+$WD`5iz;Lr+*n43zfa}q?YXIy$u?fEo!zl#0^YwGl_5Z z-%X!g2V)PtzrU_LhhKKp!$V|PCY|*8_2fGithfUj$n)50%5H#8YfT**%G0q!KW-=w z6HeN2^h9-v3zsXxzT6is7l#4(AY3jb7S2X;6*q04SZR}f)^V|7)cMjnIfe`sYUII7j!?Tn@vr-ZLuICA8`+%=Wj2s zw-l=X-3j*eX{69pmD#9Q>tdWF-q)6utGh?nu=KTyURb(eHlIE{*39Zd&({hGqGncxg!7aFD>dN z_r(o6>n(>hobj&{Hs&*8+AU2KrT38TBR7zBgHw~t)G{lbUe3IV?r z_S3dLSQnD%U!f54pTf%Z_7uuewZ2H97>(--g#+(@r{MWpVLcs%LY_mAh;lwN#)(Jv z9dhf3@hVeLKlzu}!go#%p^3Se(AW%DanpomVtZamk^SW{)fxz?<(LrWhY*6+;0Q98 zNwzsM#)V^#IKth(1Y$ve_qj*4SGVHyi`eqJu~qgcdFRpF;dUhSk|7)tzvehu9MAGj}GU$j&J^}e(zm~S^TmTb2 zDY~t^owfgetU*+F7%Y~7)NPnNi$BZIGl$91%zd-XNnEMdXSh>e58OMxzLnB>?!2_} zfRjw4M#z0wNqRIwuE#&Uq@p9`$bx%bib7n75q6lswgIufavCLsxq{wFi6iAg#aEds zwjfBbQHJ_ehir?TSqw~&f6=v(*j={Tx!IF`*)WB2YG_%>~0TK2>0J!7<7uULT>sPxh7>>OMFUcYiO!%*5r zUWn}bCY{xJh(k3#zJFyj?1)cGe-|9KSu7vuRW*4F>(=p<~;30 z2t)Du7`dr)7c;J5?e?R2e-$g@i3BJKSjhuk22&s0rRdR)t>;HBtV}qK~Cpz?L@_5 z8H%TAu~;Y*X@9IdfPZ*Q#V5<<_>;%fcCuU-k1J?0?73z1X0kjH=k@_pP`W?#gemd^ zhKuCBadI)Nv9sgkAw27W{xeR_$1&;LXUNCJabX6UcLY_RDff3Oe9y_D?zAuAi3Lm0 zj+y8%7)~?gDg5k=62G98}7zL7VlY_%+z+yXaTf$ex^~kZmnYFDJd?I#e8PyO!(a>To|K zu9x%Rp~-n#wLuQ#Y3J$k2Dt?;$_sClyYR2)DRv`ld4D}-lMKIMJ36vizQ#|d=}B8; zJZn5mSGUR)_>Z$>*@j^jK>^!fHUB}aw#mg@=bXm8Vr4L;u&O#AJFU;yCimj73tsP# zmv~1CX}9_q>{t&s!M!!@*hmlP^`@0OWiO0?{W~#X;b#53ORmeWoYbrBmK`npB3$)s%jGnTVjfP*ifmr9F^yqyeH!CgEWjt>>qTkVFcMFa*Zw|`+yhj~E+@v5g&AT7}E9*iU zatLKj(Ptf!txO$p5Ni37>Bla)v};yhVI{o}$EH#S*;mwW{3Q?N{J2hGC^GtL)KMIK zy3(biatH579WjqrGBdEg`!9pRROc8n7^Js9CW_}pDaX+mxA*F|kBi2*u$!KpLSro6 zjpz&hFdL)sX*9-g8gN>U3hpEXzGcmHduJHw7C>pn3;vmSvoW^%%@QjZ+9T?BdyLTe z8Ihqc)j5lCc4McWa8_23;WkP>kGZvsj-Qufyd$?EnO*)O`3ttJXaA?z%`TwWG1T{h zycDK@bP=OsE|tBAjkh7KzlZ}_m4%n&c>Zh)C0xQ3C`{`v$)oV*Q!jW~{zK)b*V3k& za(z~w-rU3qd^UBsCGX)mf0D~>%n2(6-o|3^;7=NN8%FF7I&)jD$`;Yj+wxuO+dsu} z==^vMJ-#D%w#J*}^)(cFSFWRlnB+l9xGNWTatCGjR-AX!p}X==EpUyomNunh_vFIP z5~u^X*%l|Y=Je*CY_saC4TY<#>Ct_;uyu|}-d;`W139eGPLQxVgAus+JMR{OE@z4$ zAk?p}i@&_Xj=KV%G;oApbEs|r?j1kPZyq`VPv8zx$^&_?YrR#Xc;|l?!9pD$%EPSb z;G#g^SJB@O<&i~tf;`}47P2dnv9%g1x;3LAe3wxBjOTujf%zZTe{p+^EC4EC{LFlbbf^tjKd4`BPl0c4s_o2e^_A@ z^i=NP+~ohTJZZ&Koc`z0rKj>0t*(){$6{LhOfIZ>nOXJIFFwO?&HeW>G?;jBhGL~H zrF_rjGJ%Vi8YN$|1SRj%UySp#oQxQF(?b)Qh~kfh+${d%#Wdu(T)?>nIKPWnoEANo zqw@f-6ZzPOS7nN~$t8SmqLff?i&SkDoW#7E;1?v8D{)$wK$mRt3~L2L z$N9@r>YRaPejV-4z}~lmMP+<k`|QS49Z=%|!H zNvAj}?Qper*HLML*Kft06h9QAv6E66MT~V)>f#>$m=j9sPU%k2>`B47lwdq9ACXJx zgTA<*OBujlBv81saz=|vFh)}!Jzs968`D0=3(~nQ#kwf|*3%~SE1q_`C>{96c-_fW z>5K=tv#4JlB@pKO!aT}3cmcY)DWk-v3T}$0cwPD~uhNK(q+oZY7;^98jyCkCM0X{a z|C&x|?n-p&)6-GMV~>7{vp_%~@`RPN%Ot;ho8%gkOr@3{N+_OSlZR5qyEEdA6i2ye z01?U4)aptPJ(RvhT)?%Y!Toq#$QD2RnUg8*BTh{-#{r!Cz4Iw`Y99rSwec?m(6ir! z>;`2kgD4RRGvB)zHzAY6d0Fs9YHkpLd^U}q*u9};}pMJ1TE=@2ry5)l^Xo>RGQ+gY)AV~3177FKR zeq{pe%}od#Ursg)aP&5<_E9`2w15)jIMvRqXJ@YOAO|_R9DM3BLX^2WSqdtDR@oQ( zUm0uMGRoN6&rB=OBxCZJWI>P-rqgB(-7lzgVVx=1SDC`UPolqkmC4wiY86tZV0;}# zz#mMcdWDs#ICh>ctVC4_p7>ujN3I*$d<`g7oK12SNMsX^5*DQPMU=ws1rSv< z?m$Rz4=ua_xE(H{bmE^UP(~3Y2Dcs2MU^hK+D~wDfKN{DY7hI`!=d(Yj6Ixe4`GL=<+C)LZI!_39jmu&&R*1G&#whiQ%i<%2qkQ93M~Ap9C&O=%HjY-5 zR7&RAfXKF@Muvu6zkzO*REl~mu&4R@f|({_fAAq^f5jWl=92!(GJHXB%3oRI6FC-{ zCnnZ~)5rEbpXv~@AN#Y|Olr~O0HqY3A#4dyd|7@vAE2C*;>S2S;JLR_z&h4tU)Uw6 zb)&yZDTDa4(Nwy$;@$b%XtOS>($IN9z0yXuhR#vvk)e@hD+W#~hZftbeSXknmI!}?>CS=YR@B2X!#X@<(RD^VmZ)q>T2QeZmp< z;rFuO;herxSkehs&_{EqPGv+bP5mn?W4x{(ht6|6pZovlydFZ|D=PsQQGQjRq$Slc^n>UCf+BDF zJ2^B>IE#sU=gaZJn8vTc0Cn}taTrwc+y3;ex>Adu?N8NeD1{0}6*K#G&VDfguh>eg z{+VODh99_!TGEIb7!2@6Lle#RR}H0U$r=Y8ar8N98?Zv?I1e0vc|A9;aj?0A$%#{S zM54!uepJ4u;*;->en#FGP!bg3m|2AB)U~Ek6DRoPHI=gbTtB*0Q>iXmx)#>2zbK#< z2FvaK)U1}00N26&T1x5Gks_D$y+3m@><@cn!MF-W=Gc(QlU&99;BM42Ea75^%Yx$} zamQT^c4k=!Rzo1Fwpyq+4sqFMEI>H3)mHL*yh91YUBb?XBpeTqj}RZkc>L3k{-~|o zE}Y*UFK&-$n#;B=2gMtZJE-Bv#!%}?Q|c)B;Kf-~N14lUli^`tta zQi^AFr!tKc5+5Ek!i3pPnT?czn5pd>E3I+qzOAvcnqTfl&6_CY`KN9)wTW^bA89m= zP*(A?UFlr}c7-w2rl~RsTle2hF%UR!Ri_7Df45E3E~8U~6Rr ze;!5Ytzl)}iqgxsQF<_*)rC^pVq#CEV{Oqw@KCkI2K&4VHEM@7;Z+wJ*-lBp?U-YG zB?N6A(O!vWE%dGJF-uYT&mEL4Fhy2(ghf(_d^;&^aLyUoNvX^qbka9>QflKfl#cYO zv+|6;Y)@CZC^Pu&c6z%gC6&S1>eN*k#hTpw(D-SmrnP$-|%EyU0b(=esZ1L8G{KjvJume}2@ z4^VvRWq+k+t``4OYD?t?V38?9T_N@U(%i`*B%!q*zKHbr4jhpX>NnIdk!gU^+B+UB z*qcoQAx;!HAxxxxq6J9SL!%HNo2)!+XSVug-zBAPF8xlV0KNNi+#j%O?hN;xbe>%@nXp#W1d*0 z62(zJ33Jdw)sxUQWvN|~Qi1MF zJ5-oc*!1S3E`6!jd<@24Woh1g^yFdMKOYNODBYcp_25}F6-&khy%tSvl9l)Tc6n;J zK>3D)N8J?V8EZj)3o*QF>&+G_6FAQZ)GsYkmRgW>zh#)eRq4tyOqXZ=lz+Jr%HQ}? zx8+Jpc0k{=TyeCB{b-eP46QSHH73#3;&f{@dS(J;tyY?`X?mkI%1?$niH2*HR(NHy zbgeR&iMg^KhsFc?wDn3J&c7DYX(J{qrdF=aN(1a#tu{m4oGxuv*7LgsDP{{6to{17 zEeal--YGywwqkq^r%u~2+FR1hZ8)=eP}(-^mAIfryAK9rL3()`_u8-G%Z*6)Vh&PYl{1ShJPigU(SPa2YndD}vN zovJjmU~<>gmHgtEt)pPK@-EyD^W%J83f+&{xt&(-$Nr2roBJ`drc?6+N^jgq=m(S~ z{D&L0I;b3AV|3?3iYw}@mc|LVItORIvO$x6m4=ar^MQ=j|w-~vO zU^?}sS|S)igO4EfApO!2>0(Y)OtrVffe{z zCoEz5DZ@G9vD40-1#VO0Rhx%14Lzgei{5URL_vaTQL>psGm~7@*CgwT)LiuGf0@Z8 z*&_uAcEWV~^T1>~H!rxzM4X=%(epEyAahAMs~q(F$BJdfat!rrl#^!j4K#9mXQkU` zm16vrRnI<)3Bb>&6n-9t*eZS7dF(Q*wQju#n*_5g;F5AU-$F_Br9Atd)d8O+!p@kC zo{gvMONzG^Ysm00q2OgD33o+1E-UGH^qO=9TOpn+U%{X%PH9)*_!vYRuPP__2}T32 zDFtg^VeqPb!!ZV{pbq>cL}D0g=(1iM5SBCOe4)owUs)gRaf$3hGHXyLravaI! zZ>76aa*m?|GB{%!UWj(xR3a$tZ^aec_}#yi-u(G53b~0$b7;d&jP`1j`B&!f zlpzJpC>L%aN<2Nd1@A{F1>aVN^Ym=mb{nPyZt3qRnb_KtyEt#LZF=E*FlcJA_Hs9BeP0=e!GGmG96ET6h=70lM!pYVqEw*J2TFHrVjCVn zxRNf5U>KjE89Q^og|Xkx~Q;+LcFGSUG)sq?G0_ zK2hPvSZ4ZB#ABE*5j0i=`s~KFwGqFy-$^itia|Euy4&LA3IzyX2m`7 zDtxhrzU!k${(}X89~+}@e-1+f`%vzeN>>K=(GVL#*L5Atmh*5l`nszAI0K6ltE5-Y z6uzgoZS_U3#po@fe|iH3PUkJ&p*+(l{+;rH?bgS?hk3y2>IMH*<}v>AslMYQmI(B4 zozF@YG-S+Y>xHU-C9H>ytmS?PD-*|5fZ+xOeYn>{=ej zzf;&ZwESwC_DyMyx92y%Ddl{RZAEom!qUvM!U>bY1QKV3Q(LKM7EHSo>YIgw8~k8d z*k(>Xq-R-5oq}t&{NI>sP0Ai~J=OURv+vFpn)F@qh=qk)z`Z;^Oq9OKiws_9BicD?^0+E zTB}@2Rkb(^-rHFn%3)bu;-C)dAGtb;VTtW8sg)TnEG4y!xXF{$mHgXH8Z4`I`P-Yc zQ-lw0>i^2B7p}bj)^n-qKnC~ZSWT^uA$3?&1M2j?=7`Uw(1Nyzzq4}=I12aj?yK42 z&~xZ2*8EY|vhih4nG1KF9Y*#uo$fDp$e8VE7%JzkQgN$V6prt3D+-3K!>aCN4fH~e zYJgb18ab&|InEA~oz*Lhg;8@C)t{fbq>pn^%W-%yHs?{>I%Z!GeR|90evWqLQQdW4 zH}x`x2ffJ)VUhEu@Y)$eNZs7k3;3ImG9GF*4il)Cr#cY_3;mgw>S7V@>-=gv_)(h_ zP$PNf34Lh+wU-mysxyCe9Ao|T2?6SEJmb{Ml~#vZv_ZR!o>)j5%BlfcO_OS>-zlr^ zw!+X)2vUQrcQ+V1uQt%dAT`js)+Diyv0$|d+ox9xRv$5*vyMD#sBQVekI zr@pb4ir21t$h(gE5-T68i?*LjgX^m4{P`+s5u*0R*VP9@)V&r~MIRcb)@5)9?`oj> zGFiXcKyAZvuUvxmKe0vhg~!PydZlnRiaA$W3})NS26ME&qLHeI?=03eRx|OcB&mry zsA$w@Y!5idz1UzVm3wBAw?QT}gkfAJ4;7A3-LWTEiBS6l&R*!~5OTOMMu6~USH__l z*(HN1(x`+;5sCH30E639Pm567GA!VQBGt}3YXMD+R15JZ3ur~8Ivf)`PcwBGjudgt z)Ic|n1&;9aemTw%*YJ@QpySO{FW3Cwi=>^_8pXrkBs5bORXuBLzL#x1{>0yxR6<_c z!4S749*YIQZHdP$0mP;=S^(jb?M-@fwRyfU(3Z66=a8!5d2R~Yc9pKSP#fl&4v$n> zb7jD;-$HH2FU_ZOEl`W;RJbJuOD!6MfPYS+Yc0XW8KxDugJ^OqwV(K73Q&$W=9xwQ zb0y_!r#7&DInE95QUWb$4RL+D_~VM-V$=DAzaVG>@u9h9?E3$S>l(4g)Au&Wu`;!2 ztG4IQ=Fpb5$ayna+Ntfa8FUtbKkaR&_T#q_sc?JfuAtHFq0^SGh#)tW?0}-5nN5>B zKsZUi-9gR6(G3}$)MU7>CU#cmRh%1d=6-vj(cJI{TPbx}LwF|vO1td2F26)Mmht)5#$Y% zEI>uNsUFC?S~s;5##eGTHHtr*P9M9eJA4~YH+AdbE9mgJqiN#w<1+0EelbF7(XsAo zb+}D`AmHz&QMDdwaGq1s%qS!08M&RFMpJsI&2XZ))|q>N$(6^JG&iF~QLC zr9OStJ#cgt=%)tbB-pl}S|0Abnf=rX*x?TJQ@i8QQ-S`VaGr}Cp!N|P=e~jJ1YC$w zrNL^NaFz^K5As`MY5x%QxO1hk7^Z{cjKt^aGlr@?8Gkxje?3ecBlf#qBh>BuZVY*h zR9E2j)1Hy)M~vBxF{%yTqoh%44R}m0jZ*#HXoMr~?CbhwR(IJK?u$y#Aa%4_7N%0w z(dv1(J?CM_Sx(#j8IPGI{MMzgW7W|{|Sh)Ww05g7!8}C zHdf8~(w*{8#B}np%Z87l@{^FBX56P-9YmKWBB41{a+2BsM${A$IMRItj%fqU3k2` zIz?^oIN5$ec{0q*C7&T}K+#jxBJOLCna$R7m}s^J;?8R|EtrbU1MbH-3>Mt_#i<8i z{iIA&*TQbBK3zS}hU)SRb%uECHhUJfSy=V6)DmIuPz;;f0Q_ZCBis%ohWx6@MtR{` zBpeSOslCi1e(Vo%DTpU$apUgdJR)W0nw(`Z&RYNUB%gS7JC0}P;??W$>Mi?2ZNX1Q z)4M;^NES=A6VxHFY_}(<1@K1xLV}v#XGVAA%K9SNd>9uIiJ^X7(W}UE;=@}NYS1)19MV1dw zY_k)7IF0;lLi)Fh}|Y zHA=V=_ktPD&~x1>Wu96`ytcbF4^zyGh#lh6c( z^nCNtJTUf0B&%ikr%tpcSv?JhQr`vYDE_u1Jz0P)^m9jIDe7c67pA7DU17*POHuEm zy^bza-(yeONoo=NgV1$MY}P|>yh!cMk?F1_$Zdsw8-J-MjO@EhL6)VQrRrGzpbd>% zhAH`?jlOD`nk|Vx5L}Jvwx2$)hSovKzea7&#_9vtsCziiZc0Vhsn_6y__R*ltT07i zxmDF=4EuJw)fcRxUOQF2!T878dfvV2Q&|}02i4UwKV3;*d`#`3!65sYrV_(;m2w_t zP*#Bc=DdpUb(ZUkE~<%=_Oh5^MQx-q*VIzlUXwbaM_*G{scibbyDDDo*P{XV)T!D` zA49b;eZHrbvbHg)TSLkJKH7T}wY#tOa$VI+bn_LPzn8Ii4x@|r(S2>`{e3mgiK)VK zFGdj2qz9^(=R$7`+!mg~kcry}Qvfj`>`U7oz!baXMe`o2Z z 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 From 7cae653287cc11e37ced96640358abe5cd9dda65 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 19 Jun 2019 15:10:46 -0400 Subject: [PATCH 30/33] Add more info to DataSourceIngestJob logging --- .../autopsy/ingest/DataSourceIngestJob.java | 67 ++++++++++++++----- .../autopsy/ingest/IngestManager.java | 2 +- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index 6563dd3540..bdf8d75505 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2018 Basis Technology Corp. + * Copyright 2014-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -286,7 +286,7 @@ public final class DataSourceIngestJob { this.addIngestModules(fileIngestModuleTemplates, IngestModuleType.FILE_LEVEL, skCase); this.addIngestModules(secondStageDataSourceModuleTemplates, IngestModuleType.DATA_SOURCE_LEVEL, skCase); } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to add ingest modules to database.", ex); + logErrorMessage(Level.WARNING, "Failed to add ingest modules listing to case database", ex); } } @@ -421,13 +421,13 @@ public final class DataSourceIngestJob { try { this.ingestJob = Case.getCurrentCaseThrows().getSleuthkitCase().addIngestJob(dataSource, NetworkUtils.getLocalHostName(), ingestModules, new Date(this.createTime), new Date(0), IngestJobStatusType.STARTED, ""); } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to add ingest job to database.", ex); + logErrorMessage(Level.WARNING, "Failed to add ingest job info to case database", ex); //NON-NLS } if (this.hasFirstStageDataSourceIngestPipeline() || this.hasFileIngestPipeline()) { - logger.log(Level.INFO, "Starting first stage analysis for {0} (jobId={1})", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Starting first stage analysis"); //NON-NLS this.startFirstStage(); } else if (this.hasSecondStageDataSourceIngestPipeline()) { - logger.log(Level.INFO, "Starting second stage analysis for {0} (jobId={1}), no first stage configured", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Starting second stage analysis"); //NON-NLS this.startSecondStage(); } } @@ -521,13 +521,13 @@ public final class DataSourceIngestJob { * Schedule the first stage tasks. */ if (this.hasFirstStageDataSourceIngestPipeline() && this.hasFileIngestPipeline()) { - logger.log(Level.INFO, "Scheduling first stage data source and file level analysis tasks for {0} (jobId={1})", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Scheduling first stage data source and file level analysis tasks"); //NON-NLS DataSourceIngestJob.taskScheduler.scheduleIngestTasks(this); } else if (this.hasFirstStageDataSourceIngestPipeline()) { - logger.log(Level.INFO, "Scheduling first stage data source level analysis tasks for {0} (jobId={1}), no file level analysis configured", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Scheduling first stage data source level analysis tasks"); //NON-NLS DataSourceIngestJob.taskScheduler.scheduleDataSourceIngestTask(this); } else { - logger.log(Level.INFO, "Scheduling file level analysis tasks for {0} (jobId={1}), no first stage data source level analysis configured", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Scheduling file level analysis tasks, no first stage data source level analysis configured"); //NON-NLS DataSourceIngestJob.taskScheduler.scheduleFileIngestTasks(this, this.files); /** @@ -546,7 +546,7 @@ public final class DataSourceIngestJob { * Starts the second stage of this ingest job. */ private void startSecondStage() { - logger.log(Level.INFO, "Starting second stage analysis for {0} (jobId={1})", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Starting second stage analysis"); //NON-NLS this.stage = DataSourceIngestJob.Stages.SECOND; if (this.doUI) { this.startDataSourceIngestProgressBar(); @@ -554,7 +554,7 @@ public final class DataSourceIngestJob { synchronized (this.dataSourceIngestPipelineLock) { this.currentDataSourceIngestPipeline = this.secondStageDataSourceIngestPipeline; } - logger.log(Level.INFO, "Scheduling second stage data source level analysis tasks for {0} (jobId={1})", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Scheduling second stage data source level analysis tasks"); //NON-NLS DataSourceIngestJob.taskScheduler.scheduleDataSourceIngestTask(this); } @@ -643,7 +643,7 @@ public final class DataSourceIngestJob { * job and starts the second stage, if appropriate. */ private void finishFirstStage() { - logger.log(Level.INFO, "Finished first stage analysis for {0} (jobId={1})", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Finished first stage analysis"); //NON-NLS // Shut down the file ingest pipelines. Note that no shut down is // required for the data source ingest pipeline because data source @@ -693,7 +693,7 @@ public final class DataSourceIngestJob { * Shuts down the ingest pipelines and progress bars for this job. */ private void finish() { - logger.log(Level.INFO, "Finished analysis for {0} (jobId={1})", new Object[]{dataSource.getName(), this.id}); //NON-NLS + logInfoMessage("Finished analysis"); //NON-NLS this.stage = DataSourceIngestJob.Stages.FINALIZATION; if (this.doUI) { @@ -711,19 +711,19 @@ public final class DataSourceIngestJob { try { ingestJob.setIngestJobStatus(IngestJobStatusType.CANCELLED); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to set ingest status for ingest job in database.", ex); + logErrorMessage(Level.WARNING, "Failed to update ingest job status in case database", ex); } } else { try { ingestJob.setIngestJobStatus(IngestJobStatusType.COMPLETED); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to set ingest status for ingest job in database.", ex); + logErrorMessage(Level.WARNING, "Failed to update ingest job status in case database", ex); } } try { this.ingestJob.setEndDateTime(new Date()); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to set end date for ingest job in database.", ex); + logErrorMessage(Level.WARNING, "Failed to set job end date in case database", ex); } } this.parentJob.dataSourceJobFinished(this); @@ -842,6 +842,7 @@ public final class DataSourceIngestJob { if (DataSourceIngestJob.Stages.FIRST == this.stage) { DataSourceIngestJob.taskScheduler.fastTrackFileIngestTasks(this, files); } else { + logErrorMessage(Level.SEVERE, "Adding files to job during second stage analysis not supported"); DataSourceIngestJob.logger.log(Level.SEVERE, "Adding files during second stage not supported"); //NON-NLS } @@ -1066,6 +1067,40 @@ public final class DataSourceIngestJob { return this.cancellationReason; } + /** + * Writes an info message to the application log that includes the data + * source name, data source object id, and the job id. + * + * @param message The message. + */ + private void logInfoMessage(String message) { + logger.log(Level.INFO, String.format("%s (data source = %s, objId = %d, jobId = %d)", message, dataSource.getName(), dataSource.getId(), id)); //NON-NLS + } + + /** + * Writes an error message to the application log that includes the data + * source name, data source object id, and the job id. + * + * @param level The logging level for the message. + * @param message The message. + * @param throwable The error associated with the error, may be null. + */ + private void logErrorMessage(Level level, String message, Throwable throwable) { + logger.log(level, String.format("%s (data source = %s, objId = %d, jobId = %d)", message, dataSource.getName(), dataSource.getId(), id), throwable); //NON-NLS + } + + /** + * Writes an error message to the application log that includes the data + * source name, data source object id, and the job id. + * + * @param level The logging level for the message. + * @param message The message. + * @param ex The exception associated with the error, may be null. + */ + private void logErrorMessage(Level level, String message) { + logger.log(level, String.format("%s (data source = %s, objId = %d, jobId = %d)", message, dataSource.getName(), dataSource.getId(), id)); //NON-NLS + } + /** * Write ingest module errors to the log. * @@ -1073,7 +1108,7 @@ public final class DataSourceIngestJob { */ private void logIngestModuleErrors(List errors) { for (IngestModuleError error : errors) { - DataSourceIngestJob.logger.log(Level.SEVERE, String.format("%s experienced an error analyzing %s (jobId=%d)", error.getModuleDisplayName(), dataSource.getName(), this.id), error.getThrowable()); //NON-NLS + logErrorMessage(Level.SEVERE, String.format("%s experienced an error during analysis", error.getModuleDisplayName()), error.getThrowable()); //NON-NLS } } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index d5a2240ca5..8205621450 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -403,10 +403,10 @@ public class IngestManager implements IngestProgressSnapshotProvider { synchronized (ingestJobsById) { ingestJobsById.put(job.getId(), job); } + IngestManager.logger.log(Level.INFO, "Starting ingest job {0}", job.getId()); //NON-NLS errors = job.start(); if (errors.isEmpty()) { this.fireIngestJobStarted(job.getId()); - IngestManager.logger.log(Level.INFO, "Ingest job {0} started", job.getId()); //NON-NLS } else { synchronized (ingestJobsById) { this.ingestJobsById.remove(job.getId()); From 8920e94dedeb85bc65609ee67362bea654e76268 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 19 Jun 2019 15:15:39 -0400 Subject: [PATCH 31/33] Add more info to DataSourceIngestJob logging --- Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java | 3 +-- Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index bdf8d75505..674f8488af 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -843,7 +843,6 @@ public final class DataSourceIngestJob { DataSourceIngestJob.taskScheduler.fastTrackFileIngestTasks(this, files); } else { logErrorMessage(Level.SEVERE, "Adding files to job during second stage analysis not supported"); - DataSourceIngestJob.logger.log(Level.SEVERE, "Adding files during second stage not supported"); //NON-NLS } /** @@ -1095,7 +1094,7 @@ public final class DataSourceIngestJob { * * @param level The logging level for the message. * @param message The message. - * @param ex The exception associated with the error, may be null. + * @param ex The exception associated with the error. */ private void logErrorMessage(Level level, String message) { logger.log(level, String.format("%s (data source = %s, objId = %d, jobId = %d)", message, dataSource.getName(), dataSource.getId(), id)); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 8205621450..b59e0a6ccb 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2018 Basis Technology Corp. + * Copyright 2012-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); From cded8ca655d53a822b43b0ce1674c1f7465980a1 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 19 Jun 2019 15:17:20 -0400 Subject: [PATCH 32/33] Add more info to DataSourceIngestJob logging --- Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index 674f8488af..22745a3c54 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -1082,7 +1082,7 @@ public final class DataSourceIngestJob { * * @param level The logging level for the message. * @param message The message. - * @param throwable The error associated with the error, may be null. + * @param throwable The throwable associated with the error. */ private void logErrorMessage(Level level, String message, Throwable throwable) { logger.log(level, String.format("%s (data source = %s, objId = %d, jobId = %d)", message, dataSource.getName(), dataSource.getId(), id), throwable); //NON-NLS @@ -1094,7 +1094,6 @@ public final class DataSourceIngestJob { * * @param level The logging level for the message. * @param message The message. - * @param ex The exception associated with the error. */ private void logErrorMessage(Level level, String message) { logger.log(level, String.format("%s (data source = %s, objId = %d, jobId = %d)", message, dataSource.getName(), dataSource.getId(), id)); //NON-NLS From 08d9b97bcd9d96cd48a862e823b5863d7a1146f7 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 21 Jun 2019 13:08:31 -0400 Subject: [PATCH 33/33] Update versioning for TSK 4.6.7, Autopsy 4.12.0 releases --- Core/nbproject/project.properties | 2 +- Core/nbproject/project.xml | 4 ++-- TSKVersion.xml | 2 +- docs/doxygen-user/Doxyfile | 4 ++-- docs/doxygen/Doxyfile | 4 ++-- nbproject/project.properties | 6 +++--- unix_setup.sh | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 8209da0221..5e07fadf28 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -75,7 +75,7 @@ file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0 file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar -file.reference.sleuthkit-postgresql-4.6.6.jar=release/modules/ext/sleuthkit-postgresql-4.6.6.jar +file.reference.sleuthkit-postgresql-4.6.7.jar=release/modules/ext/sleuthkit-postgresql-4.6.7.jar file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 3dbc3069e3..967399cc20 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -482,8 +482,8 @@ release\modules\ext\jmatio-1.5.jar - ext/sleuthkit-postgresql-4.6.6.jar - release/modules/ext/sleuthkit-postgresql-4.6.6.jar + ext/sleuthkit-postgresql-4.6.7.jar + release/modules/ext/sleuthkit-postgresql-4.6.7.jar ext/tika-parsers-1.20.jar diff --git a/TSKVersion.xml b/TSKVersion.xml index 3c03006088..7e65087fae 100644 --- a/TSKVersion.xml +++ b/TSKVersion.xml @@ -1,3 +1,3 @@ - + diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile index 6f16153151..7991a0cd95 100644 --- a/docs/doxygen-user/Doxyfile +++ b/docs/doxygen-user/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.11.0 +PROJECT_NUMBER = 4.12.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.11.0 +HTML_OUTPUT = 4.12.0 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index a7463ac441..bf66159b48 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.11.0 +PROJECT_NUMBER = 4.12.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears a the top of each page and should give viewer a @@ -1063,7 +1063,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api-docs/4.11.0/ +HTML_OUTPUT = api-docs/4.12.0/ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/nbproject/project.properties b/nbproject/project.properties index dbc771a543..0014be6b88 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,10 +4,10 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.11.0 +app.version=4.12.0 ### build.type must be one of: DEVELOPMENT, RELEASE -#build.type=RELEASE -build.type=DEVELOPMENT +build.type=RELEASE +#build.type=DEVELOPMENT project.org.netbeans.progress=org-netbeans-api-progress project.org.sleuthkit.autopsy.experimental=Experimental diff --git a/unix_setup.sh b/unix_setup.sh index 0b252b2598..eecf1ed8c5 100644 --- a/unix_setup.sh +++ b/unix_setup.sh @@ -5,7 +5,7 @@ # NOTE: update_sleuthkit_version.pl updates this value and relies # on it keeping the same name and whitespace. Don't change it. -TSK_VERSION=4.6.6 +TSK_VERSION=4.6.7 # In the beginning...