diff --git a/Core/ivy.xml b/Core/ivy.xml index eea67feb19..35cb8de904 100755 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -22,5 +22,6 @@ + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 7c36c61db7..b6931ff315 100755 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -5,6 +5,7 @@ file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1. file.reference.commons-pool2-2.4.2.jar=release\\modules\\ext\\commons-pool2-2.4.2.jar file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar +file.reference.jsoup-1.10.3.jar=release/modules/ext/jsoup-1.10.3.jar file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar file.reference.metadata-extractor-2.9.1.jar=release/modules/ext/metadata-extractor-2.9.1.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 525e6da6a7..c4fea0885c 100755 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -320,6 +320,10 @@ org.sleuthkit.autopsy.report org.sleuthkit.datamodel + + ext/xmpcore-5.1.2.jar + release/modules/ext/xmpcore-5.1.2.jar + ext/zookeeper-3.4.6.jar release/modules/ext/zookeeper-3.4.6.jar @@ -335,6 +339,10 @@ ext/Tsk_DataModel_PostgreSQL.jar release/modules/ext/Tsk_DataModel_PostgreSQL.jar + + + ext/StixLib.jar + release/modules/ext/StixLib.jar ext/opencv-248.jar @@ -344,6 +352,10 @@ ext/curator-framework-2.8.0.jar release/modules/ext/curator-framework-2.8.0.jar + + ext/curator-client-2.8.0.jar + release/modules/ext/curator-client-2.8.0.jar + ext/commons-dbcp2-2.1.1.jar release\modules\ext\commons-dbcp2-2.1.1.jar @@ -356,6 +368,14 @@ ext/jython-standalone-2.7.0.jar release/modules/ext/jython-standalone-2.7.0.jar + + ext/activemq-all-5.11.1.jar + release/modules/ext/activemq-all-5.11.1.jar + + + ext/opencv-248.jar + release/modules/ext/opencv-248.jar + ext/sevenzipjbinding.jar release/modules/ext/sevenzipjbinding.jar @@ -377,12 +397,12 @@ release/modules/ext/xmpcore-5.1.2.jar - ext/StixLib.jar - release/modules/ext/StixLib.jar + ext/sevenzipjbinding-AllPlatforms.jar + release/modules/ext/sevenzipjbinding-AllPlatforms.jar - ext/curator-client-2.8.0.jar - release/modules/ext/curator-client-2.8.0.jar + ext/commons-pool2-2.4.2.jar + release\modules\ext\commons-pool2-2.4.2.jar ext/sqlite-jdbc-3.8.11.jar @@ -392,18 +412,14 @@ ext/activemq-all-5.11.1.jar release/modules/ext/activemq-all-5.11.1.jar + + ext/jsoup-1.10.3.jar + release/modules/ext/jsoup-1.10.3.jar + ext/Rejistry-1.0-SNAPSHOT.jar release/modules/ext/Rejistry-1.0-SNAPSHOT.jar - - ext/sevenzipjbinding-AllPlatforms.jar - release/modules/ext/sevenzipjbinding-AllPlatforms.jar - - - ext/commons-pool2-2.4.2.jar - release\modules\ext\commons-pool2-2.4.2.jar - ext/metadata-extractor-2.9.1.jar release/modules/ext/metadata-extractor-2.9.1.jar @@ -420,6 +436,10 @@ ext/c3p0-0.9.5.jar release/modules/ext/c3p0-0.9.5.jar + + ext/c3p0-0.9.5.jar + release/modules/ext/c3p0-0.9.5.jar + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 52a2b7fa97..e2e58c5b5a 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -76,6 +76,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.services.Services; +import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; @@ -1050,6 +1051,7 @@ public class Case { CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); + CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true); CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); /* @@ -1092,24 +1094,13 @@ public class Case { /* * Disable the case-specific menu items. */ - CallableSystemAction - .get(AddImageAction.class - ).setEnabled(false); - CallableSystemAction - .get(CaseCloseAction.class - ).setEnabled(false); - CallableSystemAction - .get(CasePropertiesAction.class - ).setEnabled(false); - CallableSystemAction - .get(CaseDeleteAction.class - ).setEnabled(false); - CallableSystemAction - .get(OpenTimelineAction.class - ).setEnabled(false); - CallableSystemAction - .get(OpenOutputFolderAction.class - ).setEnabled(false); + CallableSystemAction.get(AddImageAction.class).setEnabled(false); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); + CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); + CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false); + CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); /* * Clear the notifications in the notfier component in the lower diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java new file mode 100644 index 0000000000..69761aa12f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java @@ -0,0 +1,78 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * 'Root' Node for the Account/Messages area. Has children which are all the + * relationships of all the accounts in this node. + * + */ +class AccountDetailsNode extends AbstractNode { + + private final static Logger logger = Logger.getLogger(AccountDetailsNode.class.getName()); + + AccountDetailsNode(Set accountDeviceInstances, CommunicationsFilter filter, CommunicationsManager commsManager) { + super(Children.create(new AccountRelationshipChildren(accountDeviceInstances, commsManager, filter), true)); + } + + /** + * Children object for the relationships that the accounts are part of. + */ + private static class AccountRelationshipChildren extends ChildFactory { + + private final Set accountDeviceInstances; + private final CommunicationsManager commsManager; + private final CommunicationsFilter filter; + + private AccountRelationshipChildren(Set accountDeviceInstances, CommunicationsManager commsManager, CommunicationsFilter filter) { + this.accountDeviceInstances = accountDeviceInstances; + this.commsManager = commsManager; + this.filter = filter; + } + + @Override + protected boolean createKeys(List list) { + try { + list.addAll(commsManager.getRelationshipSources(accountDeviceInstances, filter)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting communications", ex); + } + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact t) { + return new RelationShipNode(t); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java new file mode 100644 index 0000000000..92b6938ac0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java @@ -0,0 +1,54 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.CommunicationsFilter; + +/** + * Key for AccountDeviceInstance node. + * + * Encapsulates a AccountDeviceInstance, and CommunicationsFilter. + */ +class AccountDeviceInstanceKey { + + private final AccountDeviceInstance accountDeviceInstance; + private final CommunicationsFilter filter; + private final long messageCount; + + + + AccountDeviceInstanceKey(AccountDeviceInstance accountDeviceInstance, CommunicationsFilter filter, long msgCount) { + this.accountDeviceInstance = accountDeviceInstance; + this.filter = filter; + this.messageCount = msgCount; + } + + AccountDeviceInstance getAccountDeviceInstance() { + return accountDeviceInstance; + } + + CommunicationsFilter getCommunicationsFilter() { + return filter; + } + + long getMessageCount() { + return messageCount; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.form b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.form new file mode 100644 index 0000000000..83a3e75e9a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.form @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java new file mode 100644 index 0000000000..8e418d65fb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java @@ -0,0 +1,129 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.awt.Component; +import javax.swing.JPanel; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.table.TableCellRenderer; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; + +/** + * A panel that goes in the Browse tab of the Communications Visualization Tool. + * Hosts an OutlineView that shows information about Accounts. + */ +public class AccountsBrowser extends JPanel { + + private static final long serialVersionUID = 1L; + + private final Outline outline; + private ExplorerManager em; + + /** + * Creates new form AccountsBrowser + */ + public AccountsBrowser() { + initComponents(); + outline = outlineView.getOutline(); + outlineView.setPropertyColumns( + "device", Bundle.AccountNode_device(), + "type", Bundle.AccountNode_accountType(), + "count", Bundle.AccountNode_messageCount() + ); + + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AccountNode_accountName()); + outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + outline.setColumnSorted(3, false, 1); //it would be nice if the column index wasn't hardcoded + } + + @Override + public void addNotify() { + super.addNotify(); + em = ExplorerManager.find(this); + em.addPropertyChangeListener(evt -> { + if (ExplorerManager.PROP_ROOT_CONTEXT.equals(evt.getPropertyName())) { + SwingUtilities.invokeLater(this::setColumnWidths); + } else if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) { + SwingUtilities.invokeLater(this::setColumnWidths); + } + }); + } + + private void setColumnWidths() { + int margin = 4; + int padding = 8; + + final int rows = Math.min(100, outline.getRowCount()); + + for (int column = 0; column < outline.getModel().getColumnCount(); column++) { + int columnWidthLimit = 500; + int columnWidth = 0; + + // find the maximum width needed to fit the values for the first 100 rows, at most + for (int row = 0; row < rows; row++) { + TableCellRenderer renderer = outline.getCellRenderer(row, column); + Component comp = outline.prepareRenderer(renderer, row, column); + columnWidth = Math.max(comp.getPreferredSize().width, columnWidth); + } + + columnWidth += 2 * margin + padding; // add margin and regular padding + columnWidth = Math.min(columnWidth, columnWidthLimit); + + outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth); + } + } + + /** + * 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() { + + outlineView = new org.openide.explorer.view.OutlineView(); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.openide.explorer.view.OutlineView outlineView; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsRootChildren.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsRootChildren.java new file mode 100644 index 0000000000..9b0b5081d2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsRootChildren.java @@ -0,0 +1,142 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +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.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +class AccountsRootChildren extends ChildFactory { + + private static final Logger logger = Logger.getLogger(AccountsRootChildren.class.getName()); + + private final CommunicationsManager commsManager; + private final CommunicationsFilter commsFilter; + + AccountsRootChildren(CommunicationsManager commsManager, CommunicationsFilter commsFilter) { + super(); + this.commsManager = commsManager; + this.commsFilter = commsFilter; + } + + @Override + protected boolean createKeys(List list) { + List accountDeviceInstanceKeys = new ArrayList<>(); + try { + for (AccountDeviceInstance adi : commsManager.getAccountDeviceInstancesWithRelationships(commsFilter)) { + long communicationsCount = commsManager.getRelationshipSourcesCount(adi, commsFilter); + accountDeviceInstanceKeys.add(new AccountDeviceInstanceKey(adi, commsFilter, communicationsCount)); + }; + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error getting filtered account device instances", tskCoreException); + } + list.addAll(accountDeviceInstanceKeys); + + return true; + } + + @Override + protected Node createNodeForKey(AccountDeviceInstanceKey key) { + return new AccountDeviceInstanceNode(key, commsManager); + } + + /** + * Node to represent an Account in the AccountsBrowser + */ + static class AccountDeviceInstanceNode extends AbstractNode { + + private final AccountDeviceInstanceKey accountDeviceInstanceKey; + private final CommunicationsManager commsManager; + private final Account account; + + private AccountDeviceInstanceNode(AccountDeviceInstanceKey accountDeviceInstanceKey, CommunicationsManager commsManager) { + super(Children.LEAF, Lookups.fixed(accountDeviceInstanceKey, commsManager)); + this.accountDeviceInstanceKey = accountDeviceInstanceKey; + this.commsManager = commsManager; + this.account = accountDeviceInstanceKey.getAccountDeviceInstance().getAccount(); + setName(account.getTypeSpecificID()); + setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/" + Utils.getIconFileName(account.getAccountType())); + } + + public AccountDeviceInstance getAccountDeviceInstance() { + return accountDeviceInstanceKey.getAccountDeviceInstance(); + } + + public CommunicationsManager getCommsManager() { + return commsManager; + } + + public CommunicationsFilter getFilter() { + return accountDeviceInstanceKey.getCommunicationsFilter(); + } + + @Override + @NbBundle.Messages(value = {"AccountNode.device=Device", + "AccountNode.accountName=Account", + "AccountNode.accountType=Type", + "AccountNode.messageCount=Msgs"}) + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set properties = s.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + s.put(properties); + } + + properties.put(new NodeProperty<>("type", Bundle.AccountNode_accountType(), "type", + account.getAccountType().getDisplayName())); // NON-NLS + properties.put(new NodeProperty<>("count", Bundle.AccountNode_messageCount(), "count", + accountDeviceInstanceKey.getMessageCount())); // NON-NLS + properties.put(new NodeProperty<>("device", Bundle.AccountNode_device(), "device", + getDataSourceName())); // NON-NLS + return s; + } + + private String getDataSourceName() { + try { + final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + for (DataSource dataSource : sleuthkitCase.getDataSources()) { + if (dataSource.getDeviceId().equals(getAccountDeviceInstance().getDeviceId())) { + return sleuthkitCase.getContentById(dataSource.getId()).getName(); + } + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting datasource name, falling back on device ID.", ex); + } + return getAccountDeviceInstance().getDeviceId(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties new file mode 100644 index 0000000000..af6d79d8ab --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -0,0 +1,14 @@ +CVTTopComponent.TabConstraints.tabTitle=Visualize +CVTTopComponent.accountsBrowser.TabConstraints.tabTitle=Browse +FiltersPanel.applyFiltersButton.text=Apply +FiltersPanel.devicesLabel.text=Devices: +FiltersPanel.accountTypesLabel.text=Account Types: +FiltersPanel.filtersTitleLabel.text=Filters +FiltersPanel.unCheckAllAccountTypesButton.text=Uncheck All +FiltersPanel.checkAllAccountTypesButton.text=Check All +FiltersPanel.unCheckAllDevicesButton.text=Uncheck All +FiltersPanel.checkAllDevicesButton.text=Check All +FiltersPanel.dateRangeLabel.text=Date Range: +FiltersPanel.startCheckBox.text=Start: +FiltersPanel.endCheckBox.text=End: +FiltersPanel.refreshButton.text=Refresh diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form new file mode 100644 index 0000000000..e8d83b24f1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java new file mode 100644 index 0000000000..4099af0bfa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -0,0 +1,158 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.util.List; +import java.util.stream.Collectors; +import org.openide.explorer.ExplorerManager; +import org.openide.util.NbBundle; +import org.openide.windows.Mode; +import org.openide.windows.RetainLocation; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; + +/** + * Top component which displays the Communications Visualization Tool. + */ +@TopComponent.Description( + preferredID = "CVTTopComponent", + //iconBase="SET/PATH/TO/ICON/HERE", //use this to put icon in window title area, + persistenceType = TopComponent.PERSISTENCE_NEVER) +@TopComponent.Registration(mode = "cvt", openAtStartup = false) +@RetainLocation("cvt") +@NbBundle.Messages("CVTTopComponent.name= Communications Visualization") +public final class CVTTopComponent extends TopComponent implements ExplorerManager.Provider { + + private static final long serialVersionUID = 1L; + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private final ExplorerManager em = new ExplorerManager(); + + public CVTTopComponent() { + initComponents(); + browseVisualizeTabPane.setEnabledAt(1, false); + setName(Bundle.CVTTopComponent_name()); + splitPane.setRightComponent(new MessageBrowser()); + } + + /** + * 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. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + splitPane = new javax.swing.JSplitPane(); + browseVisualizeTabPane = new javax.swing.JTabbedPane(); + accountsBrowser = new org.sleuthkit.autopsy.communications.AccountsBrowser(); + jPanel1 = new javax.swing.JPanel(); + filtersPane = new org.sleuthkit.autopsy.communications.FiltersPanel(); + + splitPane.setDividerLocation(400); + splitPane.setResizeWeight(0.7); + + browseVisualizeTabPane.setFont(new java.awt.Font("Tahoma", 0, 18)); // NOI18N + browseVisualizeTabPane.addTab(org.openide.util.NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle"), new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N + + jPanel1.setName(""); // NOI18N + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 397, Short.MAX_VALUE) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 725, Short.MAX_VALUE) + ); + + browseVisualizeTabPane.addTab(org.openide.util.NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.TabConstraints.tabTitle"), new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), jPanel1); // NOI18N + + splitPane.setLeftComponent(browseVisualizeTabPane); + + filtersPane.setMinimumSize(new java.awt.Dimension(256, 495)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addComponent(filtersPane, javax.swing.GroupLayout.PREFERRED_SIZE, 250, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(splitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1339, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(filtersPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(splitPane)) + .addGap(5, 5, 5)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.communications.AccountsBrowser accountsBrowser; + private javax.swing.JTabbedPane browseVisualizeTabPane; + private org.sleuthkit.autopsy.communications.FiltersPanel filtersPane; + private javax.swing.JPanel jPanel1; + private javax.swing.JSplitPane splitPane; + // End of variables declaration//GEN-END:variables + + @Override + public void componentOpened() { + super.componentOpened(); + WindowManager.getDefault().setTopComponentFloating(this, true); + } + + @Override + public ExplorerManager getExplorerManager() { + return em; + } + + @Override + public void open() { + super.open(); + /* + * when the window is (re)opened make sure the filters and accounts are + * in an up to date and consistent state. + * + * Re-applying the filters means we will lose the selection... + */ + filtersPane.updateAndApplyFilters(); + } + + @Override + public List availableModes(List modes) { + /* + * This looks like the right thing to do, but online discussions seems + * to indicate this method is effectively deprecated. A break point + * placed here was never hit. + */ + return modes.stream().filter(mode -> mode.getName().equals("cvt")) + .collect(Collectors.toList()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form new file mode 100644 index 0000000000..da402cb4e6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java new file mode 100644 index 0000000000..93e3230576 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -0,0 +1,556 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import com.google.common.collect.ImmutableSet; +import java.beans.PropertyChangeListener; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.swing.JCheckBox; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; +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.IngestModuleEvent.DATA_ADDED; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; +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.MESSAGE; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Panel that holds the Filter control widgets and translates user filtering + * changes into queries against the CommunicationsManager. + */ +final public class FiltersPanel extends javax.swing.JPanel { + + private static final Logger logger = Logger.getLogger(FiltersPanel.class.getName()); + private static final long serialVersionUID = 1L; + + private ExplorerManager em; + + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private final Map accountTypeMap = new HashMap<>(); + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private final Map devicesMap = new HashMap<>(); + + private final PropertyChangeListener ingestListener; + + @NbBundle.Messages({"refreshText=Refresh Results", + "applyText=Apply"}) + public FiltersPanel() { + initComponents(); + startDatePicker.setDate(LocalDate.now().minusWeeks(3)); + endDatePicker.setDateToToday(); + startDatePicker.getSettings().setVetoPolicy( + //no end date, or start is before end + startDate -> endCheckBox.isSelected() == false + || startDate.compareTo(endDatePicker.getDate()) <= 0 + ); + endDatePicker.getSettings().setVetoPolicy( + //no start date, or end is after start + endDate -> startCheckBox.isSelected() == false + || endDate.compareTo(startDatePicker.getDate()) >= 0 + ); + + updateTimeZone(); + updateFilters(); + UserPreferences.addChangeListener(preferenceChangeEvent -> { + if (preferenceChangeEvent.getKey().equals(UserPreferences.DISPLAY_TIMES_IN_LOCAL_TIME)) { + updateTimeZone(); + } + }); + + this.ingestListener = pce -> { + String eventType = pce.getPropertyName(); + if (eventType.equals(DATA_ADDED.toString())) { + updateFilters(); + refreshButton.setEnabled(true); + } + }; + + applyFiltersButton.addActionListener(e -> applyFilters()); + refreshButton.addActionListener(e -> applyFilters()); + } + + /** + * Update the filter widgets, and apply them. + */ + void updateAndApplyFilters() { + updateFilters(); + if (em != null) { + applyFilters(); + } + } + + private void updateTimeZone() { + dateRangeLabel.setText("Date Range ( " + Utils.getUserPreferredZoneId().toString() + "):"); + } + + private void updateFilters() { + updateAccountTypeFilter(); + updateDeviceFilter(); + } + + @Override + public void addNotify() { + super.addNotify(); + /* + * Since we get the exploreremanager from the parent JComponenet, wait + * till this FiltersPanel is actaully added to a parent. + */ + em = ExplorerManager.find(this); + IngestManager.getInstance().addIngestModuleEventListener(ingestListener); + Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { + devicesMap.clear(); + devicesPane.removeAll(); + }); + } + + @Override + public void removeNotify() { + super.removeNotify(); + IngestManager.getInstance().removeIngestModuleEventListener(ingestListener); + } + + /** + * Populate the Account Types filter widgets + */ + 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.getCurrentCase().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 if (type.equals(Account.Type.DEVICE)) { + //don't show a check box fro device + } else { + accountTypeMap.computeIfAbsent(type, t -> { + final JCheckBox jCheckBox = new JCheckBox( + "" + type.getDisplayName() + "", + true + ); + accountTypePane.add(jCheckBox); + return jCheckBox; + }); + } + } + ); + } + + /** + * Populate the devices filter widgets + */ + private void updateDeviceFilter() { + try { + final SleuthkitCase sleuthkitCase = Case.getCurrentCase().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, false); + devicesPane.add(jCheckBox); + return jCheckBox; + }); + }; + + } catch (IllegalStateException ex) { + logger.log(Level.WARNING, "Communications Visualization Tool opened with no open case.", ex); + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "There was a error loading the datasources for the case.", tskCoreException); + } + } + + + /** + * 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() { + + applyFiltersButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/tick.png"))); // NOI18N + applyFiltersButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.applyFiltersButton.text")); // NOI18N + applyFiltersButton.setPreferredSize(null); + + filtersTitleLabel.setFont(new java.awt.Font("Tahoma", 0, 16)); // NOI18N + filtersTitleLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/funnel.png"))); // NOI18N + filtersTitleLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.filtersTitleLabel.text")); // NOI18N + + unCheckAllAccountTypesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllAccountTypesButton.text")); // NOI18N + unCheckAllAccountTypesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + unCheckAllAccountTypesButtonActionPerformed(evt); + } + }); + + accountTypesLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/accounts.png"))); // NOI18N + accountTypesLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.accountTypesLabel.text")); // NOI18N + + checkAllAccountTypesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.checkAllAccountTypesButton.text")); // NOI18N + checkAllAccountTypesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkAllAccountTypesButtonActionPerformed(evt); + } + }); + + accountTypePane.setLayout(new javax.swing.BoxLayout(accountTypePane, javax.swing.BoxLayout.Y_AXIS)); + jScrollPane3.setViewportView(accountTypePane); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel2Layout.createSequentialGroup() + .addComponent(accountTypesLabel) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jScrollPane3) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(unCheckAllAccountTypesButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(checkAllAccountTypesButton))))) + .addGap(0, 0, 0)) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(accountTypesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkAllAccountTypesButton) + .addComponent(unCheckAllAccountTypesButton))) + ); + + unCheckAllDevicesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllDevicesButton.text")); // NOI18N + unCheckAllDevicesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + unCheckAllDevicesButtonActionPerformed(evt); + } + }); + + devicesLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/image.png"))); // NOI18N + devicesLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.devicesLabel.text")); // NOI18N + + checkAllDevicesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.checkAllDevicesButton.text")); // NOI18N + checkAllDevicesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkAllDevicesButtonActionPerformed(evt); + } + }); + + jScrollPane2.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + jScrollPane2.setMinimumSize(new java.awt.Dimension(27, 75)); + + devicesPane.setMinimumSize(new java.awt.Dimension(4, 100)); + devicesPane.setLayout(new javax.swing.BoxLayout(devicesPane, javax.swing.BoxLayout.Y_AXIS)); + jScrollPane2.setViewportView(devicesPane); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(devicesLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(unCheckAllDevicesButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(checkAllDevicesButton)) + .addComponent(jScrollPane2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(devicesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 96, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkAllDevicesButton) + .addComponent(unCheckAllDevicesButton)) + .addGap(5, 5, 5)) + ); + + startDatePicker.setEnabled(false); + + dateRangeLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/calendar.png"))); // NOI18N + dateRangeLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.dateRangeLabel.text")); // NOI18N + + startCheckBox.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.startCheckBox.text")); // NOI18N + startCheckBox.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + startCheckBoxStateChanged(evt); + } + }); + + endCheckBox.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.endCheckBox.text")); // NOI18N + endCheckBox.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + endCheckBoxStateChanged(evt); + } + }); + + endDatePicker.setEnabled(false); + + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addComponent(dateRangeLabel) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addComponent(endCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(endDatePicker, javax.swing.GroupLayout.PREFERRED_SIZE, 163, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel4Layout.createSequentialGroup() + .addComponent(startCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(startDatePicker, javax.swing.GroupLayout.PREFERRED_SIZE, 162, javax.swing.GroupLayout.PREFERRED_SIZE)))) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addComponent(dateRangeLabel) + .addGap(6, 6, 6) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(startDatePicker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(startCheckBox)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(endDatePicker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(endCheckBox))) + ); + + refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N + refreshButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.refreshButton.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(filtersTitleLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(applyFiltersButton, javax.swing.GroupLayout.PREFERRED_SIZE, 83, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(refreshButton)) + .addComponent(jPanel4, 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() + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(filtersTitleLabel) + .addComponent(applyFiltersButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(refreshButton)) + .addGap(18, 18, 18) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(21, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + /** + * Query for accounts using the selected filters, and send the results to + * the AccountsBrowser via the ExplorerManager. + */ + private void applyFilters() { + CommunicationsFilter commsFilter = new CommunicationsFilter(); + commsFilter.addAndFilter(getDeviceFilter()); + commsFilter.addAndFilter(getAccountTypeFilter()); + commsFilter.addAndFilter(getDateRangeFilter()); + commsFilter.addAndFilter(new CommunicationsFilter.RelationshipTypeFilter( + ImmutableSet.of(CALL_LOG, MESSAGE))); + + try { + final CommunicationsManager commsManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager(); + em.setRootContext(new AbstractNode(Children.create(new AccountsRootChildren(commsManager, commsFilter), true))); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "There was an error getting the CommunicationsManager for the current case.", ex); + } + + refreshButton.setEnabled(false); + } + + /** + * Get a DeviceFilter that matches the state of the UI widgets. + * + * @return a DeviceFilter + */ + private DeviceFilter getDeviceFilter() { + DeviceFilter deviceFilter = new DeviceFilter( + devicesMap.entrySet().stream() + .filter(entry -> entry.getValue().isSelected()) + .map(Entry::getKey) + .collect(Collectors.toSet())); + return deviceFilter; + } + + /** + * Get an AccountTypeFilter that matches the state of the UI widgets + * + * @return an AccountTypeFilter + */ + private AccountTypeFilter getAccountTypeFilter() { + AccountTypeFilter accountTypeFilter = new AccountTypeFilter(accountTypeMap.entrySet().stream() + .filter(entry -> entry.getValue().isSelected()) + .map(entry -> entry.getKey()).collect(Collectors.toSet())); + return accountTypeFilter; + } + + private DateRangeFilter getDateRangeFilter() { + ZoneId zone = Utils.getUserPreferredZoneId(); + long start = startDatePicker.isEnabled() ? startDatePicker.getDate().atStartOfDay(zone).toEpochSecond() : 0; + long end = endDatePicker.isEnabled() ? endDatePicker.getDate().atStartOfDay(zone).toEpochSecond() : 0; + return new DateRangeFilter(start, end); + } + + /** + * Set the selection state of all the account type check boxes + * + * @param selected The selection state to set the check boxes to. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void setAllAccountTypesSelected(boolean selected) { + setAllSelected(accountTypeMap, selected); + } + + /** + * Set the selection state of all the device check boxes + * + * @param selected The selection state to set the check boxes to. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void setAllDevicesSelected(boolean selected) { + setAllSelected(devicesMap, selected); + } + + /** + * Helper method that sets all the checkboxes in the given map to the given + * selection state. + * + * @param map A map from anything to JCheckBoxes. + * @param selected The selection state to set all the checkboxes to. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private void setAllSelected(Map, JCheckBox> map, boolean selected) { + map.values().forEach(box -> box.setSelected(selected)); + } + private void unCheckAllAccountTypesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_unCheckAllAccountTypesButtonActionPerformed + setAllAccountTypesSelected(false); + }//GEN-LAST:event_unCheckAllAccountTypesButtonActionPerformed + + private void checkAllAccountTypesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkAllAccountTypesButtonActionPerformed + setAllAccountTypesSelected(true); + }//GEN-LAST:event_checkAllAccountTypesButtonActionPerformed + + private void unCheckAllDevicesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_unCheckAllDevicesButtonActionPerformed + setAllDevicesSelected(false); + }//GEN-LAST:event_unCheckAllDevicesButtonActionPerformed + + private void checkAllDevicesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkAllDevicesButtonActionPerformed + setAllDevicesSelected(true); + }//GEN-LAST:event_checkAllDevicesButtonActionPerformed + + private void startCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_startCheckBoxStateChanged + startDatePicker.setEnabled(startCheckBox.isSelected()); + }//GEN-LAST:event_startCheckBoxStateChanged + + private void endCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_endCheckBoxStateChanged + endDatePicker.setEnabled(endCheckBox.isSelected()); + }//GEN-LAST:event_endCheckBoxStateChanged + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private final javax.swing.JPanel accountTypePane = new javax.swing.JPanel(); + private final javax.swing.JLabel accountTypesLabel = new javax.swing.JLabel(); + private final javax.swing.JButton applyFiltersButton = new javax.swing.JButton(); + private final javax.swing.JButton checkAllAccountTypesButton = new javax.swing.JButton(); + private final javax.swing.JButton checkAllDevicesButton = new javax.swing.JButton(); + private final javax.swing.JLabel dateRangeLabel = new javax.swing.JLabel(); + private final javax.swing.JLabel devicesLabel = new javax.swing.JLabel(); + private final javax.swing.JPanel devicesPane = new javax.swing.JPanel(); + private final javax.swing.JCheckBox endCheckBox = new javax.swing.JCheckBox(); + private final com.github.lgooddatepicker.components.DatePicker endDatePicker = new com.github.lgooddatepicker.components.DatePicker(); + private final javax.swing.JLabel filtersTitleLabel = new javax.swing.JLabel(); + private final javax.swing.JPanel jPanel2 = new javax.swing.JPanel(); + private final javax.swing.JPanel jPanel3 = new javax.swing.JPanel(); + private final javax.swing.JPanel jPanel4 = new javax.swing.JPanel(); + private final javax.swing.JScrollPane jScrollPane2 = new javax.swing.JScrollPane(); + private final javax.swing.JScrollPane jScrollPane3 = new javax.swing.JScrollPane(); + private final javax.swing.JButton refreshButton = new javax.swing.JButton(); + private final javax.swing.JCheckBox startCheckBox = new javax.swing.JCheckBox(); + private final com.github.lgooddatepicker.components.DatePicker startDatePicker = new com.github.lgooddatepicker.components.DatePicker(); + private final javax.swing.JButton unCheckAllAccountTypesButton = new javax.swing.JButton(); + private final javax.swing.JButton unCheckAllDevicesButton = new javax.swing.JButton(); + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.form b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.form new file mode 100644 index 0000000000..58e19523f1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.form @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java new file mode 100644 index 0000000000..af53f7c030 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -0,0 +1,155 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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 obt ain 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; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.communications.AccountsRootChildren.AccountDeviceInstanceNode; +import org.sleuthkit.autopsy.corecomponents.DataResultPanel; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsManager; + +/** + * The right hand side of the CVT. Has a DataResultPanel to show messages and + * other account details, and a ContentViewer to show individual + */ +final class MessageBrowser extends javax.swing.JPanel implements ExplorerManager.Provider { + + private static final long serialVersionUID = 1L; + + + private final DataResultPanel messagesResultPanel; + private final ExplorerManager explorerManager = new ExplorerManager(); + private final DataResultViewerTable dataResultViewerTable = new DataResultViewerTable(explorerManager, "Messages"); + + ; + + MessageBrowser() { + initComponents(); + //create an uninitialized DataResultPanel so we can control the ResultViewers that get added. + messagesResultPanel = DataResultPanel.createInstanceUninitialized("Account", "", Node.EMPTY, 0, messageDataContent); + + splitPane.setTopComponent(messagesResultPanel); + splitPane.setBottomComponent(messageDataContent); + } + + @Override + public void addNotify() { + super.addNotify(); + ExplorerManager parentExplorereManager = ExplorerManager.find(this); + parentExplorereManager.addPropertyChangeListener(pce -> { + if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] selectedNodes = parentExplorereManager.getSelectedNodes(); + + messagesResultPanel.setNumMatches(0); + messagesResultPanel.setNode(null); + + if (selectedNodes.length == 0) { + //reset panel when there is no selection + messagesResultPanel.setPath(""); + } else { + AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0]; + CommunicationsFilter filter = adiNode.getFilter(); + CommunicationsManager commsManager = adiNode.getCommsManager(); + final Set accountDeviceInstances; + + if (selectedNodes.length == 1) { + final AccountDeviceInstance accountDeviceInstance = adiNode.getAccountDeviceInstance(); + accountDeviceInstances = Collections.singleton(accountDeviceInstance); + messagesResultPanel.setPath(accountDeviceInstance.getAccount().getTypeSpecificID()); + } else { + accountDeviceInstances = Stream.of(selectedNodes) + .map(node -> (AccountDeviceInstanceNode) node) + .map(AccountDeviceInstanceNode::getAccountDeviceInstance) + .collect(Collectors.toSet()); + messagesResultPanel.setPath(selectedNodes.length + " accounts"); + } + AccountDetailsNode accountDetailsNode = + new AccountDetailsNode(accountDeviceInstances, filter, commsManager); + TableFilterNode wrappedNode = + new TableFilterNode(new DataResultFilterNode(accountDetailsNode, parentExplorereManager), true); + messagesResultPanel.setNode(wrappedNode); + } + } + }); + + //add the required result viewers and THEN open the panel + if (messagesResultPanel.getViewers().contains(dataResultViewerTable) == false) { + messagesResultPanel.addResultViewer(dataResultViewerTable); + } + messagesResultPanel.open(); + } + + + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + /** + * 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() { + + splitPane = new javax.swing.JSplitPane(); + messageDataContent = new org.sleuthkit.autopsy.communications.MessageDataContent(); + + splitPane.setDividerLocation(400); + splitPane.setDividerSize(10); + splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); + splitPane.setResizeWeight(0.5); + splitPane.setBottomComponent(messageDataContent); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(splitPane)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(splitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1083, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.communications.MessageDataContent messageDataContent; + private javax.swing.JSplitPane splitPane; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageDataContent.java b/Core/src/org/sleuthkit/autopsy/communications/MessageDataContent.java new file mode 100644 index 0000000000..1c4ea0f7b4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageDataContent.java @@ -0,0 +1,37 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.beans.PropertyChangeEvent; +import org.sleuthkit.autopsy.contentviewers.MessageContentViewer; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; + +/** + * Extends MessageContentViewer so that it implements DataContent and can be set + * as the only ContentViewer for a DataResultPanel + */ +public class MessageDataContent extends MessageContentViewer implements DataContent { + + private static final long serialVersionUID = 1L; + + @Override + public void propertyChange(PropertyChangeEvent evt) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/OpenCommVisualizationToolAction.java b/Core/src/org/sleuthkit/autopsy/communications/OpenCommVisualizationToolAction.java new file mode 100644 index 0000000000..832926bc15 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/OpenCommVisualizationToolAction.java @@ -0,0 +1,107 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.awt.Component; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.actions.CallableSystemAction; +import org.openide.util.actions.Presenter; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * Action that opens the CVT. Available through the Tools menu and the main + * toolbar. + */ +@ActionID(category = "Tools", + id = "org.sleuthkit.autopsy.communicationsVisualization.OpenCVTAction") +@ActionRegistration(displayName = "#CTL_OpenCVTAction", lazy = false) +@ActionReferences(value = { + @ActionReference(path = "Menu/Tools", position = 102) + , @ActionReference(path = "Toolbars/Case", position = 102)}) +@Messages("CTL_OpenCVTAction=Visualize Communications") +public final class OpenCommVisualizationToolAction extends CallableSystemAction implements Presenter.Toolbar { + + private static final long serialVersionUID = 1L; + + private final JButton toolbarButton = new JButton(getName(), + new ImageIcon(getClass().getResource("images/emblem-web24.png"))); //NON-NLS + + public OpenCommVisualizationToolAction() { + toolbarButton.addActionListener(actionEvent -> performAction()); + setEnabled(false); //disabled by default. Will be enabled in Case.java when a case is opened. + } + + @Override + public void performAction() { + final TopComponent tc = WindowManager.getDefault().findTopComponent("CVTTopComponent"); + if (tc != null) { + if (tc.isOpened() == false) { + tc.open(); + } + tc.toFront(); + tc.requestActive(); + } + } + + /** + * Set this action to be enabled/disabled + * + * @param value whether to enable this action or not + */ + @Override + public void setEnabled(boolean value) { + super.setEnabled(value); + toolbarButton.setEnabled(value); + } + + @Override + @NbBundle.Messages("OpenCVTAction.displayName=Communications Visualizaton") + public String getName() { + return Bundle.OpenCVTAction_displayName(); + } + + /** + * Returns the toolbar component of this action + * + * @return component the toolbar button + */ + @Override + public Component getToolbarPresenter() { + return toolbarButton; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationShipNode.java b/Core/src/org/sleuthkit/autopsy/communications/RelationShipNode.java new file mode 100644 index 0000000000..5c060a262e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationShipNode.java @@ -0,0 +1,147 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.util.TimeZone; +import java.util.logging.Level; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.Sheet; +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; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT; +import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; +import org.sleuthkit.datamodel.TimeUtilities; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Node for a relationship, as represented by a BlackboardArtifact. + */ +public class RelationShipNode extends BlackboardArtifactNode { + + private static final Logger logger = Logger.getLogger(RelationShipNode.class.getName()); + + public RelationShipNode(BlackboardArtifact artifact) { + super(artifact); + final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); + String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); + setDisplayName(removeEndIgnoreCase.isEmpty() ? stripEnd : removeEndIgnoreCase); + } + + @Override + protected Sheet createSheet() { + Sheet s = new Sheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + + ss.put(new NodeProperty<>("Type", "Type", "Type", getDisplayName())); + + final BlackboardArtifact artifact = getArtifact(); + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID()); + if (null != fromID) { + //Consider refactoring this to reduce boilerplate + switch (fromID) { + case TSK_EMAIL_MSG: + ss.put(new NodeProperty<>("From", "From", "From", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); + ss.put(new NodeProperty<>("To", "To", "To", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); + ss.put(new NodeProperty<>("Date", "Date", "Date", + getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); + ss.put(new NodeProperty<>("Subject", "Subject", "Subject", + getAttributeDisplayString(artifact, TSK_SUBJECT))); + try { + ss.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); + } + + break; + case TSK_MESSAGE: + ss.put(new NodeProperty<>("From", "From", "From", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); + ss.put(new NodeProperty<>("To", "To", "To", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); + ss.put(new NodeProperty<>("Date", "Date", "Date", + getAttributeDisplayString(artifact, TSK_DATETIME))); + ss.put(new NodeProperty<>("Subject", "Subject", "Subject", + getAttributeDisplayString(artifact, TSK_SUBJECT))); + try { + ss.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); + } + break; + case TSK_CALLLOG: + ss.put(new NodeProperty<>("From", "From", "From", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); + ss.put(new NodeProperty<>("To", "To", "To", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); + ss.put(new NodeProperty<>("Date", "Date", "Date", + getAttributeDisplayString(artifact, TSK_DATETIME_START))); + break; + default: + break; + } + } + return s; + } + + /** + * + * Get the display string for the attribute of the given type from the given + * artifact. + * + * @param artifact the value of artifact + * @param attributeType the value of TSK_SUBJECT1 + * + * @return The display string, or an empty string if there is no such + * attribute or an an error. + */ + private static String getAttributeDisplayString(final BlackboardArtifact artifact, final ATTRIBUTE_TYPE attributeType) { + try { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attributeType.getTypeID()))); + if (attribute == null) { + return ""; + } else if (attributeType.getValueType() == DATETIME) { + return TimeUtilities.epochToTime(attribute.getValueLong(), + TimeZone.getTimeZone(Utils.getUserPreferredZoneId())); + } else { + return attribute.getDisplayString(); + } + } catch (TskCoreException tskCoreException) { + logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); + return ""; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/Utils.java b/Core/src/org/sleuthkit/autopsy/communications/Utils.java new file mode 100644 index 0000000000..91ee1e9fdf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/Utils.java @@ -0,0 +1,72 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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; + +import java.time.ZoneId; +import java.time.ZoneOffset; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.datamodel.Account; + +/** + * Utility class with helpers for dealing with accounts. + */ +class Utils { + + private Utils() { + } + + static ZoneId getUserPreferredZoneId() { + ZoneId zone = UserPreferences.displayTimesInLocalTime() ? ZoneOffset.systemDefault() : ZoneOffset.UTC; + return zone; + } + + /** + * The file name of the icon for the given Account Type. Will not include + * the path but will include the extension. + * + * @return The file name of the icon for the given Account Type. + */ + static final String getIconFileName(Account.Type type) { + if (type.equals(Account.Type.CREDIT_CARD)) { + return "credit-card.png"; + } else if (type.equals(Account.Type.DEVICE)) { + return "image.png"; + } else if (type.equals(Account.Type.EMAIL)) { + return "email.png"; + } else if (type.equals(Account.Type.FACEBOOK)) { + return "facebook.png"; + } else if (type.equals(Account.Type.INSTAGRAM)) { + return "instagram.png"; + } else if (type.equals(Account.Type.MESSAGING_APP)) { + return "messaging.png"; + } else if (type.equals(Account.Type.PHONE)) { + return "phone.png"; + } else if (type.equals(Account.Type.TWITTER)) { + return "twitter.png"; + } else if (type.equals(Account.Type.WEBSITE)) { + return "web-file.png"; + } else if (type.equals(Account.Type.WHATSAPP)) { + return "WhatsApp.png"; + } else { + //there could be a default icon instead... + throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName()); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/WhatsApp.png b/Core/src/org/sleuthkit/autopsy/communications/images/WhatsApp.png new file mode 100644 index 0000000000..2e3b64bd11 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/WhatsApp.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png b/Core/src/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png new file mode 100755 index 0000000000..4f40ba5206 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/calllog.png b/Core/src/org/sleuthkit/autopsy/communications/images/calllog.png new file mode 100755 index 0000000000..83eb9c448d Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/calllog.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/control-double.png b/Core/src/org/sleuthkit/autopsy/communications/images/control-double.png new file mode 100644 index 0000000000..4197fb468b Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/control-double.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/credit-card.png b/Core/src/org/sleuthkit/autopsy/communications/images/credit-card.png new file mode 100755 index 0000000000..e8d6624d76 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/credit-card.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/email.png b/Core/src/org/sleuthkit/autopsy/communications/images/email.png new file mode 100755 index 0000000000..a919ae3e50 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/email.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/email_link.png b/Core/src/org/sleuthkit/autopsy/communications/images/email_link.png new file mode 100644 index 0000000000..2c49f78a65 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/email_link.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/emblem-web.png b/Core/src/org/sleuthkit/autopsy/communications/images/emblem-web.png new file mode 100644 index 0000000000..54805fc1ad Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/emblem-web.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/emblem-web24.png b/Core/src/org/sleuthkit/autopsy/communications/images/emblem-web24.png new file mode 100644 index 0000000000..9b10546032 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/emblem-web24.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/facebook.png b/Core/src/org/sleuthkit/autopsy/communications/images/facebook.png new file mode 100644 index 0000000000..2487c37c04 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/facebook.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/folder-icon-16.png b/Core/src/org/sleuthkit/autopsy/communications/images/folder-icon-16.png new file mode 100755 index 0000000000..c8eb4f6708 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/folder-icon-16.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/funnel.png b/Core/src/org/sleuthkit/autopsy/communications/images/funnel.png new file mode 100755 index 0000000000..1f69604528 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/funnel.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/image.png b/Core/src/org/sleuthkit/autopsy/communications/images/image.png new file mode 100755 index 0000000000..a0fbfcfb79 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/image.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/instagram.png b/Core/src/org/sleuthkit/autopsy/communications/images/instagram.png new file mode 100644 index 0000000000..2688d3ab55 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/instagram.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/messaging.png b/Core/src/org/sleuthkit/autopsy/communications/images/messaging.png new file mode 100755 index 0000000000..6223516e3e Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/messaging.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/phone.png b/Core/src/org/sleuthkit/autopsy/communications/images/phone.png new file mode 100644 index 0000000000..c39f162f85 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/phone.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/table.png b/Core/src/org/sleuthkit/autopsy/communications/images/table.png new file mode 100644 index 0000000000..abcd93689a Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/table.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/tick.png b/Core/src/org/sleuthkit/autopsy/communications/images/tick.png new file mode 100755 index 0000000000..a7d7a96be3 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/tick.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/twitter.png b/Core/src/org/sleuthkit/autopsy/communications/images/twitter.png new file mode 100644 index 0000000000..df15c26e15 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/twitter.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/web-file.png b/Core/src/org/sleuthkit/autopsy/communications/images/web-file.png new file mode 100755 index 0000000000..ac5957ad62 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/web-file.png differ diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index f1a45c0a21..45916b87b3 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -17,3 +17,19 @@ Metadata.toolTip=Displays metadata about the file. Metadata.nodeText.nonFilePassedIn=Non-file passed in Metadata.nodeText.text=From The Sleuth Kit istat Tool\: Metadata.nodeText.exceptionNotice.text=Error getting file metadata\: +MessageContentViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF +MessageContentViewer.headersScrollPane.TabConstraints.tabTitle=Headers +MessageContentViewer.textbodyScrollPane.TabConstraints.tabTitle=Text +MessageContentViewer.htmlPane.TabConstraints.tabTitle=HTML +MessageContentViewer.fromText.text=from address goes here +MessageContentViewer.fromLabel.text=From: +MessageContentViewer.datetimeText.text=date goes here +MessageContentViewer.toText.text=to list goes here +MessageContentViewer.toLabel.text=To: +MessageContentViewer.ccText.text=cc list goes here +MessageContentViewer.subjectLabel.text=Subject: +MessageContentViewer.subjectText.text=subject goes here +MessageContentViewer.directionText.text=direction +MessageContentViewer.ccLabel.text=CC: +MessageContentViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments +MessageContentViewer.viewInNewWindowButton.text=View in New Window diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form new file mode 100644 index 0000000000..76cf355856 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form @@ -0,0 +1,395 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java new file mode 100644 index 0000000000..ec49948b78 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -0,0 +1,658 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers; + +import java.awt.Component; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.swing.text.JTextComponent; +import org.apache.commons.lang3.StringUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultPanel; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_RCVD; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_CC; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Shows SMS/MMS/EMail messages + */ +@ServiceProvider(service = DataContentViewer.class, position = 4) +public class MessageContentViewer extends javax.swing.JPanel implements DataContentViewer { + + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(MessageContentViewer.class.getName()); + + private static final int HDR_TAB_INDEX = 0; + private static final int TEXT_TAB_INDEX = 1; + private static final int HTML_TAB_INDEX = 2; + private static final int RTF_TAB_INDEX = 3; + private static final int ATTM_TAB_INDEX = 4; + + private final List textAreas; + + /** + * Artifact currently being displayed + */ + private BlackboardArtifact artifact; + private final DataResultPanel drp; + private final ExplorerManager drpExplorerManager; + + /** + * Creates new MessageContentViewer + */ + public MessageContentViewer() { + initComponents(); + drp = DataResultPanel.createInstanceUninitialized("Attachments", "", Node.EMPTY, 0, null); + attachmentsScrollPane.setViewportView(drp); + msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); + + textAreas = Arrays.asList(headersTextArea, textbodyTextArea, htmlbodyTextPane, rtfbodyTextPane); + + Utilities.configureTextPaneAsHtml(htmlbodyTextPane); + Utilities.configureTextPaneAsRtf(rtfbodyTextPane); + resetComponent(); + + drp.open(); + drpExplorerManager = drp.getExplorerManager(); + drpExplorerManager.addPropertyChangeListener(evt + -> viewInNewWindowButton.setEnabled(drpExplorerManager.getSelectedNodes().length == 1)); + } + + /** + * 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() { + + envelopePanel = new javax.swing.JPanel(); + fromLabel = new javax.swing.JLabel(); + datetimeText = new javax.swing.JLabel(); + fromText = new javax.swing.JLabel(); + toLabel = new javax.swing.JLabel(); + toText = new javax.swing.JLabel(); + ccLabel = new javax.swing.JLabel(); + ccText = new javax.swing.JLabel(); + subjectLabel = new javax.swing.JLabel(); + subjectText = new javax.swing.JLabel(); + directionText = new javax.swing.JLabel(); + msgbodyTabbedPane = new javax.swing.JTabbedPane(); + headersScrollPane = new javax.swing.JScrollPane(); + headersTextArea = new javax.swing.JTextArea(); + textbodyScrollPane = new javax.swing.JScrollPane(); + textbodyTextArea = new javax.swing.JTextArea(); + htmlPane = new javax.swing.JPanel(); + htmlScrollPane = new javax.swing.JScrollPane(); + htmlbodyTextPane = new javax.swing.JTextPane(); + showImagesToggleButton = new javax.swing.JToggleButton(); + rtfbodyScrollPane = new javax.swing.JScrollPane(); + rtfbodyTextPane = new javax.swing.JTextPane(); + attachmentsPanel = new javax.swing.JPanel(); + viewInNewWindowButton = new javax.swing.JButton(); + attachmentsScrollPane = new javax.swing.JScrollPane(); + + envelopePanel.setBackground(new java.awt.Color(204, 204, 204)); + + org.openide.awt.Mnemonics.setLocalizedText(fromLabel, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.fromLabel.text")); // NOI18N + + datetimeText.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + org.openide.awt.Mnemonics.setLocalizedText(datetimeText, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.datetimeText.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(fromText, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.fromText.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(toLabel, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.toLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(toText, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.toText.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(ccLabel, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.ccLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(ccText, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.ccText.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(subjectLabel, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.subjectLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(subjectText, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.subjectText.text")); // NOI18N + + directionText.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + org.openide.awt.Mnemonics.setLocalizedText(directionText, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.directionText.text")); // NOI18N + + javax.swing.GroupLayout envelopePanelLayout = new javax.swing.GroupLayout(envelopePanel); + envelopePanel.setLayout(envelopePanelLayout); + envelopePanelLayout.setHorizontalGroup( + envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(envelopePanelLayout.createSequentialGroup() + .addGap(5, 5, 5) + .addGroup(envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(envelopePanelLayout.createSequentialGroup() + .addGroup(envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(fromLabel) + .addComponent(toLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(envelopePanelLayout.createSequentialGroup() + .addComponent(toText, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(directionText, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(envelopePanelLayout.createSequentialGroup() + .addComponent(fromText, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(datetimeText, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(envelopePanelLayout.createSequentialGroup() + .addComponent(ccLabel) + .addGap(26, 26, 26) + .addComponent(ccText, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(envelopePanelLayout.createSequentialGroup() + .addComponent(subjectLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(subjectText, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGap(5, 5, 5)) + ); + envelopePanelLayout.setVerticalGroup( + envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(envelopePanelLayout.createSequentialGroup() + .addGap(5, 5, 5) + .addGroup(envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(fromLabel) + .addComponent(datetimeText) + .addComponent(fromText)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(toLabel) + .addComponent(toText) + .addComponent(directionText)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(ccLabel) + .addComponent(ccText)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(envelopePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(subjectLabel) + .addComponent(subjectText)) + .addGap(5, 5, 5)) + ); + + headersScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + headersScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + headersTextArea.setEditable(false); + headersTextArea.setColumns(20); + headersTextArea.setLineWrap(true); + headersTextArea.setRows(5); + headersTextArea.setWrapStyleWord(true); + headersScrollPane.setViewportView(headersTextArea); + + msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.headersScrollPane.TabConstraints.tabTitle"), headersScrollPane); // NOI18N + + textbodyScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + textbodyScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + textbodyTextArea.setEditable(false); + textbodyTextArea.setLineWrap(true); + textbodyTextArea.setRows(5); + textbodyTextArea.setWrapStyleWord(true); + textbodyScrollPane.setViewportView(textbodyTextArea); + + msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.textbodyScrollPane.TabConstraints.tabTitle"), textbodyScrollPane); // NOI18N + + htmlScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + htmlbodyTextPane.setEditable(false); + htmlScrollPane.setViewportView(htmlbodyTextPane); + + org.openide.awt.Mnemonics.setLocalizedText(showImagesToggleButton, "Show Images"); + showImagesToggleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + showImagesToggleButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout htmlPaneLayout = new javax.swing.GroupLayout(htmlPane); + htmlPane.setLayout(htmlPaneLayout); + htmlPaneLayout.setHorizontalGroup( + htmlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(htmlScrollPane) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, htmlPaneLayout.createSequentialGroup() + .addContainerGap(533, Short.MAX_VALUE) + .addComponent(showImagesToggleButton) + .addGap(3, 3, 3)) + ); + htmlPaneLayout.setVerticalGroup( + htmlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(htmlPaneLayout.createSequentialGroup() + .addComponent(showImagesToggleButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 333, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + + msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.htmlPane.TabConstraints.tabTitle"), htmlPane); // NOI18N + + rtfbodyScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + rtfbodyTextPane.setEditable(false); + rtfbodyScrollPane.setViewportView(rtfbodyTextPane); + + msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.rtfbodyScrollPane.TabConstraints.tabTitle"), rtfbodyScrollPane); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(viewInNewWindowButton, org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.viewInNewWindowButton.text")); // NOI18N + viewInNewWindowButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + viewInNewWindowButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout attachmentsPanelLayout = new javax.swing.GroupLayout(attachmentsPanel); + attachmentsPanel.setLayout(attachmentsPanelLayout); + attachmentsPanelLayout.setHorizontalGroup( + attachmentsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(attachmentsPanelLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(attachmentsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, attachmentsPanelLayout.createSequentialGroup() + .addComponent(viewInNewWindowButton) + .addGap(3, 3, 3)) + .addComponent(attachmentsScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 647, Short.MAX_VALUE))) + ); + attachmentsPanelLayout.setVerticalGroup( + attachmentsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(attachmentsPanelLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(viewInNewWindowButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(attachmentsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 333, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + + msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.attachmentsPanel.TabConstraints.tabTitle"), attachmentsPanel); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(5, 5, 5) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(msgbodyTabbedPane) + .addComponent(envelopePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(5, 5, 5)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(envelopePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(msgbodyTabbedPane) + .addGap(5, 5, 5)) + ); + }// //GEN-END:initComponents + + @NbBundle.Messages({ + "MessageContentViewer.showImagesToggleButton.hide.text=Hide Images", + "MessageContentViewer.showImagesToggleButton.text=Show Images"}) + private void showImagesToggleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showImagesToggleButtonActionPerformed + try { + String htmlText = getAttributeValueSafe(artifact, TSK_EMAIL_CONTENT_HTML); + if (false == htmlText.isEmpty()) { + if (showImagesToggleButton.isSelected()) { + showImagesToggleButton.setText(Bundle.MessageContentViewer_showImagesToggleButton_hide_text()); + this.htmlbodyTextPane.setText(wrapInHtmlBody(htmlText)); + } else { + showImagesToggleButton.setText(Bundle.MessageContentViewer_showImagesToggleButton_text()); + this.htmlbodyTextPane.setText(wrapInHtmlBody(cleanseHTML(htmlText))); + } + } + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Failed to get attributes for email message.", ex); //NON-NLS + } + }//GEN-LAST:event_showImagesToggleButtonActionPerformed + + private void viewInNewWindowButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewInNewWindowButtonActionPerformed + new NewWindowViewAction("View in new window", drpExplorerManager.getSelectedNodes()[0]).actionPerformed(evt); + }//GEN-LAST:event_viewInNewWindowButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel attachmentsPanel; + private javax.swing.JScrollPane attachmentsScrollPane; + private javax.swing.JLabel ccLabel; + private javax.swing.JLabel ccText; + private javax.swing.JLabel datetimeText; + private javax.swing.JLabel directionText; + private javax.swing.JPanel envelopePanel; + private javax.swing.JLabel fromLabel; + private javax.swing.JLabel fromText; + private javax.swing.JScrollPane headersScrollPane; + private javax.swing.JTextArea headersTextArea; + private javax.swing.JPanel htmlPane; + private javax.swing.JScrollPane htmlScrollPane; + private javax.swing.JTextPane htmlbodyTextPane; + private javax.swing.JTabbedPane msgbodyTabbedPane; + private javax.swing.JScrollPane rtfbodyScrollPane; + private javax.swing.JTextPane rtfbodyTextPane; + private javax.swing.JToggleButton showImagesToggleButton; + private javax.swing.JLabel subjectLabel; + private javax.swing.JLabel subjectText; + private javax.swing.JScrollPane textbodyScrollPane; + private javax.swing.JTextArea textbodyTextArea; + private javax.swing.JLabel toLabel; + private javax.swing.JLabel toText; + private javax.swing.JButton viewInNewWindowButton; + // End of variables declaration//GEN-END:variables + + @Override + public void setNode(Node node) { + if (node == null) { + resetComponent(); + return; + } + + artifact = node.getLookup().lookup(BlackboardArtifact.class); + if (artifact == null) { + resetComponent(); + return; + } + + if (artifact.getArtifactTypeID() == TSK_MESSAGE.getTypeID()) { + displayMsg(); + } else if (artifact.getArtifactTypeID() == TSK_EMAIL_MSG.getTypeID()) { + displayEmailMsg(); + } else { + resetComponent(); + } + } + + @Override + @NbBundle.Messages("MessageContentViewer.title=Message") + public String getTitle() { + return Bundle.MessageContentViewer_title(); + } + + @Override + @NbBundle.Messages("MessageContentViewer.toolTip=Displays messages.") + public String getToolTip() { + return Bundle.MessageContentViewer_toolTip(); + } + + @Override + public DataContentViewer createInstance() { + return new MessageContentViewer(); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + final public void resetComponent() { + // reset all fields + fromText.setText(""); + fromLabel.setEnabled(false); + toText.setText(""); + toLabel.setEnabled(false); + ccText.setText(""); + ccLabel.setEnabled(false); + subjectText.setText(""); + subjectLabel.setEnabled(false); + datetimeText.setText(""); + datetimeText.setEnabled(false); + directionText.setText(""); + directionText.setEnabled(false); + + headersTextArea.setText(""); + rtfbodyTextPane.setText(""); + htmlbodyTextPane.setText(""); + textbodyTextArea.setText(""); + drp.setNode(null); + msgbodyTabbedPane.setEnabled(false); + } + + @Override + public boolean isSupported(Node node) { + BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); + return ((artifact != null) + && ((artifact.getArtifactTypeID() == TSK_EMAIL_MSG.getTypeID()) + || (artifact.getArtifactTypeID() == TSK_MESSAGE.getTypeID()))); + } + + @Override + public int isPreferred(Node node) { + if (isSupported(node)) { + return 6; + } + return 0; + } + + /** + * Configure the text area at the given index to show the content of the + * given type. + * + * @param type The ATTRIBUT_TYPE to show in the indexed tab. + * @param index The index of the text area to configure. + * + * @throws TskCoreException + */ + private void configureTextArea(BlackboardAttribute.ATTRIBUTE_TYPE type, int index) throws TskCoreException { + String attributeText = getAttributeValueSafe(artifact, type); + + if (index == HTML_TAB_INDEX && StringUtils.isNotBlank(attributeText)) { + //special case for HTML, we need to 'cleanse' it + attributeText = wrapInHtmlBody(cleanseHTML(attributeText)); + } + JTextComponent textComponent = textAreas.get(index); + textComponent.setText(attributeText); + textComponent.setCaretPosition(0); //make sure we start at the top + final boolean hasText = attributeText.length() > 0; + + msgbodyTabbedPane.setEnabledAt(index, hasText); + if (hasText) { + msgbodyTabbedPane.setSelectedIndex(index); + } + } + + private void enableCommonFields() { + msgbodyTabbedPane.setEnabled(true); + fromLabel.setEnabled(true); + toLabel.setEnabled(true); + subjectLabel.setEnabled(true); + datetimeText.setEnabled(true); + } + + private void configureAttachments() throws TskCoreException { + //TODO: Replace this with code to get the actual attachements! + final Set attachments = artifact.getChildren().stream() + .filter(AbstractFile.class::isInstance) + .map(AbstractFile.class::cast) + .collect(Collectors.toSet()); + final int numberOfAttachments = attachments.size(); + + msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, numberOfAttachments > 0); + msgbodyTabbedPane.setTitleAt(ATTM_TAB_INDEX, "Attachments (" + numberOfAttachments + ")"); + drp.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode( + new AttachmentsChildren(attachments)), null), true)); + } + + private static String wrapInHtmlBody(String htmlText) { + return "" + htmlText + ""; + } + + private void displayEmailMsg() { + enableCommonFields(); + + directionText.setEnabled(false); + ccLabel.setEnabled(true); + + showImagesToggleButton.setText("Show Images"); + showImagesToggleButton.setSelected(false); + + try { + this.fromText.setText(getAttributeValueSafe(artifact, TSK_EMAIL_FROM)); + this.toText.setText(getAttributeValueSafe(artifact, TSK_EMAIL_TO)); + this.directionText.setText(""); + this.ccText.setText(getAttributeValueSafe(artifact, TSK_EMAIL_CC)); + this.subjectText.setText(getAttributeValueSafe(artifact, TSK_SUBJECT)); + this.datetimeText.setText(getAttributeValueSafe(artifact, TSK_DATETIME_RCVD)); + + configureTextArea(TSK_HEADERS, HDR_TAB_INDEX); + configureTextArea(TSK_EMAIL_CONTENT_PLAIN, TEXT_TAB_INDEX); + configureTextArea(TSK_EMAIL_CONTENT_HTML, HTML_TAB_INDEX); + configureTextArea(TSK_EMAIL_CONTENT_RTF, RTF_TAB_INDEX); + configureAttachments(); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Failed to get attributes for email message.", ex); //NON-NLS + } + } + + private void displayMsg() { + enableCommonFields(); + + directionText.setEnabled(true); + ccLabel.setEnabled(false); + + try { + this.fromText.setText(getAttributeValueSafe(artifact, TSK_PHONE_NUMBER_FROM)); + this.toText.setText(getAttributeValueSafe(artifact, TSK_PHONE_NUMBER_TO)); + this.directionText.setText(getAttributeValueSafe(artifact, TSK_DIRECTION)); + this.ccText.setText(""); + this.subjectText.setText(getAttributeValueSafe(artifact, TSK_SUBJECT)); + this.datetimeText.setText(getAttributeValueSafe(artifact, TSK_DATETIME)); + + msgbodyTabbedPane.setEnabledAt(HTML_TAB_INDEX, false); + msgbodyTabbedPane.setEnabledAt(RTF_TAB_INDEX, false); + msgbodyTabbedPane.setEnabledAt(HDR_TAB_INDEX, false); + msgbodyTabbedPane.setEnabledAt(HDR_TAB_INDEX, false); + configureTextArea(TSK_TEXT, TEXT_TAB_INDEX); + configureAttachments(); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Failed to get attributes for message.", ex); //NON-NLS + } + } + + private static String getAttributeValueSafe(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException { + return Optional.ofNullable(artifact.getAttribute(new BlackboardAttribute.Type(type))) + .map(BlackboardAttribute::getDisplayString) + .orElse(""); + } + + /** + * Cleans out input HTML string + * + * @param htmlInString The HTML string to cleanse + * + * @return The cleansed HTML String + */ + static private String cleanseHTML(String htmlInString) { + + Document doc = Jsoup.parse(htmlInString); + + //fix all img tags + doc.select("img[src]").forEach(img -> img.attr("src", "")); + + return doc.html(); + } + + private static class AttachmentsChildren extends Children.Keys { + + private final Set attachments; + + AttachmentsChildren(Set attachments) { + this.attachments = attachments; + } + + @Override + protected Node[] createNodes(AbstractFile t) { + return new Node[]{new AttachmentNode(t)}; + } + + @Override + protected void addNotify() { + super.addNotify(); + setKeys(attachments); + } + } + + /** + * Extension of FileNode customized for viewing attachments in the + * MessageContentViewer. It overrides createSheet() to customize what + * properties are shown in the table, and could also override getActions(), + * getPreferedAction(), etc. + */ + private static class AttachmentNode extends FileNode { + + AttachmentNode(AbstractFile file) { + super(file, true); + } + + @Override + protected Sheet createSheet() { + Sheet s = new Sheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + AbstractFile file = getContent(); + ss.put(new NodeProperty<>("Name", "Name", "Name", file.getName())); + ss.put(new NodeProperty<>("Size", "Size", "Size", file.getSize())); + ss.put(new NodeProperty<>("Mime Type", "Mime Type", "Mime Type", StringUtils.defaultString(file.getMIMEType()))); + ss.put(new NodeProperty<>("Known", "Known", "Known", file.getKnown().getName())); + + addTagProperty(ss); + return s; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Utilities.java b/Core/src/org/sleuthkit/autopsy/contentviewers/Utilities.java index 99a4f44585..96bc211890 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Utilities.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Utilities.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.contentviewers; import javax.swing.JTextPane; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; +import javax.swing.text.rtf.RTFEditorKit; /** * @@ -47,4 +48,11 @@ public class Utilities { styleSheet.addRule("th {font-family:Arial, 'ヒラギノ角ゴ Pro W3','Hiragino Kaku Gothic Pro','メイリオ',Meiryo,'ï¼ï¼³ Pゴシック','MS PGothic',sans-serif;font-size:14pt;overflow:hidden;padding-right:5px;padding-left:5px;font-weight:bold;}"); //NON-NLS styleSheet.addRule("p {font-family:Arial, 'ヒラギノ角ゴ Pro W3','Hiragino Kaku Gothic Pro','メイリオ',Meiryo,'ï¼ï¼³ Pゴシック','MS PGothic',sans-serif;font-size:14pt;}"); //NON-NLS } + + public static void configureTextPaneAsRtf(JTextPane pane) { + + pane.setContentType("text/html;charset=UTF-8"); //NON-NLS + RTFEditorKit rtfkit = new RTFEditorKit(); + pane.setEditorKit(rtfkit); + } } diff --git a/Core/src/org/sleuthkit/autopsy/core/cvtWsmode.xml b/Core/src/org/sleuthkit/autopsy/core/cvtWsmode.xml new file mode 100644 index 0000000000..fc0f8a0200 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/core/cvtWsmode.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index fb5618c6e6..e06fc9ac45 100755 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -443,6 +443,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContent.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContent.java index 92d0e39547..f51f558f74 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContent.java @@ -20,12 +20,10 @@ package org.sleuthkit.autopsy.corecomponentinterfaces; import java.beans.PropertyChangeListener; import org.openide.nodes.Node; -import org.openide.windows.TopComponent; /** * The interface for the "bottom right component" window. * - * @author jantonius */ public interface DataContent extends PropertyChangeListener { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java index f838e693d5..d88c979844 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-17 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -85,10 +85,13 @@ public interface DataResultViewer { /** * Set a custom content viewer to respond to selection events from this - * result viewer. If not set, the default content viewer is user + * result viewer. If not set, the default content viewer is used * * @param contentViewer content viewer to respond to selection events from * this viewer + * + * @deprecated All implementations of this in the standard DataResultViewers are now no-ops. */ + @Deprecated public void setContentViewer(DataContent contentViewer); } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index 13fda58c20..26d58d6eff 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-17 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,6 @@ import javax.swing.JPanel; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager.Provider; import org.openide.nodes.Node; -import org.openide.util.Lookup; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; @@ -40,11 +39,6 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); protected transient ExplorerManager em; - /** - * Content viewer to respond to selection events Either the main one, or - * custom one if set - */ - protected DataContent contentViewer; /** * This constructor is intended to allow an AbstractDataResultViewer to use @@ -53,10 +47,11 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView * TopComponent has focus. The ExplorerManager must be present when the * object is constructed so that its child components can discover it using * the ExplorerManager.find() method. + * + * @param explorerManager */ - public AbstractDataResultViewer(ExplorerManager explorerManager) { + AbstractDataResultViewer(ExplorerManager explorerManager) { this.em = explorerManager; - initialize(); } /** @@ -65,14 +60,7 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView * context lookup. */ public AbstractDataResultViewer() { - em = new ExplorerManager(); - initialize(); - } - - private void initialize() { - //DataContent is designed to return only the default viewer from lookup - //use the default one unless set otherwise - contentViewer = Lookup.getDefault().lookup(DataContent.class); + this(new ExplorerManager()); } @Override @@ -115,8 +103,8 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView } } + @Deprecated @Override public void setContentViewer(DataContent contentViewer) { - this.contentViewer = contentViewer; } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 34397cf960..e89d677ad8 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -22,6 +22,7 @@ import java.awt.Cursor; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; @@ -64,7 +65,7 @@ import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; * normally docked into the lower right hand side of the main window, underneath * the results view. A custom content view may be specified instead. */ -public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener { +public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { private static final long serialVersionUID = 1L; private static final int NO_TAB_SELECTED = -1; @@ -79,8 +80,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C private DataContent contentView; /** - * Constructs and opens a Swing JPanel with a JTabbedPane child component. - * The tabbed pane contains result viewers. + * Constructs and opens a DataResultPanel with the given initial data, and + * the default DataContent. * * @param title The title for the panel. * @param pathText Descriptive text about the source of the nodes @@ -98,8 +99,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Constructs and opens a Swing JPanel with a JTabbedPane child component. - * The tabbed pane contains result viewers. + * Constructs and opens a DataResultPanel with the given initial data, and a + * custom DataContent. * * @param title The title for the panel. * @param pathText Descriptive text about the source of the nodes @@ -119,9 +120,9 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Constructs a Swing JPanel with a JTabbedPane child component. The tabbed - * pane contains result viewers. The panel is NOT opened; the client of this - * method must call open on the panel that is returned. + * Constructs a DataResultPanel with the given initial data, and a custom + * DataContent. The panel is NOT opened; the client of this method must call + * open on the panel that is returned. * * @param title The title for the panel. * @param pathText Descriptive text about the source of the nodes @@ -159,42 +160,32 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Constructs a Swing JPanel with a JTabbedPane child component that - * contains result viewers (implementations of the DataResultViewer - * interface). - */ - private DataResultPanel() { - this.isMain = true; - initComponents(); - } - - /** - * Constructs a Swing JPanel with a JTabbedPane child component that - * contains result viewers (implementations of the DataResultViewer - * interface). + * Constructs a DataResultPanel with the default DataContent * * @param title The title for the panel. * @param isMain True if the DataResultPanel being constructed is the "main" * DataResultPanel. */ DataResultPanel(String title, boolean isMain) { - this(); + this(isMain, Lookup.getDefault().lookup(DataContent.class)); + setTitle(title); + } + + private DataResultPanel(boolean isMain, DataContent contentView) { this.isMain = isMain; - this.contentView = Lookup.getDefault().lookup(DataContent.class); + this.contentView = contentView; + initComponents(); } /** - * Constructs a Swing JPanel with a JTabbedPane child component that - * contains result viewers (implementations of the DataResultViewer - * interface). + * Constructs a DataResultPanel with the a custom DataContent. * * @param title The title for the panel. * @param customContentView A content view to use in place of the default * content view. */ DataResultPanel(String title, DataContent customContentView) { - this(title, false); - this.contentView = customContentView; + this(false, customContentView); } /** @@ -245,10 +236,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * * @param resultViewer The result viewer. */ - private void addResultViewer(DataResultViewer resultViewer) { - if (null != contentView) { - resultViewer.setContentViewer(contentView); - } + public void addResultViewer(DataResultViewer resultViewer) { resultViewers.add(resultViewer); dataResultTabbedPanel.addTab(resultViewer.getTitle(), resultViewer.getComponent()); } @@ -260,11 +248,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C */ @Override public List getViewers() { - List viewers = new ArrayList<>(); - resultViewers.forEach((viewer) -> { - viewers.add(viewer); - }); - return viewers; + return Collections.unmodifiableList(resultViewers); } /** @@ -397,9 +381,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * @param selectedNodes The nodes to be selected. */ public void setSelectedNodes(Node[] selectedNodes) { - this.resultViewers.forEach((viewer) -> { - viewer.setSelectedNodes(selectedNodes); - }); + this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); } /** @@ -517,14 +499,10 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C explorerManager = null; } - this.resultViewers.forEach((viewer) -> { - viewer.setNode(null); - }); + this.resultViewers.forEach((viewer) -> viewer.setNode(null)); if (!this.isMain) { - this.resultViewers.forEach((viewer) -> { - viewer.clearComponent(); - }); + this.resultViewers.forEach(DataResultViewer::clearComponent); this.directoryTablePath.removeAll(); this.directoryTablePath = null; this.numberMatchLabel.removeAll(); @@ -537,6 +515,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } } + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + /** * Responds to node selection change events from the explorer manager. */ @@ -563,9 +546,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * Pass the selected nodes to all of the result viewers * sharing this explorer manager. */ - resultViewers.forEach((viewer) -> { - viewer.setSelectedNodes(selectedNodes); - }); + resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); /* * Passing null signals that either multiple nodes are diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index e922e74c4f..fe21a4ca55 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -80,6 +80,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); private static final Color TAGGED_COLOR = new Color(255, 255, 195); + + private final String title; + /** * The properties map: * @@ -108,17 +111,42 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /** * Listener for table model event and mouse clicks. */ - private TableListener tableListener; + private final TableListener tableListener; /** * Creates a DataResultViewerTable object that is compatible with node - * multiple selection actions. + * multiple selection actions, and the default title. * * @param explorerManager allow for explorer manager sharing */ public DataResultViewerTable(ExplorerManager explorerManager) { + this(explorerManager, Bundle.DataResultViewerTable_title()); + } + + /** + * Creates a DataResultViewerTable object that is compatible with node + * multiple selection actions, and a custom title. + * + * @param explorerManager allow for explorer manager sharing + * @param title The custom title. + */ + public DataResultViewerTable(ExplorerManager explorerManager, String title) { super(explorerManager); - initialize(); + this.title = title; + + initComponents(); + + outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); + outline = outlineView.getOutline(); + outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + outline.setRootVisible(false); // don't show the root node + outline.setDragEnabled(false); + outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); + // add a listener so that when columns are moved, the new order is stored + tableListener = new TableListener(); + outline.getColumnModel().addColumnModelListener(tableListener); + // the listener also moves columns back if user tries to move the first column out of place + outline.getTableHeader().addMouseListener(tableListener); } /** @@ -126,26 +154,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * multiple selection actions. */ public DataResultViewerTable() { - initialize(); + this(new ExplorerManager(),Bundle.DataResultViewerTable_title()); } - private void initialize() { - initComponents(); - - outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); - - outline = outlineView.getOutline(); - outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - outline.setRootVisible(false); // don't show the root node - outline.setDragEnabled(false); - outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); - - // add a listener so that when columns are moved, the new order is stored - tableListener = new TableListener(); - outline.getColumnModel().addColumnModelListener(tableListener); - // the listener also moves columns back if user tries to move the first column out of place - outline.getTableHeader().addMouseListener(tableListener); - } /** * Expand node @@ -579,7 +590,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { @Override @NbBundle.Messages("DataResultViewerTable.title=Table") public String getTitle() { - return Bundle.DataResultViewerTable_title(); + return title; } @Override @@ -764,7 +775,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); // only override the color if a node is not selected - if (!isSelected) { + if (currentRoot != null && !isSelected) { Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); boolean tagFound = false; if (node != null) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java index b8a6807c24..372ae3e6ea 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java @@ -61,6 +61,7 @@ public abstract class AbstractFsContentNode extends Abst } @Override + @NbBundle.Messages("AbstractFsContentNode.noDesc.text=no description") protected Sheet createSheet() { Sheet s = super.createSheet(); Sheet.Set ss = s.get(Sheet.PROPERTIES); @@ -72,7 +73,7 @@ public abstract class AbstractFsContentNode extends Abst Map map = new LinkedHashMap<>(); fillPropertyMap(map, getContent()); - final String NO_DESCR = NbBundle.getMessage(this.getClass(), "AbstractFsContentNode.noDesc.text"); + final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); for (AbstractFilePropertyType propType : AbstractFilePropertyType.values()) { final String propString = propType.toString(); ss.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index dd8a8cb5ff..7063656562 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -2,7 +2,6 @@ OpenIDE-Module-Name=DataModel AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg=No Node defined for the given SleuthkitItem AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg=No Node defined for the given DisplayableItem AbstractContentNode.exception.cannotChangeSysName.msg=Cannot change the system name. -AbstractFsContentNode.noDesc.text=no description ArtifactStringContent.getStr.srcFilePath.text=Source File Path ArtifactStringContent.getStr.err=Error getting content ArtifactTypeNode.createSheet.artType.name=Artifact Type diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index e1ba5a2ca8..3e60b9b5d7 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -71,6 +71,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountFileInstance; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -294,13 +295,13 @@ final public class Accounts implements AutopsyVisitableItem { @Override protected Node[] createNodes(String key) { try { - Account.Type accountType = Account.Type.valueOf(key); - switch (accountType) { - case CREDIT_CARD: - return new Node[]{new CreditCardNumberAccountTypeNode()}; - default: - return new Node[]{new DefaultAccountTypeNode(key)}; + String accountType = key; + if (accountType.equals(Account.Type.CREDIT_CARD.getTypeName())) { + return new Node[]{new CreditCardNumberAccountTypeNode()}; + } else { + return new Node[]{new DefaultAccountTypeNode(key)}; } + } catch (IllegalArgumentException ex) { LOGGER.log(Level.WARNING, "Unknown account type: {0}", key); //Flesh out what happens with other account types here. @@ -547,7 +548,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS - + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.name() + "'" //NON-NLS + + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS @@ -607,7 +608,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS - + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.name() + "'" //NON-NLS + + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo"; @@ -1427,7 +1428,7 @@ final public class Accounts implements AutopsyVisitableItem { final Collection extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); artifacts.forEach(artifact -> { try { - skCase.setReviewStatus(artifact, newStatus); + skCase.setReviewStatus(artifact, newStatus); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 9738f1f4a6..defd5bfd89 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -990,7 +990,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat return; } - if (accountType.equals(Account.Type.CREDIT_CARD.name())) { + if (accountType.equals(Account.Type.CREDIT_CARD.getTypeName())) { Node accountNode = accountRootChilds.findChild(Account.Type.CREDIT_CARD.getDisplayName()); if (accountNode == null) { return; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 8d5ad932ca..976804f1e7 100755 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -170,21 +170,27 @@ class TableReportGenerator { return "unknown"; } }); - for (String accountType : groupedArtifacts.keySet()) { + for (String accountTypeStr : groupedArtifacts.keySet()) { /* If the report is a ReportHTML, the data type name * eventualy makes it to useDataTypeIcon which expects but * does not require a artifact name, so we make a synthetic * compund name by appending a ":" and the account type. */ - String accountDisplayname = accountType; - for (Account.Type acct : Account.Type.values()) { - if (acct.equals(Account.Type.valueOf(accountType))) { - accountDisplayname = acct.getDisplayName(); - break; + String accountDisplayname = accountTypeStr; + if (accountTypeStr != null) { + try { + Account.Type acctType = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().getAccountType(accountTypeStr); + if (acctType != null) { + accountDisplayname = acctType.getDisplayName(); + } + } + catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to get display name for account type " + accountTypeStr, ex); } } + final String compundDataTypeName = BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName() + ": " + accountDisplayname; - writeTableForDataType(new ArrayList<>(groupedArtifacts.get(accountType)), type, compundDataTypeName, comment); + writeTableForDataType(new ArrayList<>(groupedArtifacts.get(accountTypeStr)), type, compundDataTypeName, comment); } } else { //all other artifact types are sent to writeTableForDataType directly diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index bfd9112f9e..32c7186ae5 100755 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -14,6 +14,7 @@ + diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 13a62b62ee..4218ccab88 100755 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -53,6 +53,7 @@ file.reference.joda-time-2.4-javadoc.jar=release/modules/ext/joda-time-2.4-javad file.reference.joda-time-2.4-sources.jar=release/modules/ext/joda-time-2.4-sources.jar file.reference.joda-time-2.4.jar=release/modules/ext/joda-time-2.4.jar file.reference.jsr305-1.3.9.jar=release/modules/ext/jsr305-1.3.9.jar +file.reference.LGoodDatePicker-10.3.1.jar=release/modules/ext/LGoodDatePicker-10.3.1.jar file.reference.log4j-1.2.17.jar=release/modules/ext/log4j-1.2.17.jar file.reference.logkit-1.0.1.jar=release/modules/ext/logkit-1.0.1.jar file.reference.mail-1.4.3.jar=release/modules/ext/mail-1.4.3.jar @@ -82,6 +83,7 @@ javadoc.reference.guava-19.0.jar=release/modules/ext/guava-19.0-javadoc.jar javadoc.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-javadoc.jar javadoc.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4-javadoc.jar javadoc.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-javadoc.jar +javadoc.reference.LGoodDatePicker-10.3.1.jar=release/modules/ext/LGoodDatePicker-10.3.1-javadoc.jar nbm.needs.restart=true source.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4-sources.jar source.reference.commons-io-2.5.jar=release/modules/ext/commons-io-2.5-sources.jar @@ -91,3 +93,4 @@ source.reference.guava-19.0.jar=release/modules/ext/guava-19.0-sources.jar source.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-sources.jar source.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4-sources.jar source.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-sources.jar +source.reference.LGoodDatePicker-10.3.1.jar=release/modules/ext/LGoodDatePicker-10.3.1-sources.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 6fcef46ccd..cc752d4833 100755 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -37,6 +37,9 @@ com.apple.eawt com.apple.eawt.event com.apple.eio + com.github.lgooddatepicker.components + com.github.lgooddatepicker.optionalusertools + com.github.lgooddatepicker.zinternaltools com.github.mustachejava com.github.mustachejava.codes com.github.mustachejava.functions @@ -679,6 +682,10 @@ ext/compiler-0.9.1.jar release/modules/ext/compiler-0.9.1.jar + + ext/LGoodDatePicker-10.3.1.jar + release/modules/ext/LGoodDatePicker-10.3.1.jar + ext/imageio-iff-3.2.jar release/modules/ext/imageio-iff-3.2.jar diff --git a/InternalPythonModules/android/calllog.py b/InternalPythonModules/android/calllog.py index 6b9cb956d8..7ea3fd85df 100755 --- a/InternalPythonModules/android/calllog.py +++ b/InternalPythonModules/android/calllog.py @@ -1,7 +1,7 @@ """ Autopsy Forensic Browser -Copyright 2016 Basis Technology Corp. +Copyright 2016-17 Basis Technology Corp. Contact: carrier sleuthkit org Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,10 +44,14 @@ from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel.BlackboardAttribute import ATTRIBUTE_TYPE from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel import Account +from org.sleuthkit.datamodel import Relationship import traceback import general +deviceAccountInstance = None + """ Locates a variety of different call log databases, parses them, and populates the blackboard. """ @@ -82,6 +86,15 @@ class CallLogAnalyzer(general.AndroidComponentAnalyzer): def analyze(self, dataSource, fileManager, context): try: + + # Create a 'Device' account using the data source device id + datasourceObjId = dataSource.getDataSource().getId() + ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId) + deviceID = ds.getDeviceId() + + global deviceAccountInstance + deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, dataSource) + absFiles = fileManager.findFiles(dataSource, "logs.db") absFiles.addAll(fileManager.findFiles(dataSource, "contacts.db")) absFiles.addAll(fileManager.findFiles(dataSource, "contacts2.db")) @@ -133,6 +146,13 @@ class CallLogAnalyzer(general.AndroidComponentAnalyzer): attributes.add(BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, general.MODULE_NAME, name)) artifact.addAttributes(attributes) + + # Create an account + calllogAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, number, general.MODULE_NAME, abstractFile); + + # create relationship between accounts + Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [calllogAccountInstance], artifact, Relationship.Type.CALL_LOG, date); + bbartifacts.append(artifact) try: diff --git a/InternalPythonModules/android/contact.py b/InternalPythonModules/android/contact.py index 22eb681061..2a4af06859 100755 --- a/InternalPythonModules/android/contact.py +++ b/InternalPythonModules/android/contact.py @@ -1,7 +1,7 @@ """ Autopsy Forensic Browser -Copyright 2016 Basis Technology Corp. +Copyright 2016-17 Basis Technology Corp. Contact: carrier sleuthkit org Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,10 +42,14 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel import Account +from org.sleuthkit.datamodel import Relationship import traceback import general +deviceAccountInstance = None + """ Locates a variety of different contacts databases, parses them, and populates the blackboard. """ @@ -56,6 +60,15 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer): def analyze(self, dataSource, fileManager, context): try: + + # Create a 'Device' account using the data source device id + datasourceObjId = dataSource.getDataSource().getId() + ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId) + deviceID = ds.getDeviceId() + + global deviceAccountInstance + deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance (Account.Type.DEVICE, deviceID, general.MODULE_NAME, dataSource) + absFiles = fileManager.findFiles(dataSource, "contacts.db") absFiles.addAll(fileManager.findFiles(dataSource, "contacts2.db")) if absFiles.isEmpty(): @@ -131,12 +144,21 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer): attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, general.MODULE_NAME, name)) if mimetype == "vnd.android.cursor.item/phone_v2": attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, general.MODULE_NAME, data1)) + acctType = Account.Type.PHONE else: attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, general.MODULE_NAME, data1)) + acctType = Account.Type.EMAIL + + artifact.addAttributes(attributes) + + # Create an account instance + contactAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance (acctType, data1, general.MODULE_NAME, abstractFile); + + # create relationship between accounts + Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [contactAccountInstance], artifact,Relationship.Type.CONTACT, 0); oldName = name - artifact.addAttributes(attributes) bbartifacts.append(artifact) try: diff --git a/InternalPythonModules/android/tangomessage.py b/InternalPythonModules/android/tangomessage.py index 9a792064da..c63f87a6af 100755 --- a/InternalPythonModules/android/tangomessage.py +++ b/InternalPythonModules/android/tangomessage.py @@ -42,10 +42,13 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel import Account import traceback import general +deviceAccountInstance = None + """ Locates database for the Tango app and adds info to blackboard. """ @@ -56,6 +59,14 @@ class TangoMessageAnalyzer(general.AndroidComponentAnalyzer): def analyze(self, dataSource, fileManager, context): try: + # Create a 'Device' account using the data source device id + datasourceObjId = dataSource.getDataSource().getId() + ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId) + deviceID = ds.getDeviceId() + + global deviceAccountInstance + deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, dataSource) + absFiles = fileManager.findFiles(dataSource, "tc.db") for abstractFile in absFiles: try: diff --git a/InternalPythonModules/android/textmessage.py b/InternalPythonModules/android/textmessage.py index 55bb563fea..ccf7456728 100755 --- a/InternalPythonModules/android/textmessage.py +++ b/InternalPythonModules/android/textmessage.py @@ -1,7 +1,7 @@ """ Autopsy Forensic Browser -Copyright 2016 Basis Technology Corp. +Copyright 2016-17 Basis Technology Corp. Contact: carrier sleuthkit org Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,10 +43,14 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel import Account +from org.sleuthkit.datamodel import Relationship import traceback import general +deviceAccountInstance = None + """ Finds database with SMS/MMS messages and adds them to blackboard. """ @@ -57,6 +61,15 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer): def analyze(self, dataSource, fileManager, context): try: + + # Create a 'Device' account using the data source device id + datasourceObjId = dataSource.getDataSource().getId() + ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId) + deviceID = ds.getDeviceId() + + global deviceAccountInstance + deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, dataSource) + absFiles = fileManager.findFiles(dataSource, "mmssms.db") for abstractFile in absFiles: try: @@ -108,6 +121,13 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer): attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, general.MODULE_NAME, "SMS Message")) artifact.addAttributes(attributes) + + # Create an account + msgAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, address, general.MODULE_NAME, abstractFile); + + # create relationship between accounts + Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [msgAccountInstance], artifact,Relationship.Type.MESSAGE, date); + bbartifacts.append(artifact) try: # index the artifact for keyword search diff --git a/InternalPythonModules/android/wwfmessage.py b/InternalPythonModules/android/wwfmessage.py index e880cc1759..f2e1a8b067 100755 --- a/InternalPythonModules/android/wwfmessage.py +++ b/InternalPythonModules/android/wwfmessage.py @@ -1,7 +1,7 @@ """ Autopsy Forensic Browser -Copyright 2016 Basis Technology Corp. +Copyright 2016-17 Basis Technology Corp. Contact: carrier sleuthkit org Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,10 +39,15 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel import Account +from org.sleuthkit.datamodel import Relationship import traceback import general +wwfAccountType = None +deviceAccountInstance = None + """ Analyzes messages from Words With Friends """ @@ -53,6 +58,18 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): def analyze(self, dataSource, fileManager, context): try: + + global wwfAccountType + wwfAccountType = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addAccountType("WWF", "Words with Friends") + + # Create a 'Device' account using the data source device id + datasourceObjId = dataSource.getDataSource().getId() + ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId) + deviceID = ds.getDeviceId() + + global deviceAccountInstance + deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, dataSource) + absFiles = fileManager.findFiles(dataSource, "WordsFramework") for abstractFile in absFiles: try: @@ -98,6 +115,13 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, general.MODULE_NAME, "Words With Friends Message")) artifact.addAttributes(attributes) + + # Create an account + wwfAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(wwfAccountType, user_id, general.MODULE_NAME, abstractFile); + + # create relationship between accounts + Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [wwfAccountInstance], artifact,Relationship.Type.MESSAGE, created_at); + try: # index the artifact for keyword search blackboard = Case.getCurrentCase().getServices().getBlackboard() diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 680eb9e67e..315066cbbe 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -173,7 +173,14 @@ public class ExtractedContentViewer implements DataContentViewer { } } panel.updateControls(currentSource); - setPanel(content.getName(), sources); + + String contentName = ""; + if (content != null) { + contentName = content.getName(); + } + setPanel(contentName, sources); + + } static private IndexedText getRawArtifactText(Lookup nodeLookup) throws TskCoreException { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java index 7037f898be..585424b3ec 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java @@ -222,9 +222,6 @@ class QueryResults { * Post an artifact for the hit to the blackboard. */ BlackboardArtifact artifact = query.postKeywordHitToBlackboard(content, keyword, hit, snippet, query.getKeywordList().getName()); - if (null == artifact) { - logger.log(Level.SEVERE, "Error posting keyword hit artifact for keyword {0} in {1} to the blackboard", new Object[]{keyword.toString(), content}); //NON-NLS - } /* * Send an ingest inbox message for the hit. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 6734cbf47d..306502603c 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -36,7 +36,9 @@ import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.CursorMarkParams; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.CreditCards; @@ -46,6 +48,7 @@ import static org.sleuthkit.autopsy.keywordsearch.TermsComponentQuery.CREDIT_CAR import static org.sleuthkit.autopsy.keywordsearch.TermsComponentQuery.KEYWORD_SEARCH_DOCUMENT_ID; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountFileInstance; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -444,97 +447,30 @@ final class RegexQuery implements KeywordSearchQuery { } /* - * Create either a "plain vanilla" keyword hit artifact with keyword and - * regex attributes, or a credit card account artifact with attributes - * parsed from from the snippet for the hit and looked up based on the - * parsed bank identifcation number. + * Credit Card number hits are handled differently + */ + if (originalKeyword.getArtifactAttributeType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { + createCCNAccount(content, foundKeyword, hit, snippet, listName); + return null; + } + + /* + * Create a "plain vanilla" keyword hit artifact with keyword and + * regex attributes */ BlackboardArtifact newArtifact; Collection attributes = new ArrayList<>(); - if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, getQueryString())); - try { - newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS - return null; - } - } else { - /* - * Parse the credit card account attributes from the snippet for the - * hit. - */ - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, MODULE_NAME, Account.Type.CREDIT_CARD.name())); - Map parsedTrackAttributeMap = new HashMap<>(); - Matcher matcher = TermsComponentQuery.CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet()); - if (matcher.find()) { - parseTrack1Data(parsedTrackAttributeMap, matcher); - } - matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet()); - if (matcher.find()) { - parseTrack2Data(parsedTrackAttributeMap, matcher); - } - final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); - if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) { - if (hit.isArtifactHit()) { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS - } else { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getSolrObjectId())); //NON-NLS - } - return null; - } - attributes.addAll(parsedTrackAttributeMap.values()); - - /* - * Look up the bank name, scheme, etc. attributes for the bank - * indentification number (BIN). - */ - final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8)); - CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin); - if (binInfo != null) { - binInfo.getScheme().ifPresent(scheme - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme))); - binInfo.getCardType().ifPresent(cardType - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType))); - binInfo.getBrand().ifPresent(brand - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand))); - binInfo.getBankName().ifPresent(bankName - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName))); - binInfo.getBankPhoneNumber().ifPresent(phoneNumber - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber))); - binInfo.getBankURL().ifPresent(url - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url))); - binInfo.getCountry().ifPresent(country - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country))); - binInfo.getBankCity().ifPresent(city - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city))); - } - - /* - * If the hit is from unused or unallocated space, record the Solr - * document id to support showing just the chunk that contained the - * hit. - */ - if (content instanceof AbstractFile) { - AbstractFile file = (AbstractFile) content; - if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS - || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { - attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); - } - } - - /* - * Create an account artifact. - */ - try { - newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error adding artifact for account to blackboard", ex); //NON-NLS - return null; - } + + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, getQueryString())); + + try { + newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS + return null; } - + if (StringUtils.isNotBlank(listName)) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); } @@ -557,6 +493,113 @@ final class RegexQuery implements KeywordSearchQuery { } } + private void createCCNAccount(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { + + final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); + + if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { + LOGGER.log(Level.SEVERE, "Keyword hit is not a credit card number"); //NON-NLS + return; + } + /* + * Create a credit card account with attributes + * parsed from the snippet for the hit and looked up based on the + * parsed bank identifcation number. + */ + Collection attributes = new ArrayList<>(); + + Map parsedTrackAttributeMap = new HashMap<>(); + Matcher matcher = TermsComponentQuery.CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet()); + if (matcher.find()) { + parseTrack1Data(parsedTrackAttributeMap, matcher); + } + matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet()); + if (matcher.find()) { + parseTrack2Data(parsedTrackAttributeMap, matcher); + } + final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); + if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) { + + if (hit.isArtifactHit()) { + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS + } else { + try { + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContentID())); //NON-NLS + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s' ", foundKeyword.getSearchTerm(), hit.getSnippet())); //NON-NLS + LOGGER.log(Level.SEVERE, "There was a error getting contentID for keyword hit.", ex); //NON-NLS + } + } + return; + } + attributes.addAll(parsedTrackAttributeMap.values()); + + /* + * Look up the bank name, scheme, etc. attributes for the bank + * indentification number (BIN). + */ + final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8)); + CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin); + if (binInfo != null) { + binInfo.getScheme().ifPresent(scheme + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme))); + binInfo.getCardType().ifPresent(cardType + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType))); + binInfo.getBrand().ifPresent(brand + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand))); + binInfo.getBankName().ifPresent(bankName + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName))); + binInfo.getBankPhoneNumber().ifPresent(phoneNumber + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber))); + binInfo.getBankURL().ifPresent(url + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url))); + binInfo.getCountry().ifPresent(country + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country))); + binInfo.getBankCity().ifPresent(city + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city))); + } + + /* + * If the hit is from unused or unallocated space, record the Solr + * document id to support showing just the chunk that contained the + * hit. + */ + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile) content; + if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS + || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { + attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); + } + } + + if (StringUtils.isNotBlank(listName)) { + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); + } + if (snippet != null) { + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); + } + + hit.getArtifactID().ifPresent(artifactID + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) + ); + + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal())); + + + /* + * Create an account instance. + */ + try { + AccountFileInstance ccAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString() , MODULE_NAME, content); + + ccAccountInstance.addAttributes(attributes); + + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error creating CCN account instance", ex); //NON-NLS + + } + + } /** * Parses the track 2 data from the snippet for a credit card account number * hit and turns them into artifact attributes. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index 68c510a7e3..d42f0b5b64 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -32,11 +32,14 @@ import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.TermsResponse.Term; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.datamodel.CreditCards; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountFileInstance; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -342,98 +345,31 @@ final class TermsComponentQuery implements KeywordSearchQuery { */ @Override public BlackboardArtifact postKeywordHitToBlackboard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { + /* - * Create either a "plain vanilla" keyword hit artifact with keyword and - * regex attributes, or a credit card account artifact with attributes - * parsed from from the snippet for the hit and looked up based on the - * parsed bank identifcation number. + * CCN hits are handled specially + */ + if (originalKeyword.getArtifactAttributeType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { + createCCNAccount(content, hit, snippet, listName); + return null; + } + + /* + * Create a "plain vanilla" keyword hit artifact with keyword and regex + * attributes, */ BlackboardArtifact newArtifact; Collection attributes = new ArrayList<>(); - if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm())); - try { - newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm())); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS - return null; - } - } else { - /* - * Parse the credit card account attributes from the snippet for the - * hit. - */ - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, MODULE_NAME, Account.Type.CREDIT_CARD.name())); - Map parsedTrackAttributeMap = new HashMap<>(); - Matcher matcher = CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet()); - if (matcher.find()) { - parseTrack1Data(parsedTrackAttributeMap, matcher); - } - matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet()); - if (matcher.find()) { - parseTrack2Data(parsedTrackAttributeMap, matcher); - } - final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); - if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) { - if (hit.isArtifactHit()) { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS - } else { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), hit.getSolrObjectId())); //NON-NLS - } - return null; - } - attributes.addAll(parsedTrackAttributeMap.values()); + try { + newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); - /* - * Look up the bank name, scheme, etc. attributes for the bank - * indentification number (BIN). - */ - final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8)); - CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin); - if (binInfo != null) { - binInfo.getScheme().ifPresent(scheme - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme))); - binInfo.getCardType().ifPresent(cardType - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType))); - binInfo.getBrand().ifPresent(brand - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand))); - binInfo.getBankName().ifPresent(bankName - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName))); - binInfo.getBankPhoneNumber().ifPresent(phoneNumber - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber))); - binInfo.getBankURL().ifPresent(url - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url))); - binInfo.getCountry().ifPresent(country - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country))); - binInfo.getBankCity().ifPresent(city - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city))); - } - - /* - * If the hit is from unused or unallocated space, record the Solr - * document id to support showing just the chunk that contained the - * hit. - */ - if (content instanceof AbstractFile) { - AbstractFile file = (AbstractFile) content; - if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS - || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { - attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); - } - } - - /* - * Create an account artifact. - */ - try { - newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error adding artifact for account to blackboard", ex); //NON-NLS - return null; - } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS + return null; } if (StringUtils.isNotBlank(listName)) { @@ -459,6 +395,114 @@ final class TermsComponentQuery implements KeywordSearchQuery { } } + private void createCCNAccount(Content content, KeywordHit hit, String snippet, String listName) { + + if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { + LOGGER.log(Level.SEVERE, "Keyword hit is not a credit card number"); //NON-NLS + return; + } + + /* + * Create a credit card account with attributes parsed from from the + * snippet for the hit and looked up based on the parsed bank + * identifcation number. + */ + Collection attributes = new ArrayList<>(); + + Map parsedTrackAttributeMap = new HashMap<>(); + Matcher matcher = CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet()); + if (matcher.find()) { + parseTrack1Data(parsedTrackAttributeMap, matcher); + } + matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet()); + if (matcher.find()) { + parseTrack2Data(parsedTrackAttributeMap, matcher); + } + final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); + if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) { + if (hit.isArtifactHit()) { + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS + } else { + long contentId = 0; + try { + contentId = hit.getContentID(); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to content id from keyword hit: term = %s, snippet = '%s'", searchTerm, hit.getSnippet()), ex); //NON-NLS + } + if (contentId > 0) { + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), contentId)); //NON-NLS + } else { + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s'", searchTerm, hit.getSnippet())); //NON-NLS + } + } + return; + } + attributes.addAll(parsedTrackAttributeMap.values()); + + /* + * Look up the bank name, scheme, etc. attributes for the bank + * indentification number (BIN). + */ + final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8)); + CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin); + if (binInfo != null) { + binInfo.getScheme().ifPresent(scheme + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme))); + binInfo.getCardType().ifPresent(cardType + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType))); + binInfo.getBrand().ifPresent(brand + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand))); + binInfo.getBankName().ifPresent(bankName + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName))); + binInfo.getBankPhoneNumber().ifPresent(phoneNumber + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber))); + binInfo.getBankURL().ifPresent(url + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url))); + binInfo.getCountry().ifPresent(country + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country))); + binInfo.getBankCity().ifPresent(city + -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city))); + } + + /* + * If the hit is from unused or unallocated space, record the Solr + * document id to support showing just the chunk that contained the hit. + */ + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile) content; + if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS + || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { + attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); + } + } + + if (StringUtils.isNotBlank(listName)) { + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); + } + if (snippet != null) { + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); + } + + hit.getArtifactID().ifPresent( + artifactID -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) + ); + + // TermsComponentQuery is now being used exclusively for substring searches. + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal())); + + /* + * Create an account. + */ + try { + AccountFileInstance ccAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString(), MODULE_NAME, content); + ccAccountInstance.addAttributes(attributes); + //newArtifact = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifact(ccAccountInstance.getArtifactId()); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error creating CCN account instance", ex); //NON-NLS + } + + } + /** * Parses the track 2 data from the snippet for a credit card account number * hit and turns them into artifact attributes. diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 0de39782ca..8bacf7c22d 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Wed, 08 Nov 2017 17:45:11 -0500 +#Wed, 06 Dec 2017 09:53:33 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index fa55dddb62..daed31a757 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Wed, 08 Nov 2017 17:45:11 -0500 +#Wed, 06 Dec 2017 09:53:33 -0500 CTL_MainWindow_Title=Autopsy 4.5.0 CTL_MainWindow_Title_No_Project=Autopsy 4.5.0 diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 367ed4292d..91c4c2ff5c 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -21,8 +21,13 @@ package org.sleuthkit.autopsy.thunderbirdparser; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -40,12 +45,16 @@ import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountFileInstance; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Relationship; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.TskException; /** @@ -354,6 +363,24 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return files; } + /** + * Finds and returns a set of unique email addresses found in the input string + * + * @param input - input string, like the To/CC line from an email header + * + * @param Set: set of email addresses found in the input string + */ + private Set findEmailAddresess(String input) { + Pattern p = Pattern.compile("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", + Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(input); + Set emailAddresses = new HashSet(); + while (m.find()) { + emailAddresses.add( m.group()); + } + return emailAddresses; + } + /** * Add a blackboard artifact for the given email message. * @@ -377,55 +404,71 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { long id = email.getId(); String localPath = email.getLocalPath(); - if (headers.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_HEADERS, EmailParserModuleFactory.getModuleName(), headers)); + List senderAddressList = new ArrayList<>(); + String senderAddress; + senderAddressList.addAll(findEmailAddresess(from)); + + AccountFileInstance senderAccountInstance = null; + if (senderAddressList.size() == 1) { + senderAddress = senderAddressList.get(0); + try { + senderAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile); + } + catch(TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS + } } - if (from.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_FROM, EmailParserModuleFactory.getModuleName(), from)); - } - if (to.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_TO, EmailParserModuleFactory.getModuleName(), to)); - } - if (subject.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SUBJECT, EmailParserModuleFactory.getModuleName(), subject)); - } - - if (dateL > 0) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, EmailParserModuleFactory.getModuleName(), dateL)); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_SENT, EmailParserModuleFactory.getModuleName(), dateL)); - } - if (body.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, EmailParserModuleFactory.getModuleName(), body)); + else { + logger.log(Level.WARNING, "Failed to find sender address, from = "+ from); //NON-NLS } - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MSG_ID, EmailParserModuleFactory.getModuleName(), ((id < 0L) ? NbBundle - .getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)))); + List recipientAddresses = new ArrayList<>(); + recipientAddresses.addAll(findEmailAddresess(to)); + recipientAddresses.addAll(findEmailAddresess(cc)); + recipientAddresses.addAll(findEmailAddresess(bcc)); - if (localPath.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, EmailParserModuleFactory.getModuleName(), localPath)); - } else { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, EmailParserModuleFactory.getModuleName(), "/foo/bar")); //NON-NLS - } + List recipientAccountInstances = new ArrayList<>(); + recipientAddresses.forEach((addr) -> { + try { + AccountFileInstance recipientAccountInstance = + Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, + EmailParserModuleFactory.getModuleName(), abstractFile); + recipientAccountInstances.add(recipientAccountInstance); + } + catch(TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create account for email address " + addr, ex); //NON-NLS + } + }); + + addEmailAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes); + addEmailAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes); + addEmailAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes); + addEmailAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes); - if (cc.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CC, EmailParserModuleFactory.getModuleName(), cc)); - } - if (bcc.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_BCC, EmailParserModuleFactory.getModuleName(), bcc)); - } + addEmailAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes); + addEmailAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes); - if (bodyHTML.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, EmailParserModuleFactory.getModuleName(), bodyHTML)); - } - if (rtf.isEmpty() == false) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, EmailParserModuleFactory.getModuleName(), rtf)); - } - + addEmailAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes); + + addEmailAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), + ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes); + + addEmailAttribute(((localPath.isEmpty() == false) ? localPath : "/foo/bar"), + ATTRIBUTE_TYPE.TSK_PATH, bbattributes); + + addEmailAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes); + addEmailAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes); + addEmailAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes); + + try { bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); bbart.addAttributes(bbattributes); + // Add account relationships + Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); + try { // index the artifact for keyword search blackboard.indexArtifact(bbart); @@ -433,13 +476,24 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_addArtifact_indexError_message(), bbart.getDisplayName()); } - } catch (TskCoreException ex) { + } catch (TskCoreException | TskDataException ex) { logger.log(Level.WARNING, null, ex); } return bbart; } + private void addEmailAttribute(String stringVal, ATTRIBUTE_TYPE attrType, Collection bbattributes) { + if (stringVal.isEmpty() == false) { + bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal)); + } + } + private void addEmailAttribute(long longVal, ATTRIBUTE_TYPE attrType, Collection bbattributes) { + if (longVal > 0) { + bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal)); + } + } + void postErrorMessage(String subj, String details) { IngestMessage ingestMessage = IngestMessage.createErrorMessage(EmailParserModuleFactory.getModuleVersion(), subj, details); services.postMessage(ingestMessage); @@ -451,5 +505,6 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { @Override public void shutDown() { + // nothing to shut down } }