From 858f3db88a598a4e2bcfd411eddbe93ed01b39cb Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Tue, 5 Nov 2019 11:03:59 -0500 Subject: [PATCH 01/41] Initial commit of Code Initial commit of code for getting country code for phone numbers in the contacts tab. --- Core/ivy.xml | 1 + Core/nbproject/project.properties | 1 + Core/nbproject/project.xml | 12 ++-- .../relationships/Bundle.properties-MERGED | 1 + .../relationships/ContactNode.java | 56 ++++++++++++++----- .../relationships/ContactsViewer.java | 4 +- 6 files changed, 56 insertions(+), 19 deletions(-) diff --git a/Core/ivy.xml b/Core/ivy.xml index 9ba4a3c371..e6c513fdf6 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -44,6 +44,7 @@ + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 2a952926ba..541ef8e4c6 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -51,6 +51,7 @@ file.reference.jul-to-slf4j-1.7.25.jar=release\\modules\\ext\\jul-to-slf4j-1.7.2 file.reference.juniversalchardet-1.0.3.jar=release\\modules\\ext\\juniversalchardet-1.0.3.jar file.reference.junrar-2.0.0.jar=release\\modules\\ext\\junrar-2.0.0.jar file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar +file.reference.libphonenumber-3.5.jar=release/modules/ext/libphonenumber-3.5.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.11.0.jar=release\\modules\\ext\\metadata-extractor-2.11.0.jar file.reference.netcdf4-4.5.5.jar=release\\modules\\ext\\netcdf4-4.5.5.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index c33dabfbc7..8f2eb52c85 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -405,6 +405,10 @@ ext/okhttp-2.7.5.jar release/modules/ext/okhttp-2.7.5.jar + + ext/libphonenumber-3.5.jar + release/modules/ext/libphonenumber-3.5.jar + ext/tika-core-1.20.jar release\modules\ext\tika-core-1.20.jar @@ -517,6 +521,10 @@ ext/google-http-client-1.29.0.jar release/modules/ext/google-http-client-1.29.0.jar + + ext/sleuthkit-postgresql-4.7.0.jar + release/modules/ext/sleuthkit-postgresql-4.7.0.jar + ext/bcpkix-jdk15on-1.60.jar release\modules\ext\bcpkix-jdk15on-1.60.jar @@ -605,10 +613,6 @@ ext/jbig2-imageio-3.0.2.jar release\modules\ext\jbig2-imageio-3.0.2.jar - - ext/sleuthkit-postgresql-4.7.0.jar - release/modules/ext/sleuthkit-postgresql-4.7.0.jar - ext/apache-mime4j-dom-0.8.2.jar release\modules\ext\apache-mime4j-dom-0.8.2.jar diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index 3d6fbef0cb..73d807796e 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -11,6 +11,7 @@ ContactNode_Name=Name ContactNode_Office_Number=Office Number ContactNode_Phone=Phone Number ContactNode_URL=URL +ContactsViewer_columnHeader_Country=Country ContactsViewer_columnHeader_Email=Email ContactsViewer_columnHeader_Name=Name ContactsViewer_columnHeader_Phone=Phone diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java index 8dbf58acef..80a6795ab3 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java @@ -18,6 +18,9 @@ */ package org.sleuthkit.autopsy.communications.relationships; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; import java.util.ArrayList; import java.util.List; import java.util.TimeZone; @@ -70,7 +73,7 @@ final class ContactNode extends BlackboardArtifactNode { @Override protected Sheet createSheet() { Sheet sheet = new Sheet(); - + final BlackboardArtifact artifact = getArtifact(); BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); if (fromID != TSK_CONTACT) { @@ -103,26 +106,26 @@ final class ContactNode extends BlackboardArtifactNode { otherList.add(bba); } } - - addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getLabel(), - sheetSet, nameList); - addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getLabel(), - sheetSet, phoneNumList); - addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getLabel(), - sheetSet, emailList); + + addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getLabel(), + sheetSet, nameList); + addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getLabel(), + sheetSet, phoneNumList); + addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getLabel(), + sheetSet, emailList); for (BlackboardAttribute bba : otherList) { sheetSet.put(new NodeProperty<>(bba.getAttributeType().getTypeName(), bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); } - + List children = artifact.getChildren(); - if(children != null) { + if (children != null) { int count = 0; String imageLabelPrefix = "Image"; - for(Content child: children) { - if(child instanceof AbstractFile) { + for (Content child : children) { + if (child instanceof AbstractFile) { String imageLabel = imageLabelPrefix; - if(count > 0) { + if (count > 0) { imageLabel = imageLabelPrefix + "-" + count; } sheetSet.put(new NodeProperty<>(imageLabel, imageLabel, imageLabel, child.getName())); @@ -136,14 +139,20 @@ final class ContactNode extends BlackboardArtifactNode { return sheet; } - + private void addPropertiesToSheet(String propertyID, Sheet.Set sheetSet, List attributeList) { int count = 0; for (BlackboardAttribute bba : attributeList) { if (count++ > 0) { sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { + sheetSet.put(new NodeProperty<>("Country" + "_" + count, bba.getAttributeType().getDisplayName() + " Country", "", getCountryCode(bba.getValueString()))); + } } else { sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { + sheetSet.put(new NodeProperty<>("Country", bba.getAttributeType().getDisplayName() + " Country", "", getCountryCode(bba.getValueString()))); + } } } } @@ -175,4 +184,23 @@ final class ContactNode extends BlackboardArtifactNode { public String getSourceName() { return getDisplayName(); } + + private String getCountryCode(String phoneNumber) { + String regionCode = ""; + try { + PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); + Phonenumber.PhoneNumber phoneNum = phoneNumberUtil.parse(phoneNumber, "US"); + regionCode = phoneNumberUtil.getRegionCodeForNumber(phoneNum); + + if (regionCode == null) { + return ""; + } else { + return regionCode; + } + } catch (NumberParseException ex) { + logger.log(Level.WARNING, "Error getting country code, for phone number: {0}", phoneNumber); + return regionCode; + } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java index 33afae7251..41d1ba3445 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java @@ -59,6 +59,7 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{ "ContactsViewer_columnHeader_Name=Name", "ContactsViewer_columnHeader_Phone=Phone", "ContactsViewer_columnHeader_Email=Email", + "ContactsViewer_columnHeader_Country=Country", "ContactsViewer_noContacts_message=" }) @@ -96,7 +97,8 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{ outline = outlineViewPanel.getOutlineView().getOutline(); outlineViewPanel.getOutlineView().setPropertyColumns( "TSK_EMAIL", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getDisplayName(), - "TSK_PHONE_NUMBER", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getDisplayName() + "TSK_PHONE_NUMBER", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getDisplayName(), + "Country", "Country" ); outline.setRootVisible(false); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.ContactsViewer_columnHeader_Name()); From 37cb49df2dd14507463a2c490bbf1ce10d46a69a Mon Sep 17 00:00:00 2001 From: Eammon Date: Tue, 5 Nov 2019 16:05:12 -0500 Subject: [PATCH 02/41] Removed timeline related context menu items when timeline functionality is not available. --- .../timeline/actions/ViewArtifactInTimelineAction.java | 5 +++++ .../autopsy/timeline/actions/ViewFileInTimelineAction.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java index ed36bff868..69f998dc9b 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewArtifactInTimelineAction.java @@ -25,6 +25,7 @@ import org.openide.util.NbBundle; import org.openide.util.actions.SystemAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -43,6 +44,10 @@ public final class ViewArtifactInTimelineAction extends AbstractAction { public ViewArtifactInTimelineAction(BlackboardArtifact artifact) { super(Bundle.ViewArtifactInTimelineAction_displayName()); this.artifact = artifact; + // If timeline functionality is not available this action is disabled. + if ("false".equals(ModuleSettings.getConfigSetting("timeline", "enable_timeline"))) { + setEnabled(false); + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java index e763f4c428..8ef6bce42c 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java @@ -25,6 +25,7 @@ import org.openide.util.NbBundle; import org.openide.util.actions.SystemAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -54,6 +55,10 @@ public final class ViewFileInTimelineAction extends AbstractAction { && file.getAtime() <= 0)) { this.setEnabled(false); } + // If timeline functionality is not available this action is disabled. + if ("false".equals(ModuleSettings.getConfigSetting("timeline", "enable_timeline"))) { + setEnabled(false); + } } @NbBundle.Messages({"ViewFileInTimelineAction.viewFile.displayName=View File in Timeline... "}) From e6368e6b00dd38744121073bbcfb452c329a4438 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 11 Nov 2019 11:03:01 -0500 Subject: [PATCH 03/41] Use of PhoneNumUtil to get Country code Use of Phone Number util to get country code. --- Core/ivy.xml | 118 +++++++++--------- .../relationships/Bundle.properties-MERGED | 1 + .../relationships/ContactNode.java | 29 +---- 3 files changed, 65 insertions(+), 83 deletions(-) diff --git a/Core/ivy.xml b/Core/ivy.xml index d875087942..2958230052 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -1,59 +1,59 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index b1b72054ca..b0465ebf1e 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -4,6 +4,7 @@ CallLogViewer_noCallLogs= CallLogViewer_recipient_label=To/From CallLogViewer_title=Call Logs ContactDetailsPane.nameLabel.text=Placeholder +ContactNode_Country=Country ContactNode_Email=Email Address ContactNode_Home_Number=Home Number ContactNode_Mobile_Number=Mobile Number diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java index 80a6795ab3..685efca53b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java @@ -18,9 +18,6 @@ */ package org.sleuthkit.autopsy.communications.relationships; -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.PhoneNumberUtil; -import com.google.i18n.phonenumbers.Phonenumber; import java.util.ArrayList; import java.util.List; import java.util.TimeZone; @@ -39,6 +36,7 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBU import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.communications.Utils; +import org.sleuthkit.autopsy.coreutils.PhoneNumUtil; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -57,7 +55,8 @@ final class ContactNode extends BlackboardArtifactNode { "ContactNode_Mobile_Number=Mobile Number", "ContactNode_Office_Number=Office Number", "ContactNode_URL=URL", - "ContactNode_Home_Number=Home Number",}) + "ContactNode_Home_Number=Home Number", + "ContactNode_Country=Country",}) ContactNode(BlackboardArtifact artifact) { super(artifact); @@ -146,12 +145,12 @@ final class ContactNode extends BlackboardArtifactNode { if (count++ > 0) { sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { - sheetSet.put(new NodeProperty<>("Country" + "_" + count, bba.getAttributeType().getDisplayName() + " Country", "", getCountryCode(bba.getValueString()))); + sheetSet.put(new NodeProperty<>(Bundle.ContactNode_Country() + "_" + count, bba.getAttributeType().getDisplayName() + " " + Bundle.ContactNode_Country(), "", PhoneNumUtil.getCountryCode(bba.getValueString()))); } } else { sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { - sheetSet.put(new NodeProperty<>("Country", bba.getAttributeType().getDisplayName() + " Country", "", getCountryCode(bba.getValueString()))); + sheetSet.put(new NodeProperty<>(Bundle.ContactNode_Country(), bba.getAttributeType().getDisplayName() + " " + Bundle.ContactNode_Country(), "", PhoneNumUtil.getCountryCode(bba.getValueString()))); } } } @@ -185,22 +184,4 @@ final class ContactNode extends BlackboardArtifactNode { return getDisplayName(); } - private String getCountryCode(String phoneNumber) { - String regionCode = ""; - try { - PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - Phonenumber.PhoneNumber phoneNum = phoneNumberUtil.parse(phoneNumber, "US"); - regionCode = phoneNumberUtil.getRegionCodeForNumber(phoneNum); - - if (regionCode == null) { - return ""; - } else { - return regionCode; - } - } catch (NumberParseException ex) { - logger.log(Level.WARNING, "Error getting country code, for phone number: {0}", phoneNumber); - return regionCode; - } - } - } From 0035c48a100802fa4ff0af07b8b39f73541524e6 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 11 Nov 2019 11:34:35 -0500 Subject: [PATCH 04/41] Update ContactsViewer.java Use bundle messages --- .../autopsy/communications/relationships/ContactsViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java index 41d1ba3445..27f8fb95c4 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java @@ -98,7 +98,7 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{ outlineViewPanel.getOutlineView().setPropertyColumns( "TSK_EMAIL", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getDisplayName(), "TSK_PHONE_NUMBER", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getDisplayName(), - "Country", "Country" + Bundle.ContactsViewer_columnHeader_Country(), Bundle.ContactsViewer_columnHeader_Country() ); outline.setRootVisible(false); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.ContactsViewer_columnHeader_Name()); From 91a07170adc7bcdfbd03b46d4ad1d272c6050344 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 11 Nov 2019 16:12:29 -0500 Subject: [PATCH 05/41] Changes to add country code to same line as phone number Changed to add country code to same line as phone number. --- .../relationships/Bundle.properties-MERGED | 2 -- .../communications/relationships/ContactNode.java | 13 +++++++------ .../relationships/ContactsViewer.java | 4 +--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index b0465ebf1e..f1530d9f89 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -4,7 +4,6 @@ CallLogViewer_noCallLogs= CallLogViewer_recipient_label=To/From CallLogViewer_title=Call Logs ContactDetailsPane.nameLabel.text=Placeholder -ContactNode_Country=Country ContactNode_Email=Email Address ContactNode_Home_Number=Home Number ContactNode_Mobile_Number=Mobile Number @@ -12,7 +11,6 @@ ContactNode_Name=Name ContactNode_Office_Number=Office Number ContactNode_Phone=Phone Number ContactNode_URL=URL -ContactsViewer_columnHeader_Country=Country ContactsViewer_columnHeader_Email=Email ContactsViewer_columnHeader_Name=Name ContactsViewer_columnHeader_Phone=Phone diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java index 685efca53b..2c0db02af7 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java @@ -55,8 +55,7 @@ final class ContactNode extends BlackboardArtifactNode { "ContactNode_Mobile_Number=Mobile Number", "ContactNode_Office_Number=Office Number", "ContactNode_URL=URL", - "ContactNode_Home_Number=Home Number", - "ContactNode_Country=Country",}) + "ContactNode_Home_Number=Home Number",}) ContactNode(BlackboardArtifact artifact) { super(artifact); @@ -143,14 +142,16 @@ final class ContactNode extends BlackboardArtifactNode { int count = 0; for (BlackboardAttribute bba : attributeList) { if (count++ > 0) { - sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { - sheetSet.put(new NodeProperty<>(Bundle.ContactNode_Country() + "_" + count, bba.getAttributeType().getDisplayName() + " " + Bundle.ContactNode_Country(), "", PhoneNumUtil.getCountryCode(bba.getValueString()))); + sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " " + PhoneNumUtil.getCountryCode(bba.getValueString()))); + } else { + sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); } } else { - sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { - sheetSet.put(new NodeProperty<>(Bundle.ContactNode_Country(), bba.getAttributeType().getDisplayName() + " " + Bundle.ContactNode_Country(), "", PhoneNumUtil.getCountryCode(bba.getValueString()))); + sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " " + PhoneNumUtil.getCountryCode(bba.getValueString()))); + } else { + sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); } } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java index 27f8fb95c4..33afae7251 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java @@ -59,7 +59,6 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{ "ContactsViewer_columnHeader_Name=Name", "ContactsViewer_columnHeader_Phone=Phone", "ContactsViewer_columnHeader_Email=Email", - "ContactsViewer_columnHeader_Country=Country", "ContactsViewer_noContacts_message=" }) @@ -97,8 +96,7 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{ outline = outlineViewPanel.getOutlineView().getOutline(); outlineViewPanel.getOutlineView().setPropertyColumns( "TSK_EMAIL", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getDisplayName(), - "TSK_PHONE_NUMBER", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getDisplayName(), - Bundle.ContactsViewer_columnHeader_Country(), Bundle.ContactsViewer_columnHeader_Country() + "TSK_PHONE_NUMBER", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getDisplayName() ); outline.setRootVisible(false); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.ContactsViewer_columnHeader_Name()); From 242ad6170acfd993de8b6ce2f63e05711df8c909 Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Tue, 12 Nov 2019 11:01:37 -0500 Subject: [PATCH 06/41] 5707: Show attachments in Message Content Viewer. --- ...java => AttachmentThumbnailsChildren.java} | 54 +++-- .../relationships/MediaViewer.java | 2 +- .../contentviewers/MessageContentViewer.java | 90 ++++---- .../autopsy/datamodel/AttachmentNode.java | 207 ++++++++++++++++++ .../datamodel/Bundle.properties-MERGED | 4 + .../datamodel/DisplayableItemNodeVisitor.java | 12 + .../autopsy/images/document-question-16.png | Bin 0 -> 269 bytes .../org/sleuthkit/autopsy/images/url-16.png | Bin 0 -> 753 bytes 8 files changed, 305 insertions(+), 64 deletions(-) rename Core/src/org/sleuthkit/autopsy/communications/relationships/{AttachmentsChildren.java => AttachmentThumbnailsChildren.java} (60%) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java create mode 100644 Core/src/org/sleuthkit/autopsy/images/document-question-16.png create mode 100644 Core/src/org/sleuthkit/autopsy/images/url-16.png diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentsChildren.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentThumbnailsChildren.java similarity index 60% rename from Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentsChildren.java rename to Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentThumbnailsChildren.java index 7dbe2133e0..816eb05d75 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentsChildren.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentThumbnailsChildren.java @@ -18,10 +18,13 @@ */ package org.sleuthkit.autopsy.communications.relationships; +import com.google.gson.Gson; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; +import java.util.function.Consumer; import java.util.logging.Level; import org.openide.nodes.Children; import org.openide.nodes.Node; @@ -32,15 +35,18 @@ import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.blackboardutils.FileAttachment; +import org.sleuthkit.datamodel.blackboardutils.MessageAttachments; /** * Factory for creating thumbnail children nodes. */ -final class AttachmentsChildren extends Children.Keys { +final class AttachmentThumbnailsChildren extends Children.Keys { - private static final Logger logger = Logger.getLogger(AttachmentsChildren.class.getName()); + private static final Logger LOGGER = Logger.getLogger(AttachmentThumbnailsChildren.class.getName()); private final Set artifacts; @@ -51,17 +57,16 @@ final class AttachmentsChildren extends Children.Keys { * The thumbnails will be initialls sorted by size, then name so that they * appear sorted by size by default. */ - AttachmentsChildren(Set artifacts) { + AttachmentThumbnailsChildren(Set artifacts) { super(false); this.artifacts = artifacts; - } @Override protected Node[] createNodes(AbstractFile t) { - return new Node[]{new AttachementNode(t)}; + return new Node[]{new AttachementThumbnailNode(t)}; } @Override @@ -77,15 +82,36 @@ final class AttachmentsChildren extends Children.Keys { return result; }); - artifacts.forEach((bba) -> { - try { - for (Content childContent : bba.getChildren()) { - if (childContent instanceof AbstractFile) { - thumbnails.add((AbstractFile) childContent); + artifacts.forEach(new Consumer() { + @Override + public void accept(BlackboardArtifact bba) { + try { + // Get the attachments from TSK_ATTACHMENTS attribute. + BlackboardAttribute attachmentsAttr = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS)); + if (attachmentsAttr != null) { + + String jsonVal = attachmentsAttr.getValueString(); + MessageAttachments msgAttachments = new Gson().fromJson(jsonVal, MessageAttachments.class); + + Collection fileAttachments = msgAttachments.getFileAttachments(); + for (FileAttachment fileAttachment : fileAttachments) { + long attachedFileObjId = fileAttachment.getObjectId(); + if (attachedFileObjId >= 0) { + AbstractFile attachedFile = bba.getSleuthkitCase().getAbstractFileById(attachedFileObjId); + thumbnails.add(attachedFile); + } + } + } else { // backward compatibility - email message attachments are derived files, children of the message. + for (Content childContent : bba.getChildren()) { + if (childContent instanceof AbstractFile) { + thumbnails.add((AbstractFile) childContent); + } + } } + + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Unable to get children from artifact.", ex); //NON-NLS } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get children from artifact.", ex); //NON-NLS } }); @@ -95,9 +121,9 @@ final class AttachmentsChildren extends Children.Keys { /** * A node for representing a thumbnail. */ - static class AttachementNode extends FileNode { + static class AttachementThumbnailNode extends FileNode { - AttachementNode(AbstractFile file) { + AttachementThumbnailNode(AbstractFile file) { super(file, false); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java index 07e6223a03..3bdb7a7a25 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java @@ -129,7 +129,7 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM thumbnailViewer.resetComponent(); - thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode(new AttachmentsChildren(artifactList)), tableEM), true, this.getClass().getName())); + thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode(new AttachmentThumbnailsChildren(artifactList)), tableEM), true, this.getClass().getName())); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 0f36c84b9e..1046c3177b 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -18,15 +18,17 @@ */ package org.sleuthkit.autopsy.contentviewers; +import org.sleuthkit.autopsy.datamodel.AttachmentNode; +import com.google.gson.Gson; import java.awt.Color; import java.awt.Component; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; 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; @@ -35,15 +37,12 @@ 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.AbstractAbstractFileNode; -import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.AbstractFile; @@ -67,7 +66,12 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHO 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.Content; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.blackboardutils.FileAttachment; +import org.sleuthkit.datamodel.blackboardutils.MessageAttachments; +import org.sleuthkit.datamodel.blackboardutils.Attachment; +import org.sleuthkit.datamodel.blackboardutils.URLAttachment; /** * Shows SMS/MMS/EMail messages @@ -541,11 +545,34 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont } 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 Set attachments; + + // Attachments are specified in an attribute TSK_ATTACHMENTS as JSON attribute + BlackboardAttribute attachmentsAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS)); + if(attachmentsAttr != null) { + + attachments = new HashSet<>(); + String jsonVal = attachmentsAttr.getValueString(); + MessageAttachments msgAttachments = new Gson().fromJson(jsonVal, MessageAttachments.class); + + Collection fileAttachments = msgAttachments.getFileAttachments(); + for (FileAttachment fileAttachment: fileAttachments) { + attachments.add(fileAttachment); + } + Collection urlAttachments = msgAttachments.getUrlAttachments(); + for (URLAttachment urlAttachment: urlAttachments) { + attachments.add(urlAttachment); + } + } else { // For backward compatibility - email attachements are derived files and children of the email message artifact + attachments = new HashSet<>(); + for (Content child: artifact.getChildren()) { + if (child instanceof AbstractFile) { + attachments.add(new FileAttachment((AbstractFile)child)); + } + } + } + final int numberOfAttachments = attachments.size(); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, numberOfAttachments > 0); @@ -633,16 +660,17 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont return doc.html(); } - private static class AttachmentsChildren extends Children.Keys { + + final class AttachmentsChildren extends Children.Keys { - private final Set attachments; + private final Set attachments; - AttachmentsChildren(Set attachments) { + AttachmentsChildren(Set attachments) { this.attachments = attachments; } @Override - protected Node[] createNodes(AbstractFile t) { + protected Node[] createNodes(Attachment t) { return new Node[]{new AttachmentNode(t)}; } @@ -652,40 +680,4 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont 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, false); - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Set keepProps = new HashSet<>(Arrays.asList( - NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.nameColLbl"), - NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.score.name"), - NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.comment.name"), - NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.count.name"), - NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.sizeColLbl"), - NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.mimeType"), - NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.knownColLbl"))); - - //Remove all other props except for the ones above - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - for (Property p : sheetSet.getProperties()) { - if (!keepProps.contains(p.getName())) { - sheetSet.remove(p.getName()); - } - } - - return sheet; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java new file mode 100644 index 0000000000..cc9e364790 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java @@ -0,0 +1,207 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.logging.Level; +import javax.swing.Action; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.contentviewers.MessageContentViewer; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.coreutils.Logger; +import static org.sleuthkit.autopsy.datamodel.FileNode.getIconForFileType; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.ViewContextAction; +import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskException; +import org.sleuthkit.datamodel.blackboardutils.Attachment; +import org.sleuthkit.datamodel.blackboardutils.FileAttachment; +import org.sleuthkit.datamodel.blackboardutils.URLAttachment; + +/** + * Node for a message attachment. + * + */ +public class AttachmentNode extends DisplayableItemNode { + + private static final Logger LOGGER = Logger.getLogger(MessageContentViewer.class.getName()); + + private final Attachment attachment; + private final AbstractFile attachmentFile; + + public AttachmentNode(Attachment attachment) { + + super(Children.LEAF, createLookup(attachment)); + + super.setName(attachment.getLocation()); + super.setDisplayName(attachment.getLocation()); // SET NODE DISPLAY NAME, I.E., TEXT IN FIRST TABLE CELL + + this.attachment = attachment; + Long attachmentObjId = attachment.getObjId(); + AbstractFile attchmentAbstractFile = null; + + if (attachmentObjId != null && attachmentObjId > 0) { + try { + attchmentAbstractFile = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(attachmentObjId); + } catch (TskException | NoCurrentCaseException ex) { + LOGGER.log(Level.WARNING, "Error loading attachment file with object id " + attachmentObjId, ex); //NON-NLS + } + } + attachmentFile = attchmentAbstractFile; + + // set the icon for node + setIcon(); + } + + @Override + @NbBundle.Messages({ + "AttachmentNode.getActions.viewFileInDir.text=View File in Directory", + "AttachmentNode.getActions.viewInNewWin.text=View in New Window", + "AttachmentNode.getActions.openInExtViewer.text=Open in External Viewer Ctrl+E", + "AttachmentNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash"}) + public Action[] getActions(boolean context) { + + List actionsList = new ArrayList<>(); + actionsList.addAll(Arrays.asList(super.getActions(true))); + + // If there is an attachment file + if (this.attachmentFile != null) { + actionsList.add(new ViewContextAction(Bundle.AttachmentNode_getActions_viewFileInDir_text(), this.attachmentFile)); + actionsList.add(null); // Creates an item separator + + actionsList.add(new NewWindowViewAction(Bundle.AttachmentNode_getActions_viewInNewWin_text(), this)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction( + Bundle.AttachmentNode_getActions_openInExtViewer_text(), this)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } + actionsList.add(ViewFileInTimelineAction.createViewFileAction(this.attachmentFile)); + actionsList.add(null); // Creates an item separator + + actionsList.add(ExtractAction.getInstance()); + actionsList.add(ExportCSVAction.getInstance()); + actionsList.add(null); // Creates an item separator + + actionsList.add(AddContentTagAction.getInstance()); + if (1 == selectedFilesList.size()) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + + } + return actionsList.toArray(new Action[actionsList.size()]); + } + + @Override + protected Sheet createSheet() { + + // Create a new property sheet. + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + + sheetSet.put(new NodeProperty<>("Location", "Location", "", this.attachment.getLocation())); + + if (attachmentFile != null) { + long size = attachmentFile.getSize(); + String mimeType = attachmentFile.getMIMEType(); + + // @TODO Vik-5762: get SCO Columns + + sheetSet.put(new NodeProperty<>("Size", "Size", "", size)); + if (StringUtils.isNotEmpty(mimeType)) { + sheetSet.put(new NodeProperty<>("Mime type", "Mime type", "", mimeType)); + } + sheetSet.put(new NodeProperty<>("Known", "Known", "", attachmentFile.getKnown().getName())); + } + + return sheet; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + private static Lookup createLookup(Attachment attachment) { + Long attachmentObjId = attachment.getObjId(); + if (attachmentObjId != null && attachmentObjId > 0) { + AbstractFile attachmentFile = null; + try { + attachmentFile = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(attachmentObjId); + if (attachmentFile != null) { + return Lookups.fixed(attachment, attachmentFile); + } else { + return Lookups.fixed(attachment); + } + } catch (TskException | NoCurrentCaseException ex) { + return Lookups.fixed(attachment); + } + } + return Lookups.fixed(attachment); + } + + /** + * Set the icon based on attachment type + */ + private void setIcon() { + if (attachmentFile != null) { + this.setIconBaseWithExtension(getIconForFileType(attachmentFile)); + } else if (attachment instanceof FileAttachment) { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/document-question-16.png"); + } else if (attachment instanceof URLAttachment) { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/url-16.png"); + } else { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); + } + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 7f0a4d2419..f834765885 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -43,6 +43,10 @@ ArtifactStringContent.attrsTableHeader.type=Type ArtifactStringContent.attrsTableHeader.value=Value ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database +AttachmentNode.getActions.openInExtViewer.text=Open in External Viewer Ctrl+E +AttachmentNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash +AttachmentNode.getActions.viewFileInDir.text=View File in Directory +AttachmentNode.getActions.viewInNewWin.text=View in New Window # {0} - node name BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0} BlackboardArtifactNode.createSheet.artifactDetails.displayName=Result Details diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index bb324bc7bb..660368de81 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.allcasessearch.CorrelationAttributeInstanceNode; +import org.sleuthkit.autopsy.contentviewers.MessageContentViewer; /** * Visitor pattern that goes over all nodes in the directory tree. This includes @@ -186,6 +187,11 @@ public interface DisplayableItemNodeVisitor { T visit(InterestingHits.InterestingItemTypeNode aThis); + /* + * Attachments + */ + T visit(AttachmentNode node); + /** * Visitor with an implementable default behavior for all types. Override * specific visit types to not use the default behavior. @@ -522,5 +528,11 @@ public interface DisplayableItemNodeVisitor { public T visit(Accounts.DefaultAccountTypeNode node) { return defaultVisit(node); } + + @Override + public T visit(AttachmentNode node) { + return defaultVisit(node); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/images/document-question-16.png b/Core/src/org/sleuthkit/autopsy/images/document-question-16.png new file mode 100644 index 0000000000000000000000000000000000000000..c084ead81a1ed7c285437952ccf7399fa29809e5 GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ za0`PlBg3pY5?<$RSB zi#vEIj$7HoOmE}Ic14xRd-j|Zxb^1ai4Li%ix1h7*94RtF;czI^2GAtEA3Q?)eC;5 zP162Q7T~_^yhKvF@HUAO&LtL)gzm%?{%}oblQv7-cTDzjMY5zm3ty)+_xf+CmK~7` z-iKa0&!paC;IF!W>IcuYi`QE6b*)S3b9nvm-#dkFuY2Nowa?Xetk;g~lMk~3I-SAO L)z4*}Q$iB}LVaXP literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/images/url-16.png b/Core/src/org/sleuthkit/autopsy/images/url-16.png new file mode 100644 index 0000000000000000000000000000000000000000..414cff505ddee9ce483e13a6d08027c3be0b90c9 GIT binary patch literal 753 zcmV`hv$7iJ^~wQ0Pqt3 zrVc#V{|_4!H*dqlF20eem~mV+tJ_O z53AJ*nO7tLQk|96c&x7W(w)wSXgXPs^R*2)S91oX`%hqGTEe3jZ!j@64YSz{gTa7c zZ~-otYb_;6^6;rEZROXl-msMK+Y4hJja-8o5)vrLr%|%ogwd%ex?jInrfh9(MOW7| z^!E0y8R+x|+flQ%K8M+%KU`ENKMwLpXL|%{A>qSdT j%lKx4HUJOs|6KkBwK_NX>lS^`00000NkvXXu0mjfc2-dl literal 0 HcmV?d00001 From 604c8a877fa5296bc711db51d1517405c3310fb0 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Tue, 12 Nov 2019 11:59:07 -0500 Subject: [PATCH 07/41] Update ContactNode.java Add brackets around country code if it exists. --- .../relationships/ContactNode.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java index 2c0db02af7..6937368fa3 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java @@ -143,15 +143,25 @@ final class ContactNode extends BlackboardArtifactNode { for (BlackboardAttribute bba : attributeList) { if (count++ > 0) { if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { - sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " " + PhoneNumUtil.getCountryCode(bba.getValueString()))); + String phoneNumCountry = PhoneNumUtil.getCountryCode(bba.getValueString()); + if (phoneNumCountry != "") { + sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " [" + phoneNumCountry + "]")); + } else { + sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + } } else { - sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); } } else { if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { - sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " " + PhoneNumUtil.getCountryCode(bba.getValueString()))); + String phoneNumCountry = PhoneNumUtil.getCountryCode(bba.getValueString()); + if (phoneNumCountry != "") { + sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " [" + phoneNumCountry + "]")); + } else { + sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + } } else { - sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); } } } From 2c23b5b52f54e3995b5090883170545b1762f34e Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Tue, 12 Nov 2019 13:04:31 -0500 Subject: [PATCH 08/41] Address Codacy comments on previous commit. --- .../sleuthkit/autopsy/contentviewers/MessageContentViewer.java | 3 +++ Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java | 2 +- .../autopsy/datamodel/DisplayableItemNodeVisitor.java | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 1046c3177b..66f0af29fe 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -661,6 +661,9 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont } + /** + * Creates child nodes for message attachments. + */ final class AttachmentsChildren extends Children.Keys { private final Set attachments; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java index cc9e364790..7932b1e421 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java @@ -127,7 +127,7 @@ public class AttachmentNode extends DisplayableItemNode { actionsList.addAll(ContextMenuExtensionPoint.getActions()); } - return actionsList.toArray(new Action[actionsList.size()]); + return actionsList.toArray(new Action[0]); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 660368de81..541cbc38f3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -32,7 +32,6 @@ import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.allcasessearch.CorrelationAttributeInstanceNode; -import org.sleuthkit.autopsy.contentviewers.MessageContentViewer; /** * Visitor pattern that goes over all nodes in the directory tree. This includes From bf46b394684584be3025c3619a96a06a430196dc Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Tue, 12 Nov 2019 14:33:45 -0500 Subject: [PATCH 09/41] Update ContactNode.java Make codacy happier. --- .../communications/relationships/ContactNode.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java index 6937368fa3..769ba682a6 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java @@ -144,10 +144,10 @@ final class ContactNode extends BlackboardArtifactNode { if (count++ > 0) { if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { String phoneNumCountry = PhoneNumUtil.getCountryCode(bba.getValueString()); - if (phoneNumCountry != "") { - sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " [" + phoneNumCountry + "]")); - } else { + if (phoneNumCountry.equals("")) { sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + } else { + sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " [" + phoneNumCountry + "]")); } } else { sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); @@ -155,10 +155,10 @@ final class ContactNode extends BlackboardArtifactNode { } else { if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { String phoneNumCountry = PhoneNumUtil.getCountryCode(bba.getValueString()); - if (phoneNumCountry != "") { - sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " [" + phoneNumCountry + "]")); - } else { + if (phoneNumCountry.equals("")) { sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + } else { + sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString() + " [" + phoneNumCountry + "]")); } } else { sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); From e30843c7c6bcc2d9bc48ff9deb39d75256ad5eff Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:15:45 -0500 Subject: [PATCH 10/41] Implemented record parsing and testing for call logs, web bookmarks and contacts --- .../xry/AbstractSingleKeyValueParser.java | 158 +++++++++++++++++ .../xry/XRYCallsFileParser.java | 165 ++++++++++++++++++ .../xry/XRYContactsFileParser.java | 76 ++++++++ .../xry/XRYFileParser.java | 46 +++++ .../xry/XRYFileParserFactory.java | 80 +++++++++ .../xry/XRYFileReader.java | 74 ++++++-- .../datasourceprocessors/xry/XRYFolder.java | 2 +- .../xry/XRYWebBookmarksFileParser.java | 68 ++++++++ 8 files changed, 658 insertions(+), 11 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java new file mode 100755 index 0000000000..0ed74bdbef --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -0,0 +1,158 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Template parse method for reports that make blackboard attributes from a + * single key value pairs. + * + * This parse implementation will create 1 artifact per XRY entity. + */ +abstract class AbstractSingleKeyValueParser implements XRYFileParser { + + private static final Logger logger = Logger.getLogger(AbstractSingleKeyValueParser.class.getName()); + + private static final char KEY_VALUE_DELIMITER = ':'; + + @Override + public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { + Path reportPath = reader.getReportPath(); + logger.log(Level.INFO, String.format("INFO: Processing report at [ %s ]", reportPath.toString())); + + while (reader.hasNextEntity()) { + String xryEntity = reader.nextEntity(); + String[] xryLines = xryEntity.split("\n"); + + List attributes = new ArrayList<>(); + + if (xryLines.length > 0) { + logger.log(Level.INFO, String.format("INFO: Processing [ %s ]", xryLines[0])); + } + + String namespace = ""; + for (int i = 1; i < xryLines.length; i++) { + String xryLine = xryLines[i]; + + if (isNamespace(xryLine)) { + logger.log(Level.INFO, String.format("INFO: Detected XRY " + + "namespace keyword [ %s ]. Applying to all key value pairs following it.", xryLine)); + namespace = xryLine.trim(); + continue; + } + + //Find the XRY key on this line. Assume key is the value between + //the start of the line and the first delimiter. + int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER); + if (keyDelimiter == -1) { + logger.log(Level.SEVERE, String.format("Expected a key value " + + "pair on this line (in brackets) [ %s ], but one was not detected." + + " Here is the previous line (in brackets) [ %s ]. What does this key mean?", xryLine, xryLines[i - 1])); + continue; + } + String key = xryLine.substring(0, keyDelimiter).trim(); + String value = xryLine.substring(keyDelimiter + 1).trim(); + + if (!isKey(key)) { + logger.log(Level.SEVERE, String.format("The following key, " + + "value pair (in brackets, respectively) [ %s ], [ %s ] was not recognized. Discarding..." + + " Here is the previous line [ %s ] for context. What is it?", key, value, xryLines[i - 1])); + continue; + } + + if (value.isEmpty()) { + logger.log(Level.SEVERE, String.format("The following key " + + "(in brackets) [ %s ] was recognized, but the value was empty. Discarding..." + + " Here is the previous line for context [ %s ]. What does this mean?", key, xryLines[i - 1])); + continue; + } + + BlackboardAttribute attribute = makeAttribute(namespace, key, value); + //Returning null is temporary solution until we map out how we will deal with + //attributes we are currently ignoring. + if (attribute != null) { + attributes.add(makeAttribute(namespace, key, value)); + } + } + + if (attributes.size() > 0) { + makeArtifact(attributes, parent); + } + } + } + + /** + * Determines if the key candidate is a known key. A key candidate is a + * string literal that begins a line and is terminated by a semi-colon. + * + * Ex: + * + * Call Type : Missed + * + * "Call Type" would be the key candidate that was extracted. + * + * @param key Key to test. These keys are trimmed of whitespace only. + * @return Indication if this key can be processed. + */ + abstract boolean isKey(String key); + + /** + * Determines if the namespace candidate is a known namespace. A namespace + * candidate is a string literal that makes up an entire line. + * + * Ex: + * + * To Tel : +1245325 + * + * "To" would be the candidate namespace that was extracted. + * + * @param nameSpace Namespace to test. Namespaces are trimmed of whitespace + * only. + * @return Indication if this namespace can be processed. + */ + abstract boolean isNamespace(String nameSpace); + + /** + * Creates an attribute from the extracted key value pair. + * + * @param nameSpace The namespace of this key value pair. + * It will have been verified with isNamespace, otherwise it will be empty. + * @param key The key that was verified with isKey. + * @param value The value associated with that key. + * @return + */ + abstract BlackboardAttribute makeAttribute(String nameSpace, String key, String value); + + /** + * Makes an artifact from the parsed attributes. + * + * @return + */ + abstract void makeArtifact(List attributes, Content parent) throws TskCoreException; + +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java new file mode 100755 index 0000000000..05934a4904 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -0,0 +1,165 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Set; +import java.util.HashSet; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Parses XRY Calls files and creates artifacts. + */ +final class XRYCallsFileParser extends AbstractSingleKeyValueParser { + + private static final Logger logger = Logger.getLogger(XRYCallsFileParser.class.getName()); + + //Human readable name of this parser. + private static final String PARSER_NAME = "XRY Calls"; + + private static final DateTimeFormatter DATE_TIME_PARSER + = DateTimeFormatter.ofPattern("M/d/y h:m:s [a][ z]"); + + private static final String INCOMING = "Incoming"; + + //All known XRY keys for call reports. + private static final Set XRY_KEYS = new HashSet() { + { + add("tel"); + add("number"); + add("call type"); + add("name (matched)"); + add("time"); + add("duration"); + add("storage"); + add("index"); + } + }; + + //All known XRY namespaces for call reports. + private static final Set XRY_NAMESPACES = new HashSet() { + { + add("to"); + add("from"); + } + }; + + @Override + boolean isKey(String key) { + String normalizedKey = key.toLowerCase(); + return XRY_KEYS.contains(normalizedKey); + } + + @Override + boolean isNamespace(String nameSpace) { + String normalizedNamespace = nameSpace.toLowerCase(); + return XRY_NAMESPACES.contains(normalizedNamespace); + } + + @Override + BlackboardAttribute makeAttribute(String nameSpace, String key, String value) { + String normalizedKey = key.toLowerCase(); + String normalizedNamespace = nameSpace.toLowerCase(); + + switch (normalizedKey) { + case "time": + //Tranform value to epoch ms + String dateTime = removeDateTimeLocale(value); + String normalizedDateTime = dateTime.trim(); + long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, PARSER_NAME, dateTimeInEpoch); + case "duration": + //Ignore for now. + return null; + case "storage": + //Ignore for now. + return null; + case "index": + //Ignore for now. + return null; + case "tel": + //Apply the namespace + switch (normalizedNamespace) { + case "from": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value); + default: + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, PARSER_NAME, value); + } + case "call type": + String normalizedValue = value.toLowerCase(); + switch (normalizedValue) { + case "missed": + case "received": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, INCOMING); + case "dialed": + return null; + case "last dialed": + return null; + default: + logger.log(Level.SEVERE, String.format("Call type (in brackets) [ %s ] not recognized.", value)); + return null; + } + case "number": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, PARSER_NAME, value); + case "name (matched)": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, PARSER_NAME, value); + default: + throw new IllegalArgumentException(String.format("key [ %s ] was not recognized.", key)); + } + } + + @Override + void makeArtifact(List attributes, Content parent) throws TskCoreException { + //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); + //artifact.addAttributes(attributes); + } + + /** + * Removes the locale from the date time value. + * + * @param dateTime + * @return + */ + private String removeDateTimeLocale(String dateTime) { + int index = dateTime.indexOf('('); + if (index == -1) { + return dateTime; + } + + return dateTime.substring(0, index); + } + + /** + * + * @param dateTime + * @return + */ + private long calculateMsSinceEpoch(String dateTime) { + LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); + //Assume dates have no offset. + return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java new file mode 100755 index 0000000000..233fd3da16 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -0,0 +1,76 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Parses XRY Contacts-Contacts files and creates artifacts. + */ +final class XRYContactsFileParser extends AbstractSingleKeyValueParser { + + //Human readable name of this parser. + private static final String PARSER_NAME = "XRY Contacts"; + + //All of the known XRY keys for contacts. + private static final Set XRY_KEYS = new HashSet() {{ + add("name"); + add("tel"); + add("storage"); + }}; + + @Override + boolean isKey(String key) { + String normalizedKey = key.toLowerCase(); + return XRY_KEYS.contains(normalizedKey); + } + + @Override + boolean isNamespace(String nameSpace) { + //No namespaces are currently known for this report type. + return false; + } + + @Override + BlackboardAttribute makeAttribute(String nameSpace, String key, String value) { + String normalizedKey = key.toLowerCase(); + switch(normalizedKey) { + case "name": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, PARSER_NAME, value); + case "tel": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, PARSER_NAME, value); + case "storage": + //Ignore for now. + return null; + default: + throw new IllegalArgumentException(String.format("Key [ %s ] was not recognized", key)); + } + } + + @Override + void makeArtifact(List attributes, Content parent) throws TskCoreException { + //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + //artifact.addAttributes(attributes); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java new file mode 100755 index 0000000000..1787641e78 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java @@ -0,0 +1,46 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.io.IOException; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Interface for XRY file parsing. + */ +interface XRYFileParser { + + /** + * Parses XRY entities and creates artifacts from the interpreted content. + * + * See XRYFileReader for more information on XRY entities. It is expected + * that implementations will create artifacts on the supplied Content + * object. + * + * @param reader Produces XRY entities from a given XRY file. + * @param parent Content object that will act as the source of the + * artifacts. + * @throws IOException If an I/O error occurs during reading. + * @throws TskCoreException If an error occurs during artifact creation. + */ + void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException; + +} + diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java new file mode 100755 index 0000000000..8dd64996fa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java @@ -0,0 +1,80 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +/** + * Instantiates XRYFileParsers by report type. + */ +final class XRYFileParserFactory { + + /** + * Creates the correct implementation of a XRYFileParser for the specified + * report type. + * + * It is assumed that the report type is supported, which means the client + * needs to have tested with supports beforehand. Otherwise, an + * IllegalArgumentException is thrown. + * + * @param reportType A supported XRY report type. + * @return A XRYFileParser with defined behavior for the report type. + * @throws IllegalArgumentException if the report type is not supported or + * is null. This is a misuse of the API. It is assumed that the report type + * has been tested with the supports method. + */ + public static XRYFileParser get(String reportType) { + if (reportType == null) { + throw new IllegalArgumentException("Report type cannot be null"); + } + + switch (reportType.toLowerCase()) { + case "calls": + return new XRYCallsFileParser(); + case "contacts/contacts": + return new XRYContactsFileParser(); + case "device/general information": + return new XRYDeviceGenInfoFileParser(); + case "messages/sms": + return new XRYMessagesFileParser(); + case "web/bookmarks": + return new XRYWebBookmarksFileParser(); + default: + throw new IllegalArgumentException(reportType + " not recognized."); + } + } + + /** + * Tests if a XRYFileParser implementation exists for the report type. + * + * @param reportType Report type to test. + * @return Indication if the report type can be parsed. + */ + public static boolean supports(String reportType) { + try { + //Attempt a get. + get(reportType); + return true; + } catch (IllegalArgumentException ex) { + return false; + } + } + + //Prevent direct instantiation + private XRYFileParserFactory() { + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index ff854a16ea..32a0b7eb33 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -45,36 +45,43 @@ import org.apache.commons.io.FilenameUtils; * From * Tel: 12345678 */ -public final class XRYFileReader implements AutoCloseable { +final class XRYFileReader implements AutoCloseable { private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName()); //Assume UTF_16LE private static final Charset CHARSET = StandardCharsets.UTF_16LE; - //Assume TXT extension - private static final String EXTENSION = "txt"; - - //Assume 0xFFFE is the BOM - private static final int[] BOM = {0xFF, 0xFE}; - //Assume all XRY reports have the type on the 3rd line. private static final int LINE_WITH_REPORT_TYPE = 3; //Assume all headers are 5 lines in length. private static final int HEADER_LENGTH_IN_LINES = 5; + //Assume TXT extension + private static final String EXTENSION = "txt"; + + //Assume 0xFFFE is the BOM + private static final int[] BOM = {0xFF, 0xFE}; + + //Entity to be consumed during file iteration. + private final StringBuilder xryEntity; + //Underlying reader for the xry file. private final BufferedReader reader; - private final StringBuilder xryEntity; + //Reference to the original xry file. + private final Path xryFilePath; /** * Creates an XRYFileReader. As part of construction, the XRY file is opened * and the reader is advanced past the header. This leaves the reader * positioned at the start of the first XRY entity. * - * The file is assumed to be encoded in UTF-16LE. + * The file is assumed to be encoded in UTF-16LE and is NOT verified to be + * an XRY file before reading. It is expected that the isXRYFile function + * has been called on the path beforehand. Otherwise, the behavior is + * undefined. * * @param xryFile XRY file to read. It is assumed that the caller has read * access to the path. @@ -82,6 +89,7 @@ public final class XRYFileReader implements AutoCloseable { */ public XRYFileReader(Path xryFile) throws IOException { reader = Files.newBufferedReader(xryFile, CHARSET); + xryFilePath = xryFile; //Advance the reader to the start of the first XRY entity. for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) { @@ -91,6 +99,35 @@ public final class XRYFileReader implements AutoCloseable { xryEntity = new StringBuilder(); } + /** + * Extracts the report type from the XRY file. + * + * @return The XRY report type + * @throws IOException if an I/O error occurs. + * @throws IllegalArgumentExcepton If the XRY file does not have a report + * type. This is a misuse of the API. The validity of the Path should have + * been checked with isXRYFile before creating an XRYFileReader. + */ + public String getReportType() throws IOException { + Optional reportType = getType(xryFilePath); + if (reportType.isPresent()) { + return reportType.get(); + } + + throw new IllegalArgumentException(xryFilePath.toString() + " does not " + + "have a report type."); + } + + /** + * Returns the raw path of the XRY report file. + * + * @return + * @throws IOException + */ + public Path getReportPath() throws IOException { + return xryFilePath; + } + /** * Advances the reader until a valid XRY entity is detected or EOF is * reached. @@ -113,7 +150,7 @@ public final class XRYFileReader implements AutoCloseable { return true; } } else { - xryEntity.append(line).append("\n"); + xryEntity.append(line).append('\n'); } } @@ -138,6 +175,23 @@ public final class XRYFileReader implements AutoCloseable { throw new NoSuchElementException(); } } + + /** + * Peek at the next XRY entity without consuming it. + * If there are not more XRY entities left, an exception is thrown. + * + * @return A non-empty XRY entity. + * @throws IOException + * @throws NoSuchElementException if there are no more XRY entities to + * read. + */ + public String peek() throws IOException { + if(hasNextEntity()) { + return xryEntity.toString(); + } else { + throw new NoSuchElementException(); + } + } /** * Closes any file handles this reader may have open. diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index b9b999f270..bac2bc2364 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -30,7 +30,7 @@ import java.util.stream.Stream; /** * Extracts XRY files and (optionally) non-XRY files from a XRY (Report) folder. */ -public final class XRYFolder { +final class XRYFolder { //Depth that will contain XRY files. All XRY files will be immediate //children of their parent folder. diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java new file mode 100755 index 0000000000..9cc6a4d745 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -0,0 +1,68 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Parses XRY Web-Bookmark files and creates artifacts. + */ +final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { + + //Human readable name of this parser. + private static final String PARSER_NAME = "XRY Web Bookmarks"; + + //All known XRY keys for web bookmarks. + private static final Map KEY_TO_TYPE + = new HashMap() { + { + put("web address", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL); + put("domain", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN); + } + }; + + @Override + boolean isKey(String key) { + String normalizedKey = key.toLowerCase(); + return KEY_TO_TYPE.containsKey(normalizedKey); + } + + @Override + boolean isNamespace(String nameSpace) { + //No known namespaces for web reports. + return false; + } + + @Override + BlackboardAttribute makeAttribute(String nameSpace, String key, String value) { + String normalizedKey = key.toLowerCase(); + return new BlackboardAttribute(KEY_TO_TYPE.get(normalizedKey), PARSER_NAME, value); + } + + @Override + void makeArtifact(List attributes, Content parent) throws TskCoreException { + //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); + //artifact.addAttributes(attributes); + } +} From e67da6aec6663dfa537c4ffff53ccf918d138db8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:16:50 -0500 Subject: [PATCH 11/41] Uncommented to artifact creation code --- .../autopsy/datasourceprocessors/xry/XRYCallsFileParser.java | 4 ++-- .../datasourceprocessors/xry/XRYContactsFileParser.java | 4 ++-- .../datasourceprocessors/xry/XRYWebBookmarksFileParser.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 05934a4904..f422cd1ead 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -133,8 +133,8 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); - //artifact.addAttributes(attributes); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); + artifact.addAttributes(attributes); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java index 233fd3da16..ac74ae9ada 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -70,7 +70,7 @@ final class XRYContactsFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); - //artifact.addAttributes(attributes); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + artifact.addAttributes(attributes); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index 9cc6a4d745..b9f2528f33 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -62,7 +62,7 @@ final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); - //artifact.addAttributes(attributes); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); + artifact.addAttributes(attributes); } } From c1117164037c4008c1a26d77ccb933468a5eb3ed Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:26:28 -0500 Subject: [PATCH 12/41] Tied together any loose ends. Added imports and fixed comments --- .../xry/AbstractSingleKeyValueParser.java | 14 +++++++++----- .../xry/XRYCallsFileParser.java | 19 ++++++++++++------- .../xry/XRYContactsFileParser.java | 1 + .../xry/XRYWebBookmarksFileParser.java | 1 + 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 0ed74bdbef..317ca17299 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Template parse method for reports that make blackboard attributes from a - * single key value pairs. + * single key value pair. * * This parse implementation will create 1 artifact per XRY entity. */ @@ -59,6 +59,8 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { for (int i = 1; i < xryLines.length; i++) { String xryLine = xryLines[i]; + //Check if the line is a namespace, which gives context to the keys + //that follow. if (isNamespace(xryLine)) { logger.log(Level.INFO, String.format("INFO: Detected XRY " + "namespace keyword [ %s ]. Applying to all key value pairs following it.", xryLine)); @@ -81,7 +83,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { if (!isKey(key)) { logger.log(Level.SEVERE, String.format("The following key, " + "value pair (in brackets, respectively) [ %s ], [ %s ] was not recognized. Discarding..." - + " Here is the previous line [ %s ] for context. What is it?", key, value, xryLines[i - 1])); + + " Here is the previous line [ %s ] for context. What does this key mean?", key, value, xryLines[i - 1])); continue; } @@ -93,13 +95,14 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { } BlackboardAttribute attribute = makeAttribute(namespace, key, value); - //Returning null is temporary solution until we map out how we will deal with - //attributes we are currently ignoring. + //Temporarily allowing null to be valid return type until a decision + //is made about how to handle keys we are choosing to ignore. if (attribute != null) { attributes.add(makeAttribute(namespace, key, value)); } } + //Only create artifacts with non-empty attributes. if (attributes.size() > 0) { makeArtifact(attributes, parent); } @@ -127,7 +130,8 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { * * Ex: * - * To Tel : +1245325 + * To + * Tel : +1245325 * * "To" would be the candidate namespace that was extracted. * diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index f422cd1ead..e417703606 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -34,7 +35,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Parses XRY Calls files and creates artifacts. */ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { - + private static final Logger logger = Logger.getLogger(XRYCallsFileParser.class.getName()); //Human readable name of this parser. @@ -42,7 +43,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { private static final DateTimeFormatter DATE_TIME_PARSER = DateTimeFormatter.ofPattern("M/d/y h:m:s [a][ z]"); - + private static final String INCOMING = "Incoming"; //All known XRY keys for call reports. @@ -113,7 +114,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { switch (normalizedValue) { case "missed": case "received": - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, INCOMING); + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, INCOMING); case "dialed": return null; case "last dialed": @@ -130,7 +131,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { throw new IllegalArgumentException(String.format("key [ %s ] was not recognized.", key)); } } - + @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); @@ -139,9 +140,11 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { /** * Removes the locale from the date time value. + * + * Locale in this case being (Device) or (Network). * - * @param dateTime - * @return + * @param dateTime XRY datetime value to be sanitized. + * @return A purer date time value. */ private String removeDateTimeLocale(String dateTime) { int index = dateTime.indexOf('('); @@ -153,7 +156,9 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { } /** - * + * Parses the datatime value and calculates ms since epoch. It time zone is + * assumed to be UTC. + * * @param dateTime * @return */ diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java index ac74ae9ada..ff47f33e95 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index b9f2528f33..e3ba5d6f23 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.HashMap; import java.util.List; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; From d39ed4d7323a65819fcd0f081f1c9c6f09f8917d Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:45:25 -0500 Subject: [PATCH 13/41] Fixed type in artifact naem --- .../datasourceprocessors/xry/XRYWebBookmarksFileParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index e3ba5d6f23..3c6e881ada 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -63,7 +63,7 @@ final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK); artifact.addAttributes(attributes); } } From f181a1f862fafd51b9820bfe7d96f95033c84ac1 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:55:51 -0500 Subject: [PATCH 14/41] Updated comments --- .../xry/AbstractSingleKeyValueParser.java | 4 +++- .../datasourceprocessors/xry/XRYCallsFileParser.java | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 317ca17299..d3a031a776 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -56,6 +56,8 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { } String namespace = ""; + //Process each line, searching for a key value pair or a namespace. + //If neither are found, an error message is logged. for (int i = 1; i < xryLines.length; i++) { String xryLine = xryLines[i]; @@ -74,7 +76,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { if (keyDelimiter == -1) { logger.log(Level.SEVERE, String.format("Expected a key value " + "pair on this line (in brackets) [ %s ], but one was not detected." - + " Here is the previous line (in brackets) [ %s ]. What does this key mean?", xryLine, xryLines[i - 1])); + + " Here is the previous line (in brackets) [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); continue; } String key = xryLine.substring(0, keyDelimiter).trim(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index e417703606..b4c556079c 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -140,7 +140,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { /** * Removes the locale from the date time value. - * + * * Locale in this case being (Device) or (Network). * * @param dateTime XRY datetime value to be sanitized. @@ -156,9 +156,9 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { } /** - * Parses the datatime value and calculates ms since epoch. It time zone is + * Parses the datatime value and calculates ms since epoch. The time zone is * assumed to be UTC. - * + * * @param dateTime * @return */ From 36e9c7a5c63369e55df51069c2769c3a7cbf3b1e Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 11:17:48 -0500 Subject: [PATCH 15/41] Addressed some of the codacy comments --- .../xry/AbstractSingleKeyValueParser.java | 2 +- .../datasourceprocessors/xry/XRYCallsFileParser.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index d3a031a776..b5f4220f85 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -105,7 +105,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { } //Only create artifacts with non-empty attributes. - if (attributes.size() > 0) { + if (!attributes.isEmpty()) { makeArtifact(attributes, parent); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index b4c556079c..297f3d06cd 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -103,11 +103,10 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { return null; case "tel": //Apply the namespace - switch (normalizedNamespace) { - case "from": - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value); - default: - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, PARSER_NAME, value); + if(normalizedNamespace.equals("from")) { + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value); + } else { + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, PARSER_NAME, value); } case "call type": String normalizedValue = value.toLowerCase(); From a2584ef060059021bb8ad66185e973fedee37544 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 11:20:37 -0500 Subject: [PATCH 16/41] Fixed typo in comment and added a line for clarity --- .../datasourceprocessors/xry/AbstractSingleKeyValueParser.java | 1 + .../autopsy/datasourceprocessors/xry/XRYCallsFileParser.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index b5f4220f85..9ebf7ec32b 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -51,6 +51,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { List attributes = new ArrayList<>(); + //First line of the entity is the title. if (xryLines.length > 0) { logger.log(Level.INFO, String.format("INFO: Processing [ %s ]", xryLines[0])); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 297f3d06cd..48aff2e762 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -155,7 +155,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { } /** - * Parses the datatime value and calculates ms since epoch. The time zone is + * Parses the date time value and calculates ms since epoch. The time zone is * assumed to be UTC. * * @param dateTime From eb1a9cd279a295a2e0d5e77ef978425d8e199269 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 12:17:55 -0500 Subject: [PATCH 17/41] Implemented and tested the device gen info parser --- .../xry/XRYDeviceGenInfoFileParser.java | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java new file mode 100755 index 0000000000..937627d61d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -0,0 +1,189 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Parses XRY Device-General Information files and creates artifacts. + */ +final class XRYDeviceGenInfoFileParser implements XRYFileParser { + + private static final Logger logger = Logger.getLogger(XRYDeviceGenInfoFileParser.class.getName()); + + //Human readable name of this parser. + private static final String PARSER_NAME = "XRY Device General Info"; + private static final char KEY_VALUE_DELIMITER = ':'; + + //All known XRY keys for Device Gen Info reports. + private static final String ATTRIBUTE_KEY = "attribute"; + private static final String DATA_KEY = "data"; + + //All of the known XRY keys for device gen info. + private static final Map KEY_TO_TYPE + = new HashMap() { + { + put("device name", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_NAME); + put("device family", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); + put("device type", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); + put("mobile id (imei)", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI); + put("security code", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PASSWORD); + } + }; + + /** + * Device-General Information reports have 2 key value pairs for every + * attribute. The two only known keys are "Data" and "Attribute", where data + * is some generic information that the Attribute key describes. + * + * Example: + * + * Data: Nokia XYZ + * Attribute: Device Name + * + * This parse implementation assumes that the data field does not span + * multiple lines. If the data does span multiple lines, it will log an + * error describing an expectation for an "Attribute" key that is not found. + * + * @param reader The XRYFileReader that reads XRY entities from the + * Device-General Information report. + * @param parent The parent Content to create artifacts from. + * @throws IOException + * @throws TskCoreException + */ + @Override + public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { + Path reportPath = reader.getReportPath(); + logger.log(Level.INFO, String.format("Processing report at [ %s ]", reportPath.toString())); + + while (reader.hasNextEntity()) { + String xryEntity = reader.nextEntity(); + String[] xryLines = xryEntity.split("\n"); + + List attributes = new ArrayList<>(); + + //First line of the entity is the title. + if (xryLines.length > 0) { + logger.log(Level.INFO, String.format("Processing [ %s ]", xryLines[0])); + } + + for (int i = 1; i < xryLines.length; i++) { + String xryLine = xryLines[i]; + + //Expecting to see a "Data" key. + if (!hasDataKey(xryLine)) { + logger.log(Level.SEVERE, String.format("Expected a 'Data' key " + + "on this line (in brackets) [ %s ], but none was found. " + + "Discarding... Here is the previous line for context [ %s ]. " + + "What does this mean?", xryLine, xryLines[i - 1])); + continue; + } + + if (i + 1 == xryLines.length) { + logger.log(Level.SEVERE, String.format("Found a 'Data' key " + + "but no corresponding 'Attribute' key. Discarding... Here " + + "is the 'Data' line (in brackets) [ %s ]. Here is the previous " + + "line for context [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); + continue; + } + + int dataKeyIndex = xryLine.indexOf(KEY_VALUE_DELIMITER); + String dataValue = xryLine.substring(dataKeyIndex + 1).trim(); + + String nextXryLine = xryLines[++i]; + + //Expecting to see an "Attribute" key + if (!hasAttributeKey(nextXryLine)) { + logger.log(Level.SEVERE, String.format("SEVERE: Expected an 'Attribute' " + + "key on this line (in brackets) [ %s ], but none was found. " + + "Discarding... Here is the previous line for context [ %s ]. " + + "What does this mean?", nextXryLine, xryLine)); + continue; + } + + int attributeKeyIndex = nextXryLine.indexOf(KEY_VALUE_DELIMITER); + String attributeValue = nextXryLine.substring(attributeKeyIndex + 1).trim(); + String normalizedAttributeValue = attributeValue.toLowerCase(); + + //Check if the attribute value is recognized. + if (KEY_TO_TYPE.containsKey(normalizedAttributeValue)) { + //All of the attribute types in the map expect a string. + attributes.add(new BlackboardAttribute(KEY_TO_TYPE.get(normalizedAttributeValue), PARSER_NAME, dataValue)); + } else { + logger.log(Level.SEVERE, String.format("Attribute type (in brackets) " + + "[ %s ] was not recognized. Discarding... Here is the " + + "previous line for context [ %s ]. What does this mean?", nextXryLine, xryLine)); + } + } + + if(!attributes.isEmpty()) { + //Build the artifact. + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_INFO); + artifact.addAttributes(attributes); + } + } + } + + /** + * Determines if the XRY line has a data key on it. + * + * @param xryLine + * @return + */ + private boolean hasDataKey(String xryLine) { + int dataKeyIndex = xryLine.indexOf(KEY_VALUE_DELIMITER); + //No key structure found. + if (dataKeyIndex == -1) { + return false; + } + + String normalizedDataKey = xryLine.substring(0, + dataKeyIndex).trim().toLowerCase(); + return normalizedDataKey.equals(DATA_KEY); + } + + /** + * Determines if the XRY line has an attribute key on it. + * + * @param xryLine + * @return + */ + private boolean hasAttributeKey(String xryLine) { + int attributeKeyIndex = xryLine.indexOf(KEY_VALUE_DELIMITER); + //No key structure found. + if (attributeKeyIndex == -1) { + return false; + } + + String normalizedDataKey = xryLine.substring(0, + attributeKeyIndex).trim().toLowerCase(); + return normalizedDataKey.equals(ATTRIBUTE_KEY); + } +} From 3c4aaa3f8dbe8d08ecd63d372b7e5629256036a2 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 12:24:16 -0500 Subject: [PATCH 18/41] Updated comments --- .../xry/XRYDeviceGenInfoFileParser.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java index 937627d61d..5b50e534a6 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -46,7 +46,9 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { private static final String ATTRIBUTE_KEY = "attribute"; private static final String DATA_KEY = "data"; - //All of the known XRY keys for device gen info. + //All of the known XRY Attribute values for device gen info. The value of the + //attribute keys are actionable for this parser. See parse header for more + //details. private static final Map KEY_TO_TYPE = new HashMap() { { @@ -75,8 +77,8 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { * @param reader The XRYFileReader that reads XRY entities from the * Device-General Information report. * @param parent The parent Content to create artifacts from. - * @throws IOException - * @throws TskCoreException + * @throws IOException If an I/O error is encountered during report reading + * @throws TskCoreException If an error during artifact creation is encountered. */ @Override public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { From 18bffe4d186bfb5aa5bde3eb81e45923e3a90fad Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Wed, 13 Nov 2019 14:24:26 -0500 Subject: [PATCH 19/41] 5708: fix the attachment count in CVT messages table. --- .../relationships/MessageNode.java | 22 ++++++++++++++++++- .../contentviewers/MessageContentViewer.java | 2 +- .../autopsy/datamodel/AttachmentNode.java | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index 1c18ba5952..aad258ed0f 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.communications.relationships; +import com.google.gson.Gson; import java.util.logging.Level; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; @@ -38,6 +39,8 @@ import static org.sleuthkit.autopsy.communications.relationships.RelationshipsNo import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; 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 org.sleuthkit.datamodel.blackboardutils.MessageAttachments; /** * Wraps a BlackboardArtifact as an AbstractNode for use in an OutlookView @@ -97,7 +100,7 @@ class MessageNode extends BlackboardArtifactNode { sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS try { - sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS + sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", getAttachmentsCount())); //NON-NLS } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS } @@ -144,4 +147,21 @@ class MessageNode extends BlackboardArtifactNode { public Action getPreferredAction() { return preferredAction; } + + private int getAttachmentsCount() throws TskCoreException { + final BlackboardArtifact artifact = getArtifact(); + int attachmentsCount; + + // Attachments are specified in an attribute TSK_ATTACHMENTS as JSON attribute + BlackboardAttribute attachmentsAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS)); + if (attachmentsAttr != null) { + String jsonVal = attachmentsAttr.getValueString(); + MessageAttachments msgAttachments = new Gson().fromJson(jsonVal, MessageAttachments.class); + attachmentsCount = msgAttachments.getAttachmentsCount(); + } else { // legacy attachments may be children of message artifact. + attachmentsCount = artifact.getChildrenCount(); + } + + return attachmentsCount; + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 66f0af29fe..35f9efeddb 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -664,7 +664,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont /** * Creates child nodes for message attachments. */ - final class AttachmentsChildren extends Children.Keys { + private static class AttachmentsChildren extends Children.Keys { private final Set attachments; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java index 7932b1e421..c095f48fee 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AttachmentNode.java @@ -57,7 +57,7 @@ import org.sleuthkit.datamodel.blackboardutils.URLAttachment; * Node for a message attachment. * */ -public class AttachmentNode extends DisplayableItemNode { +public final class AttachmentNode extends DisplayableItemNode { private static final Logger LOGGER = Logger.getLogger(MessageContentViewer.class.getName()); From 1144649e370de6696991d527ca40e2c12c0eb3f9 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 15:52:38 -0500 Subject: [PATCH 20/41] Implemented and tested XRY messages parser, implemented code review feedback --- .../xry/AbstractSingleKeyValueParser.java | 16 +- .../xry/XRYCallsFileParser.java | 18 +- .../xry/XRYContactsFileParser.java | 3 - .../xry/XRYDeviceGenInfoFileParser.java | 14 +- .../xry/XRYFileParserFactory.java | 4 +- .../xry/XRYFileReader.java | 25 +- .../xry/XRYMessagesFileParser.java | 483 ++++++++++++++++++ .../xry/XRYWebBookmarksFileParser.java | 3 - 8 files changed, 524 insertions(+), 42 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 9ebf7ec32b..20b6b7c1bf 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -39,11 +39,13 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { private static final Logger logger = Logger.getLogger(AbstractSingleKeyValueParser.class.getName()); private static final char KEY_VALUE_DELIMITER = ':'; + + protected static final String PARSER_NAME = "XRY DSP"; @Override public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { Path reportPath = reader.getReportPath(); - logger.log(Level.INFO, String.format("INFO: Processing report at [ %s ]", reportPath.toString())); + logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -53,7 +55,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("INFO: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); } String namespace = ""; @@ -65,8 +67,6 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //Check if the line is a namespace, which gives context to the keys //that follow. if (isNamespace(xryLine)) { - logger.log(Level.INFO, String.format("INFO: Detected XRY " - + "namespace keyword [ %s ]. Applying to all key value pairs following it.", xryLine)); namespace = xryLine.trim(); continue; } @@ -75,23 +75,23 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //the start of the line and the first delimiter. int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER); if (keyDelimiter == -1) { - logger.log(Level.SEVERE, String.format("Expected a key value " + logger.log(Level.SEVERE, String.format("XRY DSP: Expected a key value " + "pair on this line (in brackets) [ %s ], but one was not detected." - + " Here is the previous line (in brackets) [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); + + " Here is the previous line [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); continue; } String key = xryLine.substring(0, keyDelimiter).trim(); String value = xryLine.substring(keyDelimiter + 1).trim(); if (!isKey(key)) { - logger.log(Level.SEVERE, String.format("The following key, " + logger.log(Level.SEVERE, String.format("XRY DSP: The following key, " + "value pair (in brackets, respectively) [ %s ], [ %s ] was not recognized. Discarding..." + " Here is the previous line [ %s ] for context. What does this key mean?", key, value, xryLines[i - 1])); continue; } if (value.isEmpty()) { - logger.log(Level.SEVERE, String.format("The following key " + logger.log(Level.SEVERE, String.format("XRY DSP: The following key " + "(in brackets) [ %s ] was recognized, but the value was empty. Discarding..." + " Here is the previous line for context [ %s ]. What does this mean?", key, xryLines[i - 1])); continue; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 48aff2e762..af59c6891d 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datasourceprocessors.xry; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Set; import java.util.HashSet; import java.util.List; @@ -38,9 +39,6 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { private static final Logger logger = Logger.getLogger(XRYCallsFileParser.class.getName()); - //Human readable name of this parser. - private static final String PARSER_NAME = "XRY Calls"; - private static final DateTimeFormatter DATE_TIME_PARSER = DateTimeFormatter.ofPattern("M/d/y h:m:s [a][ z]"); @@ -88,10 +86,16 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { switch (normalizedKey) { case "time": //Tranform value to epoch ms - String dateTime = removeDateTimeLocale(value); - String normalizedDateTime = dateTime.trim(); - long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, PARSER_NAME, dateTimeInEpoch); + try { + String dateTime = removeDateTimeLocale(value); + String normalizedDateTime = dateTime.trim(); + long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, PARSER_NAME, dateTimeInEpoch); + } catch (DateTimeParseException ex) { + logger.log(Level.SEVERE, String.format("XRY DSP: Assumption about the date time " + + "formatting of call logs is not right. Here is the value [ %s ]", value), ex); + return null; + } case "duration": //Ignore for now. return null; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java index ff47f33e95..ec8fd40187 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -31,9 +31,6 @@ import org.sleuthkit.datamodel.TskCoreException; */ final class XRYContactsFileParser extends AbstractSingleKeyValueParser { - //Human readable name of this parser. - private static final String PARSER_NAME = "XRY Contacts"; - //All of the known XRY keys for contacts. private static final Set XRY_KEYS = new HashSet() {{ add("name"); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java index 5b50e534a6..d610ffb89d 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -39,7 +39,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { private static final Logger logger = Logger.getLogger(XRYDeviceGenInfoFileParser.class.getName()); //Human readable name of this parser. - private static final String PARSER_NAME = "XRY Device General Info"; + private static final String PARSER_NAME = "XRY DSP"; private static final char KEY_VALUE_DELIMITER = ':'; //All known XRY keys for Device Gen Info reports. @@ -83,7 +83,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { @Override public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { Path reportPath = reader.getReportPath(); - logger.log(Level.INFO, String.format("Processing report at [ %s ]", reportPath.toString())); + logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -93,7 +93,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); } for (int i = 1; i < xryLines.length; i++) { @@ -101,7 +101,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see a "Data" key. if (!hasDataKey(xryLine)) { - logger.log(Level.SEVERE, String.format("Expected a 'Data' key " + logger.log(Level.SEVERE, String.format("XRY DSP: Expected a 'Data' key " + "on this line (in brackets) [ %s ], but none was found. " + "Discarding... Here is the previous line for context [ %s ]. " + "What does this mean?", xryLine, xryLines[i - 1])); @@ -109,7 +109,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { } if (i + 1 == xryLines.length) { - logger.log(Level.SEVERE, String.format("Found a 'Data' key " + logger.log(Level.SEVERE, String.format("XRY DSP: Found a 'Data' key " + "but no corresponding 'Attribute' key. Discarding... Here " + "is the 'Data' line (in brackets) [ %s ]. Here is the previous " + "line for context [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); @@ -123,7 +123,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see an "Attribute" key if (!hasAttributeKey(nextXryLine)) { - logger.log(Level.SEVERE, String.format("SEVERE: Expected an 'Attribute' " + logger.log(Level.SEVERE, String.format("XRY DSP: Expected an 'Attribute' " + "key on this line (in brackets) [ %s ], but none was found. " + "Discarding... Here is the previous line for context [ %s ]. " + "What does this mean?", nextXryLine, xryLine)); @@ -139,7 +139,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //All of the attribute types in the map expect a string. attributes.add(new BlackboardAttribute(KEY_TO_TYPE.get(normalizedAttributeValue), PARSER_NAME, dataValue)); } else { - logger.log(Level.SEVERE, String.format("Attribute type (in brackets) " + logger.log(Level.SEVERE, String.format("XRY DSP: Attribute type (in brackets) " + "[ %s ] was not recognized. Discarding... Here is the " + "previous line for context [ %s ]. What does this mean?", nextXryLine, xryLine)); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java index 8dd64996fa..d650510827 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java @@ -37,7 +37,7 @@ final class XRYFileParserFactory { * is null. This is a misuse of the API. It is assumed that the report type * has been tested with the supports method. */ - public static XRYFileParser get(String reportType) { + static XRYFileParser get(String reportType) { if (reportType == null) { throw new IllegalArgumentException("Report type cannot be null"); } @@ -64,7 +64,7 @@ final class XRYFileParserFactory { * @param reportType Report type to test. * @return Indication if the report type can be parsed. */ - public static boolean supports(String reportType) { + static boolean supports(String reportType) { try { //Attempt a get. get(reportType); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index 32a0b7eb33..bc3c04259b 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -101,7 +101,7 @@ final class XRYFileReader implements AutoCloseable { /** * Extracts the report type from the XRY file. - * + * * @return The XRY report type * @throws IOException if an I/O error occurs. * @throws IllegalArgumentExcepton If the XRY file does not have a report @@ -117,12 +117,12 @@ final class XRYFileReader implements AutoCloseable { throw new IllegalArgumentException(xryFilePath.toString() + " does not " + "have a report type."); } - + /** * Returns the raw path of the XRY report file. - * + * * @return - * @throws IOException + * @throws IOException */ public Path getReportPath() throws IOException { return xryFilePath; @@ -160,6 +160,7 @@ final class XRYFileReader implements AutoCloseable { /** * Returns an XRY entity if there is one, otherwise an exception is thrown. + * Clients should test for another entity by calling hasNextEntity(). * * @return A non-empty XRY entity. * @throws IOException if an I/O error occurs. @@ -175,18 +176,18 @@ final class XRYFileReader implements AutoCloseable { throw new NoSuchElementException(); } } - + /** - * Peek at the next XRY entity without consuming it. - * If there are not more XRY entities left, an exception is thrown. - * + * Peek at the next XRY entity without consuming it. If there are not more + * XRY entities left, an exception is thrown. Clients should test for + * another entity by calling hasNextEntity(). + * * @return A non-empty XRY entity. - * @throws IOException - * @throws NoSuchElementException if there are no more XRY entities to - * read. + * @throws IOException if an I/O error occurs. + * @throws NoSuchElementException if there are no more XRY entities to peek. */ public String peek() throws IOException { - if(hasNextEntity()) { + if (hasNextEntity()) { return xryEntity.toString(); } else { throw new NoSuchElementException(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java new file mode 100755 index 0000000000..c1a80f59ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -0,0 +1,483 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Parses Messages-SMS files and creates artifacts. + */ +final class XRYMessagesFileParser implements XRYFileParser { + + private static final Logger logger = Logger.getLogger( + XRYMessagesFileParser.class.getName()); + + private static final String PARSER_NAME = "XRY DSP"; + private static final char KEY_VALUE_DELIMITER = ':'; + private static final DateTimeFormatter DATE_TIME_PARSER + = DateTimeFormatter.ofPattern("M/d/y h:m:s [a][ z]"); + + //Meta keys. These describe how the XRY message entites are split + //up in the report file. + private static final String SEGMENT_COUNT = "segments"; + private static final String SEGMENT_NUMBER = "segment number"; + private static final String REFERENCE_NUMBER = "reference number"; + + //A more readable version of these values. Referring to if the user + //has read the message. + private static final int READ = 1; + private static final int UNREAD = 0; + + private static final String TEXT_KEY = "text"; + + //All known XRY keys for message reports. + private static final Set XRY_KEYS = new HashSet() { + { + add(TEXT_KEY); + add("direction"); + add("time"); + add("status"); + add("tel"); + add("storage"); + add("index"); + add("folder"); + add("service center"); + add("type"); + } + }; + + //All known XRY namespaces for message reports. + private static final Set XRY_NAMESPACES = new HashSet() { + { + add("to"); + add("from"); + add("participant"); + } + }; + + //All known meta keys. + private static final Set XRY_META_KEYS = new HashSet() { + { + add(REFERENCE_NUMBER); + add(SEGMENT_NUMBER); + add(SEGMENT_COUNT); + } + }; + + /** + * Message-SMS report artifacts can span multiple XRY entities and their + * attributes can span multiple lines. The "Text" key is the only known key + * value pair that can span multiple lines. Messages can be segmented, + * meaning that their "Text" content can appear in multiple XRY entities. + * Our goal for a segmented message is aggregate all of the text pieces and + * create 1 artifact. + * + * This parse implementation assumes that segments are contiguous and that + * they ascend incrementally. There are checks in place to verify this + * assumption are correct, otherwise an error will appear in the logs. + * + * @param reader The XRYFileReader that reads XRY entities from the + * Message-SMS report. + * @param parent The parent Content to create artifacts from. + * @throws IOException If an I/O error is encountered during report reading + * @throws TskCoreException If an error during artifact creation is + * encountered. + */ + @Override + public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { + Path reportPath = reader.getReportPath(); + logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); + + //Keep track of the reference numbers that have been parsed. + Set referenceNumbersSeen = new HashSet<>(); + + while (reader.hasNextEntity()) { + String xryEntity = reader.nextEntity(); + String[] xryLines = xryEntity.split("\n"); + + //First line of the entity is the title. + if (xryLines.length > 0) { + logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); + } + + List attributes = new ArrayList<>(); + + String namespace = ""; + for (int i = 1; i < xryLines.length; i++) { + String xryLine = xryLines[i]; + String normalizedXryLine = xryLine.trim().toLowerCase(); + + if (XRY_NAMESPACES.contains(normalizedXryLine)) { + namespace = xryLine.trim(); + continue; + } + + //Find the XRY key on this line. + int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER); + if (keyDelimiter == -1) { + logger.log(Level.SEVERE, String.format("XRY DSP: Expected a key value " + + "pair on this line (in brackets) [ %s ], but one was not detected." + + " Is this the continuation of a previous line?" + + " Here is the previous line (in brackets) [ %s ]. " + + "What does this key mean?", xryLine, xryLines[i - 1])); + continue; + } + + //Extract the key value pair + String key = xryLine.substring(0, keyDelimiter).trim(); + String value = xryLine.substring(keyDelimiter + 1).trim(); + + String normalizedKey = key.toLowerCase(); + + if (XRY_META_KEYS.contains(normalizedKey)) { + //Skip meta keys, they are being dealt with seperately. + continue; + } + + if (!XRY_KEYS.contains(normalizedKey)) { + logger.log(Level.SEVERE, String.format("XRY DSP: The following key, " + + "value pair (in brackets, respectively) [ %s ], [ %s ] " + + "was not recognized. Discarding... Here is the previous line " + + "[ %s ] for context. What does this key mean?", key, value, xryLines[i - 1])); + continue; + } + + if (value.isEmpty()) { + logger.log(Level.SEVERE, String.format("XRY DSP: The following key " + + "(in brackets) [ %s ] was recognized, but the value " + + "was empty. Discarding... Here is the previous line " + + "for context [ %s ]. Is this a continuation of this line? " + + "What does an empty key mean?", key, xryLines[i - 1])); + continue; + } + + //Assume text is the only field that can span multiple lines. + if (normalizedKey.equals(TEXT_KEY)) { + //Build up multiple lines. + for (; i + 1 < xryLines.length + && !hasKey(xryLines[i + 1]) + && !hasNamespace(xryLines[i + 1]); i++) { + String continuedValue = xryLines[i + 1].trim(); + //Assume multi lined values are split by word. + value = value + " " + continuedValue; + } + + int referenceNumber = getMetaInfo(xryLines, REFERENCE_NUMBER); + //Check if there is any segmented text. Min val is used to + //signify that no reference number was found. + if (referenceNumber != Integer.MIN_VALUE) { + logger.log(Level.INFO, String.format("XRY DSP: Message entity " + + "appears to be segmented with reference number [ %d ]", referenceNumber)); + + if (referenceNumbersSeen.contains(referenceNumber)) { + logger.log(Level.SEVERE, "XRY DSP: This reference has already " + + "been seen. This means that the segments are not " + + "contiguous. Any segments contiguous with this " + + "one will be aggregated and another " + + "(otherwise duplicate) artifact will be created."); + } + + referenceNumbersSeen.add(referenceNumber); + + int segmentNumber = getMetaInfo(xryLines, SEGMENT_NUMBER); + + //Unify segmented text, if there is any. + String segmentedText = getSegmentedText(referenceNumber, + segmentNumber, reader); + //Assume it was segmented by word. + value = value + " " + segmentedText; + } + } + + BlackboardAttribute attribute = makeAttribute(namespace, key, value); + if (attribute != null) { + attributes.add(attribute); + } + } + + //Only create artifacts with non-empty attributes. + if(!attributes.isEmpty()) { + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); + artifact.addAttributes(attributes); + } + } + } + + /** + * + * @param referenceNumber + * @param segmentNumber + * @param reader + * @return + * @throws IOException + */ + private String getSegmentedText(int referenceNumber, int segmentNumber, XRYFileReader reader) throws IOException { + StringBuilder segmentedText = new StringBuilder(); + + while (reader.hasNextEntity()) { + //Peek at the next to see if it has the same reference number. + String nextEntity = reader.peek(); + String[] nextEntityLines = nextEntity.split("\n"); + int nextReferenceNumber = getMetaInfo(nextEntityLines, REFERENCE_NUMBER); + + if (nextReferenceNumber != referenceNumber) { + //Don't consume the next entity. It is not related + //to the current message thread. + break; + } + + //Consume the entity. + reader.nextEntity(); + + int nextSegmentNumber = getMetaInfo(nextEntityLines, SEGMENT_NUMBER); + + //Extract the text key from the entity, which is potentially + //multi-lined. + if (nextEntityLines.length > 0) { + logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ] " + + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber)); + } + + if (nextSegmentNumber != segmentNumber + 1) { + logger.log(Level.SEVERE, String.format("XRY DSP: Contiguous " + + "segments are not ascending incrementally. Encountered " + + "segment [ %d ] after segment [ %d ]. This means the reconstructed " + + "text will be out of order.", nextSegmentNumber, segmentNumber)); + } + + for (int i = 1; i < nextEntityLines.length; i++) { + String xryLine = nextEntityLines[i]; + //Find the XRY key on this line. + int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER); + if (keyDelimiter == -1) { + //Skip this line, we are searching only for a text key-value pair. + continue; + } + + String key = xryLine.substring(0, keyDelimiter); + String normalizedKey = key.trim().toLowerCase(); + if (normalizedKey.equals(TEXT_KEY)) { + String value = xryLine.substring(keyDelimiter + 1).trim(); + segmentedText.append(value).append(' '); + + //Build up multiple lines. + for (; (i + 1) < nextEntityLines.length + && !hasKey(nextEntityLines[i + 1]) + && !hasNamespace(nextEntityLines[i + 1]); i++) { + String continuedValue = nextEntityLines[i + 1].trim(); + segmentedText.append(continuedValue).append(' '); + } + } + } + + segmentNumber = nextSegmentNumber; + } + + //Remove the trailing space. + if (segmentedText.length() > 0) { + segmentedText.setLength(segmentedText.length() - 1); + } + return segmentedText.toString(); + } + + /** + * Determines if the line has recognized key value on it. + * + * @param xryLine + * @return + */ + private boolean hasKey(String xryLine) { + int delimiter = xryLine.indexOf(':'); + if (delimiter != -1) { + String key = xryLine.substring(0, delimiter); + String normalizedKey = key.trim().toLowerCase(); + return XRY_KEYS.contains(normalizedKey); + } else { + return false; + } + } + + /** + * Determines if the line is a recognized namespace. + * + * @param xryLine + * @return + */ + private boolean hasNamespace(String xryLine) { + String normalizedLine = xryLine.trim().toLowerCase(); + return XRY_NAMESPACES.contains(normalizedLine); + } + + /** + * Extracts meta keys from the XRY entity. All of the known meta + * keys are integers and describe the message segments. + * + * @param xryLines Current XRY entity + * @param expectedKey The meta key to search for + * @return The interpreted integer value or Integer.MIN_VALUE if + * no meta key was found. + */ + private int getMetaInfo(String[] xryLines, String metaKey) { + for (int i = 0; i < xryLines.length; i++) { + String xryLine = xryLines[i]; + + String normalizedXryLine = xryLine.trim().toLowerCase(); + int firstDelimiter = normalizedXryLine.indexOf(KEY_VALUE_DELIMITER); + if (firstDelimiter != -1) { + String key = normalizedXryLine.substring(0, firstDelimiter); + if (key.equals(metaKey)) { + String value = normalizedXryLine.substring(firstDelimiter + 1).trim(); + try { + return Integer.parseInt(value); + } catch (NumberFormatException ex) { + logger.log(Level.SEVERE, String.format("XRY DSP: Value [ %s ] for " + + "meta key [ %s ] was not an integer.", value, metaKey), ex); + } + } + } + } + + return Integer.MIN_VALUE; + } + + /** + * Creates an attribute from the extracted key value pair. + * + * @param nameSpace The namespace of this key value pair. + * It will have been verified beforehand, otherwise it will be empty. + * @param key The key that was verified beforehand + * @param value The value associated with that key. + * @return + */ + private BlackboardAttribute makeAttribute(String namespace, String key, String value) { + String normalizedKey = key.toLowerCase(); + String normalizedNamespace = namespace.toLowerCase(); + + switch (normalizedKey) { + case "time": + //Tranform value to epoch ms + try { + String dateTime = removeDateTimeLocale(value); + String normalizedDateTime = dateTime.trim(); + long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, PARSER_NAME, dateTimeInEpoch); + } catch (DateTimeParseException ex) { + logger.log(Level.SEVERE, String.format("XRY DSP: Assumption " + + "about the date time formatting of messages is not " + + "right. Here is the value [ %s ].", value), ex); + return null; + } + case "direction": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, value); + case "text": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, PARSER_NAME, value); + case "status": + String normalizedValue = value.toLowerCase(); + switch (normalizedValue) { + case "read": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, PARSER_NAME, READ); + case "unread": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, PARSER_NAME, UNREAD); + case "sending failed": + //Ignore for now. + return null; + case "deleted": + //Ignore for now. + return null; + case "unsent": + //Ignore for now. + return null; + default: + logger.log(Level.SEVERE, String.format("XRY DSP: Unrecognized " + + "status value [ %s ].", value)); + return null; + } + case "type": + //Ignore for now. + return null; + case "storage": + //Ignore for now. + return null; + case "index": + //Ignore for now. + return null; + case "folder": + //Ignore for now. + return null; + case "service center": + //Ignore for now. + return null; + case "tel": + //Apply the namespace + if (normalizedNamespace.equals("from")) { + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value); + } else { + //Assume to and participant are both equivalent to TSK_PHONE_NUMBER_TO + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, PARSER_NAME, value); + } + default: + throw new IllegalArgumentException(String.format("key [ %s ] was not recognized.", key)); + } + } + + /** + * Removes the locale from the date time value. + * + * Locale in this case being (Device) or (Network). + * + * @param dateTime XRY datetime value to be sanitized. + * @return A purer date time value. + */ + private String removeDateTimeLocale(String dateTime) { + int index = dateTime.indexOf('('); + if (index == -1) { + return dateTime; + } + + return dateTime.substring(0, index); + } + + /** + * Parses the date time value and calculates ms since epoch. The time zone is + * assumed to be UTC. + * + * @param dateTime + * @return + */ + private long calculateMsSinceEpoch(String dateTime) { + LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); + //Assume dates have no offset. + return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index 3c6e881ada..a7443e7f48 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -31,9 +31,6 @@ import org.sleuthkit.datamodel.TskCoreException; */ final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { - //Human readable name of this parser. - private static final String PARSER_NAME = "XRY Web Bookmarks"; - //All known XRY keys for web bookmarks. private static final Map KEY_TO_TYPE = new HashMap() { From 875b789abb8439a1b95a12fb5c56cfaa56bd5c7a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 16:26:02 -0500 Subject: [PATCH 21/41] Update comments, add some additional logging, and address some codacy comments --- .../xry/XRYCallsFileParser.java | 2 + .../xry/XRYMessagesFileParser.java | 42 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index af59c6891d..260fccd40f 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -119,8 +119,10 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { case "received": return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, INCOMING); case "dialed": + //Ignore for now. return null; case "last dialed": + //Ignore for now. return null; default: logger.log(Level.SEVERE, String.format("Call type (in brackets) [ %s ] not recognized.", value)); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java index c1a80f59ae..253676f0e9 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -74,6 +74,7 @@ final class XRYMessagesFileParser implements XRYFileParser { add("folder"); add("service center"); add("type"); + add("name"); } }; @@ -100,12 +101,12 @@ final class XRYMessagesFileParser implements XRYFileParser { * attributes can span multiple lines. The "Text" key is the only known key * value pair that can span multiple lines. Messages can be segmented, * meaning that their "Text" content can appear in multiple XRY entities. - * Our goal for a segmented message is aggregate all of the text pieces and + * Our goal for a segmented message is to aggregate all of the text pieces and * create 1 artifact. * * This parse implementation assumes that segments are contiguous and that * they ascend incrementally. There are checks in place to verify this - * assumption are correct, otherwise an error will appear in the logs. + * assumption is correct, otherwise an error will appear in the logs. * * @param reader The XRYFileReader that reads XRY entities from the * Message-SMS report. @@ -235,9 +236,11 @@ final class XRYMessagesFileParser implements XRYFileParser { } /** - * - * @param referenceNumber - * @param segmentNumber + * Builds up segmented message entities so that the text is unified in the + * artifact. + * + * @param referenceNumber Reference number that messages are group by + * @param segmentNumber Segment number of the starting segment. * @param reader * @return * @throws IOException @@ -245,6 +248,7 @@ final class XRYMessagesFileParser implements XRYFileParser { private String getSegmentedText(int referenceNumber, int segmentNumber, XRYFileReader reader) throws IOException { StringBuilder segmentedText = new StringBuilder(); + int currentSegmentNumber = segmentNumber; while (reader.hasNextEntity()) { //Peek at the next to see if it has the same reference number. String nextEntity = reader.peek(); @@ -269,11 +273,11 @@ final class XRYMessagesFileParser implements XRYFileParser { + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber)); } - if (nextSegmentNumber != segmentNumber + 1) { + if (nextSegmentNumber != currentSegmentNumber + 1) { logger.log(Level.SEVERE, String.format("XRY DSP: Contiguous " + "segments are not ascending incrementally. Encountered " + "segment [ %d ] after segment [ %d ]. This means the reconstructed " - + "text will be out of order.", nextSegmentNumber, segmentNumber)); + + "text will be out of order.", nextSegmentNumber, currentSegmentNumber)); } for (int i = 1; i < nextEntityLines.length; i++) { @@ -301,7 +305,7 @@ final class XRYMessagesFileParser implements XRYFileParser { } } - segmentNumber = nextSegmentNumber; + currentSegmentNumber = nextSegmentNumber; } //Remove the trailing space. @@ -383,6 +387,7 @@ final class XRYMessagesFileParser implements XRYFileParser { private BlackboardAttribute makeAttribute(String namespace, String key, String value) { String normalizedKey = key.toLowerCase(); String normalizedNamespace = namespace.toLowerCase(); + String normalizedValue = value.toLowerCase(); switch (normalizedKey) { case "time": @@ -403,7 +408,6 @@ final class XRYMessagesFileParser implements XRYFileParser { case "text": return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, PARSER_NAME, value); case "status": - String normalizedValue = value.toLowerCase(); switch (normalizedValue) { case "read": return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, PARSER_NAME, READ); @@ -424,8 +428,21 @@ final class XRYMessagesFileParser implements XRYFileParser { return null; } case "type": - //Ignore for now. - return null; + switch (normalizedValue) { + case "deliver": + //Ignore for now. + return null; + case "submit": + //Ignore for now. + return null; + case "status report": + //Ignore for now. + return null; + default: + logger.log(Level.SEVERE, String.format("XRY DSP: Unrecognized " + + "type value [ %s ]", value)); + return null; + } case "storage": //Ignore for now. return null; @@ -435,6 +452,9 @@ final class XRYMessagesFileParser implements XRYFileParser { case "folder": //Ignore for now. return null; + case "name": + //Ignore for now. + return null; case "service center": //Ignore for now. return null; From 4a8ad067c80bd61b16426f341bda95c8fc5db2a7 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 14 Nov 2019 12:51:59 -0500 Subject: [PATCH 22/41] Make IG check for offline deletion of data sources --- .../sleuthkit/autopsy/casemodule/Case.java | 7 ++- .../imagegallery/datamodel/DrawableDB.java | 62 ++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 99ec09d315..a66b6f5c71 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2300,7 +2300,6 @@ public class Case { } else { throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled()); } - caseDb.registerForEvents(sleuthkitEventListener); } catch (TskUnsupportedSchemaVersionException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex); } catch (UserPreferencesException ex) { @@ -2321,6 +2320,12 @@ public class Case { private void openCaseLevelServices(ProgressIndicator progressIndicator) { progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices()); this.caseServices = new Services(caseDb); + /* + * RC Note: JM put this initialization here. I'm not sure why. However, + * my attempt to put it in the openCaseDatabase method seems to lead to + * intermittent unchecked exceptions concerning a missing subscriber. + */ + caseDb.registerForEvents(sleuthkitEventListener); } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index d875657cc1..2097fea5f3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -49,7 +49,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; @@ -80,6 +79,7 @@ import org.sleuthkit.datamodel.TskData.DbType; import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.VersionNumber; import org.sqlite.SQLiteJDBCLoader; +import java.util.stream.Collectors; /** * Provides access to the drawables database and selected tables in the case @@ -230,7 +230,7 @@ public final class DrawableDB { dbWriteLock(); try { con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS - if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !initializeImageList()) { + if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !removeDeletedDataSources() || !initializeImageList()) { close(); throw new TskCoreException("Failed to initialize drawables database for Image Gallery use"); //NON-NLS } @@ -374,6 +374,62 @@ public final class DrawableDB { } } + /** + * Removes any data sources from the local drawables database that have been + * deleted from the case database. This is necessary for multi-user cases + * where the case database is shared, but each user has his or her own local + * drawables database and may not have had the case open when a data source + * was deleted. + * + * @return True on success, false on failure. + */ + private boolean removeDeletedDataSources() { + dbWriteLock(); + try (SleuthkitCase.CaseDbQuery caseDbQuery = tskCase.executeQuery("SELECT obj_id FROM data_source_info"); //NON-NLS + Statement statement = con.createStatement()) { + /* + * Get the data source object IDs from the case database. + */ + ResultSet caseDbResults = caseDbQuery.getResultSet(); + Set validDataSourceObjIDs = new HashSet<>(); + while (caseDbResults.next()) { + validDataSourceObjIDs.add(caseDbResults.getLong(1)); + } + + /* + * Get the data source object IDs from the drawables database and + * determine which ones, if any, have been deleted from the case + * database. + */ + List staleDataSourceObjIDs = new ArrayList<>(); + try (ResultSet drawablesDbResults = statement.executeQuery("SELECT ds_obj_id FROM datasources")) { //NON-NLS + while (drawablesDbResults.next()) { + long dataSourceObjID = drawablesDbResults.getLong(1); + if (!validDataSourceObjIDs.contains(dataSourceObjID)) { + staleDataSourceObjIDs.add(dataSourceObjID); + } + } + } + + /* + * Delete the surplus data sources from this local drawables + * database. The delete cascades. + */ + if (!staleDataSourceObjIDs.isEmpty()) { + String inClause = StringUtils.join(staleDataSourceObjIDs, ','); + String delete = "DELETE FROM datasources where ds_obj_id IN (" + inClause + ")"; //NON-NLS + statement.execute(delete); + } + return true; + } catch (TskCoreException | SQLException ex) { + logger.log(Level.SEVERE, "Failed to remove deleted data sources from drawables database", ex); //NON-NLS + return false; + + } finally { + dbWriteUnlock(); + } + } + /** * Public factory method. Creates and opens a connection to a new database * * at the given path. If there is already a db at the path, it is checked @@ -2037,7 +2093,7 @@ public final class DrawableDB { * * @param dataSourceID The object ID of the data source to delete. * - * @throws SQLException + * @throws SQLException * @throws TskCoreException */ public void deleteDataSource(long dataSourceID) throws SQLException, TskCoreException { From d154ac98b69b52bee5b2aa44b16dca117225d6ef Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 13:11:08 -0500 Subject: [PATCH 23/41] First pass of the waypoint filtering --- .../autopsy/geolocation/Bundle.properties | 10 + .../geolocation/Bundle.properties-MERGED | 12 + .../autopsy/geolocation/CheckBoxJList.java | 109 ++++ .../geolocation/CheckBoxListPanel.form | 71 +++ .../geolocation/CheckBoxListPanel.java | 234 +++++++++ .../autopsy/geolocation/GeoFilterPanel.form | 168 ++++++ .../autopsy/geolocation/GeoFilterPanel.java | 360 +++++++++++++ .../geolocation/GeoLocationUIException.java | 46 ++ .../geolocation/GeolocationTopComponent.form | 14 +- .../geolocation/GeolocationTopComponent.java | 77 ++- .../autopsy/geolocation/HidingPane.java | 120 +++++ .../autopsy/geolocation/MapPanel.java | 4 +- .../autopsy/geolocation/MapWaypoint.java | 25 +- .../autopsy/geolocation/VerticalLabelUI.java | 110 ++++ .../geolocation/datamodel/Waypoint.java | 180 ------- .../datamodel/WaypointBuilder.java | 484 ++++++++++++++++++ .../autopsy/geolocation/images/blueGeo16.png | Bin 0 -> 741 bytes .../autopsy/geolocation/images/blueGeo64.png | Bin 0 -> 3972 bytes .../autopsy/geolocation/images/funnel.png | Bin 0 -> 591 bytes .../autopsy/geolocation/images/tick.png | Bin 0 -> 582 bytes .../autopsy/report/modules/kml/KMLReport.java | 11 +- 21 files changed, 1844 insertions(+), 191 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo64.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/funnel.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties index 8fa233ea33..4fe5148ba5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties @@ -7,3 +7,13 @@ RefreshPanel.closeButton.text= MapPanel.cordLabel.text= WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= +GeoFilterPanel.waypointSettings.border.title= +GeoFilterPanel.allButton.text=Show All +GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.applyButton.text=Apply +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp +GeoFilterPanel.daysLabel.text=days +CheckBoxListPanel.titleLabel.text=jLabel1 +CheckBoxListPanel.checkButton.text=Check All +CheckBoxListPanel.uncheckButton.text=Uncheck All +GeoFilterPanel.optionsLabel.text=Waypoints diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED index 95dd0d23f0..ed550cd8db 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -1,6 +1,8 @@ CTL_OpenGeolocation=Geolocation CTL_GeolocationTopComponentAction=GeolocationTopComponent CTL_GeolocationTopComponent=Geolocation +GeoFilterPanel_DataSource_List_Title=Data Sources +GeoFilterPanel_empty_dataSource=Data Source list is empty. GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete. GLTopComponent_name=Geolocation MayWaypoint_ExternalViewer_label=Open in ExternalViewer @@ -12,4 +14,14 @@ RefreshPanel.closeButton.text= MapPanel.cordLabel.text= WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= +GeoFilterPanel.waypointSettings.border.title= +GeoFilterPanel.allButton.text=Show All +GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.applyButton.text=Apply +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp +GeoFilterPanel.daysLabel.text=days +CheckBoxListPanel.titleLabel.text=jLabel1 +CheckBoxListPanel.checkButton.text=Check All +CheckBoxListPanel.uncheckButton.text=Uncheck All +GeoFilterPanel.optionsLabel.text=Waypoints WaypointExtractAction_label=Extract Files(s) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java new file mode 100755 index 0000000000..52276b6046 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.awt.Component; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.JCheckBox; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.ListSelectionModel; + +/** + * A JList that renders the list items as check boxes. + */ +final class CheckBoxJList extends JList { + + private static final long serialVersionUID = 1L; + + /** + * Simple interface that must be implement for an object to be displayed as + * a checkbox in CheckBoxJList. + * + */ + interface CheckboxListItem { + + /** + * Returns the checkbox state. + * + * @return True if the check box should be checked + */ + boolean isChecked(); + + /** + * Set the state of the check box. + * + * @param checked + */ + void setChecked(boolean checked); + + /** + * Returns String to display as the check box label + * + * @return + */ + String getDisplayName(); + } + + /** + * Construct a new JCheckBoxList. + */ + CheckBoxJList() { + initalize(); + } + + /** + * Do all of the UI initialization. + */ + private void initalize() { + setCellRenderer(new CellRenderer()); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + int index = locationToIndex(e.getPoint()); + if (index != -1) { + CheckBoxJList.CheckboxListItem element = getModel().getElementAt(index); + element.setChecked(!element.isChecked()); + repaint(); + } + } + }); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + /** + * A ListCellRenderer that renders list elements as check boxes. + */ + class CellRenderer extends JCheckBox implements ListCellRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent( + JList list, CheckBoxJList.CheckboxListItem value, int index, + boolean isSelected, boolean cellHasFocus) { + + setBackground(list.getBackground()); + setSelected(value.isChecked()); + setText(value.getDisplayName()); + return this; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form new file mode 100755 index 0000000000..75fb1d4c62 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form @@ -0,0 +1,71 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java new file mode 100755 index 0000000000..8e465dd3a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java @@ -0,0 +1,234 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import javax.swing.DefaultListModel; +import javax.swing.Icon; + +/** + * A panel for showing Content objects in a check box list. + */ +final class CheckBoxListPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private final DefaultListModel> model = new DefaultListModel<>(); + private final CheckBoxJList> checkboxList; + + /** + * Creates new CheckboxFilterPanel + */ + CheckBoxListPanel() { + initComponents(); + + checkboxList = new CheckBoxJList<>(); + checkboxList.setModel(model); + scrollPane.setViewportView(checkboxList); + } + + /** + * Add a new element to the check box list. + * + * @param displayName display name for the checkbox + * @param obj Object that the checkbox represents + */ + void addElement(String displayName, T obj) { + model.addElement(new ObjectCheckBox<>(displayName, true, obj)); + } + + /** + * Returns a list of all of the selected elements. + * + * @return List of selected elements. + */ + List getSelectedElements() { + List selectedElements = new ArrayList<>(); + Enumeration> elements = model.elements(); + + while (elements.hasMoreElements()) { + ObjectCheckBox element = elements.nextElement(); + if (element.isChecked()) { + selectedElements.add(element.getObject()); + } + } + + return selectedElements; + } + + /** + * Sets the selection state of the all the check boxes in the list. + * + * @param selected True to check the boxes, false to unchecked + */ + void setSetAllSelected(boolean selected) { + Enumeration> enumeration = model.elements(); + while (enumeration.hasMoreElements()) { + ObjectCheckBox element = enumeration.nextElement(); + element.setChecked(selected); + checkboxList.repaint(); + checkboxList.revalidate(); + + } + } + + /** + * Sets the panel title. + * + * @param title Panel title or null for no title. + */ + void setPanelTitle(String title) { + titleLabel.setText(title); + } + + /** + * Sets the panel title icon. + * + * @param icon Icon to set or null for no icon + */ + void setPanelTitleIcon(Icon icon) { + titleLabel.setIcon(icon); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + titleLabel = new javax.swing.JLabel(); + uncheckButton = new javax.swing.JButton(); + checkButton = new javax.swing.JButton(); + scrollPane = new javax.swing.JScrollPane(); + + setLayout(new java.awt.GridBagLayout()); + + org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.titleLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + add(titleLabel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(uncheckButton, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.uncheckButton.text")); // NOI18N + uncheckButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + uncheckButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 9); + add(uncheckButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(checkButton, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.checkButton.text")); // NOI18N + checkButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + add(checkButton, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 0, 9, 0); + add(scrollPane, gridBagConstraints); + }// //GEN-END:initComponents + + private void uncheckButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uncheckButtonActionPerformed + setSetAllSelected(false); + }//GEN-LAST:event_uncheckButtonActionPerformed + + private void checkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkButtonActionPerformed + setSetAllSelected(true); + }//GEN-LAST:event_checkButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton checkButton; + private javax.swing.JScrollPane scrollPane; + private javax.swing.JLabel titleLabel; + private javax.swing.JButton uncheckButton; + // End of variables declaration//GEN-END:variables + + /** + * Wrapper around T that implements CheckboxListItem + * + * @param + */ + final class ObjectCheckBox implements CheckBoxJList.CheckboxListItem { + + private static final long serialVersionUID = 1L; + + private final T object; + private final String displayName; + private boolean checked; + + /** + * Constructs a new ObjectCheckBox + * + * @param displayName String to show as the check box label + * @param initialState Sets the initial state of the check box + * @param object Object that the check box represents. + */ + ObjectCheckBox(String displayName, boolean initialState, T object) { + this.displayName = displayName; + this.object = object; + this.checked = initialState; + } + + T getObject() { + return object; + } + + @Override + public boolean isChecked() { + return checked; + } + + @Override + public void setChecked(boolean checked) { + this.checked = checked; + } + + @Override + public String getDisplayName() { + return displayName; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form new file mode 100755 index 0000000000..897341c482 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form @@ -0,0 +1,168 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java new file mode 100755 index 0000000000..bb3035b5d5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -0,0 +1,360 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.awt.GridBagConstraints; +import java.awt.event.ActionListener; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import javax.swing.ImageIcon; +import javax.swing.SpinnerNumberModel; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * Panel to display the filter options for geolocation waypoints. + */ +class GeoFilterPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(GeoFilterPanel.class.getName()); + + private final SpinnerNumberModel numberModel; + private final CheckBoxListPanel checkboxPanel; + + /** + * Creates new GeoFilterPanel + */ + @Messages({ + "GeoFilterPanel_DataSource_List_Title=Data Sources" + }) + GeoFilterPanel() { + // numberModel is used in initComponents + numberModel = new SpinnerNumberModel(10, 1, Integer.MAX_VALUE, 1); + + initComponents(); + + // The gui builder cannot handle using CheckBoxListPanel due to its + // use of generics so we will initalize it here. + checkboxPanel = new CheckBoxListPanel<>(); + checkboxPanel.setPanelTitle(Bundle.GeoFilterPanel_DataSource_List_Title()); + checkboxPanel.setPanelTitleIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/image.png"))); + checkboxPanel.setSetAllSelected(true); + + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15); + add(checkboxPanel, gridBagConstraints); + + try { + initCheckboxList(); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to initialize the CheckboxListPane", ex); //NON-NLS + } + } + + /** + * Adds an actionListener to listen for the filter apply action + * + * @param listener + */ + void addActionListener(ActionListener listener) { + applyButton.addActionListener(listener); + } + + /** + * Returns the selected filter values. + * + * @return A GeoFilter object with the user selected filter values + * + * @throws GeoLocationUIException + */ + @Messages({ + "GeoFilterPanel_empty_dataSource=Data Source list is empty." + }) + GeoFilter getFilterState() throws GeoLocationUIException { + boolean showAll = allButton.isSelected(); + boolean withTimeStamp = showWaypointsWOTSCheckBox.isSelected(); + int dayCnt = numberModel.getNumber().intValue(); + + List dataSources = checkboxPanel.getSelectedElements(); + + if (dataSources.isEmpty()) { + throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource()); + } + + return new GeoFilter(showAll, withTimeStamp, dayCnt, dataSources); + } + + /** + * Initialize the checkbox list panel + * + * @throws TskCoreException + */ + private void initCheckboxList() throws TskCoreException { + final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + + for (DataSource dataSource : sleuthkitCase.getDataSources()) { + String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); + checkboxPanel.addElement(dsName, dataSource); + } + } + + /** + * Based on the state of mostRecent radio button Change the state of the cnt + * spinner and the time stamp checkbox. + */ + private void updateWaypointOptions() { + boolean selected = mostRecentButton.isSelected(); + showWaypointsWOTSCheckBox.setEnabled(selected); + daysSpinner.setEnabled(selected); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + buttonGroup = new javax.swing.ButtonGroup(); + waypointSettings = new javax.swing.JPanel(); + allButton = new javax.swing.JRadioButton(); + mostRecentButton = new javax.swing.JRadioButton(); + showWaypointsWOTSCheckBox = new javax.swing.JCheckBox(); + daysSpinner = new javax.swing.JSpinner(numberModel); + javax.swing.JLabel daysLabel = new javax.swing.JLabel(); + buttonPanel = new javax.swing.JPanel(); + applyButton = new javax.swing.JButton(); + javax.swing.JLabel optionsLabel = new javax.swing.JLabel(); + + setLayout(new java.awt.GridBagLayout()); + + waypointSettings.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.waypointSettings.border.title"))); // NOI18N + waypointSettings.setLayout(new java.awt.GridBagLayout()); + + buttonGroup.add(allButton); + allButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(allButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.allButton.text")); // NOI18N + allButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + allButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + waypointSettings.add(allButton, gridBagConstraints); + + buttonGroup.add(mostRecentButton); + org.openide.awt.Mnemonics.setLocalizedText(mostRecentButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.mostRecentButton.text")); // NOI18N + mostRecentButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + mostRecentButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); + waypointSettings.add(mostRecentButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(showWaypointsWOTSCheckBox, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.showWaypointsWOTSCheckBox.text")); // NOI18N + showWaypointsWOTSCheckBox.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 30, 0, 0); + waypointSettings.add(showWaypointsWOTSCheckBox, gridBagConstraints); + + daysSpinner.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); + waypointSettings.add(daysSpinner, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(daysLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.daysLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 5, 0, 0); + waypointSettings.add(daysLabel, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 15, 9, 15); + add(waypointSettings, gridBagConstraints); + + buttonPanel.setLayout(new java.awt.GridBagLayout()); + + applyButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/tick.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(applyButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.applyButton.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.weightx = 1.0; + buttonPanel.add(applyButton, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 15, 0, 15); + add(buttonPanel, gridBagConstraints); + + optionsLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.optionsLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 0); + add(optionsLabel, gridBagConstraints); + }// //GEN-END:initComponents + + private void allButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allButtonActionPerformed + updateWaypointOptions(); + }//GEN-LAST:event_allButtonActionPerformed + + private void mostRecentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mostRecentButtonActionPerformed + updateWaypointOptions(); + }//GEN-LAST:event_mostRecentButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton allButton; + private javax.swing.JButton applyButton; + private javax.swing.ButtonGroup buttonGroup; + private javax.swing.JPanel buttonPanel; + private javax.swing.JSpinner daysSpinner; + private javax.swing.JRadioButton mostRecentButton; + private javax.swing.JCheckBox showWaypointsWOTSCheckBox; + private javax.swing.JPanel waypointSettings; + // End of variables declaration//GEN-END:variables + + /** + * Class to store the values of the Geolocation user set filter parameters + */ + final class GeoFilter { + + private final boolean showAll; + private final boolean showWithoutTimeStamp; + private final int mostRecentNumDays; + private final List dataSources; + + /** + * Construct a Geolocation filter. showAll and mostRecentNumDays are + * exclusive filters, ie they cannot be used together. + * + * withoutTimeStamp is only applicable if mostRecentNumDays is true. + * + * When using the filters "most recent days" means to include waypoints + * for the numbers of days after the most recent waypoint, not the + * current date. + * + * @param showAll True if all waypoints should be shown + * @param withoutTimeStamp True to show waypoints without timeStamps, + * this filter is only applicable if + * mostRecentNumDays is true + * @param mostRecentNumDays Show Waypoint for the most recent given + * number of days. This parameter is ignored if + * showAll is true. + * @param dataSources A list of dataSources to filter waypoint + * for. + */ + GeoFilter(boolean showAll, boolean withoutTimeStamp, int mostRecentNumDays, List dataSources) { + this.showAll = showAll; + this.showWithoutTimeStamp = withoutTimeStamp; + this.mostRecentNumDays = mostRecentNumDays; + this.dataSources = dataSources; + } + + /** + * Returns whether or not to show all waypoints. + * + * @return True if all waypoints should be shown. + */ + boolean showAll() { + return showAll; + } + + /** + * Returns whether or not to include waypoints with time stamps. + * + * This filter is only applicable if "showAll" is true. + * + * @return True if waypoints with time stamps should be shown. + */ + boolean showWithoutTimeStamp() { + return showWithoutTimeStamp; + } + + /** + * Returns the number of most recent days to show waypoints for. This + * value should be ignored if showAll is true. + * + * @return The number of most recent days to show waypoints for + */ + int getMostRecentNumDays() { + return mostRecentNumDays; + } + + /** + * Returns a list of data sources to filter the waypoints by, or null if + * all datasources should be include. + * + * @return A list of dataSources or null if all dataSources should be + * included. + */ + List getDataSources() { + return Collections.unmodifiableList(dataSources); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java new file mode 100755 index 0000000000..7ce6837914 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java @@ -0,0 +1,46 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.geolocation; + +/** + * + * An exception call for Exceptions that occure in the geolocation dialog. + */ +public class GeoLocationUIException extends Exception{ + private static final long serialVersionUID = 1L; + + /** + * Create exception containing the error message + * + * @param msg the message + */ + public GeoLocationUIException(String msg) { + super(msg); + } + + /** + * Create exception containing the error message and cause exception + * + * @param msg the message + * @param ex cause exception + */ + public GeoLocationUIException(String msg, Exception ex) { + super(msg, ex); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form index bddebcbc0c..16cd5368a6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form @@ -11,6 +11,7 @@ + @@ -23,6 +24,17 @@ + + + + + + + + + + + - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index bf54ece196..f29bfca802 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; @@ -37,6 +38,11 @@ import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.geolocation.GeoFilterPanel.GeoFilter; +import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; +import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder.WaypointFilterQueryCallBack; import org.sleuthkit.autopsy.ingest.IngestManager; import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -59,6 +65,7 @@ public final class GeolocationTopComponent extends TopComponent { private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED); private final PropertyChangeListener ingestListener; + private final GeoFilterPanel geoFilterPanel; final RefreshPanel refreshPanel = new RefreshPanel(); @@ -109,6 +116,16 @@ public final class GeolocationTopComponent extends TopComponent { showRefreshPanel(false); } }); + + geoFilterPanel = new GeoFilterPanel(); + filterPane.setPanel(geoFilterPanel); + geoFilterPanel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + filterWaypoints(); + } + }); + } @Override @@ -151,14 +168,12 @@ public final class GeolocationTopComponent extends TopComponent { /** * Use a SwingWorker thread to get a list of waypoints. - * */ private void initWaypoints() { SwingWorker, MapWaypoint> worker = new SwingWorker, MapWaypoint>() { @Override protected List doInBackground() throws Exception { Case currentCase = Case.getCurrentCaseThrows(); - return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase()); } @@ -189,6 +204,59 @@ public final class GeolocationTopComponent extends TopComponent { worker.execute(); } + /** + * Filters the list of waypoints based on the user selections in the filter + * pane. + */ + @Messages({ + "GeoTopComponent_no_waypoints_returned_mgs=Applied filter failed to find waypoints that matched criteria.\nRevise filter options and try again.", + "GeoTopComponent_no_waypoints_returned_Title=No Waypoints Found", + "GeoTopComponent_filter_exception_msg=Exception occured during waypoint filtering.", + "GeoTopComponent_filter_exception_Title=Filter Failure", + "GeoTopComponent_filer_data_invalid_msg=Unable to run waypoint filter.\nPlease select one or more data sources.", + "GeoTopComponent_filer_data_invalid_Title=Filter Failure" + }) + private void filterWaypoints() { + GeoFilter filters; + + // Show a warning message if the user has not selected a data source + try { + filters = geoFilterPanel.getFilterState(); + } catch (GeoLocationUIException ex) { + MessageNotifyUtil.Notify.info( + Bundle.GeoTopComponent_filer_data_invalid_Title(), + Bundle.GeoTopComponent_filer_data_invalid_msg()); + return; + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Case currentCase = Case.getCurrentCase(); + try { + WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAll(), filters.getMostRecentNumDays(), filters.showWithoutTimeStamp(), new WaypointFilterQueryCallBack() { + @Override + public void process(List waypoints) { + // If the list is empty, tell the user and do not change + // the visible waypoints. + if (waypoints == null || waypoints.isEmpty()) { + MessageNotifyUtil.Notify.info( + Bundle.GeoTopComponent_no_waypoints_returned_Title(), + Bundle.GeoTopComponent_no_waypoints_returned()); + return; + } + mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints)); + } + }); + } catch (GeoLocationDataException ex) { + logger.log(Level.SEVERE, "Failed to filter waypoints.", ex); + MessageNotifyUtil.Notify.error( + Bundle.GeoTopComponent_filter_exception_Title(), + Bundle.GeoTopComponent_filter_exception_msg()); + } + } + }); + } + /** * 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 @@ -199,13 +267,18 @@ public final class GeolocationTopComponent extends TopComponent { private void initComponents() { mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel(); + filterPane = new org.sleuthkit.autopsy.geolocation.HidingPane(); setLayout(new java.awt.BorderLayout()); + + mapPanel.add(filterPane, java.awt.BorderLayout.LINE_START); + add(mapPanel, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.geolocation.HidingPane filterPane; private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java new file mode 100755 index 0000000000..492888d15f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java @@ -0,0 +1,120 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.Point; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import org.openide.util.NbBundle.Messages; + +/** + * + * A JTabbed pane with one tab that says "Filters". When the user clicks on that + * table the content of the tab will be hidden. + * + * The content pane provides support for scrolling. + */ +public final class HidingPane extends JTabbedPane { + + private static final long serialVersionUID = 1L; + + private final JScrollPane scrollPane; + private final JPanel panel; + private final JLabel tabLabel; + + private boolean panelVisible = true; + + /** + * Constructs a new HidingFilterPane + */ + @Messages({ + "HidingPane_default_title=Filters" + }) + public HidingPane() { + super(); + + scrollPane = new JScrollPane(); + panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(scrollPane, BorderLayout.CENTER); + tabLabel = new JLabel(Bundle.HidingPane_default_title()); + tabLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/funnel.png"))); + tabLabel.setUI(new VerticalLabelUI(true)); + tabLabel.setOpaque(false); + Font font = tabLabel.getFont().deriveFont(18).deriveFont(Font.BOLD); + tabLabel.setFont(font); + + addTab(null, panel); + setTabComponentAt(0, tabLabel); + + this.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent evt) { + handleMouseClick(evt.getPoint()); + } + }); + + this.setTabPlacement(JTabbedPane.RIGHT); + } + + /** + * Change the title of the tab. + * + * @param title + */ + void setTitle(String title) { + tabLabel.setText(title); + } + + /** + * Set the icon that appears on the tab. + * + * @param icon + */ + void setIcon(Icon icon) { + tabLabel.setIcon(icon); + } + + /** + * Set the content for this panel. + * + * @param panel A panel to display in the tabbed pane. + */ + void setPanel(JPanel panel) { + scrollPane.setViewportView(panel); + } + + /** + * Handle the mouse click. + * + * @param point + */ + private void handleMouseClick(Point point) { + int index = indexAtLocation(point.x, point.y); + + if(index == -1) { + return; + } + + if(panelVisible) { + panel.removeAll(); + panel.revalidate(); + panelVisible = false; + } else { + panel.add(scrollPane, BorderLayout.CENTER); + panel.revalidate(); + panelVisible = true; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 326d33f23e..da4a704dcf 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -56,7 +56,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * The map panel. This panel contains the jxmapviewer MapViewer */ -final class MapPanel extends javax.swing.JPanel { +final public class MapPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(MapPanel.class.getName()); @@ -76,7 +76,7 @@ final class MapPanel extends javax.swing.JPanel { /** * Creates new form MapPanel */ - MapPanel() { + public MapPanel() { initComponents(); initMap(); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index 7eb765151a..7a28f49737 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -49,6 +49,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -79,7 +80,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe * @throws GeoLocationDataException */ static List getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List points = Waypoint.getAllWaypoints(skCase); + List points = WaypointBuilder.getAllWaypoints(skCase); List routes = Route.getRoutes(skCase); for (Route route : routes) { @@ -94,6 +95,28 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe return mapPoints; } + + /** + * Returns a list of of MapWaypoint objects for the given list of + * datamodel.Waypoint objects. + * + * @param dmWaypoints + * + * @return List of MapWaypoint objects. List will be empty if dmWaypoints was + * empty or null. + */ + static List getWaypoints(List dmWaypoints) { + List mapPoints = new ArrayList<>(); + + if (dmWaypoints != null) { + + for (Waypoint point : dmWaypoints) { + mapPoints.add(new MapWaypoint(point)); + } + } + + return mapPoints; + } /** * Returns a MapWaypoint without a reference to the datamodel waypoint. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java new file mode 100755 index 0000000000..2580031dfb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java @@ -0,0 +1,110 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.plaf.basic.BasicLabelUI; + + +/** + * This class is an overload of BasicLabelUI to draw labels vertically. + * + * This code was found at: + * https://tech.chitgoks.com/2009/11/13/rotate-jlabel-vertically/ + * + */ +final class VerticalLabelUI extends BasicLabelUI { + + private static final Rectangle paintIconR = new Rectangle(); + private static final Rectangle paintTextR = new Rectangle(); + private static final Rectangle paintViewR = new Rectangle(); + private static Insets paintViewInsets = new Insets(0, 0, 0, 0); + + static { + labelUI = new VerticalLabelUI(false); + } + + final boolean clockwise; + + /** + * Construct a new VerticalLabelUI + * @param clockwise + */ + VerticalLabelUI(boolean clockwise) { + super(); + this.clockwise = clockwise; + } + + @Override + public Dimension getPreferredSize(JComponent c) { + Dimension dim = super.getPreferredSize(c); + return new Dimension( dim.height, dim.width ); + } + + @Override + public void paint(Graphics g, JComponent c) { + JLabel label = (JLabel)c; + String text = label.getText(); + Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon(); + + if ((icon == null) && (text == null)) { + return; + } + + FontMetrics fm = g.getFontMetrics(); + paintViewInsets = c.getInsets(paintViewInsets); + + paintViewR.x = paintViewInsets.left; + paintViewR.y = paintViewInsets.top; + + // Use inverted height & width + paintViewR.height = c.getWidth() - (paintViewInsets.left + paintViewInsets.right); + paintViewR.width = c.getHeight() - (paintViewInsets.top + paintViewInsets.bottom); + + paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; + paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; + + String clippedText = layoutCL(label, fm, text, icon, paintViewR, paintIconR, paintTextR); + + Graphics2D g2 = (Graphics2D) g; + AffineTransform tr = g2.getTransform(); + if (clockwise) { + g2.rotate( Math.PI / 2 ); + g2.translate( 0, - c.getWidth() ); + } else { + g2.rotate( - Math.PI / 2 ); + g2.translate( - c.getHeight(), 0 ); + } + + if (icon != null) { + icon.paintIcon(c, g, paintIconR.x, paintIconR.y); + } + + if (text != null) { + int textX = paintTextR.x; + int textY = paintTextR.y + fm.getAscent(); + + if (label.isEnabled()) { + paintEnabledText(label, g, clippedText, textX, textY); + } else { + paintDisabledText(label, g, clippedText, textX, textY); + } + } + g2.setTransform( tr ); + } + +} + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index bd5b39b6b7..428c659eca 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -262,186 +262,6 @@ public class Waypoint { return attributeMap; } - - /** - * Returns a list of Waypoints for the artifacts with geolocation - * information. - * - * List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH - * TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List points = new ArrayList<>(); - - points.addAll(getTrackpointWaypoints(skCase)); - points.addAll(getEXIFWaypoints(skCase)); - points.addAll(getSearchWaypoints(skCase)); - points.addAll(getLastKnownWaypoints(skCase)); - points.addAll(getBookmarkWaypoints(skCase)); - - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex); - } - - List points = new ArrayList<>(); - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new TrackpointWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID())); - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_METADATA_EXIF artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - static public List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new EXIFWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - // I am a little relucant to log this error because I suspect - // this will happen more often than not. It is valid for - // METADAT_EXIF to not have longitude and latitude - } - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_SEARCH artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new SearchWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID())); - } - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new LastKnownWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID())); - } - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new Waypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID())); - } - } - } - return points; - } /** * Get a list of Waypoint.Property objects for the given artifact. This list diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java new file mode 100755 index 0000000000..0776d029fd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -0,0 +1,484 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.geolocation.datamodel; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CaseDbAccessManager; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.DataSource; + +/** + * Class for building lists of waypoints. + * + */ +public final class WaypointBuilder { + + private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName()); + + // SELECT statement for getting a list of waypoints. Replace the %s after + // after the SELECT with the list of parameters to return + final static String GEO_ARTIFACT_QUERY + = "SELECT artifact_id, artifact_type_id " + + "FROM blackboard_attributes " + + "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS + + // This Query will return a list of waypoint artifacts + final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY + = "SELECT blackboard_attributes.artifact_id " + + "FROM blackboard_attributes " + + "JOIN blackboard_artifacts ON blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id " + + "WHERE blackboard_attributes.attribute_type_id IN(%d, %d) " + + "AND data_source_obj_id IN (%s)"; //NON-NLS + + final static String MOST_RECENT_TIME + = "SELECT MAX(value_int64) - (%d * 86400)" //86400 is the number of seconds in a day. + + "FROM blackboard_attributes " + + "WHERE attribute_type_id IN(%d, %d) " + + "AND artifact_id " + + "IN ( " + + "%s" //GEO_ARTIFACT with or without data source + + " )"; + + final static String SELECT_WO_TIMESTAMP = + "SELECT DISTINCT artifact_id, artifact_type_id " + + "FROM blackboard_attributes " + + "WHERE artifact_id NOT IN (%s) " + + "AND artifact_id IN (%s)"; //NON-NLS + + /** + * A callback interface to process the results of waypoint filtering. + */ + public interface WaypointFilterQueryCallBack { + + /** + * This function will be called after the waypoints have been filtered. + * + * @param wwaypoints This of waypoints. + */ + void process(List wwaypoints); + } + + /** + * private constructor + */ + private WaypointBuilder() { + + } + + /** + * Returns a list of Waypoints for the artifacts with geolocation + * information. + * + * List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH + * TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List points = new ArrayList<>(); + + points.addAll(getTrackpointWaypoints(skCase)); + points.addAll(getEXIFWaypoints(skCase)); + points.addAll(getSearchWaypoints(skCase)); + points.addAll(getLastKnownWaypoints(skCase)); + points.addAll(getBookmarkWaypoints(skCase)); + + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex);//NON-NLS + } + + List points = new ArrayList<>(); + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new TrackpointWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID()));//NON-NLS + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_METADATA_EXIF artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + static public List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new EXIFWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + // I am a little relucant to log this error because I suspect + // this will happen more often than not. It is valid for + // METADAT_EXIF to not have longitude and latitude + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_SEARCH artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new SearchWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID()));//NON-NLS + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new LastKnownWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID()));//NON-NLS + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new Waypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID()), ex);//NON-NLS + } + } + } + return points; + } + + /** + * Get a filtered list of waypoints. + * + * If showAll is true, the values of cntDaysFromRecent and notTimeStamp will + * be ignored. + * + * To include data from all dataSources pass a null or empty dataSource + * list. + * + * + * @param skCase Currently open sleuthkit case. + * @param dataSources This of data sources to filter the waypoints by. + * Pass a null or empty list to show way points for + * all dataSources. + * + * @param showAll True to get all waypoints. + * + * @param cntDaysFromRecent Number of days from the most recent time stamp + * to get waypoints for. This parameter will be + * ignored if showAll is true; + * + * @param noTimeStamp True to include waypoints without timestamp. + * This parameter will be ignored if showAll is + * true. + * + * @param queryCallBack Function to call after the DB query has + * completed. + * + * @throws GeoLocationDataException + */ + static public void getAllWaypoints(SleuthkitCase skCase, List dataSources, boolean showAll, int cntDaysFromRecent, boolean noTimeStamp, WaypointFilterQueryCallBack queryCallBack) throws GeoLocationDataException { + String query = buildQuery(dataSources, showAll, cntDaysFromRecent, noTimeStamp); + + logger.log(Level.INFO, query); + + try { + // The CaseDBAccessManager.select function will add a SELECT + // to the beginning of the query + if (query.startsWith("SELECT")) { //NON-NLS + query = query.replaceFirst("SELECT", ""); //NON-NLS + } + + skCase.getCaseDbAccessManager().select(query, new CaseDbAccessManager.CaseDbAccessQueryCallback() { + @Override + public void process(ResultSet rs) { + List waypoints = new ArrayList<>(); + try { + while (rs.next()) { + int artifact_type_id = rs.getInt("artifact_type_id"); //NON-NLS + long artifact_id = rs.getLong("artifact_id"); //NON-NLS + + BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact_type_id); + + waypoints.addAll(getWaypointForArtifact(skCase.getBlackboardArtifact(artifact_id), type)); + + } + queryCallBack.process(waypoints); + } catch (GeoLocationDataException | SQLException | TskCoreException ex) { + logger.log(Level.WARNING, "Failed to filter waypoint.", ex); //NON-NLS + } + + } + }); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to filter waypoint.", ex); //NON-NLS + } + } + + /** + * Create the query for getting a list of waypoints that do not have time + * stamps. + * + * @param dataSources List of data Sources to filter by + * + * @return SQL SELECT statement + */ + static private String buildQueryForWaypointsWOTimeStamps(List dataSources) { + String query = ""; + + query = String.format(SELECT_WO_TIMESTAMP, + String.format(GEO_ARTIFACT_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()), + getWaypointListQuery(dataSources)); + + return query; + } + + /** + * Build the query to filter the list of waypoints. + * + * If showAll is true, the values of cntDaysFromRecent and noTimeStamp are + * ignored. + * + * @param dataSources This of data sources to filter the waypoints by. + * Pass a null or empty list to show way points for + * all dataSources. + * + * @param showAll True to get all waypoints. + * + * @param cntDaysFromRecent Number of days from the most recent time stamp + * to get waypoints for. This parameter will be + * ignored if showAll is true; + * + * @param noTimeStamp True to include waypoints without timestamp. + * This parameter will be ignored if showAll is + * true. + * + * @return + */ + static private String buildQuery(List dataSources, boolean showAll, int cntDaysFromRecent, boolean noTimeStamp) { + String mostRecentQuery = ""; + + if (!showAll && cntDaysFromRecent > 0) { + mostRecentQuery = String.format("AND value_int64 > (%s)", //NON-NLS + String.format(MOST_RECENT_TIME, + cntDaysFromRecent, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), + getWaypointListQuery(dataSources) + )); + } + + // This givens us all artifact_ID that have time stamp + String query = String.format(GEO_ARTIFACT_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()); + + // That are in the list of artifacts for the given data Sources + query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS + query += mostRecentQuery; + + if (noTimeStamp) { + query = String.format("%s UNION %s", buildQueryForWaypointsWOTimeStamps(dataSources), query); //NON-NLS + } + + return query; + } + + /** + * Returns the query to get a list of waypoints filted by the given data + * sources. + * + * An artifact is assumed to be a "waypoint" if it has the attributes + * TSK_GEO_LATITUDE or TSK_GEO_LATITUDE_START + * + * @param dataSources A list of data sources to filter by. If the list is + * null or empty the data source list will be ignored. + * + * @return + */ + static private String getWaypointListQuery(List dataSources) { + + if (dataSources == null || dataSources.isEmpty()) { + return String.format(GEO_ARTIFACT_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID()); + } + + String dataSourceList = ""; + for (DataSource source : dataSources) { + dataSourceList += Long.toString(source.getId()) + ","; + } + + if (!dataSourceList.isEmpty()) { + // Remove the last , + dataSourceList = dataSourceList.substring(0, dataSourceList.length() - 1); + } + + return String.format(GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), + dataSourceList); + } + + /** + * Create a Waypoint object for the given Blackboard artifact. + * + * @param artifact The artifact to create the waypoint from + * @param type The type of artifact + * + * @return A new waypoint object + * + * @throws GeoLocationDataException + */ + static private List getWaypointForArtifact(BlackboardArtifact artifact, BlackboardArtifact.ARTIFACT_TYPE type) throws GeoLocationDataException { + List waypoints = new ArrayList<>(); + switch (type) { + case TSK_METADATA_EXIF: + waypoints.add(new EXIFWaypoint(artifact)); + break; + case TSK_GPS_BOOKMARK: + waypoints.add(new Waypoint(artifact)); + break; + case TSK_GPS_TRACKPOINT: + waypoints.add(new TrackpointWaypoint(artifact)); + break; + case TSK_GPS_SEARCH: + waypoints.add(new SearchWaypoint(artifact)); + break; + case TSK_GPS_ROUTE: + Route route = new Route(artifact); + waypoints.addAll(route.getRoute()); + break; + default: + waypoints.add(new Waypoint(artifact)); + break; + } + + return waypoints; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png new file mode 100755 index 0000000000000000000000000000000000000000..b89f013c410be1b7ba70c62153c598846ab759c9 GIT binary patch literal 741 zcmVL}000McNliru;|mNI9}-50hXw!u0&+=2 zK~y-)ol#v#Q(+i>&dvmn%fNv&)@GU*jDDy!9415NHndSU2BWYjq6ncI=@0BOio%O7 zyGt)3vgu7=l*h&J$u=B34Nf4Z8C$iZGcQTp2`>|LvRCRboBNd87q7|9U6STMAd|Xv^ z_8{j1oY`Ppz%T?j2RQE;RMoCS#U^sWK+~p>N_E|;tLr?)m<1vU2IWBn7={GHu)&Sc zj+$y&86Ca%te|rgNAP%(ml-3NrW12>gRe9#a$t5g9^qVlMzs?Q5;tG@o%52Ee9Klcb!0uz64pzCiwFqVa3}AaJc4Q=j z4bj+mI~@qjh?<(O!sF4z=JIbM5a@oi9@nw@Ar_0m=j%KJOo1p5B7sun3tCz(9goMO z_-BN}7p}S8(?U_ci(v5Lb^H(f{(-FD-=775NaV=69Op&`UhmL3+jdIR)41(ld49e*cDYL~ zMJ^ACq$D0%iBeJ}uxh(@B?+Z1+at9%%ieC<4b$jXG{( z#EOl?wtSb7sGSvM>x?YRvMgKBwbnsdG-ZpVwCBwCz5OvWyEC&ZK}+q@0Xo29hlAPO zd7tk+p7(h_GSv7O7+9$gnL)-s#ufr>F#y%Ni=~O^7$6Q1nMaB8F9PNW zV*x|ghYbMGG!440SN-`|HNVXLkgHZL3khNTwk(J42nO4}Ez6NPfxt|J!!fApEU1Cm zAR&PW;G83u{{*>g68ZdT7{)mWkvhxy*t10GR~V~5qU(wAS^&XnVf@OKOTW$-`@Klx z7uGjMzF5}~S&VR~AF|vG&P{O6!MT7C1kM2=06`LjU?3sEFeZ`99Y-#A2>JY>v|$|j z6`=f5lA^D~y$3r^rf z5Vp}=6wj@f3lL!eVj3SKZyZK0_gaecukgnT>nDn(^u;uxM3m*!$wl1@baTzZJK(^6>Y){|-k#(KOdO03KWC1}$g?ofx z7C6KKS_@zUfEh!YcH3P}_&K3CEdV%(4-BmORxsG|axC_zax{7~Ow+Je*fOBt>dv`s zr1cJPDuUz~UjWR05fwee0x|_5mVxv8Kxlhe)4p=g^(>$~RpT{Hy8)mV7A<tJluR=O;ETSn%Dxw)R!vrcqo~&Rh@b^90)%$Gtt?;X3yl%(?F~0uX|O^HvBE zZcvo{y?uT2x9WQ0a?L#e0OrnZ|EspPuiV|+`zKN{c{wgyx?&|EQAl!tAXYS*yF!49 zKscXu5~C>O4+7`ykfgCbSxz4A?d?C<*Vk8(7f6-X`;{w~&R3L%yL)=Stq>&7s=D2m z3Nc^I;v^V^$ipjxD%$Ul$z!YWU~VhqWw|E zjE+jHfpV=kS|x7<6C!&@d;5yTb%B`$!#qa2+?gEX$pR{!Ie<*&J!G>-J~50FXN0&k z4lpK3(HV*|e}N?RMA30g!( zTPW1=otBoH!K6By&bo~*6Z25Pjd5J~^ciF_2M!s=>7Owc*vDA#j3hOE1`s1kH5g3B&4irh)dULn&= zV{GgRW*DdcM%Rb`Xn1(v#lgYHsT^ss+;+2>(088##Mg*+K0|n&31Rqt$D3q%fmAW1 z(l-I12dYklG))5$L6+;j+R!i`!tUh!?(T4Y>$_{FJeqa zATSHo1EG9!64vTxv&WFj9e+uZ!Uw0?VgsS)WclC-aKt|(7L*pbw6S>?`1He9APneYg;CPF!CsF|WwJaYZA>=|c(?|#0C`vzMdGgwNq`)zj`Mo(`(-v=V@Nxq!C?9*bN3hL{R2qA)dYC(lfBIBcU`Zz$6rzI`Y z)1v6f*c_0l2+Av4?$JG4q5Jb7~pHJMZ9#3U%mQfnKASGx!jFwGMSr-NtXQG zAQilja|Ml!2LZ6*O7czx05IZw{MB^!7z|^gp!*fFF!`k{4zOM*$~|DL{Y6c?^?prT zuQ>U@{ebS_il%AJM1e;PBlhsb#2>&kBQ8y`R1YYYKaQrR{UBmbRFZeDSrSVolj!a3 z&I9Hh0X5bxE1g!o=U(;4n;1<1vjOxgM5F6~LrePlVj`J5eb_BqC6h^L+KR}$d9yc2 zQu>i>cEvpt6Mu|+es&@0dO;@7HEZ<`ptW_!7{E3plP7Az1Hc#?GmO!vQt7=vRMdV5 z5%B2wMfZ2tYZF9003HC*`*$+7X{DwudlbMS#&E&%LYfPRdHMWrHKtPEs>|hi!Fdow zW~shdBA*k81lroRL6Wlnsp|=;3IXf=)Vbc?nJuO{dRs8Kj9EiWFFo~?M(!0#NN{l82+m_L@{3@Yi(nXYAh_yM zm#Cz~dcd_yFegBkQt0Y>1(C?nzckJI9f?GoYBm{il9ePyew54Ye>amE1`sGCg}cgP znW2Zp>I@JJ>zz#3D9lA~U0tWU+bsNsAT%`{K(u-PcFsdDB@#mwcLP$T>`c=%#N%5= zC8^>2snkXo##smUuRrjQ%?w-!?iG z%3bknu-L0_dy^>tTsplGM2csC;WogZwH*ylxD%#(3Ph#sl5^L<)c#(R%a}Q92z7Pm zcWxcp`HQJ`T2mg5+8_Te=SL`S?1m7E)td4$K=9IOk8q8}g&PoOrr@|d6)TK8nJVfy z=FEOM&3WDZ*E1r&A^>aGjj*-rM%dLtLD%(5M5%|2{1yn2vSh0(&HBi-KfrEXi~YR2 zwI=U~jJwO74TLFUe(VVl$;lc~XcI@0;c>o1HPAYx@VIrBmb5 zp{T#jE&gqo0kpR5Ktn@v?9!#aKbvZ|c~t<`t{cJT!7jdeuq(TE-N;q*fv)dK1E2n@ z5W^5O?h;AK(=3(fhlM=hzMovp6_pv0GX&HN=Hjy+5I;Zj87p0GkK9%AQ~O z2o3@tye`CHyH%x<$*x;Ub8`dlSgni)xR@X35?Wh#Asjxpi7~$UdRMmSerlQx2dlCARRf^75_Ny4C~sT_ z&Vg)-Oztr7cXIA7kC)$2;doIXFow>~p|hNao=zl&a@APR1`&%r%^Mq!{&;9;*E8;QPUQn#Dw}Q5|y=ZCK{~70X|29>Xx#j@4N8EsMcQB}f;S+mXjC-4mj|P*w zji!>lL5xZ0>3JRidmtVkzErK)uWAVRLxDk@XVPRh7xiDx<`-F8U3{%<>E43U?Quc` z(B1t4nwk#n*Y(8rrm_ugS^;oCIGOy2$L7uYSdu62$z~S@IBzVo&&NGWEbc8RDoirM z;dAKec^;~ozT@P{k4CF)eAPq1qaah=+p3y;DiqpNPTw7~wZ(B=n>addQSMIbQ# z!+3n#d)18lngd|vf^8;|`ahA#;3-A>2ViR1OP?Jpra#>y1Gw(vVQ%pW2oUFp~eU24gVMp z9lTgyzYpA0ikm3NtM0f*+o8}X+S_;U6GGj%VMBGx-n0UsX&MrVEhdrr=k4tq0hYJh z<)XyJU9NBGjRtyqH)a9z$#{JGnOgm`RL!bDc_lgdVJy};z>w=6A726@vt+Jlc}oyo zU0cx6q3_9LIv+fJ`a@BR%s;Im;F|M)r?GKAg+j+5xa?tl019QVvvV_XUiZ*jZ*4Ma z>H5>+0UmP1#JHkdmI1MRd}6W3?JWRQ#9~jQzW&sYb$#2D)#Ur1D*y#@bn+vbH*d}v zRh_)+@?>9gHrrYNfJi|!`Yt*;;**kO+?7lwvzVqs!ZaRRx9&I|3Y~kRwe3wXmM=`_ zWI2slvxcFnlYg)4`o-yR{b>ilG0HTFRm|&CTy(#*7b!I1j#7?Oy*{4mRD0d;jZcATV*9a|wo z*|KEKlz}ct89+ipctWkZ*HXK5WI(V{i9JH)C$JF2gjD4ZFri8nLW#r@7As+}6Fbcp z=f3!rv}stl((#M$yZ78rM_Viw^gm+o!}GkS-EMcM)9Fx>BueiJA?$oUPlZBZYTNeM zahx&E1n)mvt=6Y&0?1Cg-9DR6r;;F!V;T;}6a*vE^$Z9sf+Ja0ftb+upC8@GfThuB z_|0bXEDS?94=D^I8Vn*b3{yJ0u1m#YkxoxvogjmoB_Q&99EX)kh5G$I90!PMLlu!} z=BZRFQMp_u$0yTJ=#koJ`7O3=%>^1_oubS(KvF!NHRku=|qs z=`TSl9VV&OYJM)4lO(#nB-`HJqP@LGuW|nw_`W&?ace4)q+YKdF9|eB;CT<rcA zygf#7$7q@4e0TT3OV=&a{{G{)c>e%gY}|uhuSb)~gqx}KKUR%!_brMddCybgKCKb_ zOPHL&B(vE}Vr>uZ5VhlTrC`U=XtZ9!s%V;~eqKrM`8?+D1t+bJZzBO4r!~&gCA(5( zY;0mGI>0-+g=n0HX;~IW7D)YmY7+w?^Im7n5!19Y{80h-SPwi~R-}D}jq37m;1`gn dzkxpj3;^AW#{FvID!%{#002ovPDHLkV1h0A2_^sl literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png new file mode 100755 index 0000000000000000000000000000000000000000..a7d7a96be3f2282a62e3c0733bac89c7f6de7b4a GIT binary patch literal 582 zcmV-M0=fN(P)tYd4K$mX5uyr2F@fdffp{&DSHtl4|Bn9=ukpCx`#%PTUr-D(@b7;C zhJXJjrnw{;1KBM=6&|E`fsNrGL!Y6%zUh}QUl`(@V)PmQFtotEKmafTHP0uP}7&%m4pcKQ#X)BiAJ2y+PrtBI>9eEIt2-_c7)?*Lsf z5vX5*Af@m-wBJRV%#Fiz=BdPr0!2^az8K(fJ0K@xl?-_o)`1bna-SH57SeZUButL)d$cI_) zkpl5Q!w#U+YtHUCagF&eebP3jhEB literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java index eabdeef98b..a140338524 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.Route; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.datamodel.AbstractFile; @@ -331,11 +332,11 @@ class KMLReport implements GeneralReportModule { * @throws IOException */ void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException { - addExifMetadataContent(Waypoint.getEXIFWaypoints(skCase), baseReportDir); - addWaypoints(Waypoint.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String()); - addWaypoints(Waypoint.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String()); - addWaypoints(Waypoint.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String()); - addWaypoints(Waypoint.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String()); + addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(skCase), baseReportDir); + addWaypoints(WaypointBuilder.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String()); + addWaypoints(WaypointBuilder.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String()); + addWaypoints(WaypointBuilder.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String()); + addWaypoints(WaypointBuilder.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String()); } /** From d8b4cf355cdc3ebd3821415b67febe94b09b0712 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 14:10:49 -0500 Subject: [PATCH 24/41] Addressed most codacy issues and moved images --- .../autopsy/geolocation/Bundle.properties | 6 ++-- .../geolocation/Bundle.properties-MERGED | 13 +++++++-- .../geolocation/CheckBoxListPanel.form | 8 ++++++ .../geolocation/CheckBoxListPanel.java | 6 ++-- .../autopsy/geolocation/GeoFilterPanel.form | 16 +++++++++-- .../autopsy/geolocation/GeoFilterPanel.java | 27 +++++++----------- .../geolocation/GeolocationTopComponent.java | 23 +++++++++------ .../autopsy/geolocation/HidingPane.java | 2 +- .../autopsy/geolocation/RefreshPanel.form | 4 +-- .../autopsy/geolocation/RefreshPanel.java | 4 +-- .../datamodel/WaypointBuilder.java | 16 +++++++---- .../geolocation/images/cross-script.png | Bin 623 -> 0 bytes .../autopsy/geolocation/images/tick.png | Bin 582 -> 0 bytes .../images/arrow-circle-double-135.png | Bin .../{geolocation => }/images/blueGeo16.png | Bin .../{geolocation => }/images/blueGeo64.png | Bin .../{geolocation => }/images/funnel.png | Bin 17 files changed, 78 insertions(+), 47 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/arrow-circle-double-135.png (100%) rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/blueGeo16.png (100%) rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/blueGeo64.png (100%) rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/funnel.png (100%) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties index 4fe5148ba5..bec0dda190 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties @@ -9,10 +9,10 @@ WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= GeoFilterPanel.waypointSettings.border.title= GeoFilterPanel.allButton.text=Show All -GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.mostRecentButton.text=Show only last GeoFilterPanel.applyButton.text=Apply -GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp -GeoFilterPanel.daysLabel.text=days +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Include waypoints with no time stamps +GeoFilterPanel.daysLabel.text=days of activity CheckBoxListPanel.titleLabel.text=jLabel1 CheckBoxListPanel.checkButton.text=Check All CheckBoxListPanel.uncheckButton.text=Uncheck All diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED index ed550cd8db..6b90c60944 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -3,8 +3,15 @@ CTL_GeolocationTopComponentAction=GeolocationTopComponent CTL_GeolocationTopComponent=Geolocation GeoFilterPanel_DataSource_List_Title=Data Sources GeoFilterPanel_empty_dataSource=Data Source list is empty. +GeoTopComponent_filer_data_invalid_msg=Unable to run waypoint filter.\nPlease select one or more data sources. +GeoTopComponent_filer_data_invalid_Title=Filter Failure +GeoTopComponent_filter_exception_msg=Exception occured during waypoint filtering. +GeoTopComponent_filter_exception_Title=Filter Failure +GeoTopComponent_no_waypoints_returned_mgs=Applied filter failed to find waypoints that matched criteria.\nRevise filter options and try again. +GeoTopComponent_no_waypoints_returned_Title=No Waypoints Found GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete. GLTopComponent_name=Geolocation +HidingPane_default_title=Filters MayWaypoint_ExternalViewer_label=Open in ExternalViewer OpenGeolocationAction_displayName=Geolocation OpenGeolocationAction_name=Geolocation @@ -16,10 +23,10 @@ WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= GeoFilterPanel.waypointSettings.border.title= GeoFilterPanel.allButton.text=Show All -GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.mostRecentButton.text=Show only last GeoFilterPanel.applyButton.text=Apply -GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp -GeoFilterPanel.daysLabel.text=days +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Include waypoints with no time stamps +GeoFilterPanel.daysLabel.text=days of activity CheckBoxListPanel.titleLabel.text=jLabel1 CheckBoxListPanel.checkButton.text=Check All CheckBoxListPanel.uncheckButton.text=Uncheck All diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form index 75fb1d4c62..112b734f1e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form @@ -37,6 +37,10 @@ + + + + @@ -52,6 +56,10 @@ + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java index 8e465dd3a3..b91acf6fd4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java @@ -119,8 +119,8 @@ final class CheckBoxListPanel extends javax.swing.JPanel { java.awt.GridBagConstraints gridBagConstraints; titleLabel = new javax.swing.JLabel(); - uncheckButton = new javax.swing.JButton(); - checkButton = new javax.swing.JButton(); + javax.swing.JButton uncheckButton = new javax.swing.JButton(); + javax.swing.JButton checkButton = new javax.swing.JButton(); scrollPane = new javax.swing.JScrollPane(); setLayout(new java.awt.GridBagLayout()); @@ -179,10 +179,8 @@ final class CheckBoxListPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton checkButton; private javax.swing.JScrollPane scrollPane; private javax.swing.JLabel titleLabel; - private javax.swing.JButton uncheckButton; // End of variables declaration//GEN-END:variables /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form index 897341c482..79a852b4d4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form @@ -3,6 +3,10 @@
+ + + + @@ -30,6 +34,10 @@ + + + + @@ -120,6 +128,10 @@ + + + + @@ -131,7 +143,7 @@ - + @@ -148,7 +160,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java index bb3035b5d5..39109d2c83 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -100,17 +100,15 @@ class GeoFilterPanel extends javax.swing.JPanel { "GeoFilterPanel_empty_dataSource=Data Source list is empty." }) GeoFilter getFilterState() throws GeoLocationUIException { - boolean showAll = allButton.isSelected(); - boolean withTimeStamp = showWaypointsWOTSCheckBox.isSelected(); - int dayCnt = numberModel.getNumber().intValue(); - List dataSources = checkboxPanel.getSelectedElements(); if (dataSources.isEmpty()) { throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource()); } - - return new GeoFilter(showAll, withTimeStamp, dayCnt, dataSources); + return new GeoFilter(allButton.isSelected(), + showWaypointsWOTSCheckBox.isSelected(), + numberModel.getNumber().intValue(), + dataSources); } /** @@ -147,14 +145,14 @@ class GeoFilterPanel extends javax.swing.JPanel { private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; - buttonGroup = new javax.swing.ButtonGroup(); - waypointSettings = new javax.swing.JPanel(); + javax.swing.ButtonGroup buttonGroup = new javax.swing.ButtonGroup(); + javax.swing.JPanel waypointSettings = new javax.swing.JPanel(); allButton = new javax.swing.JRadioButton(); mostRecentButton = new javax.swing.JRadioButton(); showWaypointsWOTSCheckBox = new javax.swing.JCheckBox(); daysSpinner = new javax.swing.JSpinner(numberModel); javax.swing.JLabel daysLabel = new javax.swing.JLabel(); - buttonPanel = new javax.swing.JPanel(); + javax.swing.JPanel buttonPanel = new javax.swing.JPanel(); applyButton = new javax.swing.JButton(); javax.swing.JLabel optionsLabel = new javax.swing.JLabel(); @@ -232,7 +230,7 @@ class GeoFilterPanel extends javax.swing.JPanel { buttonPanel.setLayout(new java.awt.GridBagLayout()); - applyButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/tick.png"))); // NOI18N + applyButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/tick.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(applyButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.applyButton.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; @@ -248,7 +246,7 @@ class GeoFilterPanel extends javax.swing.JPanel { gridBagConstraints.insets = new java.awt.Insets(9, 15, 0, 15); add(buttonPanel, gridBagConstraints); - optionsLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png"))); // NOI18N + optionsLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/blueGeo16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.optionsLabel.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; @@ -270,12 +268,9 @@ class GeoFilterPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JRadioButton allButton; private javax.swing.JButton applyButton; - private javax.swing.ButtonGroup buttonGroup; - private javax.swing.JPanel buttonPanel; private javax.swing.JSpinner daysSpinner; private javax.swing.JRadioButton mostRecentButton; private javax.swing.JCheckBox showWaypointsWOTSCheckBox; - private javax.swing.JPanel waypointSettings; // End of variables declaration//GEN-END:variables /** @@ -320,7 +315,7 @@ class GeoFilterPanel extends javax.swing.JPanel { * * @return True if all waypoints should be shown. */ - boolean showAll() { + boolean showAllWaypoints() { return showAll; } @@ -331,7 +326,7 @@ class GeoFilterPanel extends javax.swing.JPanel { * * @return True if waypoints with time stamps should be shown. */ - boolean showWithoutTimeStamp() { + boolean showWaypointsWithoutTimeStamp() { return showWithoutTimeStamp; } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index f29bfca802..076fc787ff 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.openide.util.NbBundle.Messages; @@ -223,9 +224,10 @@ public final class GeolocationTopComponent extends TopComponent { try { filters = geoFilterPanel.getFilterState(); } catch (GeoLocationUIException ex) { - MessageNotifyUtil.Notify.info( - Bundle.GeoTopComponent_filer_data_invalid_Title(), - Bundle.GeoTopComponent_filer_data_invalid_msg()); + JOptionPane.showMessageDialog(GeolocationTopComponent.this, + Bundle.GeoTopComponent_filer_data_invalid_msg(), + Bundle.GeoTopComponent_filer_data_invalid_Title(), + JOptionPane.INFORMATION_MESSAGE); return; } @@ -233,15 +235,17 @@ public final class GeolocationTopComponent extends TopComponent { public void run() { Case currentCase = Case.getCurrentCase(); try { - WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAll(), filters.getMostRecentNumDays(), filters.showWithoutTimeStamp(), new WaypointFilterQueryCallBack() { + WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAllWaypoints(), filters.getMostRecentNumDays(), filters.showWaypointsWithoutTimeStamp(), new WaypointFilterQueryCallBack() { @Override public void process(List waypoints) { // If the list is empty, tell the user and do not change // the visible waypoints. if (waypoints == null || waypoints.isEmpty()) { - MessageNotifyUtil.Notify.info( + JOptionPane.showMessageDialog(GeolocationTopComponent.this, Bundle.GeoTopComponent_no_waypoints_returned_Title(), - Bundle.GeoTopComponent_no_waypoints_returned()); + Bundle.GeoTopComponent_no_waypoints_returned_mgs(), + JOptionPane.INFORMATION_MESSAGE); + return; } mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints)); @@ -249,9 +253,10 @@ public final class GeolocationTopComponent extends TopComponent { }); } catch (GeoLocationDataException ex) { logger.log(Level.SEVERE, "Failed to filter waypoints.", ex); - MessageNotifyUtil.Notify.error( - Bundle.GeoTopComponent_filter_exception_Title(), - Bundle.GeoTopComponent_filter_exception_msg()); + JOptionPane.showMessageDialog(GeolocationTopComponent.this, + Bundle.GeoTopComponent_filter_exception_Title(), + Bundle.GeoTopComponent_filter_exception_msg(), + JOptionPane.ERROR_MESSAGE); } } }); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java index 492888d15f..ca2afea1a4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java @@ -48,7 +48,7 @@ public final class HidingPane extends JTabbedPane { panel.setLayout(new BorderLayout()); panel.add(scrollPane, BorderLayout.CENTER); tabLabel = new JLabel(Bundle.HidingPane_default_title()); - tabLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/funnel.png"))); + tabLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/funnel.png"))); tabLabel.setUI(new VerticalLabelUI(true)); tabLabel.setOpaque(false); Font font = tabLabel.getFont().deriveFont(18).deriveFont(Font.BOLD); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form index 400c788d42..145c22444a 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form @@ -42,7 +42,7 @@ - + @@ -63,7 +63,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java index a4e30c9129..f1f93a620d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java @@ -82,13 +82,13 @@ final class RefreshPanel extends JPanel { gridBagConstraints.insets = new java.awt.Insets(15, 10, 15, 10); add(refreshLabel, gridBagConstraints); - refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png"))); // NOI18N + refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/arrow-circle-double-135.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.refreshButton.text")); // NOI18N refreshButton.setMargin(new java.awt.Insets(2, 5, 2, 5)); add(refreshButton, new java.awt.GridBagConstraints()); closeButton.setBackground(new java.awt.Color(0, 0, 0)); - closeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/cross-script.png"))); // NOI18N + closeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/close-icon.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.closeButton.text")); // NOI18N closeButton.setMargin(new java.awt.Insets(0, 0, 0, 0)); closeButton.setOpaque(false); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index 0776d029fd..0e29ca9dd2 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -40,21 +40,26 @@ public final class WaypointBuilder { private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName()); - // SELECT statement for getting a list of waypoints. Replace the %s after - // after the SELECT with the list of parameters to return + // SELECT statement for getting a list of waypoints. final static String GEO_ARTIFACT_QUERY = "SELECT artifact_id, artifact_type_id " + "FROM blackboard_attributes " + "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS + + // SELECT statement to get only artifact_ids + final static String GEO_ARTIFACT_QUERY_ID_ONLY + = "SELECT artifact_id " + + "FROM blackboard_attributes " + + "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS // This Query will return a list of waypoint artifacts final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY = "SELECT blackboard_attributes.artifact_id " - + "FROM blackboard_attributes " - + "JOIN blackboard_artifacts ON blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id " + + "FROM blackboard_attributes, blackboard_artifacts " + "WHERE blackboard_attributes.attribute_type_id IN(%d, %d) " + "AND data_source_obj_id IN (%s)"; //NON-NLS + // Select will return the "most recent" timestamp from all waypoings final static String MOST_RECENT_TIME = "SELECT MAX(value_int64) - (%d * 86400)" //86400 is the number of seconds in a day. + "FROM blackboard_attributes " @@ -64,6 +69,7 @@ public final class WaypointBuilder { + "%s" //GEO_ARTIFACT with or without data source + " )"; + // Returns a list of artifacts with no time stamp final static String SELECT_WO_TIMESTAMP = "SELECT DISTINCT artifact_id, artifact_type_id " + "FROM blackboard_attributes " @@ -350,7 +356,7 @@ public final class WaypointBuilder { String query = ""; query = String.format(SELECT_WO_TIMESTAMP, - String.format(GEO_ARTIFACT_QUERY, + String.format(GEO_ARTIFACT_QUERY_ID_ONLY, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()), getWaypointListQuery(dataSources)); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png deleted file mode 100755 index f37cf4183046203c9d77c03ea46c672084ae3dbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 623 zcmV-#0+9WQP)uz-;<4aiE#*F2T#| zt;WP8_2kp17jJ+5TnKdXT%g9FN8i3}lmYYqfHOcjj~~!Ph-`p^4LiH!rC-0^KL7i72TcG6TZ`7-bKTQXvCc;3)#dB$oaEf1Gi^b0a{20RY5Z;(@&ISGfQH002ov JPDHLkV1iqyA&vk5 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png deleted file mode 100755 index a7d7a96be3f2282a62e3c0733bac89c7f6de7b4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 582 zcmV-M0=fN(P)tYd4K$mX5uyr2F@fdffp{&DSHtl4|Bn9=ukpCx`#%PTUr-D(@b7;C zhJXJjrnw{;1KBM=6&|E`fsNrGL!Y6%zUh}QUl`(@V)PmQFtotEKmafTHP0uP}7&%m4pcKQ#X)BiAJ2y+PrtBI>9eEIt2-_c7)?*Lsf z5vX5*Af@m-wBJRV%#Fiz=BdPr0!2^az8K(fJ0K@xl?-_o)`1bna-SH57SeZUButL)d$cI_) zkpl5Q!w#U+YtHUCagF&eebP3jhEB diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png b/Core/src/org/sleuthkit/autopsy/images/arrow-circle-double-135.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png rename to Core/src/org/sleuthkit/autopsy/images/arrow-circle-double-135.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png b/Core/src/org/sleuthkit/autopsy/images/blueGeo16.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png rename to Core/src/org/sleuthkit/autopsy/images/blueGeo16.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo64.png b/Core/src/org/sleuthkit/autopsy/images/blueGeo64.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo64.png rename to Core/src/org/sleuthkit/autopsy/images/blueGeo64.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/funnel.png b/Core/src/org/sleuthkit/autopsy/images/funnel.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/funnel.png rename to Core/src/org/sleuthkit/autopsy/images/funnel.png From aa88bc69b5a6645e6a4c45c8be5bdf284418b0a8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 14:14:14 -0500 Subject: [PATCH 25/41] Implemented and tested the XRY DSP --- .../xry/Bundle.properties | 3 + .../xry/Bundle.properties-MERGED | 12 + .../xry/XRYDataSourceProcessor.java | 242 ++++++++++++++++++ .../XRYDataSourceProcessorConfigPanel.form | 75 ++++++ .../XRYDataSourceProcessorConfigPanel.java | 136 ++++++++++ .../datasourceprocessors/xry/XRYFolder.java | 60 ++++- .../xry/XRYReportProcessor.java | 61 +++++ 7 files changed, 588 insertions(+), 1 deletion(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties new file mode 100755 index 0000000000..998296e54f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties @@ -0,0 +1,3 @@ +XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse +XRYDataSourceProcessorConfigPanel.filePathTextField.text= +XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED new file mode 100755 index 0000000000..7591e1fa21 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED @@ -0,0 +1,12 @@ +XRYDataSourceProcessor.dataSourceType=Import Tool Report +XRYDataSourceProcessor.fileAdded=Added %s to the case database +XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder +XRYDataSourceProcessor.notReadable=Could not read from the selected folder +XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files +XRYDataSourceProcessor.preppingFiles=Preparing to add files to the case database +XRYDataSourceProcessor.processingFiles=Processing all XRY files... +XRYDataSourceProcessor.testingFolder=Testing input folder... +XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report +XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse +XRYDataSourceProcessorConfigPanel.filePathTextField.text= +XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java new file mode 100755 index 0000000000..04a8d3a7a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -0,0 +1,242 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import com.google.common.collect.Lists; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.swing.JPanel; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; + +/** + * An XRY Report data source processor. + */ +@ServiceProviders(value = { + @ServiceProvider(service = DataSourceProcessor.class)} +) +public class XRYDataSourceProcessor implements DataSourceProcessor { + + private final XRYDataSourceProcessorConfigPanel configPanel; + + //Background processor to relieve the EDT from adding files to the case + //database and parsing the report files. + private XRYReportProcessorSwingWorker swingWorker; + + private static final Logger logger = Logger.getLogger(XRYDataSourceProcessor.class.getName()); + + public XRYDataSourceProcessor() { + configPanel = XRYDataSourceProcessorConfigPanel.getInstance(); + } + + @Override + @NbBundle.Messages({ + "XRYDataSourceProcessor.dataSourceType=Import Tool Report" + }) + public String getDataSourceType() { + return Bundle.XRYDataSourceProcessor_dataSourceType(); + } + + @Override + public JPanel getPanel() { + return configPanel; + } + + @Override + public boolean isPanelValid() { + return true; + } + + /** + * Processes the XRY folder the examiner selected. The heavy lifting + * is handed off to a dedicated thread. This function will + * test the minimum requirements needed to successfully process the input. + */ + @Override + @NbBundle.Messages({ + "XRYDataSourceProcessor.testingFolder=Testing input folder...", + "XRYDataSourceProcessor.notReadable=Could not read from the selected folder", + "XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files", + "XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder" + }) + public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_testingFolder()); + + String selectedFilePath = configPanel.getSelectedFilePath(); + File selectedFile = new File(selectedFilePath); + Path selectedPath = selectedFile.toPath(); + + //Test permissions + if (!Files.isReadable(selectedPath)) { + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_notReadable()), + Lists.newArrayList()); + return; + } + + try { + //Validate the folder. + if (!XRYFolder.isXRYFolder(selectedPath)) { + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_notXRYFolder()), + Lists.newArrayList()); + return; + } + } catch (IOException ex) { + logger.log(Level.WARNING, "[XRY DSP] I/O exception encountered trying to test the XRY folder.", ex); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_ioError(), ex.toString()), Lists.newArrayList()); + return; + } + + try { + XRYFolder xryFolder = new XRYFolder(selectedPath); + FileManager fileManager = Case.getCurrentCaseThrows() + .getServices().getFileManager(); + + //Move heavy lifting to a dedicated thread. + swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor, + callback, fileManager); + swingWorker.execute(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "[XRY DSP] No case is currently open.", ex); + } + } + + @Override + public void cancel() { + if (swingWorker != null) { + swingWorker.cancel(true); + } + } + + @Override + public void reset() { + //Clear the current selected file path. + configPanel.clearSelectedFilePath(); + } + + /** + * Relieves the EDT from add images to the case database and processing the + * XRY report files. + */ + private class XRYReportProcessorSwingWorker extends SwingWorker { + + private final DataSourceProcessorProgressMonitor progressMonitor; + private final DataSourceProcessorCallback callback; + private final FileManager fileManager; + private final XRYFolder xryFolder; + + public XRYReportProcessorSwingWorker(XRYFolder folder, DataSourceProcessorProgressMonitor progressMonitor, + DataSourceProcessorCallback callback, FileManager fileManager) { + this.xryFolder = folder; + this.progressMonitor = progressMonitor; + this.callback = callback; + this.fileManager = fileManager; + } + + @Override + @NbBundle.Messages({ + "XRYDataSourceProcessor.preppingFiles=Preparing to add files to the case database", + "XRYDataSourceProcessor.processingFiles=Processing all XRY files..." + }) + protected LocalFilesDataSource doInBackground() throws TskCoreException, + TskDataException, IOException { + progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_preppingFiles()); + + List nonXRYFiles = xryFolder.getNonXRYFiles(); + List filePaths = nonXRYFiles.stream() + //Map paths to string representations. + .map(Path::toString) + .collect(Collectors.toList()); + String uniqueUUID = UUID.randomUUID().toString(); + LocalFilesDataSource dataSource = fileManager.addLocalFilesDataSource( + uniqueUUID, + "XRY Report", //Name + "", //Timezone + filePaths, + new ProgressMonitorAdapter(progressMonitor)); + + //Process the report files. + progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_processingFiles()); + XRYReportProcessor.process(xryFolder, dataSource); + return dataSource; + } + + @Override + @NbBundle.Messages({ + "XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report" + }) + public void done() { + try { + LocalFilesDataSource newDataSource = get(); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, + Lists.newArrayList(), Lists.newArrayList(newDataSource)); + } catch (InterruptedException ex) { + //DSP was cancelled. Not an error. + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "[XRY DSP] Unexpected internal error while processing XRY report.", ex); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_unexpectedError(), + ex.toString()), Lists.newArrayList()); + } + } + + /** + * Makes the DSP progress monitor compatible with the File Manager + * progress updater. + */ + private class ProgressMonitorAdapter implements FileManager.FileAddProgressUpdater { + + private final DataSourceProcessorProgressMonitor progressMonitor; + + ProgressMonitorAdapter(DataSourceProcessorProgressMonitor progressMonitor) { + this.progressMonitor = progressMonitor; + } + + @Override + @NbBundle.Messages({ + "XRYDataSourceProcessor.fileAdded=Added %s to the case database" + }) + public void fileAdded(AbstractFile newFile) { + progressMonitor.setProgressText(String.format(Bundle.XRYDataSourceProcessor_fileAdded(), newFile.getName())); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form new file mode 100755 index 0000000000..b78c62357e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java new file mode 100755 index 0000000000..fd03d47787 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -0,0 +1,136 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.io.File; +import javax.swing.JFileChooser; +import javax.swing.JPanel; + +/** + * Allows an examiner to configure the XRY Data source processor. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +class XRYDataSourceProcessorConfigPanel extends JPanel { + + private static final XRYDataSourceProcessorConfigPanel INSTANCE = + new XRYDataSourceProcessorConfigPanel(); + + /** + * Creates new form XRYDataSourceConfigPanel. + * Prevent direct instantiation. + */ + private XRYDataSourceProcessorConfigPanel() { + initComponents(); + } + + /** + * Gets the singleton XRYDataSourceProcessorConfigPanel. + */ + static XRYDataSourceProcessorConfigPanel getInstance() { + return INSTANCE; + } + + /** + * Clears the selected file path. + */ + void clearSelectedFilePath() { + filePathTextField.setText(null); + } + + /** + * Gets the file path selected by the examiner. + */ + String getSelectedFilePath() { + return filePathTextField.getText(); + } + + /** + * 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() { + + filePathTextField = new javax.swing.JTextField(); + fileBrowserButton = new javax.swing.JButton(); + xrySelectFolderLabel = new javax.swing.JLabel(); + + filePathTextField.setEditable(false); + filePathTextField.setText(org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.filePathTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(fileBrowserButton, org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.fileBrowserButton.text")); // NOI18N + fileBrowserButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + fileBrowserButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(xrySelectFolderLabel, org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(xrySelectFolderLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(filePathTextField) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(fileBrowserButton))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(xrySelectFolderLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(filePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(fileBrowserButton)) + .addContainerGap(246, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + /** + * Opens a JFileChooser instance so that the examiner can select a XRY + * report folder. + */ + private void fileBrowserButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileBrowserButtonActionPerformed + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setMultiSelectionEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int returnVal = fileChooser.showOpenDialog(this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File selection = fileChooser.getSelectedFile(); + filePathTextField.setText(selection.getAbsolutePath()); + } + }//GEN-LAST:event_fileBrowserButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton fileBrowserButton; + private javax.swing.JTextField filePathTextField; + private javax.swing.JLabel xrySelectFolderLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index bac2bc2364..f0d40a8ca8 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -24,7 +24,9 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.stream.Stream; /** @@ -36,6 +38,62 @@ final class XRYFolder { //children of their parent folder. private static final int XRY_FILES_DEPTH = 1; + //Raw path to the XRY folder. + private final Path xryFolder; + + public XRYFolder(Path folder) { + xryFolder = folder; + } + + /** + * Finds all paths in the XRY report folder which are not XRY files. Only + * the first directory level is searched. As a result, some paths may point + * to directories. + * + * @return A non-null collection of paths + * @throws IOException If an I/O error occurs. + */ + public List getNonXRYFiles() throws IOException { + try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + List otherFiles = new ArrayList<>(); + Iterator allFilesIterator = allFiles.iterator(); + while (allFilesIterator.hasNext()) { + Path currentPath = allFilesIterator.next(); + if (!currentPath.equals(xryFolder) + && !XRYFileReader.isXRYFile(currentPath)) { + otherFiles.add(currentPath); + } + } + return otherFiles; + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } + + /** + * Creates XRYFileReader instances for all XRY files found in the top level + * of the folder. + * + * @return A non-null collection of file readers. + * @throws IOException If an I/O error occurs. + */ + public List getXRYFileReaders() throws IOException { + try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + List fileReaders = new ArrayList<>(); + + Iterator allFilesIterator = allFiles.iterator(); + while (allFilesIterator.hasNext()) { + Path currentFile = allFilesIterator.next(); + if (XRYFileReader.isXRYFile(currentFile)) { + fileReaders.add(new XRYFileReader(currentFile)); + } + } + return fileReaders; + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } + /** * Searches for XRY files at the top level of a given folder. If at least * one file matches, the entire directory is assumed to be an XRY report. @@ -48,7 +106,7 @@ final class XRYFolder { * @return Indicates whether the Path is an XRY report. * * @throws IOException Error occurred during File I/O. - * @throws SecurityException If the security manager denies access any of + * @throws SecurityException If the security manager denies access to any of * the files. */ public static boolean isXRYFolder(Path folder) throws IOException { diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java new file mode 100755 index 0000000000..2d3bfc110a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors.xry; + +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Processes all XRY files in an XRY folder. + */ +class XRYReportProcessor { + + private static final Logger logger = Logger.getLogger(XRYReportProcessor.class.getName()); + + /** + * Processes all XRY Files and creates artifacts on the given Content + * instance. + * + * @param folder XRY folder to process + * @param parent Content instance to hold newly created artifacts. + * @throws IOException If an I/O exception occurs. + * @throws TskCoreException If an error occurs adding artifacts. + */ + static void process(XRYFolder folder, Content parent) throws IOException, TskCoreException { + //Get all XRY file readers from this folder. + List xryFileReaders = folder.getXRYFileReaders(); + + for (XRYFileReader xryFileReader : xryFileReaders) { + String reportType = xryFileReader.getReportType(); + if (XRYFileParserFactory.supports(reportType)) { + XRYFileParser parser = XRYFileParserFactory.get(reportType); + parser.parse(xryFileReader, parent); + } else { + logger.log(Level.SEVERE, String.format("[XRY DSP] XRY File (in brackets) " + + "[ %s ] was found, but no parser to support its report type exists. " + + "Report type is (in brackets) [ %s ]", xryFileReader.getReportPath().toString(), reportType)); + } + xryFileReader.close(); + } + } +} From 9037b1e03f2f4e1869a7fbc7bad187e973d97b0f Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 14:17:55 -0500 Subject: [PATCH 26/41] Fixed codacy issue --- .../sleuthkit/autopsy/geolocation/GeolocationTopComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 076fc787ff..05cd3afbbd 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -224,7 +224,7 @@ public final class GeolocationTopComponent extends TopComponent { try { filters = geoFilterPanel.getFilterState(); } catch (GeoLocationUIException ex) { - JOptionPane.showMessageDialog(GeolocationTopComponent.this, + JOptionPane.showMessageDialog(this, Bundle.GeoTopComponent_filer_data_invalid_msg(), Bundle.GeoTopComponent_filer_data_invalid_Title(), JOptionPane.INFORMATION_MESSAGE); From 53b134daedb6e90d660ec409fb9a4943061bfeda Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 14:23:59 -0500 Subject: [PATCH 27/41] Changed the log messages to be easier to read and fixed the date time bug --- .../xry/AbstractSingleKeyValueParser.java | 10 +++--- .../xry/XRYCallsFileParser.java | 8 ++--- .../xry/XRYDeviceGenInfoFileParser.java | 12 +++---- .../xry/XRYFileParserFactory.java | 2 +- .../xry/XRYMessagesFileParser.java | 36 +++++++++---------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 20b6b7c1bf..edd218adbb 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -45,7 +45,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { @Override public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { Path reportPath = reader.getReportPath(); - logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); + logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -55,7 +55,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); } String namespace = ""; @@ -75,7 +75,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //the start of the line and the first delimiter. int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER); if (keyDelimiter == -1) { - logger.log(Level.SEVERE, String.format("XRY DSP: Expected a key value " + logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a key value " + "pair on this line (in brackets) [ %s ], but one was not detected." + " Here is the previous line [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); continue; @@ -84,14 +84,14 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { String value = xryLine.substring(keyDelimiter + 1).trim(); if (!isKey(key)) { - logger.log(Level.SEVERE, String.format("XRY DSP: The following key, " + logger.log(Level.SEVERE, String.format("[XRY DSP] The following key, " + "value pair (in brackets, respectively) [ %s ], [ %s ] was not recognized. Discarding..." + " Here is the previous line [ %s ] for context. What does this key mean?", key, value, xryLines[i - 1])); continue; } if (value.isEmpty()) { - logger.log(Level.SEVERE, String.format("XRY DSP: The following key " + logger.log(Level.SEVERE, String.format("[XRY DSP] The following key " + "(in brackets) [ %s ] was recognized, but the value was empty. Discarding..." + " Here is the previous line for context [ %s ]. What does this mean?", key, xryLines[i - 1])); continue; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 260fccd40f..596f25e7b1 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -89,10 +89,10 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { try { String dateTime = removeDateTimeLocale(value); String normalizedDateTime = dateTime.trim(); - long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + long dateTimeInEpoch = calculateSecondsSinceEpoch(normalizedDateTime); return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, PARSER_NAME, dateTimeInEpoch); } catch (DateTimeParseException ex) { - logger.log(Level.SEVERE, String.format("XRY DSP: Assumption about the date time " + logger.log(Level.SEVERE, String.format("[XRY DSP] Assumption about the date time " + "formatting of call logs is not right. Here is the value [ %s ]", value), ex); return null; } @@ -167,9 +167,9 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { * @param dateTime * @return */ - private long calculateMsSinceEpoch(String dateTime) { + private long calculateSecondsSinceEpoch(String dateTime) { LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); //Assume dates have no offset. - return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + return localDateTime.toInstant(ZoneOffset.UTC).getEpochSecond(); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java index d610ffb89d..d3bba45bfc 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -83,7 +83,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { @Override public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { Path reportPath = reader.getReportPath(); - logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); + logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -93,7 +93,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); } for (int i = 1; i < xryLines.length; i++) { @@ -101,7 +101,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see a "Data" key. if (!hasDataKey(xryLine)) { - logger.log(Level.SEVERE, String.format("XRY DSP: Expected a 'Data' key " + logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a 'Data' key " + "on this line (in brackets) [ %s ], but none was found. " + "Discarding... Here is the previous line for context [ %s ]. " + "What does this mean?", xryLine, xryLines[i - 1])); @@ -109,7 +109,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { } if (i + 1 == xryLines.length) { - logger.log(Level.SEVERE, String.format("XRY DSP: Found a 'Data' key " + logger.log(Level.SEVERE, String.format("[XRY DSP] Found a 'Data' key " + "but no corresponding 'Attribute' key. Discarding... Here " + "is the 'Data' line (in brackets) [ %s ]. Here is the previous " + "line for context [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); @@ -123,7 +123,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see an "Attribute" key if (!hasAttributeKey(nextXryLine)) { - logger.log(Level.SEVERE, String.format("XRY DSP: Expected an 'Attribute' " + logger.log(Level.SEVERE, String.format("[XRY DSP] Expected an 'Attribute' " + "key on this line (in brackets) [ %s ], but none was found. " + "Discarding... Here is the previous line for context [ %s ]. " + "What does this mean?", nextXryLine, xryLine)); @@ -139,7 +139,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //All of the attribute types in the map expect a string. attributes.add(new BlackboardAttribute(KEY_TO_TYPE.get(normalizedAttributeValue), PARSER_NAME, dataValue)); } else { - logger.log(Level.SEVERE, String.format("XRY DSP: Attribute type (in brackets) " + logger.log(Level.SEVERE, String.format("[XRY DSP] Attribute type (in brackets) " + "[ %s ] was not recognized. Discarding... Here is the " + "previous line for context [ %s ]. What does this mean?", nextXryLine, xryLine)); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java index d650510827..06492de07b 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java @@ -42,7 +42,7 @@ final class XRYFileParserFactory { throw new IllegalArgumentException("Report type cannot be null"); } - switch (reportType.toLowerCase()) { + switch (reportType.trim().toLowerCase()) { case "calls": return new XRYCallsFileParser(); case "contacts/contacts": diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java index 253676f0e9..ac78e62509 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -118,7 +118,7 @@ final class XRYMessagesFileParser implements XRYFileParser { @Override public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { Path reportPath = reader.getReportPath(); - logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); + logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString())); //Keep track of the reference numbers that have been parsed. Set referenceNumbersSeen = new HashSet<>(); @@ -129,7 +129,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); } List attributes = new ArrayList<>(); @@ -147,7 +147,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //Find the XRY key on this line. int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER); if (keyDelimiter == -1) { - logger.log(Level.SEVERE, String.format("XRY DSP: Expected a key value " + logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a key value " + "pair on this line (in brackets) [ %s ], but one was not detected." + " Is this the continuation of a previous line?" + " Here is the previous line (in brackets) [ %s ]. " @@ -167,7 +167,7 @@ final class XRYMessagesFileParser implements XRYFileParser { } if (!XRY_KEYS.contains(normalizedKey)) { - logger.log(Level.SEVERE, String.format("XRY DSP: The following key, " + logger.log(Level.SEVERE, String.format("[XRY DSP] The following key, " + "value pair (in brackets, respectively) [ %s ], [ %s ] " + "was not recognized. Discarding... Here is the previous line " + "[ %s ] for context. What does this key mean?", key, value, xryLines[i - 1])); @@ -175,7 +175,7 @@ final class XRYMessagesFileParser implements XRYFileParser { } if (value.isEmpty()) { - logger.log(Level.SEVERE, String.format("XRY DSP: The following key " + logger.log(Level.SEVERE, String.format("[XRY DSP] The following key " + "(in brackets) [ %s ] was recognized, but the value " + "was empty. Discarding... Here is the previous line " + "for context [ %s ]. Is this a continuation of this line? " @@ -186,7 +186,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //Assume text is the only field that can span multiple lines. if (normalizedKey.equals(TEXT_KEY)) { //Build up multiple lines. - for (; i + 1 < xryLines.length + for (; (i + 1) < xryLines.length && !hasKey(xryLines[i + 1]) && !hasNamespace(xryLines[i + 1]); i++) { String continuedValue = xryLines[i + 1].trim(); @@ -198,15 +198,15 @@ final class XRYMessagesFileParser implements XRYFileParser { //Check if there is any segmented text. Min val is used to //signify that no reference number was found. if (referenceNumber != Integer.MIN_VALUE) { - logger.log(Level.INFO, String.format("XRY DSP: Message entity " + logger.log(Level.INFO, String.format("[XRY DSP] Message entity " + "appears to be segmented with reference number [ %d ]", referenceNumber)); if (referenceNumbersSeen.contains(referenceNumber)) { - logger.log(Level.SEVERE, "XRY DSP: This reference has already " + logger.log(Level.SEVERE, String.format("[XRY DSP] This reference [ %d ] has already " + "been seen. This means that the segments are not " + "contiguous. Any segments contiguous with this " + "one will be aggregated and another " - + "(otherwise duplicate) artifact will be created."); + + "(otherwise duplicate) artifact will be created.", referenceNumber)); } referenceNumbersSeen.add(referenceNumber); @@ -269,12 +269,12 @@ final class XRYMessagesFileParser implements XRYFileParser { //Extract the text key from the entity, which is potentially //multi-lined. if (nextEntityLines.length > 0) { - logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ] " + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ] " + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber)); } if (nextSegmentNumber != currentSegmentNumber + 1) { - logger.log(Level.SEVERE, String.format("XRY DSP: Contiguous " + logger.log(Level.SEVERE, String.format("[XRY DSP] Contiguous " + "segments are not ascending incrementally. Encountered " + "segment [ %d ] after segment [ %d ]. This means the reconstructed " + "text will be out of order.", nextSegmentNumber, currentSegmentNumber)); @@ -365,7 +365,7 @@ final class XRYMessagesFileParser implements XRYFileParser { try { return Integer.parseInt(value); } catch (NumberFormatException ex) { - logger.log(Level.SEVERE, String.format("XRY DSP: Value [ %s ] for " + logger.log(Level.SEVERE, String.format("[XRY DSP] Value [ %s ] for " + "meta key [ %s ] was not an integer.", value, metaKey), ex); } } @@ -395,10 +395,10 @@ final class XRYMessagesFileParser implements XRYFileParser { try { String dateTime = removeDateTimeLocale(value); String normalizedDateTime = dateTime.trim(); - long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + long dateTimeInEpoch = calculateSecondsSinceEpoch(normalizedDateTime); return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, PARSER_NAME, dateTimeInEpoch); } catch (DateTimeParseException ex) { - logger.log(Level.SEVERE, String.format("XRY DSP: Assumption " + logger.log(Level.SEVERE, String.format("[XRY DSP] Assumption " + "about the date time formatting of messages is not " + "right. Here is the value [ %s ].", value), ex); return null; @@ -423,7 +423,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //Ignore for now. return null; default: - logger.log(Level.SEVERE, String.format("XRY DSP: Unrecognized " + logger.log(Level.SEVERE, String.format("[XRY DSP] Unrecognized " + "status value [ %s ].", value)); return null; } @@ -439,7 +439,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //Ignore for now. return null; default: - logger.log(Level.SEVERE, String.format("XRY DSP: Unrecognized " + logger.log(Level.SEVERE, String.format("[XRY DSP] Unrecognized " + "type value [ %s ]", value)); return null; } @@ -495,9 +495,9 @@ final class XRYMessagesFileParser implements XRYFileParser { * @param dateTime * @return */ - private long calculateMsSinceEpoch(String dateTime) { + private long calculateSecondsSinceEpoch(String dateTime) { LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); //Assume dates have no offset. - return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + return localDateTime.toInstant(ZoneOffset.UTC).getEpochSecond(); } } From 85d6da4c8f70fc752155dc91b00e74cba4f53b25 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 14:50:53 -0500 Subject: [PATCH 28/41] Removed a redundant 'in brackets' message --- .../autopsy/datasourceprocessors/xry/XRYReportProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java index 2d3bfc110a..f89af3c021 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -53,7 +53,7 @@ class XRYReportProcessor { } else { logger.log(Level.SEVERE, String.format("[XRY DSP] XRY File (in brackets) " + "[ %s ] was found, but no parser to support its report type exists. " - + "Report type is (in brackets) [ %s ]", xryFileReader.getReportPath().toString(), reportType)); + + "Report type is [ %s ]", xryFileReader.getReportPath().toString(), reportType)); } xryFileReader.close(); } From b9044d641686e6dd40c5e66887a155427599d16c Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 15:16:10 -0500 Subject: [PATCH 29/41] Fixed file leak in the event of an exception --- .../xry/XRYReportProcessor.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java index f89af3c021..a256309187 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -35,6 +35,8 @@ class XRYReportProcessor { /** * Processes all XRY Files and creates artifacts on the given Content * instance. + * + * All resources will be closed if an exception is encountered. * * @param folder XRY folder to process * @param parent Content instance to hold newly created artifacts. @@ -45,17 +47,27 @@ class XRYReportProcessor { //Get all XRY file readers from this folder. List xryFileReaders = folder.getXRYFileReaders(); - for (XRYFileReader xryFileReader : xryFileReaders) { - String reportType = xryFileReader.getReportType(); - if (XRYFileParserFactory.supports(reportType)) { - XRYFileParser parser = XRYFileParserFactory.get(reportType); - parser.parse(xryFileReader, parent); - } else { - logger.log(Level.SEVERE, String.format("[XRY DSP] XRY File (in brackets) " - + "[ %s ] was found, but no parser to support its report type exists. " - + "Report type is [ %s ]", xryFileReader.getReportPath().toString(), reportType)); + try { + for (XRYFileReader xryFileReader : xryFileReaders) { + String reportType = xryFileReader.getReportType(); + if (XRYFileParserFactory.supports(reportType)) { + XRYFileParser parser = XRYFileParserFactory.get(reportType); + parser.parse(xryFileReader, parent); + } else { + logger.log(Level.SEVERE, String.format("[XRY DSP] XRY File (in brackets) " + + "[ %s ] was found, but no parser to support its report type exists. " + + "Report type is [ %s ]", xryFileReader.getReportPath().toString(), reportType)); + } + } + } finally { + try { + //Try to close all resources + for (XRYFileReader xryFileReader : xryFileReaders) { + xryFileReader.close(); + } + } catch (IOException ex) { + //Best effort closing all resources. } - xryFileReader.close(); } } } From f4b5ffcaf991b85dc192f91e16b5705ffb00be92 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 15:42:06 -0500 Subject: [PATCH 30/41] Addressed codacy comments --- .../xry/XRYDataSourceProcessorConfigPanel.java | 2 +- .../autopsy/datasourceprocessors/xry/XRYFolder.java | 10 +++++----- .../datasourceprocessors/xry/XRYReportProcessor.java | 12 +++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java index fd03d47787..04ccf31b35 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -26,7 +26,7 @@ import javax.swing.JPanel; * Allows an examiner to configure the XRY Data source processor. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -class XRYDataSourceProcessorConfigPanel extends JPanel { +final class XRYDataSourceProcessorConfigPanel extends JPanel { private static final XRYDataSourceProcessorConfigPanel INSTANCE = new XRYDataSourceProcessorConfigPanel(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index f0d40a8ca8..f78154d796 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -39,10 +39,10 @@ final class XRYFolder { private static final int XRY_FILES_DEPTH = 1; //Raw path to the XRY folder. - private final Path xryFolder; + private final Path xryFolderPath; public XRYFolder(Path folder) { - xryFolder = folder; + xryFolderPath = folder; } /** @@ -54,12 +54,12 @@ final class XRYFolder { * @throws IOException If an I/O error occurs. */ public List getNonXRYFiles() throws IOException { - try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + try (Stream allFiles = Files.walk(xryFolderPath, XRY_FILES_DEPTH)) { List otherFiles = new ArrayList<>(); Iterator allFilesIterator = allFiles.iterator(); while (allFilesIterator.hasNext()) { Path currentPath = allFilesIterator.next(); - if (!currentPath.equals(xryFolder) + if (!currentPath.equals(xryFolderPath) && !XRYFileReader.isXRYFile(currentPath)) { otherFiles.add(currentPath); } @@ -78,7 +78,7 @@ final class XRYFolder { * @throws IOException If an I/O error occurs. */ public List getXRYFileReaders() throws IOException { - try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + try (Stream allFiles = Files.walk(xryFolderPath, XRY_FILES_DEPTH)) { List fileReaders = new ArrayList<>(); Iterator allFilesIterator = allFiles.iterator(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java index a256309187..98ee693101 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -28,14 +28,14 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Processes all XRY files in an XRY folder. */ -class XRYReportProcessor { +final class XRYReportProcessor { private static final Logger logger = Logger.getLogger(XRYReportProcessor.class.getName()); /** * Processes all XRY Files and creates artifacts on the given Content * instance. - * + * * All resources will be closed if an exception is encountered. * * @param folder XRY folder to process @@ -66,8 +66,14 @@ class XRYReportProcessor { xryFileReader.close(); } } catch (IOException ex) { - //Best effort closing all resources. + logger.log(Level.WARNING, "[XRY DSP] Encountered I/O exception trying " + + "to close all xry file readers.", ex); } } } + + //Prevent direct instantiation. + private XRYReportProcessor() { + + } } From 88551a7b2a2d5ad5ddf17f5ac07b5dc8b7c3d2b6 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 14 Nov 2019 15:42:36 -0500 Subject: [PATCH 31/41] 5515 Image gallery service needs to check for deleted data sources --- .../imagegallery/datamodel/DrawableDB.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 2097fea5f3..1a259c0adf 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -386,14 +386,14 @@ public final class DrawableDB { private boolean removeDeletedDataSources() { dbWriteLock(); try (SleuthkitCase.CaseDbQuery caseDbQuery = tskCase.executeQuery("SELECT obj_id FROM data_source_info"); //NON-NLS - Statement statement = con.createStatement()) { + Statement drawablesDbStmt = con.createStatement()) { /* * Get the data source object IDs from the case database. */ ResultSet caseDbResults = caseDbQuery.getResultSet(); - Set validDataSourceObjIDs = new HashSet<>(); + Set currentDataSourceObjIDs = new HashSet<>(); while (caseDbResults.next()) { - validDataSourceObjIDs.add(caseDbResults.getLong(1)); + currentDataSourceObjIDs.add(caseDbResults.getLong(1)); } /* @@ -402,10 +402,10 @@ public final class DrawableDB { * database. */ List staleDataSourceObjIDs = new ArrayList<>(); - try (ResultSet drawablesDbResults = statement.executeQuery("SELECT ds_obj_id FROM datasources")) { //NON-NLS + try (ResultSet drawablesDbResults = drawablesDbStmt.executeQuery("SELECT ds_obj_id FROM datasources")) { //NON-NLS while (drawablesDbResults.next()) { long dataSourceObjID = drawablesDbResults.getLong(1); - if (!validDataSourceObjIDs.contains(dataSourceObjID)) { + if (!currentDataSourceObjIDs.contains(dataSourceObjID)) { staleDataSourceObjIDs.add(dataSourceObjID); } } @@ -416,11 +416,11 @@ public final class DrawableDB { * database. The delete cascades. */ if (!staleDataSourceObjIDs.isEmpty()) { - String inClause = StringUtils.join(staleDataSourceObjIDs, ','); - String delete = "DELETE FROM datasources where ds_obj_id IN (" + inClause + ")"; //NON-NLS - statement.execute(delete); + String deleteCommand = "DELETE FROM datasources where ds_obj_id IN (" + StringUtils.join(staleDataSourceObjIDs, ',') + ")"; //NON-NLS + drawablesDbStmt.execute(deleteCommand); } return true; + } catch (TskCoreException | SQLException ex) { logger.log(Level.SEVERE, "Failed to remove deleted data sources from drawables database", ex); //NON-NLS return false; From 34edd7ec2cc8b0e55ebade83b542305bff9fd5ce Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 14 Nov 2019 15:45:27 -0500 Subject: [PATCH 32/41] 5515 Image gallery service needs to check for deleted data sources --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 1a259c0adf..7bc846fdc0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -420,7 +420,7 @@ public final class DrawableDB { drawablesDbStmt.execute(deleteCommand); } return true; - + } catch (TskCoreException | SQLException ex) { logger.log(Level.SEVERE, "Failed to remove deleted data sources from drawables database", ex); //NON-NLS return false; From bc66d0bd1139bb794961b583af7d8a740cf78e38 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 16:01:47 -0500 Subject: [PATCH 33/41] Added a serial version UID --- .../xry/XRYDataSourceProcessorConfigPanel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java index 04ccf31b35..ac32eeb7ca 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -28,6 +28,7 @@ import javax.swing.JPanel; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class XRYDataSourceProcessorConfigPanel extends JPanel { + private static final long serialVersionUID = 1L; private static final XRYDataSourceProcessorConfigPanel INSTANCE = new XRYDataSourceProcessorConfigPanel(); From 09961c71badd439e7144b9099a1d0d1e8d990c0b Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 16:50:20 -0500 Subject: [PATCH 34/41] Minor fixes that fell out of testing --- .../xry/AbstractSingleKeyValueParser.java | 7 +++---- .../xry/XRYMessagesFileParser.java | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index edd218adbb..08dad04991 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -64,10 +64,11 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { for (int i = 1; i < xryLines.length; i++) { String xryLine = xryLines[i]; + String candidateNamespace = xryLine.trim(); //Check if the line is a namespace, which gives context to the keys //that follow. - if (isNamespace(xryLine)) { - namespace = xryLine.trim(); + if (isNamespace(candidateNamespace)) { + namespace = candidateNamespace; continue; } @@ -157,8 +158,6 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { /** * Makes an artifact from the parsed attributes. - * - * @return */ abstract void makeArtifact(List attributes, Content parent) throws TskCoreException; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java index ac78e62509..820a6ebaad 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -137,9 +137,9 @@ final class XRYMessagesFileParser implements XRYFileParser { String namespace = ""; for (int i = 1; i < xryLines.length; i++) { String xryLine = xryLines[i]; - String normalizedXryLine = xryLine.trim().toLowerCase(); + String candidateNamespace = xryLine.trim().toLowerCase(); - if (XRY_NAMESPACES.contains(normalizedXryLine)) { + if (XRY_NAMESPACES.contains(candidateNamespace)) { namespace = xryLine.trim(); continue; } @@ -273,7 +273,11 @@ final class XRYMessagesFileParser implements XRYFileParser { + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber)); } - if (nextSegmentNumber != currentSegmentNumber + 1) { + if(nextSegmentNumber == Integer.MIN_VALUE) { + logger.log(Level.SEVERE, String.format("[XRY DSP] Segment with reference" + + " number [ %d ] did not have a segment number associated with it." + + " It cannot be determined if the reconstructed text will be in order.", referenceNumber)); + } else if (nextSegmentNumber != currentSegmentNumber + 1) { logger.log(Level.SEVERE, String.format("[XRY DSP] Contiguous " + "segments are not ascending incrementally. Encountered " + "segment [ %d ] after segment [ %d ]. This means the reconstructed " From 7e1755102b0dd782c682246c05d1c1e1a02a3572 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 18:18:01 -0500 Subject: [PATCH 35/41] addressed review comments and fixed bug from jira 5778 --- .../geolocation/CheckBoxListPanel.java | 7 +++ .../autopsy/geolocation/GeoFilterPanel.java | 8 ++- .../geolocation/GeolocationTopComponent.java | 59 ++++--------------- .../autopsy/geolocation/HidingPane.java | 19 +++++- .../autopsy/geolocation/VerticalLabelUI.java | 20 ++++++- .../geolocation/datamodel/Waypoint.java | 5 -- .../datamodel/WaypointBuilder.java | 8 +-- 7 files changed, 60 insertions(+), 66 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java index b91acf6fd4..37dea163f5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java @@ -54,6 +54,13 @@ final class CheckBoxListPanel extends javax.swing.JPanel { void addElement(String displayName, T obj) { model.addElement(new ObjectCheckBox<>(displayName, true, obj)); } + + /** + * Remove all objects from the checkbox list. + */ + void clearList() { + model.removeAllElements(); + } /** * Returns a list of all of the selected elements. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java index 39109d2c83..9e009d1245 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -72,8 +72,10 @@ class GeoFilterPanel extends javax.swing.JPanel { gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15); add(checkboxPanel, gridBagConstraints); - - try { + } + + void updateDataSourceList() { + try { initCheckboxList(); } catch (TskCoreException ex) { logger.log(Level.WARNING, "Failed to initialize the CheckboxListPane", ex); //NON-NLS @@ -119,6 +121,8 @@ class GeoFilterPanel extends javax.swing.JPanel { private void initCheckboxList() throws TskCoreException { final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + checkboxPanel.clearList(); + for (DataSource dataSource : sleuthkitCase.getDataSources()) { String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); checkboxPanel.addElement(dsName, dataSource); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 05cd3afbbd..e1556dc27a 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -25,11 +25,9 @@ import java.beans.PropertyChangeListener; import java.util.EnumSet; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; @@ -37,7 +35,6 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.geolocation.GeoFilterPanel.GeoFilter; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; @@ -81,7 +78,7 @@ public final class GeolocationTopComponent extends TopComponent { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public GeolocationTopComponent() { initComponents(); - initWaypoints(); + setName(Bundle.GLTopComponent_name()); this.ingestListener = pce -> { @@ -113,7 +110,7 @@ public final class GeolocationTopComponent extends TopComponent { @Override public void actionPerformed(ActionEvent e) { mapPanel.clearWaypoints(); - initWaypoints(); + updateWaypoints(); showRefreshPanel(false); } }); @@ -123,10 +120,9 @@ public final class GeolocationTopComponent extends TopComponent { geoFilterPanel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - filterWaypoints(); + updateWaypoints(); } }); - } @Override @@ -136,7 +132,7 @@ public final class GeolocationTopComponent extends TopComponent { Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { mapPanel.clearWaypoints(); if (evt.getNewValue() != null) { - initWaypoints(); + updateWaypoints(); } }); } @@ -152,6 +148,13 @@ public final class GeolocationTopComponent extends TopComponent { super.componentOpened(); WindowManager.getDefault().setTopComponentFloating(this, true); } + + @Override + public void open() { + super.open(); + geoFilterPanel.updateDataSourceList(); + updateWaypoints(); + } /** * Set the state of the refresh panel at the top of the mapPanel. @@ -167,44 +170,6 @@ public final class GeolocationTopComponent extends TopComponent { mapPanel.revalidate(); } - /** - * Use a SwingWorker thread to get a list of waypoints. - */ - private void initWaypoints() { - SwingWorker, MapWaypoint> worker = new SwingWorker, MapWaypoint>() { - @Override - protected List doInBackground() throws Exception { - Case currentCase = Case.getCurrentCaseThrows(); - return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase()); - } - - @Override - protected void done() { - if (isDone() && !isCancelled()) { - try { - List waypoints = get(); - if (waypoints == null || waypoints.isEmpty()) { - return; - } - mapPanel.setWaypoints(waypoints); - - // There might be a better way to decide how to center - // but for now just use the first way point. - mapPanel.setCenterLocation(waypoints.get(0)); - - } catch (ExecutionException ex) { - logger.log(Level.WARNING, "An exception occured while initializing waypoints for geolocation window.", ex); //NON-NLS - MessageNotifyUtil.Message.error(Bundle.GLTopComponent_initilzation_error()); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, "The initializing thread for geolocation window was interrupted.", ex); //NON-NLS - } - } - } - }; - - worker.execute(); - } - /** * Filters the list of waypoints based on the user selections in the filter * pane. @@ -217,7 +182,7 @@ public final class GeolocationTopComponent extends TopComponent { "GeoTopComponent_filer_data_invalid_msg=Unable to run waypoint filter.\nPlease select one or more data sources.", "GeoTopComponent_filer_data_invalid_Title=Filter Failure" }) - private void filterWaypoints() { + private void updateWaypoints() { GeoFilter filters; // Show a warning message if the user has not selected a data source diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java index ca2afea1a4..e842e5df86 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.geolocation; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java index 2580031dfb..1a76953267 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java @@ -1,7 +1,21 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.geolocation; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 428c659eca..27e5da0dbd 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -25,12 +25,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -68,8 +65,6 @@ public class Waypoint { BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,}; - private static final Logger logger = Logger.getLogger(Waypoint.class.getName()); - /** * Construct a waypoint with the given artifact. * diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index 0e29ca9dd2..7189302809 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -353,15 +353,11 @@ public final class WaypointBuilder { * @return SQL SELECT statement */ static private String buildQueryForWaypointsWOTimeStamps(List dataSources) { - String query = ""; - - query = String.format(SELECT_WO_TIMESTAMP, + return String.format(SELECT_WO_TIMESTAMP, String.format(GEO_ARTIFACT_QUERY_ID_ONLY, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()), getWaypointListQuery(dataSources)); - - return query; } /** @@ -408,7 +404,7 @@ public final class WaypointBuilder { query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS query += mostRecentQuery; - if (noTimeStamp) { + if (showAll || noTimeStamp) { query = String.format("%s UNION %s", buildQueryForWaypointsWOTimeStamps(dataSources), query); //NON-NLS } From 5085d3de5a1776dec1830fb23946a3412ea6e91d Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 15 Nov 2019 11:36:50 -0500 Subject: [PATCH 36/41] Make progress bar indeterminate --- .../autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java index 04a8d3a7a3..acb12a6f46 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -96,6 +96,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { "XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder" }) public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + progressMonitor.setIndeterminate(true); progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_testingFolder()); String selectedFilePath = configPanel.getSelectedFilePath(); From 858f9fab9d6da058b14f67322850e81024cab718 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 15 Nov 2019 13:06:45 -0500 Subject: [PATCH 37/41] Addressed review comments --- .../xry/Bundle.properties | 1 + .../xry/Bundle.properties-MERGED | 5 +- .../xry/XRYDataSourceProcessor.java | 86 ++++++++++--------- .../XRYDataSourceProcessorConfigPanel.form | 15 +++- .../XRYDataSourceProcessorConfigPanel.java | 43 +++++++++- 5 files changed, 105 insertions(+), 45 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties index 998296e54f..05c5199962 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties @@ -1,3 +1,4 @@ XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse XRYDataSourceProcessorConfigPanel.filePathTextField.text= XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder +XRYDataSourceProcessorConfigPanel.errorLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED index 7591e1fa21..f8d924a1a2 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED @@ -1,12 +1,13 @@ -XRYDataSourceProcessor.dataSourceType=Import Tool Report +XRYDataSourceProcessor.dataSourceType=XRY Logical Report XRYDataSourceProcessor.fileAdded=Added %s to the case database XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder +XRYDataSourceProcessor.noPathSelected=Please select a XRY folder XRYDataSourceProcessor.notReadable=Could not read from the selected folder XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files XRYDataSourceProcessor.preppingFiles=Preparing to add files to the case database XRYDataSourceProcessor.processingFiles=Processing all XRY files... -XRYDataSourceProcessor.testingFolder=Testing input folder... XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse XRYDataSourceProcessorConfigPanel.filePathTextField.text= XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder +XRYDataSourceProcessorConfigPanel.errorLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java index acb12a6f46..082f098a61 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -54,7 +54,7 @@ import org.sleuthkit.datamodel.TskDataException; public class XRYDataSourceProcessor implements DataSourceProcessor { private final XRYDataSourceProcessorConfigPanel configPanel; - + //Background processor to relieve the EDT from adding files to the case //database and parsing the report files. private XRYReportProcessorSwingWorker swingWorker; @@ -67,7 +67,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { @Override @NbBundle.Messages({ - "XRYDataSourceProcessor.dataSourceType=Import Tool Report" + "XRYDataSourceProcessor.dataSourceType=XRY Logical Report" }) public String getDataSourceType() { return Bundle.XRYDataSourceProcessor_dataSourceType(); @@ -78,60 +78,63 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { return configPanel; } - @Override - public boolean isPanelValid() { - return true; - } - - /** - * Processes the XRY folder the examiner selected. The heavy lifting - * is handed off to a dedicated thread. This function will - * test the minimum requirements needed to successfully process the input. - */ @Override @NbBundle.Messages({ - "XRYDataSourceProcessor.testingFolder=Testing input folder...", + "XRYDataSourceProcessor.noPathSelected=Please select a XRY folder", "XRYDataSourceProcessor.notReadable=Could not read from the selected folder", "XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files", "XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder" }) - public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - progressMonitor.setIndeterminate(true); - progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_testingFolder()); - + public boolean isPanelValid() { + configPanel.clearErrorText(); String selectedFilePath = configPanel.getSelectedFilePath(); + if(selectedFilePath.isEmpty()) { + configPanel.setErrorText(Bundle.XRYDataSourceProcessor_noPathSelected()); + return false; + } + File selectedFile = new File(selectedFilePath); Path selectedPath = selectedFile.toPath(); //Test permissions if (!Files.isReadable(selectedPath)) { - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, - Lists.newArrayList(Bundle.XRYDataSourceProcessor_notReadable()), - Lists.newArrayList()); - return; + configPanel.setErrorText(Bundle.XRYDataSourceProcessor_notReadable()); + return false; } try { //Validate the folder. if (!XRYFolder.isXRYFolder(selectedPath)) { - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, - Lists.newArrayList(Bundle.XRYDataSourceProcessor_notXRYFolder()), - Lists.newArrayList()); - return; + configPanel.setErrorText(Bundle.XRYDataSourceProcessor_notXRYFolder()); + return false; } } catch (IOException ex) { + configPanel.setErrorText(Bundle.XRYDataSourceProcessor_ioError()); logger.log(Level.WARNING, "[XRY DSP] I/O exception encountered trying to test the XRY folder.", ex); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, - Lists.newArrayList(Bundle.XRYDataSourceProcessor_ioError(), ex.toString()), Lists.newArrayList()); - return; + return false; } + return true; + } + + /** + * Processes the XRY folder that the examiner selected. The heavy lifting is + * done off of the EDT. + */ + @Override + public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + progressMonitor.setIndeterminate(true); + + String selectedFilePath = configPanel.getSelectedFilePath(); + File selectedFile = new File(selectedFilePath); + Path selectedPath = selectedFile.toPath(); + try { XRYFolder xryFolder = new XRYFolder(selectedPath); FileManager fileManager = Case.getCurrentCaseThrows() .getServices().getFileManager(); - - //Move heavy lifting to a dedicated thread. + + //Move heavy lifting to a backround task. swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor, callback, fileManager); swingWorker.execute(); @@ -154,8 +157,8 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { } /** - * Relieves the EDT from add images to the case database and processing the - * XRY report files. + * Relieves the EDT from having to process the XRY report and write to the + * case database. */ private class XRYReportProcessorSwingWorker extends SwingWorker { @@ -177,10 +180,10 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { "XRYDataSourceProcessor.preppingFiles=Preparing to add files to the case database", "XRYDataSourceProcessor.processingFiles=Processing all XRY files..." }) - protected LocalFilesDataSource doInBackground() throws TskCoreException, + protected LocalFilesDataSource doInBackground() throws TskCoreException, TskDataException, IOException { progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_preppingFiles()); - + List nonXRYFiles = xryFolder.getNonXRYFiles(); List filePaths = nonXRYFiles.stream() //Map paths to string representations. @@ -188,10 +191,10 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { .collect(Collectors.toList()); String uniqueUUID = UUID.randomUUID().toString(); LocalFilesDataSource dataSource = fileManager.addLocalFilesDataSource( - uniqueUUID, + uniqueUUID, "XRY Report", //Name "", //Timezone - filePaths, + filePaths, new ProgressMonitorAdapter(progressMonitor)); //Process the report files. @@ -199,7 +202,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { XRYReportProcessor.process(xryFolder, dataSource); return dataSource; } - + @Override @NbBundle.Messages({ "XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report" @@ -207,14 +210,15 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { public void done() { try { LocalFilesDataSource newDataSource = get(); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, Lists.newArrayList(), Lists.newArrayList(newDataSource)); } catch (InterruptedException ex) { - //DSP was cancelled. Not an error. + logger.log(Level.WARNING, "[XRY DSP] Thread was interrupted while processing the XRY report." + + " The case may or may not have the complete XRY report.", ex); } catch (ExecutionException ex) { logger.log(Level.SEVERE, "[XRY DSP] Unexpected internal error while processing XRY report.", ex); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, - Lists.newArrayList(Bundle.XRYDataSourceProcessor_unexpectedError(), + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_unexpectedError(), ex.toString()), Lists.newArrayList()); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form index b78c62357e..2fd4848e91 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form @@ -19,6 +19,7 @@ + @@ -40,7 +41,9 @@ - + + + @@ -71,5 +74,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java index ac32eeb7ca..c35666d9c1 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -18,9 +18,12 @@ */ package org.sleuthkit.autopsy.datasourceprocessors.xry; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.File; import javax.swing.JFileChooser; import javax.swing.JPanel; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; /** * Allows an examiner to configure the XRY Data source processor. @@ -32,12 +35,16 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { private static final XRYDataSourceProcessorConfigPanel INSTANCE = new XRYDataSourceProcessorConfigPanel(); + //Communicates + private final PropertyChangeSupport pcs; + /** * Creates new form XRYDataSourceConfigPanel. * Prevent direct instantiation. */ private XRYDataSourceProcessorConfigPanel() { initComponents(); + pcs = new PropertyChangeSupport(this); } /** @@ -47,6 +54,20 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { return INSTANCE; } + /** + * Clears the error label. + */ + void clearErrorText() { + errorLabel.setText(null); + } + + /** + * Sets the error label to show the supplied text. + */ + void setErrorText(String text) { + errorLabel.setText(text); + } + /** * Clears the selected file path. */ @@ -60,6 +81,15 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { String getSelectedFilePath() { return filePathTextField.getText(); } + + /** + * Adds a property change listener to this config panel. + */ + @Override + public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) { + super.addPropertyChangeListener(pcl); + pcs.addPropertyChangeListener(pcl); + } /** * This method is called from within the constructor to initialize the form. @@ -73,6 +103,7 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { filePathTextField = new javax.swing.JTextField(); fileBrowserButton = new javax.swing.JButton(); xrySelectFolderLabel = new javax.swing.JLabel(); + errorLabel = new javax.swing.JLabel(); filePathTextField.setEditable(false); filePathTextField.setText(org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.filePathTextField.text")); // NOI18N @@ -86,6 +117,9 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(xrySelectFolderLabel, org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text")); // NOI18N + errorLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.errorLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -93,6 +127,7 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(xrySelectFolderLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addComponent(filePathTextField) @@ -109,7 +144,9 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(filePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(fileBrowserButton)) - .addContainerGap(246, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(errorLabel) + .addContainerGap(235, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -125,11 +162,15 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { if (returnVal == JFileChooser.APPROVE_OPTION) { File selection = fileChooser.getSelectedFile(); filePathTextField.setText(selection.getAbsolutePath()); + + //This will notify the wizard to revalidate the data source processor. + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } }//GEN-LAST:event_fileBrowserButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel errorLabel; private javax.swing.JButton fileBrowserButton; private javax.swing.JTextField filePathTextField; private javax.swing.JLabel xrySelectFolderLabel; From d0592d62c8589f625e6226d16739263b44c911a3 Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 18 Nov 2019 14:27:24 -0500 Subject: [PATCH 38/41] Switch to BellSoft Java distribution and add troubleshooting section. --- Running_Linux_OSX.txt | 56 +++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/Running_Linux_OSX.txt b/Running_Linux_OSX.txt index bb60569a56..0ddf25b1f3 100644 --- a/Running_Linux_OSX.txt +++ b/Running_Linux_OSX.txt @@ -9,33 +9,41 @@ The following need to be done at least once. They do not need to be repeated for -- Linux: % sudo apt-get install testdisk -- OS X: % brew install testdisk -- Install a Java 8 JRE and JavaFX 8 and set JAVA_HOME. --- Linux: Any Java 8 version of OpenJDK/OpenJFX distribution should suffice. The following instructions use the Zulu Community distribution. - 1. Download a 64 bit Java 8 JRE for your specific platform from https://www.azul.com/downloads/zulu-community - 2. Install the JRE. e.g. % sudo apt install ./zulu8.40.0.25-ca-jre8.0.222-linux_amd64.deb - 3. Download a 64 bit Java 8 JavaFX for your specific platform from the same location. - - Note that you may need to select "Older Zulu versions" for FX to become available in the "Java Package" dropdown. - 4. Extract the contents of the JavaFX archive into the folder where the JRE was installed. - e.g. % sudo tar xzf ~/Downloads/zulu8.40.0.25-ca-fx-jre8.0.222-linux_x64.tar.gz -C /usr/lib/jvm/zre-8-amd64 --strip-components=1 - +- Install the BellSoft Java 8 JRE and JavaFX 8 distribution and set JAVA_HOME. + * The BellSoft distribution bundles OpenJDK and OpenJFX. Other distributions we have tried either don't + bundle OpenJFX (AdoptOpenJDK) or don't include all necessary binaries (Amazon Corretto). +-- Linux: + 1. Install BellSoft Java 8 + % wget -q -O - https://download.bell-sw.com/pki/GPG-KEY-bellsoft | sudo apt-key add - + % echo "deb [arch=amd64] https://apt.bell-sw.com/ stable main" | sudo tee /etc/apt/sources.list.d/bellsoft.list + % sudo apt-get update + % sudo apt-get install bellsoft-java8 + 2. Set JAVA_HOME + % export JAVA_HOME=/usr/lib/jvm/bellsoft-java8-amd64 + NOTE: You may need to log out and back in again after setting JAVA_HOME before the Autopsy unix_setup.sh script can see the value. --- OS X: Any Java 8 version of OpenJDK/OpenJFX distribution should suffice. The following instructions use the AdoptOpenJDK distribution. - 1. Install a 64 bit Java 8 JRE. - % brew cask install adoptopenjdk8 +-- OS X: + 1. Install BellSoft Java 8. + % brew tap bell-sw/liberica + % brew cask install liberica-jdk8 2. Set JAVA_HOME environment variable to location of JRE installation. e.g. add the following to ~/.bashrc export JAVA_HOME=$(/usr/libexec/java_home -v 1.8) - 3. Confirm your version of Java by running - % java -version +- Confirm your version of Java by running + % java -version + openjdk version "1.8.0.232" + OpenJDK Runtime Environment (build 1.8.0_232-BellSoft-b10) + OpenJDK 64-Bit Server VM (build 25.232-b10, mixed mode) + * Install The Sleuth Kit Java Bindings * Autopsy depends on a specific version of The Sleuth Kit. You need the Java libraries of The Sleuth Kit installed, which is not part of all packages. - Linux: Install the sleuthkit-java.deb file that you can download from github.com/sleuthkit/sleuthkit/releases. This will install libewf, etc. --- % sudo apt install ./sleuthkit-java_4.6.0-1_amd64.deb +-- % sudo apt install ./sleuthkit-java_4.7.0-1_amd64.deb - OS X: Install The Sleuth Kit from brew. -- % brew install sleuthkit @@ -55,6 +63,24 @@ Autopsy depends on a specific version of The Sleuth Kit. You need the Java libr - Run Autopsy % ./autopsy +* Troubleshooting * + +- If you see something like "Cannot create case: javafx/scene/paint/Color" it is an indication that Java FX + is not being found. + Confirm that the file $JAVA_HOME/jre/lib/ext/jfxrt.jar exists. If it does not exist, return the the Java + setup steps above. +- If you see something like "An illegal reflective access operation has occurred" it is an indication that + the wrong version of Java is being used to run Autopsy. + Check the version of Java reported in the ~/.autopsy/dev/var/log/messages.log file. It should contain lines that looks like: + Java; VM; Vendor = 1.8.0_232; OpenJDK 64-Bit Server V 25.232-b10; BellSoft + Runtime = OpenJDK Runtime Environment 1.8.0_232-BellSoft-b10 + Java Home = /usr/lib/jvm/bellsoft-java8-amd64/jre + + If your messages.log file indicates that Java 8 is not being used: + (a) confirm that you have a version of Java 8 installed and + (b) confirm that your JAVA_HOME environment variable is set correctly: + % echo $JAVA_HOME + * Limitations (Updated May 2018) * - Timeline does not work on OS X - Video thumbnails are not generated (need to get a consistent version of OpenCV) From 54edfd6c30bf079dcf16b1a6636aea2b781fb350 Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 18 Nov 2019 14:30:56 -0500 Subject: [PATCH 39/41] Fix typo. --- Running_Linux_OSX.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Running_Linux_OSX.txt b/Running_Linux_OSX.txt index 0ddf25b1f3..c47dc5b418 100644 --- a/Running_Linux_OSX.txt +++ b/Running_Linux_OSX.txt @@ -67,7 +67,7 @@ Autopsy depends on a specific version of The Sleuth Kit. You need the Java libr - If you see something like "Cannot create case: javafx/scene/paint/Color" it is an indication that Java FX is not being found. - Confirm that the file $JAVA_HOME/jre/lib/ext/jfxrt.jar exists. If it does not exist, return the the Java + Confirm that the file $JAVA_HOME/jre/lib/ext/jfxrt.jar exists. If it does not exist, return to the Java setup steps above. - If you see something like "An illegal reflective access operation has occurred" it is an indication that the wrong version of Java is being used to run Autopsy. From 6d336f5bde05767d8a3b017b78dd545e4eb1e699 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 18 Nov 2019 17:38:07 -0500 Subject: [PATCH 40/41] Update fbmessenger.py Add attachments from the database to the message --- InternalPythonModules/android/fbmessenger.py | 73 +++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/InternalPythonModules/android/fbmessenger.py b/InternalPythonModules/android/fbmessenger.py index 2347144f09..2145d9aa4e 100644 --- a/InternalPythonModules/android/fbmessenger.py +++ b/InternalPythonModules/android/fbmessenger.py @@ -17,6 +17,11 @@ See the License for the specific language governing permissions and limitations under the License. """ +import json +import traceback +import general +import ast + from java.io import File from java.lang import Class from java.lang import ClassNotFoundException @@ -43,14 +48,13 @@ from org.sleuthkit.datamodel import TskCoreException from org.sleuthkit.datamodel.Blackboard import BlackboardException from org.sleuthkit.datamodel import Account from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper +from org.sleuthkit.datamodel.blackboardutils import MessageAttachments +from org.sleuthkit.datamodel.blackboardutils import URLAttachment +from org.sleuthkit.datamodel.blackboardutils import FileAttachment from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CallMediaType -import json -import traceback -import general - class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): @@ -95,6 +99,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): * have no text, * admin_text_thread_rtc_event has the specific event "group-call-started", "group-call_ended" + --- A pending_send_media_attachment - a JSON structure that has details of attachments that may or may not have been sent. --- A admin_text_thread_rtc_event column - has specific text events such as- "one-on-one-call-ended" --- A thread_key column - identifies the message thread --- A timestamp_ms column - date/time message was sent @@ -210,9 +215,20 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): else: direction = CommunicationDirection.INCOMING return direction + + ## Get the arrayList from the json passed in + def getJPGListFromJson(self, jpgJson): + jpgArray = ArrayList() + # The urls attachment will come across as unicode unless we use ast.literal_eval to change it to a dictionary + jpgDict = ast.literal_eval(jpgJson) + for jpgPreview in jpgDict.iterkeys(): + # Need to use ast.literal_eval so that the string can be converted to a dictionary + jpgUrlDict = ast.literal_eval(jpgDict[jpgPreview]) + jpgArray.add(URLAttachment(jpgUrlDict["src"])) + return jpgArray ## Analyzes messages - def analyzeMessages(self, threadsDb, threadsDBHelper): + def analyzeMessages(self, threadsDb, threadsDBHelper, dataSource): try: ## Messages are found in the messages table. @@ -223,7 +239,8 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): ## The result set is processed to collect the multiple recipients for a given message. sqlString = """ SELECT msg_id, text, sender, timestamp_ms, msg_type, messages.thread_key as thread_key, - snippet, thread_participants.user_key as user_key, thread_users.name as name + snippet, thread_participants.user_key as user_key, thread_users.name as name, + attachments, pending_send_media_attachment FROM messages JOIN thread_participants ON messages.thread_key = thread_participants.thread_key JOIN thread_users ON thread_participants.user_key = thread_users.user_key @@ -241,6 +258,8 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): timeStamp = -1 msgText = "" threadId = "" + messageAttachments = None + currentCase = Case.getCurrentCaseThrows() while messagesResultSet.next(): msgId = messagesResultSet.getString("msg_id") @@ -260,6 +279,10 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): msgText, threadId) + if (messageAttachments is not None): + threadsDBHelper.addAttachments(messageArtifact, messageAttachments) + messageAttachments = None + oldMsgId = msgId # New message - collect all attributes @@ -282,8 +305,42 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): if not msgText: msgText = messagesResultSet.getString("snippet") - # TBD: get attachment + # Get attachments and pending attachments if they exist + attachment = messagesResultSet.getString("attachments") + pendingAttachment = messagesResultSet.getString("pending_send_media_attachment") + + urlAttachments = ArrayList() + fileAttachments = ArrayList() + + if ((attachment is not None) or (pendingAttachment is not None)): + if (attachment is not None): + attachmentDict = json.loads(attachment)[0] + if (attachmentDict["mime_type"] == "image/jpeg"): + urlAttachments = self.getJPGListFromJson(attachmentDict["urls"]) + elif (attachmentDict["mime_type"] == "video/mp4"): + # filename does not have an associated path with it so it will be ignored + urlAttachments = self.getJPGListFromJson(attachmentDict["urls"]) + urlAttachments.add(URLAttachment(attachmentDict["video_data_url"])) + urlAttachments.add(URLAttachment(attachmentDict["video_data_thumbnail_url"])) + + elif (attachmentDict["mime_type"] == "audio/mpeg"): + if (attachmentDict["audio_uri"] == ""): + continue + else: + audioUri = attachmentDict["audio_uri"] + fileAttachments.add(FileAttachment(currentCase.getSleuthkitCase(), threadsDb.getDBFile().getDataSource(), audioUri.replace("file://",""))) + + else: + self._logger.log(Level.INFO, "Attachment type not handled: " + attachmentDict["mime_type"]) + + if (pendingAttachment is not None): + pendingAttachmentDict = json.loads(pendingAttachment)[0] + pendingAttachmentUri = pendingAttachmentDict["uri"] + fileAttachments.add(FileAttachment(currentCase.getSleuthkitCase(), threadsDb.getDBFile().getDataSource(), pendingAttachmentUri.replace("file://",""))) + + messageAttachments = MessageAttachments(fileAttachments, urlAttachments) + threadId = messagesResultSet.getString("thread_key") else: # same msgId as last, just collect recipient from current row @@ -430,7 +487,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): self._MODULE_NAME, threadsDb.getDBFile(), Account.Type.FACEBOOK) - self.analyzeMessages(threadsDb, threadsDBHelper) + self.analyzeMessages(threadsDb, threadsDBHelper, dataSource) self.analyzeCallLogs(threadsDb, threadsDBHelper) except TskCoreException as ex: From 51c84638a841c44d606ef7e0fc16b8360a997054 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 18 Nov 2019 21:54:21 -0500 Subject: [PATCH 41/41] Update fbmessenger.py Remove datasource from analyzeMessages --- InternalPythonModules/android/fbmessenger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InternalPythonModules/android/fbmessenger.py b/InternalPythonModules/android/fbmessenger.py index 2145d9aa4e..c19ae87796 100644 --- a/InternalPythonModules/android/fbmessenger.py +++ b/InternalPythonModules/android/fbmessenger.py @@ -228,7 +228,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): return jpgArray ## Analyzes messages - def analyzeMessages(self, threadsDb, threadsDBHelper, dataSource): + def analyzeMessages(self, threadsDb, threadsDBHelper): try: ## Messages are found in the messages table. @@ -487,7 +487,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): self._MODULE_NAME, threadsDb.getDBFile(), Account.Type.FACEBOOK) - self.analyzeMessages(threadsDb, threadsDBHelper, dataSource) + self.analyzeMessages(threadsDb, threadsDBHelper) self.analyzeCallLogs(threadsDb, threadsDBHelper) except TskCoreException as ex: