Merge remote-tracking branch 'upstream/develop' into data-src-deletion

This commit is contained in:
Richard Cordovano
2019-10-23 15:57:23 -04:00
15 changed files with 468 additions and 275 deletions
+1 -1
View File
@@ -137,7 +137,7 @@
</target>
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist,getTestDataFiles">
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist">
<mkdir dir="${ext.dir}"/>
<copy file="${thirdparty.dir}/LICENSE-2.0.txt" todir="${ext.dir}" />
<!-- fetch all the dependencies from Ivy and stick them in the right places -->
@@ -162,6 +162,8 @@ class CaseInformationPanel extends javax.swing.JPanel {
editCasePropertiesDialog.setResizable(true);
editCasePropertiesDialog.pack();
editCasePropertiesDialog.setLocationRelativeTo(this);
// Workaround to ensure dialog is not hidden on macOS
editCasePropertiesDialog.setAlwaysOnTop(true);
editCasePropertiesDialog.setVisible(true);
editCasePropertiesDialog.toFront();
caseDetailsPanel.updateCaseInfo();
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.casemodule;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -84,10 +85,17 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.setFileFilter(caseMetadataFileFilter);
if (null != ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE)) {
fileChooser.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); //NON-NLS
}
/**
* If the open multi user case dialog is open make sure it's not set
* to always be on top as this hides the file chooser on macOS.
*/
OpenMultiUserCaseDialog multiUserCaseDialog = OpenMultiUserCaseDialog.getInstance();
multiUserCaseDialog.setAlwaysOnTop(false);
String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title");
String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning");
if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) {
@@ -95,7 +103,12 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
* Pop up a file chooser to allow the user to select a case metadata
* file (.aut file).
*/
int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow());
/**
* The parent of the fileChooser will either be the multi user
* case dialog or the startup window.
*/
int retval = fileChooser.showOpenDialog(multiUserCaseDialog.isVisible()
? multiUserCaseDialog : (Component) StartupWindowProvider.getInstance().getStartupWindow());
if (retval == JFileChooser.APPROVE_OPTION) {
/*
* Close the startup window, if it is open.
@@ -105,7 +118,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
/*
* Close the Open Multi-User Case window, if it is open.
*/
OpenMultiUserCaseDialog.getInstance().setVisible(false);
multiUserCaseDialog.setVisible(false);
/*
* Try to open the case associated with the case metadata file
@@ -159,6 +172,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action
OpenMultiUserCaseDialog multiUserCaseWindow = OpenMultiUserCaseDialog.getInstance();
multiUserCaseWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
// Workaround to ensure that dialog is not hidden on macOS.
multiUserCaseWindow.setAlwaysOnTop(true);
multiUserCaseWindow.setVisible(true);
WindowManager.getDefault().getMainWindow().setCursor(null);
@@ -249,6 +249,8 @@ public class CueBannerPanel extends javax.swing.JPanel {
private void openRecentCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openRecentCaseButtonActionPerformed
recentCasesWindow.setLocationRelativeTo(this);
OpenRecentCasePanel.getInstance(); //refreshes the recent cases table
// Workaround to ensure that dialog is not hidden on macOS.
recentCasesWindow.setAlwaysOnTop(true);
recentCasesWindow.setVisible(true);
}//GEN-LAST:event_openRecentCaseButtonActionPerformed
@@ -71,6 +71,8 @@ final class NewCaseWizardAction extends CallableSystemAction {
wizardDescriptor.setTitleFormat(new MessageFormat("{0}"));
wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text"));
Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor);
// Workaround to ensure new case dialog is not hidden on macOS
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
dialog.toFront();
if (wizardDescriptor.getValue() == WizardDescriptor.FINISH_OPTION) {
@@ -144,4 +144,13 @@ public class StartupWindowProvider implements StartupWindowInterface {
startupWindowToUse.close();
}
}
/**
* Get the chosen startup window.
*
* @return The startup window.
*/
public StartupWindowInterface getStartupWindow() {
return startupWindowToUse;
}
}
@@ -16,8 +16,8 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
@@ -106,6 +106,9 @@
<Dimension value="[200, 21]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_InitCodePost" type="java.lang.String" value="progressSlider.setUI(new CircularJSliderUI(progressSlider, new CircularJSliderConfiguration(new Dimension(18,18))));"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="progressLabel">
<Properties>
@@ -172,7 +175,7 @@
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="3" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="8" ipadY="7" insetsTop="6" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="3" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="8" ipadY="7" insetsTop="6" insetsLeft="14" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@@ -192,6 +195,9 @@
<Dimension value="[200, 21]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_InitCodePost" type="java.lang.String" value="audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15))));"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="4" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="-116" ipadY="7" insetsTop="3" insetsLeft="1" insetsBottom="0" insetsRight="10" anchor="18" weightX="0.0" weightY="0.0"/>
@@ -19,8 +19,16 @@
package org.sleuthkit.autopsy.contentviewers;
import com.google.common.io.Files;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -30,6 +38,7 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.swing.BoxLayout;
@@ -52,8 +61,12 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskData;
import javafx.embed.swing.JFXPanel;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicSliderUI;
import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
import org.freedesktop.gstreamer.ClockTime;
import org.freedesktop.gstreamer.Format;
import org.freedesktop.gstreamer.GstException;
@@ -189,12 +202,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
private ExtractMedia extractMediaWorker;
//Serialize setting the value of the Video progress slider.
//The slider is a shared resource between the VideoPanelUpdater
//and the TrackListener of the JSliderUI.
private final Semaphore sliderLock;
/**
* Creates new form MediaViewVideoPanel
*/
public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError {
initComponents();
customizeComponents();
//True for fairness. In other words,
//acquire() calls are processed in order of invocation.
sliderLock = new Semaphore(1, true);
}
private void customizeComponents() {
@@ -532,6 +553,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
public void actionPerformed(ActionEvent e) {
if (!progressSlider.getValueIsAdjusting()) {
sliderLock.acquireUninterruptibly();
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
/**
@@ -547,6 +569,210 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
SwingUtilities.invokeLater(() -> {
updateTimeLabel(position, duration);
});
sliderLock.release();
}
}
}
/**
* Represents the default configuration for the circular JSliderUI.
*/
private class CircularJSliderConfiguration {
//Thumb configurations
private final Color thumbColor;
private final Dimension thumbDimension;
//Track configurations
//Progress bar can be bisected into a seen group
//and an unseen group.
private final Color unseen;
private final Color seen;
/**
* Default configuration
*
* JSlider is light blue RGB(0,130,255). Seen track is light blue
* RGB(0,130,255). Unseen track is light grey RGB(192, 192, 192).
*
* @param thumbDimension Size of the oval thumb.
*/
public CircularJSliderConfiguration(Dimension thumbDimension) {
Color lightBlue = new Color(0, 130, 255);
seen = lightBlue;
unseen = Color.LIGHT_GRAY;
thumbColor = lightBlue;
this.thumbDimension = new Dimension(thumbDimension);
}
public Color getThumbColor() {
return thumbColor;
}
public Color getUnseenTrackColor() {
return unseen;
}
public Color getSeenTrackColor() {
return seen;
}
public Dimension getThumbDimension() {
return new Dimension(thumbDimension);
}
}
/**
* Custom view for the JSlider.
*/
private class CircularJSliderUI extends BasicSliderUI {
private final CircularJSliderConfiguration config;
/**
* Creates a custom view for the JSlider. This view draws a blue oval
* thumb at the given width and height. It also paints the track blue as
* the thumb progresses.
*
* @param b JSlider component
* @param config Configuration object. Contains info about thumb
* dimensions and colors.
*/
public CircularJSliderUI(JSlider slider, CircularJSliderConfiguration config) {
super(slider);
this.config = config;
}
@Override
protected Dimension getThumbSize() {
return config.getThumbDimension();
}
/**
* Modifies the View to be an oval rather than the rectangle Controller.
*/
@Override
public void paintThumb(Graphics graphic) {
Rectangle thumb = this.thumbRect;
Color original = graphic.getColor();
//Change the thumb view from the rectangle
//controller to an oval.
graphic.setColor(config.getThumbColor());
Dimension thumbDimension = config.getThumbDimension();
graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height);
//Preserve the graphics original color
graphic.setColor(original);
}
@Override
public void paintTrack(Graphics graphic) {
//This rectangle is the bounding box for the progress bar
//portion of the slider. The track is painted in the middle
//of this rectangle and the thumb laid overtop.
Rectangle track = this.trackRect;
//Get the location of the thumb, this point splits the
//progress bar into 2 line segments, seen and unseen.
Rectangle thumb = this.thumbRect;
int thumbX = thumb.x;
int thumbY = thumb.y;
Color original = graphic.getColor();
//Paint the seen side
graphic.setColor(config.getSeenTrackColor());
graphic.drawLine(track.x, track.y + track.height / 2,
thumbX, thumbY + track.height / 2);
//Paint the unseen side
graphic.setColor(config.getUnseenTrackColor());
graphic.drawLine(thumbX, thumbY + track.height / 2,
track.x + track.width, track.y + track.height / 2);
//Preserve the graphics color.
graphic.setColor(original);
}
@Override
protected TrackListener createTrackListener(JSlider slider) {
return new CustomTrackListener();
}
@Override
protected void scrollDueToClickInTrack(int direction) {
//Set the thumb position to the mouse press location, as opposed
//to the closest "block" which is the default behavior.
Point mousePosition = slider.getMousePosition();
if (mousePosition == null) {
return;
}
int value = this.valueForXPosition(mousePosition.x);
//Lock the slider down, which is a shared resource.
//The VideoPanelUpdater (dedicated thread) keeps the
//slider in sync with the video position, so without
//proper locking our change could be overwritten.
sliderLock.acquireUninterruptibly();
slider.setValueIsAdjusting(true);
slider.setValue(value);
slider.setValueIsAdjusting(false);
sliderLock.release();
}
/**
* Applies anti-aliasing if available.
*/
@Override
public void update(Graphics graphic, JComponent component) {
if (graphic instanceof Graphics2D) {
Graphics2D graphic2 = (Graphics2D) graphic;
graphic2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
super.update(graphic, component);
}
/**
* This track listener will force the thumb to be snapped to the mouse
* location. This makes grabbing and dragging the JSlider much easier.
* Using the default track listener, the user would have to click
* exactly on the slider thumb to drag it. Now the thumb positions
* itself under the mouse so that it can always be dragged.
*/
private class CustomTrackListener extends CircularJSliderUI.TrackListener {
@Override
public void mousePressed(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
//Snap the thumb to position of the mouse
scrollDueToClickInTrack(0);
//Pause the video for convenience
gstPlayBin.pause();
//Handle the event as normal.
super.mousePressed(e);
}
@Override
public void mouseReleased(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
super.mouseReleased(e);
//Unpause once the mouse has been released.
gstPlayBin.play();
}
}
}
@@ -592,6 +818,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
progressSlider.setDoubleBuffered(true);
progressSlider.setMinimumSize(new java.awt.Dimension(36, 21));
progressSlider.setPreferredSize(new java.awt.Dimension(200, 21));
progressSlider.setUI(new CircularJSliderUI(progressSlider, new CircularJSliderConfiguration(new Dimension(18,18))));
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
@@ -645,7 +872,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
gridBagConstraints.ipadx = 8;
gridBagConstraints.ipady = 7;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.insets = new java.awt.Insets(6, 6, 0, 0);
gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0);
buttonPanel.add(VolumeIcon, gridBagConstraints);
audioSlider.setMajorTickSpacing(10);
@@ -655,6 +882,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
audioSlider.setValue(25);
audioSlider.setMinimumSize(new java.awt.Dimension(200, 21));
audioSlider.setPreferredSize(new java.awt.Dimension(200, 21));
audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15))));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 4;
gridBagConstraints.gridy = 0;
@@ -739,8 +967,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
*
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.
@@ -23,32 +23,32 @@ import org.openide.nodes.Node;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Utility classes for content viewers.
* In theory, this would live in the contentviewer package,
* but the initial method was needed only be viewers in
* Utility classes for content viewers. In theory, this would live in the
* contentviewer package, but the initial method was needed only be viewers in
* corecomponents and therefore can stay out of public API.
*/
public class DataContentViewerUtility {
/**
* Returns the first non-Blackboard Artifact from a Node.
* Needed for (at least) Hex and Strings that want to view
* all types of content (not just AbstractFile), but don't want
* to display an artifact unless that's the only thing there.
* Scenario is hash hit or interesting item hit.
*
* Returns the first non-Blackboard Artifact from a Node. Needed for (at
* least) Hex and Strings that want to view all types of content (not just
* AbstractFile), but don't want to display an artifact unless that's the
* only thing there. Scenario is hash hit or interesting item hit.
*
* @param node Node passed into content viewer
*
* @return highest priority content or null if there is no content
*/
public static Content getDefaultContent(Node node) {
Content bbContentSeen = null;
for (Content content : (node).getLookup().lookupAll(Content.class)) {
for (Content content : (node).getLookup().lookupAll(Content.class)) {
if (content instanceof BlackboardArtifact) {
bbContentSeen = content;
}
else {
} else {
return content;
}
}
return bbContentSeen;
}
}
+31 -54
View File
@@ -236,28 +236,23 @@ class LineCallLogsParser(TskCallLogsParser):
def __init__(self, calllog_db):
super(LineCallLogsParser, self).__init__(calllog_db.runQuery(
"""
SELECT Substr(CH.call_type, -1) AS direction,
CH.start_time AS start_time,
CH.end_time AS end_time,
contacts_list_with_groups.members AS group_members,
contacts_list_with_groups.member_names AS names,
CH.caller_mid,
CH.voip_type AS call_type,
CH.voip_gc_media_type AS group_call_type
SELECT Substr(calls.call_type, -1) AS direction,
calls.start_time AS start_time,
calls.end_time AS end_time,
contact_book_w_groups.members AS group_members,
calls.caller_mid,
calls.voip_type AS call_type,
calls.voip_gc_media_type AS group_call_type
FROM (SELECT id,
Group_concat(M.m_id) AS members,
Group_concat(Replace(C.server_name, ",", "")) AS member_names
Group_concat(M.m_id) AS members
FROM membership AS M
JOIN naver.contacts AS C
ON M.m_id = C.m_id
GROUP BY id
UNION
SELECT m_id,
NULL,
server_name
FROM naver.contacts) AS contacts_list_with_groups
JOIN call_history AS CH
ON CH.caller_mid = contacts_list_with_groups.id
NULL
FROM naver.contacts) AS contact_book_w_groups
JOIN call_history AS calls
ON calls.caller_mid = contact_book_w_groups.id
"""
)
)
@@ -355,43 +350,25 @@ class LineMessagesParser(TskMessagesParser):
def __init__(self, message_db):
super(LineMessagesParser, self).__init__(message_db.runQuery(
"""
SELECT contact_list_with_groups.name,
contact_list_with_groups.id,
contact_list_with_groups.members,
contact_list_with_groups.member_names,
CH.from_mid,
C.server_name AS from_name,
CH.content,
CH.created_time,
CH.attachement_type,
CH.attachement_local_uri,
CH.status
FROM (SELECT G.name,
group_members.id,
group_members.members,
group_members.member_names
FROM (SELECT id,
group_concat(M.m_id) AS members,
group_concat(replace(C.server_name,
",",
"")) as member_names
FROM membership AS M
JOIN contacts as C
ON M.m_id = C.m_id
GROUP BY id) AS group_members
JOIN groups AS G
ON G.id = group_members.id
UNION
SELECT server_name,
m_id,
NULL,
NULL
FROM contacts) AS contact_list_with_groups
JOIN chat_history AS CH
ON CH.chat_id = contact_list_with_groups.id
LEFT JOIN contacts as C
ON C.m_id = CH.from_mid
WHERE attachement_type != 6
SELECT contact_book_w_groups.id,
contact_book_w_groups.members,
messages.from_mid,
messages.content,
messages.created_time,
messages.attachement_type,
messages.attachement_local_uri,
messages.status
FROM (SELECT id,
Group_concat(M.m_id) AS members
FROM membership AS M
GROUP BY id
UNION
SELECT m_id,
NULL
FROM contacts) AS contact_book_w_groups
JOIN chat_history AS messages
ON messages.chat_id = contact_book_w_groups.id
WHERE attachement_type != 6
"""
)
)
+45 -96
View File
@@ -76,11 +76,8 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer):
as they would be excluded in the join. Since the chatItem table stores both the
group id or skype_id in one column, an implementation decision was made to union
the person and particiapnt table together so that all rows are matched in one join
with chatItem. This result is consistently labeled contact_list_with_groups in the
with chatItem. This result is consistently labeled contact_book_w_groups in the
following queries.
- In order to keep the formatting of the name consistent throughout each query,
a _format_user_name() function was created to encapsulate the CASE statement
that was being shared across them. Refer to the method for more details.
"""
def __init__(self):
@@ -93,7 +90,12 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer):
account_query_result = skype_db.runQuery(
"""
SELECT entry_id,
"""+_format_user_name()+""" AS name
CASE
WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id
WHEN first_name is NULL THEN replace(last_name, ",", "")
WHEN last_name is NULL THEN replace(first_name, ",", "")
ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "")
END AS name
FROM user
"""
)
@@ -251,14 +253,6 @@ class SkypeCallLogsParser(TskCallLogsParser):
def __init__(self, calllog_db):
"""
Big picture:
The query below creates a contacts_list_with_groups table, which
represents the recipient info. A chatItem record holds ids for
both the recipient and sender. The first join onto chatItem fills
in the blanks for the recipients. The second join back onto person
handles the sender info. The result is a table with all of the
communication details.
Implementation details:
- message_type w/ value 3 appeared to be the call type, regardless
of if it was audio or video.
@@ -266,37 +260,23 @@ class SkypeCallLogsParser(TskCallLogsParser):
"""
super(SkypeCallLogsParser, self).__init__(calllog_db.runQuery(
"""
SELECT contacts_list_with_groups.conversation_id,
contacts_list_with_groups.participant_ids,
contacts_list_with_groups.participants,
time,
duration,
is_sender_me,
person_id as sender_id,
sender_name.name as sender_name
SELECT contact_book_w_groups.conversation_id,
contact_book_w_groups.participant_ids,
messages.time,
messages.duration,
messages.is_sender_me,
messages.person_id AS sender_id
FROM (SELECT conversation_id,
Group_concat(person_id) AS participant_ids,
Group_concat("""+_format_user_name()+""") AS participants
FROM particiapnt AS PART
JOIN person AS P
ON PART.person_id = P.entry_id
Group_concat(person_id) AS participant_ids
FROM particiapnt
GROUP BY conversation_id
UNION
SELECT entry_id,
NULL,
"""+_format_user_name()+""" AS participant
FROM person) AS contacts_list_with_groups
JOIN chatitem AS C
ON C.conversation_link = contacts_list_with_groups.conversation_id
JOIN (SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM person
UNION
SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM user) AS sender_name
ON sender_name.id = C.person_id
WHERE message_type == 3
SELECT entry_id AS conversation_id,
NULL
FROM person) AS contact_book_w_groups
join chatitem AS messages
ON messages.conversation_link = contact_book_w_groups.conversation_id
WHERE message_type == 3
"""
)
)
@@ -347,7 +327,12 @@ class SkypeContactsParser(TskContactsParser):
super(SkypeContactsParser, self).__init__(contact_db.runQuery(
"""
SELECT entry_id,
"""+_format_user_name()+""" AS name
CASE
WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id
WHEN first_name is NULL THEN replace(last_name, ",", "")
WHEN last_name is NULL THEN replace(first_name, ",", "")
ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "")
END AS name
FROM person
"""
)
@@ -379,39 +364,25 @@ class SkypeMessagesParser(TskMessagesParser):
"""
super(SkypeMessagesParser, self).__init__(message_db.runQuery(
"""
SELECT contacts_list_with_groups.conversation_id,
contacts_list_with_groups.participant_ids,
contacts_list_with_groups.participants,
time,
content,
device_gallery_path,
is_sender_me,
person_id as sender_id,
sender_name.name AS sender_name
FROM (SELECT conversation_id,
Group_concat(person_id) AS participant_ids,
Group_concat("""+_format_user_name()+""") AS participants
FROM particiapnt AS PART
JOIN person AS P
ON PART.person_id = P.entry_id
GROUP BY conversation_id
UNION
SELECT entry_id as conversation_id,
NULL,
"""+_format_user_name()+""" AS participant
FROM person) AS contacts_list_with_groups
JOIN chatitem AS C
ON C.conversation_link = contacts_list_with_groups.conversation_id
JOIN (SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM person
UNION
SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM user) AS sender_name
ON sender_name.id = C.person_id
SELECT contact_book_w_groups.conversation_id,
contact_book_w_groups.participant_ids,
messages.time,
messages.content,
messages.device_gallery_path,
messages.is_sender_me,
messages.person_id as sender_id
FROM (SELECT conversation_id,
Group_concat(person_id) AS participant_ids
FROM particiapnt
GROUP BY conversation_id
UNION
SELECT entry_id as conversation_id,
NULL
FROM person) AS contact_book_w_groups
JOIN chatitem AS messages
ON messages.conversation_link = contact_book_w_groups.conversation_id
WHERE message_type != 3
"""
"""
)
)
self._SKYPE_MESSAGE_TYPE = "Skype Message"
@@ -469,25 +440,3 @@ class SkypeMessagesParser(TskMessagesParser):
if group_ids is not None:
return self.result_set.getString("conversation_id")
return super(SkypeMessagesParser, self).get_thread_id()
def _format_user_name():
"""
This CASE SQL statement is used in many queries to
format the names of users. For a user, there is a first_name
column and a last_name column. Some of these columns can be null
and our goal is to produce the cleanest data possible. In the event
that both the first and last name columns are null, we return the skype_id
which is stored in the database as 'entry_id'. Commas are removed from the name
so that we can concatenate names into a comma seperate list for group chats.
"""
return """
CASE
WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id
WHEN first_name is NULL THEN replace(last_name, ",", "")
WHEN last_name is NULL THEN replace(first_name, ",", "")
ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "")
END
"""
+37 -41
View File
@@ -290,53 +290,50 @@ class TextNowMessagesParser(TskMessagesParser):
"""
super(TextNowMessagesParser, self).__init__(message_db.runQuery(
"""
SELECT CASE
WHEN message_direction == 2 THEN ""
WHEN to_addresses IS NULL THEN M.contact_value
ELSE contact_name
end from_address,
CASE
WHEN message_direction == 1 THEN ""
WHEN to_addresses IS NULL THEN M.contact_value
ELSE to_addresses
end to_address,
message_direction,
message_text,
M.READ,
M.date,
M.attach,
thread_id
FROM (SELECT group_info.contact_value,
group_info.to_addresses,
G.contact_value AS thread_id
FROM (SELECT GM.contact_value,
Group_concat(GM.member_contact_value) AS to_addresses
FROM group_members AS GM
GROUP BY GM.contact_value) AS group_info
JOIN groups AS G
ON G.contact_value = group_info.contact_value
UNION
SELECT c.contact_value,
NULL,
"-1"
FROM contacts AS c) AS to_from_map
JOIN messages AS M
ON M.contact_value = to_from_map.contact_value
WHERE message_type NOT IN ( 102, 100 )
SELECT CASE
WHEN messages.message_direction == 2 THEN NULL
WHEN contact_book_w_groups.to_addresses IS NULL THEN
messages.contact_value
END from_address,
CASE
WHEN messages.message_direction == 1 THEN NULL
WHEN contact_book_w_groups.to_addresses IS NULL THEN
messages.contact_value
ELSE contact_book_w_groups.to_addresses
END to_address,
messages.message_direction,
messages.message_text,
messages.READ,
messages.DATE,
messages.attach,
thread_id
FROM (SELECT GM.contact_value,
Group_concat(GM.member_contact_value) AS to_addresses,
G.contact_value AS thread_id
FROM group_members AS GM
join GROUPS AS G
ON G.contact_value = GM.contact_value
GROUP BY GM.contact_value
UNION
SELECT contact_value,
NULL,
NULL
FROM contacts) AS contact_book_w_groups
join messages
ON messages.contact_value = contact_book_w_groups.contact_value
WHERE message_type NOT IN ( 102, 100 )
"""
)
)
self._TEXTNOW_MESSAGE_TYPE = "TextNow Message"
self._INCOMING_MESSAGE_TYPE = 1
self._OUTGOING_MESSAGE_TYPE = 2
self._UNKNOWN_THREAD_ID = "-1"
def get_message_type(self):
return self._TEXTNOW_MESSAGE_TYPE
def get_phone_number_from(self):
if self.result_set.getString("from_address") == "":
if self.result_set.getString("from_address") is None:
return super(TextNowMessagesParser, self).get_phone_number_from()
return self.result_set.getString("from_address")
@@ -347,10 +344,9 @@ class TextNowMessagesParser(TskMessagesParser):
return self.OUTGOING
def get_phone_number_to(self):
if self.result_set.getString("to_address") == "":
if self.result_set.getString("to_address") is None:
return super(TextNowMessagesParser, self).get_phone_number_to()
recipients = self.result_set.getString("to_address").split(",")
return recipients
return self.result_set.getString("to_address").split(",")
def get_message_date_time(self):
#convert ms to s
@@ -359,7 +355,7 @@ class TextNowMessagesParser(TskMessagesParser):
def get_message_read_status(self):
read = self.result_set.getBoolean("read")
if self.get_message_direction() == self.INCOMING:
if read == True:
if read:
return self.READ
return self.UNREAD
@@ -375,6 +371,6 @@ class TextNowMessagesParser(TskMessagesParser):
def get_thread_id(self):
thread_id = self.result_set.getString("thread_id")
if thread_id == self._UNKNOWN_THREAD_ID:
if thread_id is None:
return super(TextNowMessagesParser, self).get_thread_id()
return thread_id
+24 -25
View File
@@ -433,31 +433,28 @@ class WhatsAppMessagesParser(TskMessagesParser):
def __init__(self, message_db):
super(WhatsAppMessagesParser, self).__init__(message_db.runQuery(
"""
SELECT M.key_remote_jid AS id,
contact_info.recipients,
key_from_me AS direction,
CASE
WHEN M.data IS NULL THEN ""
ELSE M.data
END AS content,
M.timestamp AS send_timestamp,
M.received_timestamp,
M.remote_resource AS group_sender,
M.media_url As attachment
FROM (SELECT jid,
recipients
FROM wadb.wa_contacts AS WC
LEFT JOIN (SELECT gjid,
group_concat(CASE
WHEN jid == "" THEN NULL
ELSE jid
END) AS recipients
FROM group_participants
GROUP BY gjid) AS group_map
ON WC.jid = group_map.gjid
GROUP BY jid) AS contact_info
JOIN messages AS M
ON M.key_remote_jid = contact_info.jid
SELECT messages.key_remote_jid AS id,
contact_book_w_groups.recipients,
key_from_me AS direction,
messages.data AS content,
messages.timestamp AS send_timestamp,
messages.received_timestamp,
messages.remote_resource AS group_sender,
messages.media_url AS attachment
FROM (SELECT jid,
recipients
FROM wadb.wa_contacts AS contacts
left join (SELECT gjid,
Group_concat(CASE
WHEN jid == "" THEN NULL
ELSE jid
END) AS recipients
FROM group_participants
GROUP BY gjid) AS groups
ON contacts.jid = groups.gjid
GROUP BY jid) AS contact_book_w_groups
join messages
ON messages.key_remote_jid = contact_book_w_groups.jid
"""
)
)
@@ -503,6 +500,8 @@ class WhatsAppMessagesParser(TskMessagesParser):
def get_message_text(self):
message = self.result_set.getString("content")
if message is None:
message = super(WhatsAppMessagesParser, self).get_message_text()
attachment = self.result_set.getString("attachment")
if attachment is not None:
return general.appendAttachmentList(message, [attachment])
+2
View File
@@ -6,6 +6,8 @@ Overview
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/"> developer's guide</a> will help you develop your own Autopsy modules.
Note: For those users running Autopsy on Mac devices, the functionality available through the "Tools" -> "Options" dialog as described in this documentation can be accessed through the system menu bar under "Preferences" or through the Cmd + , (command-comma) shortcut.
Help Topics
-------
The following topics are available here:
@@ -83,7 +83,7 @@ class EmailMessage {
void setSubject(String subject) {
if (subject != null) {
this.subject = subject;
if(subject.matches("^[R|r][E|e].*?:.*")) {
if (subject.matches("^[R|r][E|e].*?:.*")) {
this.simplifiedSubject = subject.replaceAll("[R|r][E|e].*?:", "").trim();
replySubject = true;
} else {
@@ -93,19 +93,19 @@ class EmailMessage {
this.simplifiedSubject = "";
}
}
/**
* Returns the orginal subject with the "RE:" stripped off".
*
*
* @return Message subject with the "RE" stripped off
*/
String getSimplifiedSubject() {
return simplifiedSubject;
}
/**
* Returns whether or not the message subject started with "RE:"
*
*
* @return true if the original subject started with RE otherwise false.
*/
boolean isReplySubject() {
@@ -121,6 +121,7 @@ class EmailMessage {
this.headers = headers;
}
}
String getTextBody() {
return textBody;
}
@@ -211,75 +212,80 @@ class EmailMessage {
this.localPath = localPath;
}
}
/**
* Returns the value of the Message-ID header field of this message or
* empty string if it is not present.
*
* Returns the value of the Message-ID header field of this message or empty
* string if it is not present.
*
* @return the identifier of this message.
*/
String getMessageID() {
return messageID;
}
/**
* Sets the identifier of this message.
*
*
* @param messageID identifer of this message
*/
void setMessageID(String messageID) {
this.messageID = messageID;
if (messageID != null) {
this.messageID = messageID;
} else {
this.messageID = "";
}
}
/**
* Returns the messageID of the parent message or empty String if not present.
*
* Returns the messageID of the parent message or empty String if not
* present.
*
* @return the idenifier of the message parent
*/
String getInReplyToID() {
return inReplyToID;
}
/**
* Sets the messageID of the parent message.
*
*
* @param inReplyToID messageID of the parent message.
*/
void setInReplyToID(String inReplyToID) {
this.inReplyToID = inReplyToID;
}
/**
* Returns a list of Message-IDs listing the parent, grandparent,
* great-grandparent, and so on, of this message.
*
* Returns a list of Message-IDs listing the parent, grandparent,
* great-grandparent, and so on, of this message.
*
* @return The reference list or empty string if none is available.
*/
List<String> getReferences() {
return references;
}
/**
* Set the list of reference message-IDs from the email message header.
*
* @param references
*
* @param references
*/
void setReferences(List<String> references) {
this.references = references;
}
/**
* Sets the ThreadID of this message.
*
*
* @param threadID - the thread ID to set
*/
void setMessageThreadID(String threadID) {
this.messageThreadID = threadID;
}
/**
* Returns the ThreadID for this message.
*
*
* @return - the message thread ID or "" is non is available
*/
String getMessageThreadID() {
@@ -308,7 +314,7 @@ class EmailMessage {
private long aTime = 0L;
private long mTime = 0L;
private TskData.EncodingType encodingType = TskData.EncodingType.NONE;
String getName() {
@@ -394,14 +400,14 @@ class EmailMessage {
this.mTime = mTime.getTime() / 1000;
}
}
void setEncodingType(TskData.EncodingType encodingType){
void setEncodingType(TskData.EncodingType encodingType) {
this.encodingType = encodingType;
}
TskData.EncodingType getEncodingType(){
TskData.EncodingType getEncodingType() {
return encodingType;
}
}
}