mirror of
https://github.com/elisspace/autopsy.git
synced 2026-09-29 22:09:52 +00:00
Merge remote-tracking branch 'upstream/develop' into data-src-deletion
This commit is contained in:
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user