Merge branch 'new-features-20120503' of github.com:sleuthkit/autopsy into new-features-20120503
@@ -1,3 +1,5 @@
|
||||
Last Updated: June 12, 2012
|
||||
|
||||
This file outlines what it takes to build Autopsy from source.
|
||||
|
||||
Note that it currently only works out of the box on Windows. We
|
||||
@@ -5,33 +7,49 @@ are working on getting the process working under non-WIndows systems.
|
||||
It generally works, but needs some custom mangling to find the
|
||||
correct C libraries.
|
||||
|
||||
|
||||
STEPS:
|
||||
1) Download and install 32-bit version of JDK (32-bit is currently
|
||||
1) Get Java Setup
|
||||
1a) Download and install 32-bit version of JDK (32-bit is currently
|
||||
needed even if you have a 64-bit system).
|
||||
|
||||
2) Ensure that JDK_HOME is set to the root JDK directory.
|
||||
1b) Ensure that JDK_HOME is set to the root JDK directory.
|
||||
|
||||
3) Download and install Netbeans IDE 7.0.1 (http://netbeans.org/)
|
||||
1c) Download and install Netbeans IDE 7.0.1 (http://netbeans.org/)
|
||||
|
||||
4) Download and build the release version of Libewf2 (20120304 or later). All you need is the dll file. Note that you will get a launching error if you use libewf 1.
|
||||
|
||||
2) Get Sleuth Kit Setup
|
||||
2a) Download and build the release version of Libewf2 (20120304 or later). All you need is the dll file. Note that you will get a launching error if you use libewf 1.
|
||||
- http://sourceforge.net/projects/libewf/
|
||||
|
||||
5) Set LIBEWF_HOME environment variable to root directory of LIBEWF
|
||||
2b) Set LIBEWF_HOME environment variable to root directory of LIBEWF
|
||||
|
||||
6) Download and build release version of Sleuth Kit (TSK) 3.3. You
|
||||
2c) Download and build release version of Sleuth Kit (TSK) 4.0. You
|
||||
need to build the tsk_jni project.
|
||||
- At the time of this writing, 3.3 is not released. You can get it from either
|
||||
- At the time of this writing, 4.0 is not released. You can get it from either
|
||||
-- GIT: git://github.com/sleuthkit/sleuthkit.git
|
||||
-- SVN: http://svn.github.com/sleuthkit/sleuthkit.git
|
||||
|
||||
7) Build the TSK JAR file by typing 'ant' in bindings/java from a
|
||||
2d) Build the TSK JAR file by typing 'ant' in bindings/java from a
|
||||
command line or by opening the project in NetBeans.
|
||||
|
||||
8) Set TSK_HOME environment variable to the root directory of TSK
|
||||
2e) Set TSK_HOME environment variable to the root directory of TSK
|
||||
|
||||
9) Start NetBean IDE and open the Autopsy project.
|
||||
|
||||
10) Choose to build the Autopsy project / module. It is the highest
|
||||
3) Get gstreamer Setup
|
||||
|
||||
If Autopsy installer is not used, add the following entries to Windows PATH environment variable
|
||||
(replace GSTREAMER_INSTALL_DIR with the location of the gstreamer root directory):
|
||||
GSTREAMER_INSTALL_DIR\bin\;
|
||||
GSTREAMER_INSTALL_DIR\lib\gstreamer-0.10\;
|
||||
If you don't have gstreamer already, you can find a zipped gstreamer distribution in
|
||||
AUTOPSYROOT/thirdparty/gstreamer
|
||||
|
||||
|
||||
4) Compile Autopsy
|
||||
4a) Start NetBean IDE and open the Autopsy project.
|
||||
|
||||
4b) Choose to build the Autopsy project / module. It is the highest
|
||||
level project that will then cause the other modules to be compiled.
|
||||
|
||||
|
||||
@@ -55,5 +73,4 @@ rebuild both the dll and the JAR file.
|
||||
|
||||
---------------
|
||||
Brian Carrier
|
||||
4/6/2012
|
||||
carrier <at> sleuthkit <dot> org
|
||||
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 143 KiB |
@@ -22,6 +22,7 @@
|
||||
<li>The second panel is when Autopsy is analyzing the disk image and populating the database with basic information. This can take a few minutes for large images. <br>
|
||||
<img src="AddImageWizard2_Help.png" alt="Add Image Wizard Panel 2 Help" /> </li>
|
||||
<li>The third panel allows you to choose which ingest modules to run on the image. Refer to the <a href="nbdocs:/org/sleuthkit/autopsy/ingest/docs/ingest-about.html">Image Ingest</a> part of the help guide for more details. </li>
|
||||
<img src="AddImageWizard3_Help.png" alt="Add Image Wizard Panel 3 Help" /> </li>
|
||||
<li>Once you select the ingest modules that you want to use, they will run in the background. You can choose to add another image or exit the Add Image wizard. </li>
|
||||
</ul>
|
||||
<p>Note that Autopsy will store the path to the image in its configuration file. If the image moves, then Autopsy will give an error because it can't find the image file.
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
</p>
|
||||
|
||||
<h2>Notable / Known Bad Hashsets</h2>
|
||||
<p>Autopsy allows for a single known bad hash database to be set. Future versions will support multiple hash sets. Autopsy supports three formats:
|
||||
<p>Autopsy allows for multiple known bad hash databases to be set. Autopsy supports three formats:
|
||||
<ul>
|
||||
<li>EnCase: An EnCase hashset file. </li>
|
||||
<li>MD5sum: Output from running the md5, md5sum, or md5deep program on a set of files.</li
|
||||
<li>HashKeeper: Hashkeeper hashsets (Must be merged into a single file).</li>
|
||||
<li>MD5sum: Output from running the md5, md5sum, or md5deep program on a set of files.</li>
|
||||
<li>NSRL: The format of the NSRL database </li>
|
||||
</ul>
|
||||
|
||||
<h2>NIST NSRL</h2>
|
||||
<p>Autopsy can use the <a href="http://www.nsrl.nist.gov">NIST NSRL</a> to detect 'known files'. Note that the NSRL contains hashes of 'known files' that may be good or bad depending on your perspective and investigation type. For example, the existence of a piece of financial software
|
||||
may be interesting to your investigation and that software could be in the NSRL. Therefore, Autopsy treats files that are found in the NSRL as simplyi 'known' and does not specify good or bad. Ingest modules have the option of ignoring files that were found in the NSRL.</p>
|
||||
may be interesting to your investigation and that software could be in the NSRL. Therefore, Autopsy treats files that are found in the NSRL as simply 'known' and does not specify good or bad. Ingest modules have the option of ignoring files that were found in the NSRL.</p>
|
||||
|
||||
<p>To use the NSRL, you must concatenate all of the NSRLFile.txt files together. You can use 'cat' on a Unix system or from within Cygwin to do this.</p>
|
||||
|
||||
@@ -43,5 +43,6 @@
|
||||
<p>You can also see the results in the <a href="nbdocs:/org/sleuthkit/autopsy/filesearch/docs/open-filesearch.html">File Search</a> window. There is an option to choose the 'known status'. From here, you can do a search to see all 'known bad' files.
|
||||
From here, you can also choose to ignore all 'known' files that were found in the NSRL. You can also see the status of the file in a column when the file is listed. </p>
|
||||
|
||||
<img src="hashdb.PNG" alt="Hash Database Configuration" />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
@@ -19,9 +19,9 @@ and open the template in the editor.
|
||||
|
||||
<p>The main window has three major areas:
|
||||
<ul>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/dataexplorer-about.html">Data Explorer Tree</a> (area 4 in figure below): This area is where you go find major analysis functionality. It allows you to start finding the relevant files quickly.</li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/dataresult-about.html">Result Viewers</a> (area 5 in figure below): This area is where the files and directories that were found from the explorer window can be viewed. There are different formatting options for the files.</li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/datacontent-about.html">Content Viewers</a> (area 6 in figure below): This area is where file content can be viewed after they are selected from the Result Viewer area.</li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/dataexplorer-about.html">Data Explorer Tree</a>: This area is where you go find major analysis functionality. It allows you to start finding the relevant files quickly.</li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/dataresult-about.html">Result Viewers</a>: This area is where the files and directories that were found from the explorer window can be viewed. There are different formatting options for the files.</li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/datacontent-about.html">Content Viewers</a>: This area is where file content can be viewed after they are selected from the Result Viewer area.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>The main take away from this should be that analysis techniques and result categories can be found on the left-hand side, the results from choosing something on the left are always listed in the upper right, and the file contents are displayed in the lower left.
|
||||
|
||||
@@ -71,8 +71,7 @@ public final class AddImageAction extends CallableSystemAction implements Presen
|
||||
static final String LOOKUPFILES_PROP = "lookupFiles";
|
||||
// boolean: whether or not to skip processing orphan files on FAT filesystems
|
||||
static final String NOFATORPHANS_PROP = "nofatorphans";
|
||||
// boolean: whether or not to skip processing of unallocated space
|
||||
static final String NOUNALLOC_PROP = "nounalloc";
|
||||
|
||||
|
||||
static final Logger logger = Logger.getLogger(AddImageAction.class.getName());
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
</Component>
|
||||
</NonVisualComponents>
|
||||
<Properties>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[559, 328]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[588, 308]"/>
|
||||
<Dimension value="[588, 328]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
@@ -25,16 +28,12 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="noProcessUnallocSpace" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="imgPathLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="imgPathTextField" min="-2" pref="389" max="-2" attributes="1"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="imgPathBrowserButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="10" pref="10" max="10" attributes="0"/>
|
||||
<Component id="noFatOrphansCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="imgTypeLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
@@ -48,15 +47,22 @@
|
||||
<Component id="multipleSelectLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="imgInfoLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" alignment="0" groupAlignment="1" max="-2" attributes="0">
|
||||
<Group type="102" attributes="1">
|
||||
<Group type="102" alignment="0" attributes="1">
|
||||
<Component id="timeZoneLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="timeZoneComboBox" min="-2" pref="253" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="noFatOrphansCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="imgPathLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="imgPathTextField" min="-2" pref="389" max="-2" attributes="1"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="imgPathBrowserButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="optionsLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="39" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
@@ -83,27 +89,21 @@
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="multipleSelectLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="timeZoneLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="timeZoneComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="28" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="noProcessUnallocSpace" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="noFatOrphansCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="timeZoneLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="timeZoneComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="optionsLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="noFatOrphansCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="18" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="25" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@@ -241,15 +241,12 @@
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="noProcessUnallocSpace">
|
||||
<Component class="javax.swing.JLabel" name="optionsLabel1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="AddImageVisualPanel1.noProcessUnallocSpace.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="AddImageVisualPanel1.optionsLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="noProcessUnallocSpaceActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
||||
@@ -116,14 +116,6 @@ final class AddImageVisualPanel1 extends JPanel implements DocumentListener {
|
||||
return noFatOrphansCheckbox.isSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if no unalloc space processing is selected
|
||||
*/
|
||||
boolean getNoUnallocSpaceProcess() {
|
||||
return noProcessUnallocSpace.isSelected();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -221,9 +213,10 @@ final class AddImageVisualPanel1 extends JPanel implements DocumentListener {
|
||||
timeZoneLabel = new javax.swing.JLabel();
|
||||
jLabel2 = new javax.swing.JLabel();
|
||||
noFatOrphansCheckbox = new javax.swing.JCheckBox();
|
||||
noProcessUnallocSpace = new javax.swing.JCheckBox();
|
||||
optionsLabel1 = new javax.swing.JLabel();
|
||||
|
||||
setPreferredSize(new java.awt.Dimension(588, 308));
|
||||
setMinimumSize(new java.awt.Dimension(559, 328));
|
||||
setPreferredSize(new java.awt.Dimension(588, 328));
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(rawSingle, org.openide.util.NbBundle.getMessage(AddImageVisualPanel1.class, "AddImageVisualPanel1.rawSingle.text")); // NOI18N
|
||||
rawSingle.setRequestFocusEnabled(false);
|
||||
@@ -279,12 +272,7 @@ final class AddImageVisualPanel1 extends JPanel implements DocumentListener {
|
||||
org.openide.awt.Mnemonics.setLocalizedText(noFatOrphansCheckbox, org.openide.util.NbBundle.getMessage(AddImageVisualPanel1.class, "AddImageVisualPanel1.noFatOrphansCheckbox.text")); // NOI18N
|
||||
noFatOrphansCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(AddImageVisualPanel1.class, "AddImageVisualPanel1.noFatOrphansCheckbox.toolTipText")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(noProcessUnallocSpace, org.openide.util.NbBundle.getMessage(AddImageVisualPanel1.class, "AddImageVisualPanel1.noProcessUnallocSpace.text")); // NOI18N
|
||||
noProcessUnallocSpace.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
noProcessUnallocSpaceActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
org.openide.awt.Mnemonics.setLocalizedText(optionsLabel1, org.openide.util.NbBundle.getMessage(AddImageVisualPanel1.class, "AddImageVisualPanel1.optionsLabel1.text")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
@@ -293,13 +281,9 @@ final class AddImageVisualPanel1 extends JPanel implements DocumentListener {
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(noProcessUnallocSpace)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(imgPathLabel)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(imgPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 389, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(imgPathBrowserButton))
|
||||
.addGap(10, 10, 10)
|
||||
.addComponent(noFatOrphansCheckbox))
|
||||
.addComponent(imgTypeLabel)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
@@ -310,13 +294,19 @@ final class AddImageVisualPanel1 extends JPanel implements DocumentListener {
|
||||
.addComponent(multipleSelectLabel)
|
||||
.addComponent(imgInfoLabel)
|
||||
.addComponent(jLabel1)
|
||||
.addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
|
||||
.addComponent(timeZoneLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 253, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addComponent(noFatOrphansCheckbox, javax.swing.GroupLayout.Alignment.LEADING)))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
|
||||
.addComponent(imgPathLabel)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(imgPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 389, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(imgPathBrowserButton)))
|
||||
.addComponent(optionsLabel1)
|
||||
.addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap(39, Short.MAX_VALUE))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
@@ -339,23 +329,19 @@ final class AddImageVisualPanel1 extends JPanel implements DocumentListener {
|
||||
.addComponent(imgPathBrowserButton))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(multipleSelectLabel)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(timeZoneLabel)
|
||||
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 28, Short.MAX_VALUE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(noProcessUnallocSpace)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)))
|
||||
.addComponent(noFatOrphansCheckbox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(jLabel1)
|
||||
.addContainerGap())
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(timeZoneLabel)
|
||||
.addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(optionsLabel1)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(noFatOrphansCheckbox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 18, Short.MAX_VALUE)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(jLabel1)
|
||||
.addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(25, 25, 25))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@@ -467,10 +453,6 @@ final class AddImageVisualPanel1 extends JPanel implements DocumentListener {
|
||||
this.wizPanel.moveFocusToNext();
|
||||
}//GEN-LAST:event_imgPathBrowserButtonActionPerformed
|
||||
|
||||
private void noProcessUnallocSpaceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_noProcessUnallocSpaceActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_noProcessUnallocSpaceActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.ButtonGroup buttonGroup1;
|
||||
private javax.swing.JRadioButton encase;
|
||||
@@ -483,7 +465,7 @@ private void noProcessUnallocSpaceActionPerformed(java.awt.event.ActionEvent evt
|
||||
private javax.swing.JLabel jLabel2;
|
||||
private javax.swing.JLabel multipleSelectLabel;
|
||||
private javax.swing.JCheckBox noFatOrphansCheckbox;
|
||||
private javax.swing.JCheckBox noProcessUnallocSpace;
|
||||
private javax.swing.JLabel optionsLabel1;
|
||||
private static javax.swing.JRadioButton rawSingle;
|
||||
private javax.swing.JRadioButton rawSplit;
|
||||
private javax.swing.JComboBox timeZoneComboBox;
|
||||
|
||||
@@ -214,7 +214,6 @@ class AddImageWizardPanel1 implements WizardDescriptor.Panel<WizardDescriptor>,
|
||||
settings.putProperty(AddImageAction.IMGPATHS_PROP, getComponent().getImagePaths());
|
||||
settings.putProperty(AddImageAction.TIMEZONE_PROP, getComponent().getSelectedTimezone()); // store the timezone
|
||||
settings.putProperty(AddImageAction.NOFATORPHANS_PROP, Boolean.valueOf(getComponent().getNoFatOrphans()));
|
||||
settings.putProperty(AddImageAction.NOUNALLOC_PROP, Boolean.valueOf(getComponent().getNoUnallocSpaceProcess()));
|
||||
//settings.putProperty(AddImageAction.LOOKUPFILES_PROP, getComponent().getLookupFilesCheckboxChecked());
|
||||
//settings.putProperty(AddImageAction.SOLR_PROP, getComponent().getIndexImageCheckboxChecked());
|
||||
|
||||
|
||||
@@ -55,8 +55,6 @@ class AddImageWizardPanel2 implements WizardDescriptor.Panel<WizardDescriptor> {
|
||||
private String timeZone;
|
||||
//whether to not process FAT filesystem orphans
|
||||
private boolean noFatOrphans;
|
||||
//whether to not process unalloc space
|
||||
private boolean noUnallocSpace;
|
||||
// task that will clean up the created database file if the wizard is cancelled before it finishes
|
||||
private AddImageAction.CleanupTask cleanupImage; // initialized to null in readSettings()
|
||||
// flag to control the availiablity of next action
|
||||
@@ -195,7 +193,6 @@ class AddImageWizardPanel2 implements WizardDescriptor.Panel<WizardDescriptor> {
|
||||
imgPaths = (String[]) settings.getProperty(AddImageAction.IMGPATHS_PROP);
|
||||
timeZone = settings.getProperty(AddImageAction.TIMEZONE_PROP).toString();
|
||||
noFatOrphans = ((Boolean) settings.getProperty(AddImageAction.NOFATORPHANS_PROP)).booleanValue();
|
||||
noUnallocSpace = ((Boolean) settings.getProperty(AddImageAction.NOUNALLOC_PROP)).booleanValue();
|
||||
|
||||
component.changeProgressBarTextAndColor("", 0, Color.black);
|
||||
|
||||
@@ -278,7 +275,7 @@ class AddImageWizardPanel2 implements WizardDescriptor.Panel<WizardDescriptor> {
|
||||
}
|
||||
|
||||
|
||||
process = currentCase.makeAddImageProcess(timeZone, !noUnallocSpace, noFatOrphans);
|
||||
process = currentCase.makeAddImageProcess(timeZone, true, noFatOrphans);
|
||||
cancelledWhileRunning.enable();
|
||||
try {
|
||||
process.run(imgPaths);
|
||||
|
||||
@@ -125,5 +125,5 @@ NewCaseVisualPanel2.caseNumberLabel.text=Case Number:
|
||||
NewCaseVisualPanel2.examinerTextField.text=
|
||||
NewCaseVisualPanel2.optionalLabel.text=Optional: Set Case Number and Examiner
|
||||
AddImageVisualPanel1.noFatOrphansCheckbox.toolTipText=
|
||||
AddImageVisualPanel1.noFatOrphansCheckbox.text=Disable in-depth file recovery of FAT file systems (faster results, but may miss deleted files)
|
||||
AddImageVisualPanel1.noProcessUnallocSpace.text=Disable recovery of unallocated space
|
||||
AddImageVisualPanel1.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems
|
||||
AddImageVisualPanel1.optionsLabel1.text=Options to produce results faster (although some data will not be searched):
|
||||
|
||||
|
After Width: | Height: | Size: 832 B |
|
After Width: | Height: | Size: 483 B |
|
After Width: | Height: | Size: 710 B |
|
After Width: | Height: | Size: 16 KiB |
@@ -19,19 +19,15 @@
|
||||
|
||||
<h2>Default Viewers</h2>
|
||||
<p>
|
||||
Currently, there are 3 main tabs on "Content Viewer" window:
|
||||
Currently, there are 5 main tabs on "Content Viewer" window:
|
||||
<ul>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/result-viewer.html">Result Viewer</a></li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/hex-content-viewer.html">Hex Content Viewer</a></li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/string-content-viewer.html">String Content Viewer</a></li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/picture-content-viewer.html">Media Viewer</a></li>
|
||||
<li><a href="nbdocs:/org/sleuthkit/autopsy/corecomponents/docs/text-content-viewer.html">Text Viewer</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h2>Example</h2>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<!--
|
||||
To change this template, choose Tools | Templates
|
||||
and open the template in the editor.
|
||||
-->
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Result Content Viewer</title>
|
||||
<link rel="stylesheet" href="nbdocs:/org/netbeans/modules/usersguide/ide.css" type="text/css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Result Content Viewer</h2>
|
||||
<p>
|
||||
Result Content Viewer shows the Artifacts associated with the item selected in the Result Viewer.
|
||||
<br><br>
|
||||
</p>
|
||||
|
||||
<h2>Example</h2>
|
||||
<p>
|
||||
Here's one of the example of "Result Content Viewer":
|
||||
<br><br>
|
||||
<img src="Result_Viewer.png" alt="Example of Result Content Viewer Tab" />
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
<!--
|
||||
Tip: to create a link which will open in an external web browser, try:
|
||||
<object classid="java:org.netbeans.modules.javahelp.BrowserDisplayer">
|
||||
<param name="content" value="http://www.netbeans.org/">
|
||||
<param name="text" value="<html><u>http://www.netbeans.org/</u></html>">
|
||||
<param name="textFontSize" value="medium">
|
||||
<param name="textColor" value="blue">
|
||||
</object>
|
||||
To create a link to a help set from another module, you need to know the code name base and path, e.g.:
|
||||
<a href="nbdocs://org.netbeans.modules.usersguide/org/netbeans/modules/usersguide/configure/configure_options.html">Using the Options Window</a>
|
||||
(This link will behave sanely if that module is disabled or missing.)
|
||||
-->
|
||||
@@ -12,5 +12,6 @@
|
||||
<p>This tab may have more text on it than the "Strings Content Viewer", which relies on searching the file for text-looking data. Some files, like PDF, will not have text-looking data at the byte-level, but the keyword indexing process knows how to interpret a PDF file and produce text. </p>
|
||||
|
||||
<p>If this tab is not enabled, then either the file has no text or you did not enable Keyword Search as an ingest module. Note that this viewer is also used to display keyword hits.</p>
|
||||
<img src="textview.png" alt="Text View" />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
After Width: | Height: | Size: 24 KiB |
@@ -33,6 +33,7 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import org.netbeans.swing.outline.DefaultOutlineModel;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.explorer.view.OutlineView;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
@@ -241,8 +242,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
|
||||
this.getAllChildPropertyHeadersRec(selectedNode, 100);
|
||||
List<Node.Property> props = new ArrayList<Node.Property>(propertiesAcc);
|
||||
if(props.size() > 0)
|
||||
props.remove(0);
|
||||
if(props.size() > 0) {
|
||||
Node.Property prop = props.remove(0);
|
||||
((DefaultOutlineModel)ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
|
||||
}
|
||||
|
||||
|
||||
// *********** Make the TreeTableView to be sortable ***************
|
||||
|
||||
@@ -129,6 +129,11 @@ abstract class AbstractContentChildren extends Keys<Object> {
|
||||
return hh.new HashsetHitsRootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(EmailExtracted ee) {
|
||||
return ee.new EmailExtractedRootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Images i) {
|
||||
try {
|
||||
|
||||
@@ -226,16 +226,12 @@ public abstract class AbstractFsContentNode<T extends FsContent> extends Abstrac
|
||||
* @param content to extract properties from
|
||||
*/
|
||||
public static void fillPropertyMap(Map<String, Object> map, FsContent content) {
|
||||
try {
|
||||
dateFormatter.setTimeZone(TimeZone.getTimeZone(content.getImage().getTimeZone()));
|
||||
} catch (TskException ex) {
|
||||
}
|
||||
map.put(FsContentPropertyType.NAME.toString(), content.getName());
|
||||
map.put(FsContentPropertyType.LOCATION.toString(), DataConversion.getformattedPath(ContentUtils.getDisplayPath(content), 0, 1));
|
||||
map.put(FsContentPropertyType.MOD_TIME.toString(), epochToString(content.getMtime()));
|
||||
map.put(FsContentPropertyType.CHANGED_TIME.toString(), epochToString(content.getCtime()));
|
||||
map.put(FsContentPropertyType.ACCESS_TIME.toString(), epochToString(content.getAtime()));
|
||||
map.put(FsContentPropertyType.CREATED_TIME.toString(), epochToString(content.getCrtime()));
|
||||
map.put(FsContentPropertyType.MOD_TIME.toString(), ContentUtils.getStringTime(content.getMtime(), content));
|
||||
map.put(FsContentPropertyType.CHANGED_TIME.toString(), ContentUtils.getStringTime(content.getCtime(), content));
|
||||
map.put(FsContentPropertyType.ACCESS_TIME.toString(), ContentUtils.getStringTime(content.getAtime(), content));
|
||||
map.put(FsContentPropertyType.CREATED_TIME.toString(), ContentUtils.getStringTime(content.getCrtime(), content));
|
||||
map.put(FsContentPropertyType.SIZE.toString(), content.getSize());
|
||||
map.put(FsContentPropertyType.FLAGS_DIR.toString(), content.getDirFlagsAsString());
|
||||
map.put(FsContentPropertyType.FLAGS_META.toString(), content.getMetaFlagsAsString());
|
||||
@@ -249,12 +245,4 @@ public abstract class AbstractFsContentNode<T extends FsContent> extends Abstrac
|
||||
map.put(FsContentPropertyType.KNOWN.toString(), content.getKnown().getName());
|
||||
map.put(FsContentPropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash());
|
||||
}
|
||||
|
||||
private static String epochToString(long epoch) {
|
||||
String time = "0000-00-00 00:00:00 (UTC)";
|
||||
if (epoch != 0) {
|
||||
time = dateFormatter.format(new java.util.Date(epoch * 1000));
|
||||
}
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,15 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.TimeZone;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
/**
|
||||
@@ -34,6 +38,7 @@ public class ArtifactStringContent implements StringContent {
|
||||
|
||||
BlackboardArtifact wrapped;
|
||||
static final Logger logger = Logger.getLogger(ArtifactStringContent.class.getName());
|
||||
private static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public ArtifactStringContent(BlackboardArtifact art) {
|
||||
wrapped = art;
|
||||
@@ -63,23 +68,33 @@ public class ArtifactStringContent implements StringContent {
|
||||
buffer.append(attr.getAttributeTypeDisplayName());
|
||||
buffer.append("</td>");
|
||||
buffer.append("<td>");
|
||||
switch (attr.getValueType()) {
|
||||
case STRING:
|
||||
buffer.append(attr.getValueString());
|
||||
break;
|
||||
case INTEGER:
|
||||
buffer.append(attr.getValueInt());
|
||||
break;
|
||||
case LONG:
|
||||
buffer.append(attr.getValueLong());
|
||||
break;
|
||||
case DOUBLE:
|
||||
buffer.append(attr.getValueDouble());
|
||||
break;
|
||||
case BYTE:
|
||||
buffer.append(Arrays.toString(attr.getValueBytes()));
|
||||
break;
|
||||
|
||||
if (attr.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()
|
||||
|| attr.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID()) {
|
||||
long epoch = attr.getValueLong();
|
||||
String time = "0000-00-00 00:00:00";
|
||||
if (epoch != 0) {
|
||||
dateFormatter.setTimeZone(getTimeZone(wrapped));
|
||||
time = dateFormatter.format(new java.util.Date(epoch * 1000));
|
||||
}
|
||||
buffer.append(time);
|
||||
} else {
|
||||
switch (attr.getValueType()) {
|
||||
case STRING:
|
||||
buffer.append(attr.getValueString());
|
||||
break;
|
||||
case INTEGER:
|
||||
buffer.append(attr.getValueInt());
|
||||
break;
|
||||
case LONG:
|
||||
buffer.append(attr.getValueLong());
|
||||
break;
|
||||
case DOUBLE:
|
||||
buffer.append(attr.getValueDouble());
|
||||
break;
|
||||
case BYTE:
|
||||
buffer.append(Arrays.toString(attr.getValueBytes()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!"".equals(attr.getContext())) {
|
||||
buffer.append(" (");
|
||||
@@ -96,4 +111,21 @@ public class ArtifactStringContent implements StringContent {
|
||||
return "Error getting content";
|
||||
}
|
||||
}
|
||||
|
||||
private static Content getAssociatedContent(BlackboardArtifact artifact){
|
||||
try {
|
||||
return artifact.getSleuthkitCase().getContentById(artifact.getObjectID());
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.WARNING, "Getting file failed", ex);
|
||||
}
|
||||
throw new IllegalArgumentException("Couldn't get file from database");
|
||||
}
|
||||
|
||||
private static TimeZone getTimeZone(BlackboardArtifact artifact) {
|
||||
try {
|
||||
return TimeZone.getTimeZone(getAssociatedContent(artifact).getImage().getTimeZone());
|
||||
} catch(TskException ex) {
|
||||
return TimeZone.getDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public interface AutopsyItemVisitor<T> {
|
||||
T visit(RecentFiles.RecentFilesFilter rff);
|
||||
T visit(KeywordHits kh);
|
||||
T visit(HashsetHits hh);
|
||||
T visit(EmailExtracted ee);
|
||||
T visit(Images i);
|
||||
T visit(Views v);
|
||||
T visit(Results r);
|
||||
@@ -80,6 +81,11 @@ public interface AutopsyItemVisitor<T> {
|
||||
return defaultVisit(hh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(EmailExtracted ee) {
|
||||
return defaultVisit(ee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(Images i) {
|
||||
return defaultVisit(i);
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
@@ -35,11 +33,6 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Directory;
|
||||
import org.sleuthkit.datamodel.File;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
|
||||
import org.sleuthkit.datamodel.SleuthkitVisitableItem;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
/**
|
||||
@@ -51,7 +44,6 @@ public class BlackboardArtifactNode extends AbstractNode implements DisplayableI
|
||||
BlackboardArtifact artifact;
|
||||
Content associated;
|
||||
static final Logger logger = Logger.getLogger(BlackboardArtifactNode.class.getName());
|
||||
private static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public BlackboardArtifactNode(BlackboardArtifact artifact) {
|
||||
super(Children.LEAF, getLookups(artifact));
|
||||
@@ -105,7 +97,7 @@ public class BlackboardArtifactNode extends AbstractNode implements DisplayableI
|
||||
* @param map, with preserved ordering, where property names/values are put
|
||||
* @param content to extract properties from
|
||||
*/
|
||||
public static void fillPropertyMap(Map<String, Object> map, BlackboardArtifact artifact) {
|
||||
private void fillPropertyMap(Map<String, Object> map, BlackboardArtifact artifact) {
|
||||
try {
|
||||
for(BlackboardAttribute attribute : artifact.getAttributes()){
|
||||
if(attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID())
|
||||
@@ -120,13 +112,7 @@ public class BlackboardArtifactNode extends AbstractNode implements DisplayableI
|
||||
case LONG:
|
||||
if (attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()
|
||||
|| attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID()) {
|
||||
long epoch = attribute.getValueLong();
|
||||
String time = "0000-00-00 00:00:00";
|
||||
if (epoch != 0) {
|
||||
dateFormatter.setTimeZone(getTimeZone(artifact));
|
||||
time = dateFormatter.format(new java.util.Date(epoch * 1000));
|
||||
}
|
||||
map.put(attribute.getAttributeTypeDisplayName(), time);
|
||||
map.put(attribute.getAttributeTypeDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), associated));
|
||||
} else {
|
||||
map.put(attribute.getAttributeTypeDisplayName(), attribute.getValueLong());
|
||||
}
|
||||
@@ -171,14 +157,6 @@ public class BlackboardArtifactNode extends AbstractNode implements DisplayableI
|
||||
throw new IllegalArgumentException("Couldn't get file from database");
|
||||
}
|
||||
|
||||
private static TimeZone getTimeZone(BlackboardArtifact artifact) {
|
||||
try {
|
||||
return TimeZone.getTimeZone(getAssociatedContent(artifact).getImage().getTimeZone());
|
||||
} catch(TskException ex) {
|
||||
return TimeZone.getDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private static HighlightLookup getHighlightLookup(BlackboardArtifact artifact, Content content) {
|
||||
if(artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID())
|
||||
return null;
|
||||
|
||||
@@ -22,8 +22,10 @@ package org.sleuthkit.autopsy.datamodel;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@@ -45,6 +47,7 @@ import org.sleuthkit.datamodel.VolumeSystem;
|
||||
public final class ContentUtils {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(ContentUtils.class.getName());
|
||||
private static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// don't instantiate
|
||||
private ContentUtils() {
|
||||
@@ -64,6 +67,40 @@ public final class ContentUtils {
|
||||
return content.accept(getDisplayPath).toArray(new String[]{});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert epoch seconds to a string value in the given time zone
|
||||
* @param epochSeconds
|
||||
* @param tzone
|
||||
* @return
|
||||
*/
|
||||
public static String getStringTime(long epochSeconds, TimeZone tzone) {
|
||||
String time = "0000-00-00 00:00:00";
|
||||
if (epochSeconds != 0) {
|
||||
dateFormatter.setTimeZone(tzone);
|
||||
time = dateFormatter.format(new java.util.Date(epochSeconds * 1000));
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert epoch seconds to a string value (convenience method)
|
||||
* @param epochSeconds
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
public static String getStringTime(long epochSeconds, Content c) {
|
||||
return getStringTime(epochSeconds, getTimeZone(c));
|
||||
}
|
||||
|
||||
public static TimeZone getTimeZone(Content c) {
|
||||
try {
|
||||
return TimeZone.getTimeZone(c.getImage().getTimeZone());
|
||||
} catch(TskException ex) {
|
||||
return TimeZone.getDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private static final SystemNameVisitor systemName = new SystemNameVisitor();
|
||||
|
||||
private static final GetPathVisitor getSystemPath = new GetPathVisitor(systemName);
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedAccountNode;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedFolderNode;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedRootNode;
|
||||
import org.sleuthkit.autopsy.datamodel.HashsetHits.HashsetHitsRootNode;
|
||||
import org.sleuthkit.autopsy.datamodel.HashsetHits.HashsetHitsSetNode;
|
||||
import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsKeywordNode;
|
||||
@@ -45,6 +48,9 @@ public interface DisplayableItemNodeVisitor<T> {
|
||||
T visit(KeywordHitsKeywordNode khmln);
|
||||
T visit(HashsetHitsRootNode hhrn);
|
||||
T visit(HashsetHitsSetNode hhsn);
|
||||
T visit(EmailExtractedRootNode eern);
|
||||
T visit(EmailExtractedAccountNode eean);
|
||||
T visit(EmailExtractedFolderNode eefn);
|
||||
T visit(ViewsNode vn);
|
||||
T visit(ResultsNode rn);
|
||||
T visit(ImagesNode in);
|
||||
@@ -159,6 +165,21 @@ public interface DisplayableItemNodeVisitor<T> {
|
||||
return defaultVisit(hhsn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(EmailExtractedRootNode eern) {
|
||||
return defaultVisit(eern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(EmailExtractedAccountNode eean) {
|
||||
return defaultVisit(eean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(EmailExtractedFolderNode eefn) {
|
||||
return defaultVisit(eefn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(LayoutFileNode lcn) {
|
||||
return defaultVisit(lcn);
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.ChildFactory;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
/**
|
||||
* Support for TSK_EMAIL_MSG nodes and displaying emails in the directory tree
|
||||
* Email messages are grouped into parent folders, and the folders are grouped into parent accounts
|
||||
* if TSK_PATH is available to define the relationship structure for every message
|
||||
*/
|
||||
public class EmailExtracted implements AutopsyVisitableItem {
|
||||
|
||||
private static final String LABEL_NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getLabel();
|
||||
private static final String DISPLAY_NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getDisplayName();
|
||||
private static final Logger logger = Logger.getLogger(EmailExtracted.class.getName());
|
||||
|
||||
private static final String MAIL_ACCOUNT = "Account";
|
||||
private static final String MAIL_FOLDER = "Folder";
|
||||
|
||||
private static final String MAIL_PATH_SEPARATOR = "/";
|
||||
|
||||
private SleuthkitCase skCase;
|
||||
private Map<String, Map<String,List<Long>>> accounts;
|
||||
|
||||
public EmailExtracted(SleuthkitCase skCase) {
|
||||
this.skCase = skCase;
|
||||
accounts = new LinkedHashMap<String, Map<String,List<Long>>>();
|
||||
}
|
||||
|
||||
private void initArtifacts() {
|
||||
accounts.clear();
|
||||
try {
|
||||
int artId = BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID();
|
||||
int pathAttrId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID();
|
||||
String query = "SELECT value_text,blackboard_attributes.artifact_id,attribute_type_id "
|
||||
+ "FROM blackboard_attributes,blackboard_artifacts WHERE "
|
||||
+ "attribute_type_id=" + pathAttrId
|
||||
+ " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id"
|
||||
+ " AND blackboard_artifacts.artifact_type_id=" + artId;
|
||||
ResultSet rs = skCase.runQuery(query);
|
||||
while(rs.next()){
|
||||
final String path = rs.getString("value_text");
|
||||
final long artifactId = rs.getLong("artifact_id");
|
||||
final Map<String,String> parsedPath = parsePath(path);
|
||||
final String account = parsedPath.get(MAIL_ACCOUNT);
|
||||
final String folder = parsedPath.get(MAIL_FOLDER);
|
||||
|
||||
Map<String,List<Long>> folders = accounts.get(folder);
|
||||
if (folders == null) {
|
||||
folders = new LinkedHashMap<String,List<Long>>();
|
||||
accounts.put(account, folders);
|
||||
}
|
||||
List<Long> messages = folders.get(folder);
|
||||
if (messages == null) {
|
||||
messages = new ArrayList<Long>();
|
||||
folders.put(folder, messages);
|
||||
}
|
||||
messages.add(artifactId);
|
||||
}
|
||||
skCase.closeRunQuery(rs);
|
||||
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "Cannot initialize email extraction", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Map<String,String> parsePath(String path) {
|
||||
Map<String,String> parsed = new HashMap<String,String>();
|
||||
String [] split = path.split(MAIL_PATH_SEPARATOR);
|
||||
if (split.length < 3)
|
||||
return null;
|
||||
|
||||
parsed.put(MAIL_ACCOUNT, split[1]);
|
||||
parsed.put(MAIL_FOLDER, split[2]);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(AutopsyItemVisitor<T> v) {
|
||||
return v.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail root node grouping all mail accounts
|
||||
*/
|
||||
public class EmailExtractedRootNode extends AbstractNode implements DisplayableItemNode{
|
||||
|
||||
public EmailExtractedRootNode() {
|
||||
super(Children.create(new EmailExtractedRootChildren(), true), Lookups.singleton(DISPLAY_NAME));
|
||||
super.setName(LABEL_NAME);
|
||||
super.setDisplayName(DISPLAY_NAME);
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/mail-icon-16.png");
|
||||
initArtifacts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
|
||||
return v.visit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet s = super.createSheet();
|
||||
Sheet.Set ss = s.get(Sheet.PROPERTIES);
|
||||
if (ss == null) {
|
||||
ss = Sheet.createPropertiesSet();
|
||||
s.put(ss);
|
||||
}
|
||||
|
||||
ss.put(new NodeProperty("Name",
|
||||
"Name",
|
||||
"no description",
|
||||
getName()));
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail root child node creating each account node
|
||||
*/
|
||||
private class EmailExtractedRootChildren extends ChildFactory<String> {
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<String> list) {
|
||||
list.addAll(accounts.keySet());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(String key) {
|
||||
return new EmailExtractedAccountNode(key, accounts.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Account node representation
|
||||
*/
|
||||
public class EmailExtractedAccountNode extends AbstractNode implements DisplayableItemNode {
|
||||
|
||||
public EmailExtractedAccountNode(String name, Map<String,List<Long>> children) {
|
||||
super(Children.create(new EmailExtractedAccountChildrenNode(children), true), Lookups.singleton(name));
|
||||
super.setName(name);
|
||||
super.setDisplayName(name + " (" + children.size() + ")");
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/account-icon-16.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet s = super.createSheet();
|
||||
Sheet.Set ss = s.get(Sheet.PROPERTIES);
|
||||
if (ss == null) {
|
||||
ss = Sheet.createPropertiesSet();
|
||||
s.put(ss);
|
||||
}
|
||||
|
||||
ss.put(new NodeProperty("Name",
|
||||
"Name",
|
||||
"no description",
|
||||
getName()));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
|
||||
return v.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Account node child creating sub nodes for every folder
|
||||
*/
|
||||
private class EmailExtractedAccountChildrenNode extends ChildFactory<String> {
|
||||
|
||||
private Map<String,List<Long>> folders;
|
||||
|
||||
private EmailExtractedAccountChildrenNode(Map<String,List<Long>> folders) {
|
||||
super();
|
||||
this.folders = folders;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<String> list) {
|
||||
list.addAll(folders.keySet());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(String key) {
|
||||
return new EmailExtractedFolderNode(key, folders.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node representing mail folder
|
||||
*/
|
||||
public class EmailExtractedFolderNode extends AbstractNode implements DisplayableItemNode {
|
||||
|
||||
public EmailExtractedFolderNode(String name, List<Long> children) {
|
||||
super(Children.create(new EmailExtractedFolderChildrenNode(children), true), Lookups.singleton(name));
|
||||
super.setName(name);
|
||||
super.setDisplayName(name + " (" + children.size() + ")");
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-16.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet s = super.createSheet();
|
||||
Sheet.Set ss = s.get(Sheet.PROPERTIES);
|
||||
if (ss == null) {
|
||||
ss = Sheet.createPropertiesSet();
|
||||
s.put(ss);
|
||||
}
|
||||
|
||||
ss.put(new NodeProperty("Name",
|
||||
"Name",
|
||||
"no description",
|
||||
getName()));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
|
||||
return v.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node representing mail folder content (mail messages)
|
||||
*/
|
||||
private class EmailExtractedFolderChildrenNode extends ChildFactory<BlackboardArtifact> {
|
||||
|
||||
private List<Long> messages;
|
||||
|
||||
private EmailExtractedFolderChildrenNode(List<Long> messages) {
|
||||
super();
|
||||
this.messages = messages; }
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<BlackboardArtifact> list) {
|
||||
for (long l : messages) {
|
||||
try {
|
||||
//TODO: bulk artifact gettings
|
||||
list.add(skCase.getBlackboardArtifact(l));
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.WARNING, "Error creating mail messages nodes", ex);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(BlackboardArtifact artifact) {
|
||||
return new BlackboardArtifactNode(artifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,10 @@ public class RootContentChildren extends AbstractContentChildren {
|
||||
if (o instanceof KeywordHits)
|
||||
this.refreshKey(o);
|
||||
break;
|
||||
case TSK_EMAIL_MSG:
|
||||
if (o instanceof EmailExtracted)
|
||||
this.refreshKey(o);
|
||||
break;
|
||||
default:
|
||||
if (o instanceof ExtractedContent)
|
||||
this.refreshKey(o);
|
||||
@@ -72,6 +76,8 @@ public class RootContentChildren extends AbstractContentChildren {
|
||||
this.refreshKey(o);
|
||||
else if (o instanceof KeywordHits)
|
||||
this.refreshKey(o);
|
||||
else if (o instanceof EmailExtracted)
|
||||
this.refreshKey(o);
|
||||
else if (o instanceof ExtractedContent)
|
||||
this.refreshKey(o);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 32 KiB |
@@ -39,6 +39,9 @@ import org.sleuthkit.autopsy.datamodel.ArtifactTypeNode;
|
||||
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedAccountNode;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedFolderNode;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedRootNode;
|
||||
import org.sleuthkit.autopsy.datamodel.ExtractedContentNode;
|
||||
import org.sleuthkit.autopsy.datamodel.FileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.FileSearchFilterNode;
|
||||
@@ -268,6 +271,21 @@ public class DataResultFilterNode extends FilterNode{
|
||||
return openChild(hhrn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAction visit(EmailExtractedRootNode eern) {
|
||||
return openChild(eern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAction visit(EmailExtractedAccountNode eean) {
|
||||
return openChild(eean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAction visit(EmailExtractedFolderNode eefn) {
|
||||
return openChild(eefn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAction visit(RecentFilesNode rfn) {
|
||||
return openChild(rfn);
|
||||
@@ -318,6 +336,8 @@ public class DataResultFilterNode extends FilterNode{
|
||||
return openChild(khmln);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractAction defaultVisit(DisplayableItemNode c) {
|
||||
return null;
|
||||
|
||||
@@ -26,10 +26,11 @@ import org.sleuthkit.autopsy.datamodel.VolumeNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DirectoryNode;
|
||||
import org.openide.nodes.FilterNode;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.coreutils.Log;
|
||||
import org.sleuthkit.autopsy.datamodel.ArtifactTypeNode;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedAccountNode;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedFolderNode;
|
||||
import org.sleuthkit.autopsy.datamodel.EmailExtracted.EmailExtractedRootNode;
|
||||
import org.sleuthkit.autopsy.datamodel.ExtractedContentNode;
|
||||
import org.sleuthkit.autopsy.datamodel.FileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.FileSearchFilterNode;
|
||||
import org.sleuthkit.autopsy.datamodel.HashsetHits.HashsetHitsRootNode;
|
||||
import org.sleuthkit.autopsy.datamodel.HashsetHits.HashsetHitsSetNode;
|
||||
@@ -37,7 +38,6 @@ import org.sleuthkit.autopsy.datamodel.ImagesNode;
|
||||
import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsKeywordNode;
|
||||
import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsListNode;
|
||||
import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsRootNode;
|
||||
import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
|
||||
import org.sleuthkit.autopsy.datamodel.RecentFilesFilterNode;
|
||||
import org.sleuthkit.autopsy.datamodel.RecentFilesNode;
|
||||
import org.sleuthkit.autopsy.datamodel.ResultsNode;
|
||||
@@ -87,6 +87,9 @@ class DirectoryTreeFilterChildren extends FilterNode.Children {
|
||||
|| arg0 instanceof KeywordHitsRootNode
|
||||
|| arg0 instanceof KeywordHitsListNode
|
||||
|| arg0 instanceof HashsetHitsRootNode
|
||||
|| arg0 instanceof EmailExtractedRootNode
|
||||
|| arg0 instanceof EmailExtractedAccountNode
|
||||
|| arg0 instanceof EmailExtractedFolderNode
|
||||
|| arg0 instanceof ImagesNode
|
||||
|| arg0 instanceof ViewsNode
|
||||
|| arg0 instanceof ResultsNode)) {
|
||||
@@ -99,6 +102,9 @@ class DirectoryTreeFilterChildren extends FilterNode.Children {
|
||||
|| arg0 instanceof RecentFilesFilterNode
|
||||
|| arg0 instanceof FileSearchFilterNode
|
||||
|| arg0 instanceof HashsetHitsSetNode
|
||||
|| arg0 instanceof EmailExtractedRootNode
|
||||
|| arg0 instanceof EmailExtractedAccountNode
|
||||
|| arg0 instanceof EmailExtractedFolderNode
|
||||
)) {
|
||||
return new Node[]{this.copyNode(arg0, false)};
|
||||
} else {
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
OpenIDE-Module-Name=HashDatabase
|
||||
HashDatabaseManagementPanel.okayButton.text=Okay
|
||||
HashDbPanel.fileSelectButton.text=Select...\n
|
||||
HashDbSimplePanel.knownLabel.text=Known files database:
|
||||
HashDbSimplePanel.notableLabel.text=Notable files database:
|
||||
HashDbSimplePanel.knownLabel.text=NSRL Database:
|
||||
HashDbSimplePanel.notableLabel.text=Known Bad Database(s):
|
||||
HashDbSimplePanel.knownValLabel.text=-
|
||||
HashDbSimplePanel.notableValLabel.text=-
|
||||
HashDbMgmtPanel.addNotableButton.text=Add Notable Database
|
||||
HashDbMgmtPanel.addNotableButton.text=Add Known Bad Database
|
||||
HashDbMgmtPanel.removeNotableButton.text=Remove Selected
|
||||
HashDbSimplePanel.jLabel1.text=Notable Hash Databases:
|
||||
HashDbSimplePanel.jLabel2.text=NSRL Hash Database:
|
||||
HashDbMgmtPanel.nsrlNameLabel.text=No NSRL Hashset
|
||||
HashDbSimplePanel.jLabel1.text=Known Bad Database(s):
|
||||
HashDbSimplePanel.jLabel2.text=NSRL Database:
|
||||
HashDbMgmtPanel.nsrlNameLabel.text=Not Configured
|
||||
HashDbMgmtPanel.setNSRLButton.text=Change
|
||||
HashDbSimplePanel.nsrlNameLabel.text=No NSRL database set.
|
||||
HashDbMgmtPanel.jLabel1.text=Notable Hash Databases:
|
||||
HashDbMgmtPanel.jLabel1.text=Known Bad Database(s):
|
||||
HashDbMgmtPanel.jLabel2.text=NSRL Database:
|
||||
HashDbMgmtPanel.indexNSRLButton.text=Index
|
||||
HashDbMgmtPanel.removeNSRLButton.text=Remove
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="257" max="32767" attributes="0"/>
|
||||
<EmptySpace pref="405" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
@@ -27,7 +27,7 @@
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Component id="nsrlNameLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="63" max="32767" attributes="0"/>
|
||||
<EmptySpace pref="218" max="32767" attributes="0"/>
|
||||
<Component id="indexNSRLButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="setNSRLButton" min="-2" max="-2" attributes="0"/>
|
||||
@@ -38,17 +38,17 @@
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane1" alignment="0" pref="389" max="32767" attributes="1"/>
|
||||
<Component id="jScrollPane1" alignment="0" pref="534" max="32767" attributes="1"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="addNotableButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="113" max="32767" attributes="0"/>
|
||||
<EmptySpace pref="242" max="32767" attributes="0"/>
|
||||
<Component id="removeNotableButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="ingestRunningLabel" pref="369" max="32767" attributes="0"/>
|
||||
<Component id="ingestRunningLabel" pref="514" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
@@ -28,9 +28,7 @@ import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.AbstractAction;
|
||||
@@ -95,9 +93,20 @@ public class HashDbMgmtPanel extends javax.swing.JPanel {
|
||||
fc.setMultiSelectionEnabled(false);
|
||||
|
||||
TableColumn column1 = null;
|
||||
final int width1 = jScrollPane1.getPreferredSize().width;
|
||||
for (int i = 0; i < notableHashSetTable.getColumnCount(); i++) {
|
||||
column1 = notableHashSetTable.getColumnModel().getColumn(i);
|
||||
if (i == 0) {
|
||||
column1.setPreferredWidth((int) (width1*.20));
|
||||
}
|
||||
if (i == 1) {
|
||||
column1.setPreferredWidth((int) (width1*.57));
|
||||
}
|
||||
if (i == 2) {
|
||||
column1.setPreferredWidth((int) (width1*.15));
|
||||
}
|
||||
if (i == 3) {
|
||||
column1.setPreferredWidth((int) (width1*.07));
|
||||
column1.setCellRenderer(new CheckBoxRenderer());
|
||||
}
|
||||
}
|
||||
@@ -223,14 +232,14 @@ public class HashDbMgmtPanel extends javax.swing.JPanel {
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(jLabel1)
|
||||
.addContainerGap(257, Short.MAX_VALUE))
|
||||
.addContainerGap(405, Short.MAX_VALUE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addComponent(nsrlNameLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 63, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 218, Short.MAX_VALUE)
|
||||
.addComponent(indexNSRLButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(setNSRLButton)
|
||||
@@ -238,16 +247,16 @@ public class HashDbMgmtPanel extends javax.swing.JPanel {
|
||||
.addComponent(removeNSRLButton))
|
||||
.addComponent(jLabel2))
|
||||
.addContainerGap())
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 389, Short.MAX_VALUE)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 534, Short.MAX_VALUE)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(addNotableButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 113, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 242, Short.MAX_VALUE)
|
||||
.addComponent(removeNotableButton)
|
||||
.addContainerGap())
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(ingestRunningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
|
||||
.addComponent(ingestRunningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 514, Short.MAX_VALUE)
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
@@ -443,7 +452,7 @@ public class HashDbMgmtPanel extends javax.swing.JPanel {
|
||||
case 2:
|
||||
return "Status";
|
||||
default:
|
||||
return "Use For Ingest";
|
||||
return "Ingest";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +568,7 @@ public class HashDbMgmtPanel extends javax.swing.JPanel {
|
||||
theButton.setEnabled(false);
|
||||
break;
|
||||
default:
|
||||
theButton.setText("No DB");
|
||||
theButton.setText("Index");
|
||||
theButton.setEnabled(false);
|
||||
}
|
||||
if (ingestRunning) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.1" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.4" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
@@ -18,21 +18,16 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="142" max="32767" attributes="0"/>
|
||||
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="51" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane1" alignment="1" pref="274" max="32767" attributes="1"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="10" pref="10" max="10" attributes="0"/>
|
||||
<Component id="nsrlNameLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="143" max="32767" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane1" alignment="1" pref="139" max="32767" attributes="1"/>
|
||||
<Component id="jScrollPane2" alignment="0" pref="139" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
@@ -40,11 +35,11 @@
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="nsrlNameLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane2" min="-2" pref="20" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" pref="106" max="32767" attributes="0"/>
|
||||
<Component id="jScrollPane1" pref="98" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@@ -89,12 +84,30 @@
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="nsrlNameLabel">
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/hashdatabase/Bundle.properties" key="HashDbSimplePanel.nsrlNameLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
|
||||
<EmptyBorder/>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTable" name="jTable1">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="f0" green="f0" red="f0" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="showHorizontalLines" type="boolean" value="false"/>
|
||||
<Property name="showVerticalLines" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.hashdatabase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
@@ -39,12 +37,14 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(HashDbSimplePanel.class.getName());
|
||||
private HashTableModel knownBadTableModel;
|
||||
private NSRLTableModel nsrlTableModel;
|
||||
private HashDb nsrl;
|
||||
private static boolean ingestRunning = false;
|
||||
|
||||
/** Creates new form HashDbSimplePanel */
|
||||
public HashDbSimplePanel() {
|
||||
knownBadTableModel = new HashTableModel();
|
||||
nsrlTableModel = new NSRLTableModel();
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
}
|
||||
@@ -55,18 +55,26 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
|
||||
private void customizeComponents() {
|
||||
notableHashTable.setModel(knownBadTableModel);
|
||||
jTable1.setModel(nsrlTableModel);
|
||||
|
||||
notableHashTable.setTableHeader(null);
|
||||
jTable1.setTableHeader(null);
|
||||
notableHashTable.setRowSelectionAllowed(false);
|
||||
jTable1.setRowSelectionAllowed(false);
|
||||
//customize column witdhs
|
||||
final int width1 = jScrollPane1.getPreferredSize().width;
|
||||
final int width2 = jScrollPane2.getPreferredSize().width;
|
||||
TableColumn column1 = null;
|
||||
TableColumn column2 = null;
|
||||
for (int i = 0; i < notableHashTable.getColumnCount(); i++) {
|
||||
column1 = notableHashTable.getColumnModel().getColumn(i);
|
||||
column2 = jTable1.getColumnModel().getColumn(i);
|
||||
if (i == 0) {
|
||||
column1.setPreferredWidth(((int) (width1 * 0.15)));
|
||||
column2.setPreferredWidth(((int) (width2 * 0.15)));
|
||||
} else {
|
||||
column1.setPreferredWidth(((int) (width1 * 0.84)));
|
||||
column2.setPreferredWidth(((int) (width2 * 0.84)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +94,8 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
notableHashTable = new javax.swing.JTable();
|
||||
jLabel1 = new javax.swing.JLabel();
|
||||
jLabel2 = new javax.swing.JLabel();
|
||||
nsrlNameLabel = new javax.swing.JLabel();
|
||||
jScrollPane2 = new javax.swing.JScrollPane();
|
||||
jTable1 = new javax.swing.JTable();
|
||||
|
||||
jScrollPane1.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
|
||||
|
||||
@@ -99,7 +108,12 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
|
||||
jLabel2.setText(org.openide.util.NbBundle.getMessage(HashDbSimplePanel.class, "HashDbSimplePanel.jLabel2.text")); // NOI18N
|
||||
|
||||
nsrlNameLabel.setText(org.openide.util.NbBundle.getMessage(HashDbSimplePanel.class, "HashDbSimplePanel.nsrlNameLabel.text")); // NOI18N
|
||||
jScrollPane2.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
|
||||
|
||||
jTable1.setBackground(new java.awt.Color(240, 240, 240));
|
||||
jTable1.setShowHorizontalLines(false);
|
||||
jTable1.setShowVerticalLines(false);
|
||||
jScrollPane2.setViewportView(jTable1);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
@@ -107,28 +121,25 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(jLabel1)
|
||||
.addContainerGap(142, Short.MAX_VALUE))
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 274, Short.MAX_VALUE)
|
||||
.addComponent(jLabel2)
|
||||
.addContainerGap(51, Short.MAX_VALUE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addComponent(nsrlNameLabel))
|
||||
.addComponent(jLabel2))
|
||||
.addContainerGap(143, Short.MAX_VALUE))
|
||||
.addComponent(jLabel1)
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 139, Short.MAX_VALUE)
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 139, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel2)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(nsrlNameLabel)
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jLabel1)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 106, Short.MAX_VALUE))
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 98, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@@ -136,19 +147,46 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
private javax.swing.JLabel jLabel1;
|
||||
private javax.swing.JLabel jLabel2;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
private javax.swing.JScrollPane jScrollPane2;
|
||||
private javax.swing.JTable jTable1;
|
||||
private javax.swing.JTable notableHashTable;
|
||||
private javax.swing.JLabel nsrlNameLabel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
private void reloadSets() {
|
||||
nsrl = HashDbXML.getCurrent().getNSRLSet();
|
||||
if(nsrl == null) {
|
||||
nsrlNameLabel.setText("No NSRL database set.");
|
||||
} else {
|
||||
nsrlNameLabel.setText(nsrl.getName());
|
||||
}
|
||||
nsrlTableModel.resync();
|
||||
knownBadTableModel.resync();
|
||||
}
|
||||
|
||||
private class NSRLTableModel extends AbstractTableModel {
|
||||
|
||||
private void resync() {
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
if (columnIndex == 0) {
|
||||
return "";
|
||||
} else {
|
||||
if(nsrl == null) {
|
||||
return "Not Configured";
|
||||
} else {
|
||||
return nsrl.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class HashTableModel extends AbstractTableModel {
|
||||
|
||||
@@ -160,7 +198,8 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return xmlHandle.getKnownBadSets().size();
|
||||
int size = xmlHandle.getKnownBadSets().size();
|
||||
return size == 0 ? 1 : size;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,11 +209,19 @@ public class HashDbSimplePanel extends javax.swing.JPanel {
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
HashDb db = xmlHandle.getKnownBadSets().get(rowIndex);
|
||||
if(columnIndex == 0) {
|
||||
return db.getUseForIngest();
|
||||
if (xmlHandle.getKnownBadSets().isEmpty()) {
|
||||
if (columnIndex == 0) {
|
||||
return "";
|
||||
} else {
|
||||
return "Not Configured";
|
||||
}
|
||||
} else {
|
||||
return db.getName();
|
||||
HashDb db = xmlHandle.getKnownBadSets().get(rowIndex);
|
||||
if (columnIndex == 0) {
|
||||
return db.getUseForIngest();
|
||||
} else {
|
||||
return db.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,15 +30,13 @@ import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.datamodel.File;
|
||||
import org.sleuthkit.datamodel.FileSystem;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Directory;
|
||||
import org.sleuthkit.datamodel.LayoutFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.TskData.FileKnown;
|
||||
|
||||
/**
|
||||
* Visitor for getting all the files to try to index from any Content object.
|
||||
* Currently gets all non-zero files.
|
||||
* TODO should be moved to utility module (needs resolve cyclic deps)
|
||||
* Visitor for getting all the files/unalloc files / dirs to ingest
|
||||
*/
|
||||
class GetAllFilesContentVisitor extends GetFilesContentVisitor {
|
||||
|
||||
@@ -54,6 +52,11 @@ class GetAllFilesContentVisitor extends GetFilesContentVisitor {
|
||||
return Collections.<AbstractFile>singleton(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<AbstractFile> visit(Directory drctr) {
|
||||
return Collections.<AbstractFile>singleton(drctr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<AbstractFile> visit(LayoutFile lf) {
|
||||
return Collections.<AbstractFile>singleton(lf);
|
||||
@@ -67,9 +70,12 @@ class GetAllFilesContentVisitor extends GetFilesContentVisitor {
|
||||
SleuthkitCase sc = Case.getCurrentCase().getSleuthkitCase();
|
||||
|
||||
StringBuilder queryB = new StringBuilder();
|
||||
queryB.append("SELECT * FROM tsk_files WHERE fs_obj_id = ").append(fs.getId());
|
||||
queryB.append(" AND (meta_type = ").append(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getMetaType());
|
||||
queryB.append(") AND (size > 0)");
|
||||
queryB.append("SELECT * FROM tsk_files WHERE ( (fs_obj_id = ").append(fs.getId());
|
||||
queryB.append(") OR (fs_obj_id = NULL) ) AND (size > 0)");
|
||||
queryB.append(" AND ( (meta_type = ").append(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getMetaType());
|
||||
queryB.append(") OR (meta_type = ").append(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getMetaType());
|
||||
queryB.append( " AND (name != '.') AND (name != '..')");
|
||||
queryB.append(") )");
|
||||
if (getUnallocatedFiles == false) {
|
||||
queryB.append( "AND (type = ");
|
||||
queryB.append(TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType());
|
||||
@@ -77,7 +83,9 @@ class GetAllFilesContentVisitor extends GetFilesContentVisitor {
|
||||
}
|
||||
|
||||
try {
|
||||
ResultSet rs = sc.runQuery(queryB.toString());
|
||||
final String query = queryB.toString();
|
||||
logger.log(Level.INFO, "Executing query: " + query);
|
||||
ResultSet rs = sc.runQuery(query);
|
||||
List<AbstractFile> contents = sc.resultSetToAbstractFiles(rs);
|
||||
Statement s = rs.getStatement();
|
||||
rs.close();
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="servicesScrollPane" pref="173" max="32767" attributes="1"/>
|
||||
<Component id="servicesScrollPane" pref="169" max="32767" attributes="1"/>
|
||||
<Component id="timePanel" alignment="0" max="32767" attributes="1"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
@@ -44,7 +44,7 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jPanel1" alignment="0" pref="235" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="servicesScrollPane" pref="90" max="32767" attributes="0"/>
|
||||
<Component id="servicesScrollPane" pref="82" max="32767" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="timePanel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
@@ -175,34 +175,30 @@
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="timeLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="68" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="timeRadioButton2" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="timeRadioButton3" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="timeLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="processUnallocCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="timeRadioButton1" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="timeRadioButton3" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="timeRadioButton2" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="timeRadioButton1" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="processUnallocCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="10" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="processUnallocCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="15" max="32767" attributes="0"/>
|
||||
<Component id="timeLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="timeRadioButton1" min="-2" max="-2" attributes="0"/>
|
||||
@@ -210,7 +206,9 @@
|
||||
<Component id="timeRadioButton2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="timeRadioButton3" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="processUnallocCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="8" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
|
||||
@@ -289,27 +289,25 @@ public class IngestDialogPanel extends javax.swing.JPanel implements IngestConfi
|
||||
timePanelLayout.setHorizontalGroup(
|
||||
timePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(timePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(timeLabel)
|
||||
.addContainerGap(68, Short.MAX_VALUE))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, timePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(timePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(timePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(timePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(timeRadioButton2)
|
||||
.addComponent(timeRadioButton3)))
|
||||
.addGroup(timePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(timeLabel))
|
||||
.addComponent(processUnallocCheckbox)
|
||||
.addGroup(timePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(timeRadioButton1)))
|
||||
.addComponent(timeRadioButton3)
|
||||
.addComponent(timeRadioButton2)
|
||||
.addComponent(timeRadioButton1))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGroup(timePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(processUnallocCheckbox)
|
||||
.addContainerGap(10, Short.MAX_VALUE))
|
||||
);
|
||||
timePanelLayout.setVerticalGroup(
|
||||
timePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, timePanelLayout.createSequentialGroup()
|
||||
.addGroup(timePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(processUnallocCheckbox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 15, Short.MAX_VALUE)
|
||||
.addComponent(timeLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(timeRadioButton1)
|
||||
@@ -317,7 +315,9 @@ public class IngestDialogPanel extends javax.swing.JPanel implements IngestConfi
|
||||
.addComponent(timeRadioButton2)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(timeRadioButton3)
|
||||
.addContainerGap())
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(processUnallocCheckbox)
|
||||
.addContainerGap(8, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
@@ -327,7 +327,7 @@ public class IngestDialogPanel extends javax.swing.JPanel implements IngestConfi
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(servicesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 173, Short.MAX_VALUE)
|
||||
.addComponent(servicesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 169, Short.MAX_VALUE)
|
||||
.addComponent(timePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
@@ -340,7 +340,7 @@ public class IngestDialogPanel extends javax.swing.JPanel implements IngestConfi
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 235, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(servicesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 90, Short.MAX_VALUE)
|
||||
.addComponent(servicesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 82, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(timePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addContainerGap())
|
||||
|
||||
@@ -21,35 +21,57 @@ package org.sleuthkit.autopsy.ingest;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
|
||||
/**
|
||||
* passed to the service as a limited way to control this worker
|
||||
* update progress bar, check if job is cancelled
|
||||
* Controller for image level ingest services
|
||||
* Used by services to check task status and to post progress to
|
||||
*/
|
||||
public class IngestImageWorkerController {
|
||||
|
||||
private IngestImageThread worker;
|
||||
private ProgressHandle progress;
|
||||
|
||||
public IngestImageWorkerController(IngestImageThread worker, ProgressHandle progress) {
|
||||
/**
|
||||
* Instantiate the controller for the worker
|
||||
* @param worker underlying image ingest thread
|
||||
* @param progress the progress handle
|
||||
*/
|
||||
IngestImageWorkerController(IngestImageThread worker, ProgressHandle progress) {
|
||||
this.worker = worker;
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task has been cancelled. This should be polled by the service periodically
|
||||
* And the service needs to act, i.e. break out of its processing loop and call its stop() to cleanup
|
||||
*
|
||||
* @return true if the task has been cancelled, false otherwise
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return worker.isCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress bar and switch to determinate mode once number of total work units is known
|
||||
* @param workUnits total number of work units for the image ingest task
|
||||
*/
|
||||
public void switchToDeterminate(int workUnits) {
|
||||
if (progress != null) {
|
||||
progress.switchToDeterminate(workUnits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress bar and switch to non determinate mode if number of work units is not known
|
||||
*/
|
||||
public void switchToInDeterminate() {
|
||||
if (progress != null) {
|
||||
progress.switchToIndeterminate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress bar with the number of work units performed, if in the determinate mode
|
||||
* @param workUnits number of work units performed so far by the service
|
||||
*/
|
||||
public void progress(int workUnits) {
|
||||
if (progress != null) {
|
||||
progress.progress(worker.getImage().getName(), workUnits);
|
||||
|
||||
@@ -314,6 +314,14 @@ public class IngestManager {
|
||||
|
||||
for (IngestImageThread imageWorker : toStop) {
|
||||
IngestServiceImage s = imageWorker.getService();
|
||||
|
||||
//stop the worker thread if thread is running
|
||||
boolean cancelled = imageWorker.cancel(true);
|
||||
if (!cancelled) {
|
||||
logger.log(Level.INFO, "Unable to cancel image ingest worker for service: " + imageWorker.getService().getName() + " img: " + imageWorker.getImage().getName());
|
||||
}
|
||||
|
||||
//stop notification to service to cleanup resources
|
||||
if (isServiceRunning(s)) {
|
||||
try {
|
||||
imageWorker.getService().stop();
|
||||
@@ -321,10 +329,7 @@ public class IngestManager {
|
||||
logger.log(Level.WARNING, "Exception while stopping service: " + s.getName(), e);
|
||||
}
|
||||
}
|
||||
boolean cancelled = imageWorker.cancel(true);
|
||||
if (!cancelled) {
|
||||
logger.log(Level.WARNING, "Unable to cancel image ingest worker for service: " + imageWorker.getService().getName() + " img: " + imageWorker.getImage().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, "stopped all");
|
||||
@@ -955,15 +960,9 @@ public class IngestManager {
|
||||
stats.start();
|
||||
|
||||
//notify main thread services started
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (IngestServiceAbstractFile s : AbstractFileServices) {
|
||||
IngestManager.fireServiceEvent(SERVICE_STARTED_EVT, s.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
for (IngestServiceAbstractFile s : AbstractFileServices) {
|
||||
IngestManager.fireServiceEvent(SERVICE_STARTED_EVT, s.getName());
|
||||
}
|
||||
|
||||
final String displayName = "File Ingest";
|
||||
progress = ProgressHandleFactory.createHandle(displayName, new Cancellable() {
|
||||
@@ -1036,7 +1035,10 @@ public class IngestManager {
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
Date d1 = new Date();
|
||||
super.get(); //block and get all exceptions thrown while doInBackground()
|
||||
Date d2 = new Date();
|
||||
logger.log(Level.INFO, "File ingest get() took: " + (d2.getTime()-d1.getTime()) );
|
||||
//notify services of completion
|
||||
if (!this.isCancelled()) {
|
||||
for (IngestServiceAbstractFile s : AbstractFileServices) {
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<Property name="displayName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/ingest/Bundle.properties" key="IngestMessageTopComponent.displayName" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="name" type="java.lang.String" value="Ingest Inbox" noResource="true"/>
|
||||
<Property name="name" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<PlainString value="Ingest Inbox"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
|
||||
@@ -145,6 +145,7 @@ public final class IngestMessageTopComponent extends TopComponent implements Ing
|
||||
for (int i = 0; i < tcs.length; ++i) {
|
||||
if (tcs[i] == this) //already floating
|
||||
{
|
||||
this.open();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,51 +19,66 @@
|
||||
|
||||
package org.sleuthkit.autopsy.ingest;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
||||
/**
|
||||
* Base interface for ingest services
|
||||
*/
|
||||
public interface IngestServiceAbstract {
|
||||
|
||||
public enum ServiceType {Image, AbstractFile};
|
||||
/**
|
||||
* Possible service types for the implementing classes
|
||||
*/
|
||||
public enum ServiceType {
|
||||
/**
|
||||
* Image type service
|
||||
*/
|
||||
Image,
|
||||
|
||||
/**
|
||||
* AbstractFile type service
|
||||
*/
|
||||
AbstractFile
|
||||
};
|
||||
|
||||
/**
|
||||
* notification from manager that brand new processing should be initiated.
|
||||
* Notification from manager that brand new ingest should be initiated.
|
||||
* Service loads its configuration and performs initialization
|
||||
* called once per new worker thread
|
||||
* Invoked once per new worker thread, per ingest
|
||||
*
|
||||
* @param IngestManagerProxy interface to manager for posting messages, getting configurations
|
||||
* @param IngestManagerProxy manager facade for posting messages, getting configurations
|
||||
*/
|
||||
public void init(IngestManagerProxy managerProxy);
|
||||
|
||||
/**
|
||||
* notification from manager that there is no more content to process and all work is done.
|
||||
* Service performs any clean-up, notifies viewers and may also write results to the black-board
|
||||
* Notification from manager that there is no more content to process and all work is done.
|
||||
* Service performs any clean-up of internal resources, and finalizes processing to produce complete result
|
||||
* Service also posts ingest message indicating it is done, and posts ingest stats and errors in the details of the message.
|
||||
*/
|
||||
public void complete();
|
||||
|
||||
/**
|
||||
* notification from manager to stop processing due to some interruption (user, error, exception)
|
||||
* Notification from manager to stop processing due to some interruption (user, error, exception)
|
||||
* Service performs any clean-up of internal resources
|
||||
* It may also discard any pending results, but it should ensure it is in a defined state so that ingest can be rerun later.
|
||||
*/
|
||||
public void stop();
|
||||
|
||||
/**
|
||||
* get specific name of the service
|
||||
* should be unique across services, a user-friendly name of the service shown in GUI
|
||||
* Gets specific name of the service
|
||||
* The name should be unique across services
|
||||
* @return unique service name
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* get user-friendly description of the service
|
||||
* Gets user-friendly description of the service
|
||||
* @return service description
|
||||
*/
|
||||
public String getDescription();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return specialization of the service
|
||||
* Returns type of the service
|
||||
* @return service type
|
||||
*/
|
||||
public ServiceType getType();
|
||||
|
||||
@@ -72,50 +87,59 @@ public interface IngestServiceAbstract {
|
||||
* This method provides insight to the manager if the service has truly completed its work or not.
|
||||
*
|
||||
*
|
||||
* @return true if any background threads/workers managed by this service are still running
|
||||
* false if all work has been done, or if background threads are not managed by this service
|
||||
* @return true if any background threads/workers managed by this service are still running or are pending to be run,
|
||||
* false if all work has been done, or if background threads are not used/managed by this service
|
||||
*/
|
||||
public boolean hasBackgroundJobsRunning();
|
||||
|
||||
|
||||
/**
|
||||
* @return does this service have a simple configuration?
|
||||
* There are 2 levels of configuration a service can implement: simple and advanced.
|
||||
* Provides info if the module implements simple configuration.
|
||||
*
|
||||
* @return true if this service has a simple configuration
|
||||
*/
|
||||
public boolean hasSimpleConfiguration();
|
||||
|
||||
/**
|
||||
* @return does this service have advanced configuration?
|
||||
* There are 2 levels of configuration a service can implement: simple and advanced.
|
||||
* Provides info if the module implements advanced configuration.
|
||||
*
|
||||
* @return true if this service has an advanced configuration
|
||||
*/
|
||||
public boolean hasAdvancedConfiguration();
|
||||
|
||||
/**
|
||||
* Opportunity for the module to save its configuration options from from the getSimpleConfiguration() JPanel into the module
|
||||
* This is invoked by the framework e.g. when simple configuration panel is going out of scope
|
||||
* If module implements simple configuration panel
|
||||
* it should read its current state and make it persistent / save it in this method
|
||||
* so that the new configuration will be in effect during the ingest.
|
||||
*/
|
||||
public void saveSimpleConfiguration();
|
||||
|
||||
/** Opportunity for the module to save its configuration options from from the getAdvancedConfiguration() JPanel into the module
|
||||
* This is invoked by the framework e.g. when advanced configuration dialog is going out of scope
|
||||
/**
|
||||
* If module implements advanced configuration panel
|
||||
* it should read its current state and make it persistent / save it in this method
|
||||
* so that the new configuration will be in effect during the ingest.
|
||||
*/
|
||||
public void saveAdvancedConfiguration();
|
||||
|
||||
/**
|
||||
* Provides basic module configuration to the user (available e.g. via the add image wizard)
|
||||
* Only basic configuration should be exposed in this panel due to its size limitation
|
||||
* Implements simple module configuration exposed to the user before ingest starts
|
||||
* Only basic, most frequently used configuration options should be exposed in this panel due to size limitation
|
||||
* More options, if any, should be available via userConfigureAdvanced()
|
||||
* The module is responsible for preserving / saving its configuration state
|
||||
* In addition, userConfigureSave() can be used
|
||||
* In addition, saveSimpleConfiguration() can be used
|
||||
*
|
||||
* @return JPanel containing basic configuration widgets or null
|
||||
* @return JPanel containing basic configuration widgets or null if simple configuration is not available
|
||||
*/
|
||||
public javax.swing.JPanel getSimpleConfiguration();
|
||||
|
||||
/**
|
||||
* Provides advanced module configuration to the user (available e.g. via the add image wizard)
|
||||
* Implements advanced module configuration exposed to the user before ingest starts
|
||||
* The module is responsible for preserving / saving its configuration state
|
||||
* In addition, userConfigureAdvancedSave() can be used
|
||||
* In addition, saveAdvancedConfiguration() can be used
|
||||
*
|
||||
* @return JPanel containing basic configuration widgets or null
|
||||
* @return JPanel containing advanced configuration widgets or null if advanced configuration is not available
|
||||
*/
|
||||
public javax.swing.JPanel getAdvancedConfiguration();
|
||||
}
|
||||
|
||||
@@ -21,11 +21,15 @@ package org.sleuthkit.autopsy.ingest;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
/**
|
||||
* ingest service that acts on every FsContent in image
|
||||
*
|
||||
* Ingest service interface that acts on every AbstractFile in the image
|
||||
*/
|
||||
public interface IngestServiceAbstractFile extends IngestServiceAbstract {
|
||||
|
||||
/**
|
||||
* Return value resulting from processing AbstractFile
|
||||
* Can be used by manager to stop processing the file, or by subsequent service
|
||||
* in the pipeline as a hint to stop processing the file
|
||||
*/
|
||||
public enum ProcessResult {
|
||||
UNKNOWN, //values unknown for the (service,last file)
|
||||
OK, //subsequent service continues processing the file
|
||||
@@ -35,10 +39,21 @@ public interface IngestServiceAbstractFile extends IngestServiceAbstract {
|
||||
};
|
||||
|
||||
/**
|
||||
* notification from manager to process file / directory.
|
||||
* Service may choose to perform an action or enqueue processing of a group of FsContents.
|
||||
* The service notifies viewers via IngestManager.postMessage()
|
||||
* and may also write results to the black-board as it is processing
|
||||
* Entry point to process file / directory by the service.
|
||||
*
|
||||
* Service does all the processing work in this method.
|
||||
* It may choose to skip the file if the file is not of interest to the service.
|
||||
* Results of processing, such as extracted data or analysis results should be posted to the blackboard.
|
||||
*
|
||||
* In a more advanced module, the module can enqueue the file
|
||||
* and postpone processing until more files of interest are available.
|
||||
*
|
||||
* The service notifies the ingest inbox of interesting events (data, errors, warnings, infos)
|
||||
* by posting ingest messages
|
||||
* The service notifies data viewers by firing events using IngestManager.fireServiceDataEvent
|
||||
*
|
||||
* @param abstractFile file to process
|
||||
* @return ProcessResult result of the processing that can be used in the pipeline as a hint whether to further process this file
|
||||
*/
|
||||
public ProcessResult process(AbstractFile abstractFile);
|
||||
}
|
||||
|
||||
@@ -21,18 +21,30 @@ package org.sleuthkit.autopsy.ingest;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
|
||||
/**
|
||||
* ingest service that acts on entire image (such as Internet history)
|
||||
*
|
||||
* Ingest service that acts on entire image
|
||||
* Image ingest services run each in its own background thread
|
||||
* in parallel to the file processing ingest pipeline and other image ingest modules
|
||||
*/
|
||||
public interface IngestServiceImage extends IngestServiceAbstract {
|
||||
|
||||
|
||||
/**
|
||||
* notification from manager to process image
|
||||
* The service notifies viewers via IngestManager.postMessage()
|
||||
* and may also write results to the black-board as it is processing.
|
||||
* Entry point to process the image by the service.
|
||||
*
|
||||
* @param image image to process
|
||||
* @param controller controller to the worker, to update progress (if determinate) and check if cancelled
|
||||
* Service does all the processing work in this method.
|
||||
* It is responsible for extracting content of interest from the image (i.e. using DataModel API) and processing it.
|
||||
* Results of processing, such as extracted data or analysis results, should be posted to the blackboard.
|
||||
*
|
||||
* The service notifies the ingest inbox of interesting events (data, errors, warnings, infos)
|
||||
* by posting ingest messages
|
||||
* The service notifies data viewers by firing events using IngestManager.fireServiceDataEvent
|
||||
*
|
||||
* The service is responsible for posting progress to controller
|
||||
* And to periodically check controller if it should break out of the processing loop because task has been cancelled
|
||||
*
|
||||
* @param image to process
|
||||
* @param controller to post progress to and to use for checking if cancellation has occurred
|
||||
*/
|
||||
public void process(Image image, IngestImageWorkerController controller);
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -15,7 +15,7 @@
|
||||
|
||||
<h2>Ingest Modules</h2>
|
||||
<p>
|
||||
An ingest module is resposible for extracting data from and searching images. Different modules will do different things. Examples include:
|
||||
An ingest module is responsible for extracting data from and searching images. Different modules will do different things. Examples include:
|
||||
<ul>
|
||||
<li>Calculate MD5 hash of each file</li>
|
||||
<li>Lookup MD5 hash in database</li>
|
||||
@@ -23,7 +23,7 @@
|
||||
<li>Keyword search each file</li>
|
||||
<li>Extract web artifacts (downloads, history, etc.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
|
||||
<p>Ingest modules can be created by third-party-developers and can be added independently of Autopsy. </p>
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
There are two places to configure ingest modules. When the Ingest Manager is launched so that you can choose which ingest modules to run, there maybe a small set of configuration changes that the module allows you to set from that interface. Additional configuration is typically available from a separate dialog box that can be opened from either the "Tools" menu or with the "Advanced" button in the Ingest Manager.
|
||||
|
||||
<h2>Adding Ingest Modules</h2>
|
||||
<p>ADD HERE</p>
|
||||
Not yet supported.
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<!DOCTYPE helpset PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN" "http://java.sun.com/products/javahelp/helpset_2_0.dtd">
|
||||
<helpset version="2.0">
|
||||
<title>org.sleuthkit.autopsy.ingest Help</title>
|
||||
<title>Ingest Help</title>
|
||||
<maps>
|
||||
<homeID>org.sleuthkit.autopsy.ingest.about</homeID>
|
||||
<mapref location="ingest-map.xml"/>
|
||||
|
||||
@@ -6,4 +6,5 @@ and open the template in the editor.
|
||||
<!DOCTYPE index PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 2.0//EN" "http://java.sun.com/products/javahelp/index_2_0.dtd">
|
||||
<index version="2.0">
|
||||
<indexitem text="About Ingest" target="org.sleuthkit.autopsy.ingest.about"/>
|
||||
<indexitem text="Message Inbox" target="org.sleuthkit.autopsy.ingest.inbox"/>
|
||||
</index>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Message Inbox</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Message Inbox</h2>
|
||||
<p>
|
||||
The message inbox is used by Autopsy to provide real-time updates during ingest.
|
||||
To open the inbox, click on the yellow warning sign in the top/right corner of the Autopsy window.
|
||||
</p>
|
||||
<p>
|
||||
<img src="inbox-button.png" alt="Inbox button" />
|
||||
</p>
|
||||
<p>
|
||||
Ingest modules are able to post messages when notable events occur, such as a keyword or hash database hit.
|
||||
If a module posts many similar messages in a short time span,
|
||||
the inbox will group those messages so that unique updates are not lost among the noise.
|
||||
When updates are posted with regard to a specific result or file, the message is linked to that file
|
||||
and the buttons in the top/right corner of the inbox can be used to browse to that data.
|
||||
</p>
|
||||
<img src="inbox-main.PNG" alt="Inbox Main Screen" /><br /><br />
|
||||
<img src="inbox-details.PNG" alt="Inbox Details Screen" />
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,4 +6,5 @@ and open the template in the editor.
|
||||
<!DOCTYPE map PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN" "http://java.sun.com/products/javahelp/map_2_0.dtd">
|
||||
<map version="2.0">
|
||||
<mapID target="org.sleuthkit.autopsy.ingest.about" url="ingest-about.html"/>
|
||||
<mapID target="org.sleuthkit.autopsy.ingest.inbox" url="ingest-inbox.html"/>
|
||||
</map>
|
||||
|
||||
@@ -5,7 +5,8 @@ and open the template in the editor.
|
||||
-->
|
||||
<!DOCTYPE toc PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 2.0//EN" "http://java.sun.com/products/javahelp/toc_2_0.dtd">
|
||||
<toc version="2.0">
|
||||
<tocitem text="org.sleuthkit.autopsy.ingest">
|
||||
<tocitem text="Ingest">
|
||||
<tocitem text="About Ingest" target="org.sleuthkit.autopsy.ingest.about"/>
|
||||
<tocitem text="Message Inbox" target="org.sleuthkit.autopsy.ingest.inbox"/>
|
||||
</tocitem>
|
||||
</toc>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
Known issues and limitations
|
||||
Known Issues
|
||||
|
||||
We plan to address the following issues in future releases.
|
||||
Last Reviewed: June 12, 2012
|
||||
|
||||
General:
|
||||
- Only a single instance of the application can be started at once.
|
||||
There is no check if another instance is already running. Running a second instance will cause issues.
|
||||
- Only a single case can be opened at a time.
|
||||
|
||||
Keyword search module:
|
||||
- Keyword search module does not currently search unallocated space,
|
||||
- Keyword search maximum size of files of known types to be indexed and searched is 100MB. There is no limit on size of unknown file types indexed using string extraction.
|
||||
This lists the bugs and issues thare are known and could effect
|
||||
investigation results. There are other minor interface bugs that
|
||||
are not listed here.
|
||||
|
||||
Keyword Search module:
|
||||
- Slack space of files is not added to the index and therefore will
|
||||
not be searched.
|
||||
- Files larger than 100MB AND that are file types that are supported
|
||||
by Tika (word docs, PDF, HTML, JPEG, etc.) are not being added to
|
||||
the index.
|
||||
- For unknown file types, we extract UTF-8 (Ascii) and UTF-16 English
|
||||
strings. No non-English strings are extracted.
|
||||
- Comments and java script in HTML files are not being added to index.
|
||||
|
||||
@@ -3,6 +3,6 @@ build.xml.script.CRC32=87b97b04
|
||||
build.xml.stylesheet.CRC32=a56c6a5b@1.46.2
|
||||
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
|
||||
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
|
||||
nbproject/build-impl.xml.data.CRC32=d7ecf067
|
||||
nbproject/build-impl.xml.data.CRC32=ab518119
|
||||
nbproject/build-impl.xml.script.CRC32=fe1f48d2
|
||||
nbproject/build-impl.xml.stylesheet.CRC32=238281d1@1.46.2
|
||||
|
||||
@@ -15,6 +15,15 @@
|
||||
<specification-version>1.24.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.modules.javahelp</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<release-version>1</release-version>
|
||||
<specification-version>2.22.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.modules.settings</code-name-base>
|
||||
<build-prerequisite/>
|
||||
|
||||
@@ -448,8 +448,8 @@
|
||||
this cache will not be autowarmed.
|
||||
-->
|
||||
<documentCache class="solr.LRUCache"
|
||||
size="512"
|
||||
initialSize="512"
|
||||
size="16"
|
||||
initialSize="16"
|
||||
autowarmCount="0"/>
|
||||
|
||||
<!-- Field Value Cache
|
||||
@@ -521,12 +521,12 @@
|
||||
then documents 0 through 49 will be collected and cached. Any further
|
||||
requests in that range can be satisfied via the cache.
|
||||
-->
|
||||
<queryResultWindowSize>20</queryResultWindowSize>
|
||||
<queryResultWindowSize>5</queryResultWindowSize>
|
||||
|
||||
<!-- Maximum number of documents to cache for any entry in the
|
||||
queryResultCache.
|
||||
-->
|
||||
<queryResultMaxDocsCached>200</queryResultMaxDocsCached>
|
||||
<queryResultMaxDocsCached>16</queryResultMaxDocsCached>
|
||||
|
||||
<!-- Query Related Event Listeners
|
||||
|
||||
|
||||
@@ -50,5 +50,5 @@ ExtractedContentPanel.pageTotalLabel.text=-
|
||||
ExtractedContentPanel.hitLabel.toolTipText=
|
||||
KeywordSearchEditListPanel.ingestMessagesCheckbox.text=Send messages during triage / ingest
|
||||
KeywordSearchEditListPanel.ingestMessagesCheckbox.toolTipText=Send messages during triage / ingest when hits on keyword from this list occur
|
||||
KeywordSearchIngestSimplePanel.skipKnownCheckBox.text=Skip files in NSRL
|
||||
KeywordSearchIngestSimplePanel.skipKnownCheckBox.toolTipText=Please make sure you have either selected to run or have previously run the Hash DB Ingest Service.
|
||||
KeywordSearchListsManagementPanel.skipNSRLCheckBox.text=Skip files in NSRL
|
||||
KeywordSearchListsManagementPanel.skipNSRLCheckBox.toolTipText=Please make sure you have either selected to run or have previously run the Hash DB Ingest Service.
|
||||
|
||||
@@ -101,10 +101,10 @@ public class ContentHit {
|
||||
|
||||
for (String key : results.keySet()) {
|
||||
for (ContentHit hit : results.get(key)) {
|
||||
AbstractFile AbstractFile = hit.getContent();
|
||||
AbstractFile abstractFile = hit.getContent();
|
||||
//flatten, record first chunk encountered
|
||||
if (!flattened.containsKey(AbstractFile)) {
|
||||
flattened.put(AbstractFile, hit.getChunkId());
|
||||
if (!flattened.containsKey(abstractFile)) {
|
||||
flattened.put(abstractFile, hit.getChunkId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="hitLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="hitCountLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="hitCountLabel" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="hitOfLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="hitTotalLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="hitTotalLabel" min="-2" pref="34" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="hitButtonsLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="hitPreviousButton" min="-2" pref="23" max="-2" attributes="0"/>
|
||||
|
||||
@@ -225,13 +225,13 @@ class ExtractedContentPanel extends javax.swing.JPanel {
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(hitLabel)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(hitCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(hitCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(hitOfLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(hitTotalLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(26, 26, 26)
|
||||
.addComponent(hitTotalLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(hitButtonsLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(hitPreviousButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
|
||||
@@ -37,6 +37,8 @@ import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.HighlightLookup;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentVisitor;
|
||||
import org.sleuthkit.datamodel.Directory;
|
||||
|
||||
/**
|
||||
* Displays marked-up (HTML) content for a Node. The sources are all the
|
||||
@@ -312,6 +314,19 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
panel.setSources(sources);
|
||||
}
|
||||
}
|
||||
|
||||
private class IsDirVisitor extends ContentVisitor.Default<Boolean> {
|
||||
|
||||
@Override
|
||||
protected Boolean defaultVisit(Content cntnt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(Directory d) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Solr has extracted content for a given node
|
||||
@@ -323,10 +338,17 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
if (content == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
final Server solrServer = KeywordSearch.getServer();
|
||||
|
||||
boolean isDir = content.accept(new IsDirVisitor());
|
||||
if (isDir)
|
||||
return false;
|
||||
|
||||
final long contentID = content.getId();
|
||||
|
||||
|
||||
|
||||
try {
|
||||
return solrServer.queryIsIndexed(contentID);
|
||||
@@ -358,7 +380,7 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
|
||||
String content = null;
|
||||
try {
|
||||
content = (String) solrServer.getSolrContent(contentObj, chunkId);
|
||||
content = solrServer.getSolrContent(contentObj, chunkId);
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.WARNING, "Couldn't get text content.", ex);
|
||||
return "";
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@@ -30,14 +29,15 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
|
||||
/**
|
||||
* Utility to extract and index a file as file chunks
|
||||
* Utility to extract strings and index a file with string content as chunks
|
||||
* associated with the original parent file
|
||||
*/
|
||||
public class FileExtract {
|
||||
class FileExtract {
|
||||
|
||||
KeywordSearchIngestService service;
|
||||
private int numChunks;
|
||||
public static final long MAX_CHUNK_SIZE = 10 * 1024 * 1024L;
|
||||
private static final Logger logger = Logger.getLogger(FileExtract.class.getName());
|
||||
private static final long MAX_STRING_CHUNK_SIZE = 1 * 1024 * 1024L;
|
||||
static final long MAX_STRING_CHUNK_SIZE = 1 * 1024 * 1024L;
|
||||
private AbstractFile sourceFile;
|
||||
|
||||
//single static buffer for all extractions. Safe, indexing can only happen in one thread
|
||||
@@ -50,7 +50,8 @@ public class FileExtract {
|
||||
STRING_CHUNK_BUF[2] = (byte)0xBF;
|
||||
}
|
||||
|
||||
public FileExtract(AbstractFile sourceFile) {
|
||||
public FileExtract(KeywordSearchIngestService service, AbstractFile sourceFile) {
|
||||
this.service = service;
|
||||
this.sourceFile = sourceFile;
|
||||
numChunks = 0; //unknown until indexing is done
|
||||
}
|
||||
@@ -90,6 +91,11 @@ public class FileExtract {
|
||||
logger.log(Level.WARNING, "Ingester had a problem with extracted strings from file '" + sourceFile.getName() + "' (id: " + sourceFile.getId() + ").", ingEx);
|
||||
throw ingEx; //need to rethrow/return to signal error and move on
|
||||
}
|
||||
|
||||
//check if need invoke commit/search between chunks
|
||||
//not to delay commit if timer has gone off
|
||||
service.checkRunCommitSearch();
|
||||
|
||||
//debug.close();
|
||||
}
|
||||
|
||||
@@ -115,7 +121,7 @@ public class FileExtract {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents each string chunk, a child of FileExtracted file
|
||||
* Represents each string chunk to be indexed, a child of FileExtracted file
|
||||
*/
|
||||
class FileExtractedChild {
|
||||
|
||||
@@ -159,6 +165,6 @@ class FileExtractedChild {
|
||||
}
|
||||
|
||||
public static String getFileExtractChildId(long parentID, int childID) {
|
||||
return Long.toString(parentID) + "_" + Integer.toString(childID);
|
||||
return Long.toString(parentID) + Server.ID_CHUNK_SEP + Integer.toString(childID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,14 +321,10 @@ class HighlightedMatchesSource implements MarkupSource, HighlightLookup {
|
||||
q.setQuery(sb.toString());
|
||||
} else {
|
||||
//use default field, simplifies query
|
||||
//quote only if user supplies quotes
|
||||
q.setQuery(highlightQuery);
|
||||
//always force grouping/quotes
|
||||
q.setQuery(KeywordSearchUtil.quoteQuery(highlightQuery));
|
||||
}
|
||||
|
||||
//if (isRegex)
|
||||
// q.setQuery(highLightField + ":" + highlightQuery);
|
||||
//else q.setQuery(highlightQuery); //use default field, simplifies query
|
||||
|
||||
final long contentId = content.getId();
|
||||
|
||||
String contentIdStr = Long.toString(contentId);
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.SolrException.ErrorCode;
|
||||
import org.apache.solr.common.util.ContentStream;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractContent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@@ -132,7 +133,7 @@ public class Ingester {
|
||||
params.put(Server.Schema.ID.toString(),
|
||||
FileExtractedChild.getFileExtractChildId(sourceContent.getId(), fec.getChunkId()));
|
||||
|
||||
ingest(bcs, params, FileExtract.MAX_CHUNK_SIZE);
|
||||
ingest(bcs, params, FileExtract.MAX_STRING_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,8 +144,13 @@ public class Ingester {
|
||||
* @throws IngesterException if there was an error processing a specific
|
||||
* file, but the Solr server is probably fine.
|
||||
*/
|
||||
void ingest(FsContent f) throws IngesterException {
|
||||
ingest(new FscContentStream(f), getContentFields(f), f.getSize());
|
||||
void ingest(FsContent fsContent) throws IngesterException {
|
||||
if (fsContent.isDir() ) {
|
||||
ingest(new NullContentStream(fsContent), getContentFields(fsContent), 0);
|
||||
}
|
||||
else {
|
||||
ingest(new FscContentStream(fsContent), getContentFields(fsContent), fsContent.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,10 +172,10 @@ public class Ingester {
|
||||
@Override
|
||||
public Map<String, String> visit(File f) {
|
||||
Map<String, String> params = getCommonFields(f);
|
||||
params.put(Server.Schema.CTIME.toString(), f.getCtimeAsDate());
|
||||
params.put(Server.Schema.ATIME.toString(), f.getAtimeAsDate());
|
||||
params.put(Server.Schema.MTIME.toString(), f.getMtimeAsDate());
|
||||
params.put(Server.Schema.CRTIME.toString(), f.getMtimeAsDate());
|
||||
params.put(Server.Schema.CTIME.toString(), ContentUtils.getStringTime(f.getCtime(), f));
|
||||
params.put(Server.Schema.ATIME.toString(), ContentUtils.getStringTime(f.getAtime(), f));
|
||||
params.put(Server.Schema.MTIME.toString(), ContentUtils.getStringTime(f.getMtime(), f));
|
||||
params.put(Server.Schema.CRTIME.toString(),ContentUtils.getStringTime(f.getCrtime(), f));
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -187,7 +193,12 @@ public class Ingester {
|
||||
|
||||
@Override
|
||||
public Map<String, String> visit(Directory d) {
|
||||
throw new IllegalArgumentException("Indexing directories not supported");
|
||||
Map<String, String> params = getCommonFields(d);
|
||||
params.put(Server.Schema.CTIME.toString(), ContentUtils.getStringTime(d.getCtime(), d));
|
||||
params.put(Server.Schema.ATIME.toString(), ContentUtils.getStringTime(d.getAtime(), d));
|
||||
params.put(Server.Schema.MTIME.toString(), ContentUtils.getStringTime(d.getMtime(), d));
|
||||
params.put(Server.Schema.CRTIME.toString(), ContentUtils.getStringTime(d.getCrtime(), d));
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +207,7 @@ public class Ingester {
|
||||
*
|
||||
* @param ContentStream to ingest
|
||||
* @param fields content specific fields
|
||||
* @param size size of the content
|
||||
* @param size size of the content - used to determine the Solr timeout, not used to populate meta-data
|
||||
* @throws IngesterException if there was an error processing a specific
|
||||
* content, but the Solr server is probably fine.
|
||||
*/
|
||||
@@ -427,12 +438,14 @@ public class Ingester {
|
||||
|
||||
/**
|
||||
* Determine if the file is ingestible/indexable by keyword search
|
||||
* Note: currently only checks by extension and abstract type, could be a more robust check.
|
||||
* Ingestible abstract file is either a directory, or an allocated file with supported extensions.
|
||||
* Note: currently only checks by extension and abstract type, it does not check actual file content.
|
||||
* @param aFile
|
||||
* @return true if it is ingestible, false otherwise
|
||||
*/
|
||||
static boolean isIngestible(AbstractFile aFile) {
|
||||
boolean isIngestible = false;
|
||||
|
||||
TSK_DB_FILES_TYPE_ENUM aType = aFile.getType();
|
||||
if (aType.equals(TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
|
||||
|| aType.equals(TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS))
|
||||
@@ -440,7 +453,8 @@ public class Ingester {
|
||||
|
||||
FsContent fsContent = (FsContent) aFile;
|
||||
if (fsContent.isDir())
|
||||
return isIngestible;
|
||||
//we index dir name, not content
|
||||
return true;
|
||||
|
||||
final String fileName = fsContent.getName();
|
||||
for (final String ext : ingestibleExtensions) {
|
||||
|
||||
@@ -40,7 +40,6 @@ class KeywordSearchConfigurationAction extends CallableSystemAction{
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
panel.save();
|
||||
dialog.close();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,16 +120,5 @@ public class KeywordSearchConfigurationPanel extends javax.swing.JPanel {
|
||||
private javax.swing.JPanel jPanel2;
|
||||
private javax.swing.JSplitPane mainSplitPane;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
void save() {
|
||||
KeywordSearchListsXML loader = KeywordSearchListsXML.getCurrent();
|
||||
KeywordSearchIngestService service = KeywordSearchIngestService.getDefault();
|
||||
if (IngestManager.getDefault().isServiceRunning(service)) {
|
||||
for (KeywordSearchList list : loader.getListsL()) {
|
||||
if (list.getUseForIngest()) {
|
||||
service.addToKeywordLists(list.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (lsm.isSelectionEmpty() || currentKeywordList.isLocked()) {
|
||||
deleteWordButton.setEnabled(false);
|
||||
return;
|
||||
} else {
|
||||
deleteWordButton.setEnabled(true);
|
||||
}
|
||||
@@ -529,6 +530,7 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec
|
||||
|
||||
//add & reset checkbox
|
||||
tableModel.addKeyword(keyword);
|
||||
KeywordSearchListsXML.getCurrent().addList(currentKeywordList);
|
||||
chRegex.setSelected(false);
|
||||
addWordField.setText("");
|
||||
|
||||
@@ -584,6 +586,7 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec
|
||||
|
||||
private void deleteWordButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteWordButtonActionPerformed
|
||||
tableModel.deleteSelected(keywordTable.getSelectedRows());
|
||||
KeywordSearchListsXML.getCurrent().addList(currentKeywordList);
|
||||
initButtons();
|
||||
}//GEN-LAST:event_deleteWordButtonActionPerformed
|
||||
|
||||
@@ -693,11 +696,12 @@ private void useForIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt)
|
||||
listSelectionModel.setSelectionInterval(index, index);
|
||||
KeywordSearchListsXML loader = KeywordSearchListsXML.getCurrent();
|
||||
|
||||
currentKeywordList = loader.getListsL().get(index);
|
||||
currentKeywordList = loader.getListsL(false).get(index);
|
||||
tableModel.resync();
|
||||
initButtons();
|
||||
} else {
|
||||
currentKeywordList = null;
|
||||
tableModel.resync();
|
||||
initButtons();
|
||||
}
|
||||
}
|
||||
@@ -737,6 +741,9 @@ private void useForIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt)
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
Object ret = null;
|
||||
if(currentKeywordList == null) {
|
||||
return "";
|
||||
}
|
||||
Keyword word = currentKeywordList.getKeywords().get(rowIndex);
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.SwingUtilities;
|
||||
@@ -47,13 +49,22 @@ import org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.File;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.FsContent;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
//service provider registered in layer.xml
|
||||
/**
|
||||
* An ingest service on a file level
|
||||
* Performs indexing of allocated and Solr supported files,
|
||||
* string extraction and indexing of unallocated and not Solr supported files
|
||||
* Index commit is done periodically (determined by user set ingest update interval)
|
||||
* Runs a periodic keyword / regular expression search on currently configured lists for ingest
|
||||
* and writes results to blackboard
|
||||
* Reports interesting events to Inbox and to viewers
|
||||
*
|
||||
* Registered as a service in layer.xml
|
||||
*/
|
||||
public final class KeywordSearchIngestService implements IngestServiceAbstractFile {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(KeywordSearchIngestService.class.getName());
|
||||
@@ -64,29 +75,38 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
private static final long MAX_INDEX_SIZE = 100 * (1 << 10) * (1 << 10);
|
||||
private Ingester ingester = null;
|
||||
private volatile boolean commitIndex = false; //whether to commit index next time
|
||||
private volatile boolean runSearcher = false; //whether to run searcher next time
|
||||
private List<Keyword> keywords; //keywords to search
|
||||
private List<String> keywordLists; // lists currently being searched
|
||||
private Map<String, KeywordSearchList> keywordToList; //keyword to list name mapping
|
||||
private Timer commitTimer;
|
||||
private Timer searchTimer;
|
||||
//private static final int COMMIT_INTERVAL_MS = 10 * 60 * 1000;
|
||||
private Indexer indexer;
|
||||
private Searcher currentSearcher;
|
||||
private Searcher finalSearcher;
|
||||
private volatile boolean searcherDone = true;
|
||||
private Map<Keyword, List<ContentHit>> currentResults;
|
||||
private final Object searcherLock = new Object();
|
||||
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); //use fairness policy
|
||||
private static final Lock searcherLock = rwLock.writeLock();
|
||||
private volatile int messageID = 0;
|
||||
private boolean processedFiles;
|
||||
private volatile boolean finalSearcherDone = false;
|
||||
private volatile boolean finalSearcherDone = true;
|
||||
private final String hashDBServiceName = "Hash Lookup";
|
||||
private SleuthkitCase caseHandle = null;
|
||||
private boolean skipKnown = false;
|
||||
private boolean skipKnown = true;
|
||||
boolean initialized = false;
|
||||
|
||||
public enum IngestStatus {
|
||||
private enum IngestStatus {
|
||||
|
||||
INGESTED, EXTRACTED_INGESTED, SKIPPED,
|
||||
};
|
||||
private Map<Long, IngestStatus> ingestStatus;
|
||||
|
||||
/**
|
||||
* Returns singleton instance of the service, creates one if needed
|
||||
* @return instance of the service
|
||||
*/
|
||||
public static synchronized KeywordSearchIngestService getDefault() {
|
||||
if (instance == null) {
|
||||
instance = new KeywordSearchIngestService();
|
||||
@@ -94,6 +114,12 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts processing of every file provided by IngestManager.
|
||||
* Checks if it is time to commit and run search
|
||||
* @param abstractFile file/unallocated file/directory to process
|
||||
* @return ProcessResult.OK in most cases and ERROR only if error in the pipeline, otherwise does not advice to stop the pipeline
|
||||
*/
|
||||
@Override
|
||||
public ProcessResult process(AbstractFile abstractFile) {
|
||||
|
||||
@@ -117,27 +143,17 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
processedFiles = true;
|
||||
}
|
||||
|
||||
//check if time to commit and previous search is not running
|
||||
//commiting while searching causes performance issues
|
||||
if (commitIndex && searcherDone) {
|
||||
logger.log(Level.INFO, "Commiting index");
|
||||
commit();
|
||||
commitIndex = false;
|
||||
indexChangeNotify();
|
||||
|
||||
updateKeywords();
|
||||
//start search if previous not running
|
||||
if (keywords != null && !keywords.isEmpty() && searcherDone) {
|
||||
currentSearcher = new Searcher(keywords);
|
||||
currentSearcher.execute();
|
||||
}
|
||||
}
|
||||
checkRunCommitSearch();
|
||||
|
||||
indexer.indexFile(abstractFile);
|
||||
return ProcessResult.OK;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* After all files are ingested, execute final index commit and final search
|
||||
* Cleanup resources, threads, timers
|
||||
*/
|
||||
@Override
|
||||
public void complete() {
|
||||
if (initialized == false) {
|
||||
@@ -149,25 +165,28 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
|
||||
//handle case if previous search running
|
||||
//cancel it, will re-run after final commit
|
||||
//note: cancellation of Searcher worker is graceful (between keywords)
|
||||
//note: cancellation of Searcher worker is graceful (between keywords)
|
||||
if (currentSearcher != null) {
|
||||
currentSearcher.cancel(false);
|
||||
}
|
||||
|
||||
//cancel searcher timer, ensure unwanted searcher does not start
|
||||
//before we start the final one
|
||||
if (searchTimer.isRunning()) {
|
||||
searchTimer.stop();
|
||||
}
|
||||
runSearcher = false;
|
||||
|
||||
logger.log(Level.INFO, "Running final index commit and search");
|
||||
//final commit
|
||||
commit();
|
||||
|
||||
//signal a potential change in number of indexed files
|
||||
indexChangeNotify();
|
||||
|
||||
postIndexSummary();
|
||||
|
||||
updateKeywords();
|
||||
//run one last search as there are probably some new files committed
|
||||
if (keywords != null && !keywords.isEmpty() && processedFiles == true) {
|
||||
currentSearcher = new Searcher(keywords, true); //final currentSearcher run
|
||||
currentSearcher.execute();
|
||||
finalSearcher = new Searcher(keywords, true); //final searcher run
|
||||
finalSearcher.execute();
|
||||
} else {
|
||||
finalSearcherDone = true;
|
||||
managerProxy.postMessage(IngestMessage.createMessage(++messageID, MessageType.INFO, this, "Completed"));
|
||||
@@ -176,6 +195,10 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
//postSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle stop event (ingest interrupted)
|
||||
* Cleanup resources, threads, timers
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
logger.log(Level.INFO, "stop()");
|
||||
@@ -187,10 +210,16 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
currentSearcher.cancel(true);
|
||||
}
|
||||
|
||||
//cancel searcher timer, ensure unwanted searcher does not start
|
||||
if (searchTimer.isRunning()) {
|
||||
searchTimer.stop();
|
||||
}
|
||||
runSearcher = false;
|
||||
finalSearcherDone = true;
|
||||
|
||||
//commit uncommited files, don't search again
|
||||
commit();
|
||||
|
||||
indexChangeNotify();
|
||||
//postSummary();
|
||||
}
|
||||
|
||||
@@ -204,6 +233,11 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
return MODULE_DESCRIPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the service for new ingest run
|
||||
* Sets up threads, timers, retrieves settings, keyword lists to run on
|
||||
* @param managerProxy
|
||||
*/
|
||||
@Override
|
||||
public void init(IngestManagerProxy managerProxy) {
|
||||
logger.log(Level.INFO, "init()");
|
||||
@@ -237,14 +271,17 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
|
||||
indexer = new Indexer();
|
||||
|
||||
final int commitIntervalMs = managerProxy.getUpdateFrequency() * 60 * 1000;
|
||||
logger.log(Level.INFO, "Using refresh interval (ms): " + commitIntervalMs);
|
||||
final int updateIntervalMs = managerProxy.getUpdateFrequency() * 60 * 1000;
|
||||
logger.log(Level.INFO, "Using commit interval (ms): " + updateIntervalMs);
|
||||
logger.log(Level.INFO, "Using searcher interval (ms): " + updateIntervalMs);
|
||||
|
||||
commitTimer = new Timer(commitIntervalMs, new CommitTimerAction());
|
||||
commitTimer = new Timer(updateIntervalMs, new CommitTimerAction());
|
||||
searchTimer = new Timer(updateIntervalMs, new SearchTimerAction());
|
||||
|
||||
initialized = true;
|
||||
|
||||
commitTimer.start();
|
||||
searchTimer.start();
|
||||
|
||||
managerProxy.postMessage(IngestMessage.createMessage(++messageID, MessageType.INFO, this, "Started"));
|
||||
}
|
||||
@@ -282,24 +319,38 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
public void saveSimpleConfiguration() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The services maintains background threads, return true if background threads are running
|
||||
* or there are pending tasks to be run in the future, such as the final search post-ingest completion
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean hasBackgroundJobsRunning() {
|
||||
if (currentSearcher != null && (searcherDone == false || finalSearcherDone == false)) {
|
||||
if ((currentSearcher != null && searcherDone == false)
|
||||
|| (finalSearcherDone == false)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//no need to check timer thread
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits index and notifies listeners of index update
|
||||
*/
|
||||
private void commit() {
|
||||
if (initialized) {
|
||||
logger.log(Level.INFO, "Commiting index");
|
||||
ingester.commit();
|
||||
logger.log(Level.INFO, "Index comitted");
|
||||
//signal a potential change in number of indexed files
|
||||
indexChangeNotify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts inbox message with summary of indexed files
|
||||
*/
|
||||
private void postIndexSummary() {
|
||||
int indexed = 0;
|
||||
int indexed_extr = 0;
|
||||
@@ -329,6 +380,9 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to notify listeners on index update
|
||||
*/
|
||||
private void indexChangeNotify() {
|
||||
//signal a potential change in number of indexed files
|
||||
try {
|
||||
@@ -364,36 +418,37 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the updated keyword search lists from the XML loader
|
||||
*/
|
||||
private synchronized void updateKeywords() {
|
||||
KeywordSearchListsXML loader = KeywordSearchListsXML.getCurrent();
|
||||
|
||||
keywords.clear();
|
||||
keywordToList.clear();
|
||||
|
||||
for (String name : keywordLists) {
|
||||
KeywordSearchList list = loader.getList(name);
|
||||
for (Keyword k : list.getKeywords()) {
|
||||
keywords.add(k);
|
||||
keywordToList.put(k.getQuery(), list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getKeywordLists() {
|
||||
return keywordLists == null ? new ArrayList<String>() : keywordLists;
|
||||
}
|
||||
|
||||
void addToKeywordLists(String name) {
|
||||
if (!keywordLists.contains(name)) {
|
||||
keywordLists.add(name);
|
||||
/**
|
||||
* Check if time to commit, if so, run commit.
|
||||
* Then run search if search timer is also set.
|
||||
*/
|
||||
void checkRunCommitSearch() {
|
||||
if (commitIndex) {
|
||||
logger.log(Level.INFO, "Commiting index");
|
||||
commit();
|
||||
commitIndex = false;
|
||||
|
||||
//after commit, check if time to run searcher
|
||||
//NOTE commit/searcher timings don't need to align
|
||||
//in worst case, we will run search next time after commit timer goes off, or at the end of ingest
|
||||
if (searcherDone && runSearcher) {
|
||||
//start search if previous not running
|
||||
if (keywords != null && !keywords.isEmpty()) {
|
||||
currentSearcher = new Searcher(keywords);
|
||||
currentSearcher.execute();//searcher will stop timer and restart timer when done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//CommitTimerAction to run by commitTimer
|
||||
//sets a flag for indexer to commit after indexing next file
|
||||
/**
|
||||
* CommitTimerAction to run by commitTimer
|
||||
* Sets a flag to indicate we are ready for commit
|
||||
*/
|
||||
private class CommitTimerAction implements ActionListener {
|
||||
|
||||
private final Logger logger = Logger.getLogger(CommitTimerAction.class.getName());
|
||||
@@ -405,16 +460,32 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
}
|
||||
}
|
||||
|
||||
//Indexer thread that processes files in the queue
|
||||
//commits when timer expires
|
||||
//sleeps if nothing in the queue
|
||||
/**
|
||||
* SearchTimerAction to run by searchTimer
|
||||
* Sets a flag to indicate we are ready to search
|
||||
*/
|
||||
private class SearchTimerAction implements ActionListener {
|
||||
|
||||
private final Logger logger = Logger.getLogger(SearchTimerAction.class.getName());
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
runSearcher = true;
|
||||
logger.log(Level.INFO, "SearchTimer awake");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* File indexer, processes and indexes known/allocated files,
|
||||
* unknown/unallocated files and directories accordingly
|
||||
*/
|
||||
private class Indexer {
|
||||
|
||||
private final Logger logger = Logger.getLogger(Indexer.class.getName());
|
||||
|
||||
private boolean extractAndIngest(AbstractFile aFile) {
|
||||
boolean indexed = false;
|
||||
FileExtract fe = new FileExtract(aFile);
|
||||
final FileExtract fe = new FileExtract(KeywordSearchIngestService.this, aFile);
|
||||
try {
|
||||
indexed = fe.index(ingester);
|
||||
} catch (IngesterException ex) {
|
||||
@@ -436,22 +507,28 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
}
|
||||
|
||||
if (ingestibleFile == true) {
|
||||
File file = (File) aFile;
|
||||
//we know it's an allocated file or dir (FsContent)
|
||||
FsContent fileDir = (FsContent) aFile;
|
||||
try {
|
||||
//logger.log(Level.INFO, "indexing: " + fsContent.getName());
|
||||
ingester.ingest(file);
|
||||
ingestStatus.put(file.getId(), IngestStatus.INGESTED);
|
||||
ingester.ingest(fileDir);
|
||||
ingestStatus.put(fileDir.getId(), IngestStatus.INGESTED);
|
||||
} catch (IngesterException e) {
|
||||
ingestStatus.put(file.getId(), IngestStatus.SKIPPED);
|
||||
//try to extract strings
|
||||
processNonIngestible(file);
|
||||
ingestStatus.put(fileDir.getId(), IngestStatus.SKIPPED);
|
||||
//try to extract strings if not a dir
|
||||
if (fileDir.isFile() == true) {
|
||||
processNonIngestible(fileDir);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
ingestStatus.put(file.getId(), IngestStatus.SKIPPED);
|
||||
//try to extract strings
|
||||
processNonIngestible(file);
|
||||
ingestStatus.put(fileDir.getId(), IngestStatus.SKIPPED);
|
||||
//try to extract strings if not a dir
|
||||
if (fileDir.isFile() == true) {
|
||||
processNonIngestible(fileDir);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//unallocated or unsupported type by Solr
|
||||
processNonIngestible(aFile);
|
||||
|
||||
}
|
||||
@@ -469,6 +546,12 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searcher responsible for searching the current index and writing results to blackboard
|
||||
* and the inbox. Also, posts results to listeners as Ingest data events.
|
||||
* Searches entire index, and keeps track of only new results to report and save.
|
||||
* Runs as a background thread.
|
||||
*/
|
||||
private class Searcher extends SwingWorker<Object, Void> {
|
||||
|
||||
private List<Keyword> keywords;
|
||||
@@ -489,8 +572,8 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
protected Object doInBackground() throws Exception {
|
||||
logger.log(Level.INFO, "Pending start of new searcher");
|
||||
|
||||
final String displayName = "Keyword Search" + (finalRun ? " (Finalizing)" : "");
|
||||
progress = ProgressHandleFactory.createHandle(displayName, new Cancellable() {
|
||||
final String displayName = "Keyword Search" + (finalRun ? " - Finalizing" : "");
|
||||
progress = ProgressHandleFactory.createHandle(displayName + (" (Pending)"), new Cancellable() {
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
@@ -507,18 +590,25 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
|
||||
//block to ensure previous searcher is completely done with doInBackground()
|
||||
//even after previous searcher cancellation, we need to check this
|
||||
synchronized (searcherLock) {
|
||||
searcherLock.lock();
|
||||
try {
|
||||
logger.log(Level.INFO, "Started a new searcher");
|
||||
progress.setDisplayName(displayName);
|
||||
//make sure other searchers are not spawned
|
||||
searcherDone = false;
|
||||
runSearcher = false;
|
||||
if (searchTimer.isRunning()) {
|
||||
searchTimer.stop();
|
||||
}
|
||||
|
||||
int numSearched = 0;
|
||||
|
||||
updateKeywords();
|
||||
progress.switchToDeterminate(keywords.size());
|
||||
|
||||
for (Keyword keywordQuery : keywords) {
|
||||
if (this.isCancelled()) {
|
||||
logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: " + keywordQuery.getQuery());
|
||||
finalizeSearcher();
|
||||
return null;
|
||||
}
|
||||
final String queryStr = keywordQuery.getQuery();
|
||||
@@ -549,12 +639,9 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
//no reason to continue with next query if recovery failed
|
||||
//or wait for recovery to kick in and run again later
|
||||
//likely case has closed and threads are being interrupted
|
||||
finalizeSearcher();
|
||||
return null;
|
||||
} catch (CancellationException e) {
|
||||
logger.log(Level.INFO, "Cancel detected, bailing during keyword query: " + keywordQuery.getQuery());
|
||||
|
||||
finalizeSearcher();
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Error performing query: " + keywordQuery.getQuery(), e);
|
||||
@@ -588,8 +675,6 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!newResults.isEmpty()) {
|
||||
|
||||
//write results to BB
|
||||
@@ -608,8 +693,7 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
snippet = LuceneQuery.querySnippet(snippetQuery, hitFile.getId(), chunkId, isRegex, true);
|
||||
} catch (NoOpenCoreException e) {
|
||||
logger.log(Level.WARNING, "Error querying snippet: " + snippetQuery, e);
|
||||
//no reason to continie
|
||||
finalizeSearcher();
|
||||
//no reason to continue
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Error querying snippet: " + snippetQuery, e);
|
||||
@@ -619,7 +703,7 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
KeywordWriteResult written = del.writeToBlackBoard(hitTerm.getQuery(), hitFile, snippet, listName);
|
||||
|
||||
if (written == null) {
|
||||
//logger.log(Level.INFO, "BB artifact for keyword not written: " + hitTerm.toString());
|
||||
logger.log(Level.WARNING, "BB artifact for keyword hit not written, file: " + hitFile + ", hit: " + hitTerm.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -714,15 +798,45 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
progress.progress(queryStr, ++numSearched);
|
||||
}
|
||||
|
||||
} //end try block
|
||||
catch (Exception ex) {
|
||||
logger.log(Level.WARNING, "searcher exception occurred", ex);
|
||||
} finally {
|
||||
finalizeSearcher();
|
||||
} //end synchronized block
|
||||
searcherLock.unlock();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the updated keyword search lists from the XML loader
|
||||
*/
|
||||
private void updateKeywords() {
|
||||
KeywordSearchListsXML loader = KeywordSearchListsXML.getCurrent();
|
||||
|
||||
keywords.clear();
|
||||
keywordToList.clear();
|
||||
|
||||
for (String name : keywordLists) {
|
||||
KeywordSearchList list = loader.getList(name);
|
||||
for (Keyword k : list.getKeywords()) {
|
||||
keywords.add(k);
|
||||
keywordToList.put(k.getQuery(), list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//perform all essential cleanup that needs to be done right AFTER doInBackground() returns
|
||||
//without relying on done() method that is not guaranteed to run after background thread completes
|
||||
//NEED to call this method always right before doInBackground() returns
|
||||
/**
|
||||
* Performs the cleanup that needs to be done right AFTER doInBackground() returns
|
||||
* without relying on done() method that is not guaranteed to run after background thread completes
|
||||
* REQUIRED to call this method always right before doInBackground() returns
|
||||
*/
|
||||
private void finalizeSearcher() {
|
||||
logger.log(Level.INFO, "Searcher finalizing");
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@@ -735,6 +849,7 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
searcherDone = true; //next currentSearcher can start
|
||||
|
||||
if (finalRun) {
|
||||
//this is the final searcher
|
||||
logger.log(Level.INFO, "The final searcher in this ingest done.");
|
||||
finalSearcherDone = true;
|
||||
keywords.clear();
|
||||
@@ -744,15 +859,26 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
currentResults = new HashMap<Keyword, List<ContentHit>>();
|
||||
|
||||
managerProxy.postMessage(IngestMessage.createMessage(++messageID, MessageType.INFO, KeywordSearchIngestService.instance, "Completed"));
|
||||
} else {
|
||||
//start counting time for a new searcher to start
|
||||
//unless final searcher is pending
|
||||
if (finalSearcher != null) {
|
||||
searchTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check if AbstractFile already hit, ignore chunks
|
||||
private static boolean previouslyHit(List<ContentHit> contents, ContentHit hit) {
|
||||
/**
|
||||
* Checks if the content has already been hit previously
|
||||
* @param previousHits the previous hits to check against
|
||||
* @param new hit, that potentially had already been hit
|
||||
* @return true if already hit
|
||||
*/
|
||||
private static boolean previouslyHit(List<ContentHit> previousHits, ContentHit hit) {
|
||||
boolean ret = false;
|
||||
long hitId = hit.getId();
|
||||
for (ContentHit c : contents) {
|
||||
for (ContentHit c : previousHits) {
|
||||
if (c.getId() == hitId) {
|
||||
ret = true;
|
||||
break;
|
||||
@@ -760,8 +886,16 @@ public final class KeywordSearchIngestService implements IngestServiceAbstractFi
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the skip known files setting on the service
|
||||
* @param skip true if skip, otherwise, will process known files as well, as reported by HashDB service
|
||||
*/
|
||||
void setSkipKnown(boolean skip) {
|
||||
this.skipKnown = skip;
|
||||
}
|
||||
|
||||
boolean getSkipKnown() {
|
||||
return skipKnown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,11 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jSeparator1" alignment="0" pref="295" max="32767" attributes="0"/>
|
||||
<Component id="jSeparator1" alignment="0" pref="172" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="133" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jSeparator2" alignment="1" pref="295" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="skipKnownCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="listsScrollPane" alignment="0" min="0" pref="0" max="32767" attributes="1"/>
|
||||
</Group>
|
||||
@@ -42,12 +37,7 @@
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jSeparator1" min="-2" pref="4" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="listsScrollPane" pref="118" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jSeparator2" min="-2" max="-2" attributes="1"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="skipKnownCheckBox" min="-2" max="-2" attributes="1"/>
|
||||
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
|
||||
<Component id="listsScrollPane" pref="27" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@@ -90,20 +80,5 @@
|
||||
</Component>
|
||||
<Component class="javax.swing.JSeparator" name="jSeparator1">
|
||||
</Component>
|
||||
<Component class="javax.swing.JSeparator" name="jSeparator2">
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="skipKnownCheckBox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="KeywordSearchIngestSimplePanel.skipKnownCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="KeywordSearchIngestSimplePanel.skipKnownCheckBox.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="skipKnownCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
||||
@@ -81,8 +81,6 @@ public class KeywordSearchIngestSimplePanel extends javax.swing.JPanel {
|
||||
listsTable = new javax.swing.JTable();
|
||||
jLabel1 = new javax.swing.JLabel();
|
||||
jSeparator1 = new javax.swing.JSeparator();
|
||||
jSeparator2 = new javax.swing.JSeparator();
|
||||
skipKnownCheckBox = new javax.swing.JCheckBox();
|
||||
|
||||
setPreferredSize(new java.awt.Dimension(172, 57));
|
||||
|
||||
@@ -103,27 +101,15 @@ public class KeywordSearchIngestSimplePanel extends javax.swing.JPanel {
|
||||
|
||||
jLabel1.setText(org.openide.util.NbBundle.getMessage(KeywordSearchIngestSimplePanel.class, "KeywordSearchIngestSimplePanel.jLabel1.text")); // NOI18N
|
||||
|
||||
skipKnownCheckBox.setText(org.openide.util.NbBundle.getMessage(KeywordSearchIngestSimplePanel.class, "KeywordSearchIngestSimplePanel.skipKnownCheckBox.text")); // NOI18N
|
||||
skipKnownCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(KeywordSearchIngestSimplePanel.class, "KeywordSearchIngestSimplePanel.skipKnownCheckBox.toolTipText")); // NOI18N
|
||||
skipKnownCheckBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
skipKnownCheckBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.DEFAULT_SIZE, 295, Short.MAX_VALUE)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.DEFAULT_SIZE, 172, Short.MAX_VALUE)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(jLabel1)
|
||||
.addContainerGap(133, Short.MAX_VALUE))
|
||||
.addComponent(jSeparator2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 295, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(skipKnownCheckBox)
|
||||
.addContainerGap())
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(listsScrollPane, 0, 0, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
@@ -133,26 +119,15 @@ public class KeywordSearchIngestSimplePanel extends javax.swing.JPanel {
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(listsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 118, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(skipKnownCheckBox)
|
||||
.addGap(0, 0, 0))
|
||||
.addComponent(listsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 27, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void skipKnownCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_skipKnownCheckBoxActionPerformed
|
||||
KeywordSearchIngestService.getDefault().setSkipKnown(skipKnownCheckBox.isSelected());
|
||||
}//GEN-LAST:event_skipKnownCheckBoxActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel jLabel1;
|
||||
private javax.swing.JSeparator jSeparator1;
|
||||
private javax.swing.JSeparator jSeparator2;
|
||||
private javax.swing.JScrollPane listsScrollPane;
|
||||
private javax.swing.JTable listsTable;
|
||||
private javax.swing.JCheckBox skipKnownCheckBox;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
private void reloadLists() {
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.AutopsyPropFile;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author dfickling
|
||||
*/
|
||||
public abstract class KeywordSearchListsAbstract {
|
||||
|
||||
protected String filePath;
|
||||
Map<String, KeywordSearchList> theLists; //the keyword data
|
||||
static KeywordSearchListsXML currentInstance = null;
|
||||
private static final String CUR_LISTS_FILE_NAME = "keywords.xml";
|
||||
private static String CUR_LISTS_FILE = AutopsyPropFile.getUserDirPath() + File.separator + CUR_LISTS_FILE_NAME;
|
||||
protected static final Logger logger = Logger.getLogger(KeywordSearchListsAbstract.class.getName());
|
||||
PropertyChangeSupport changeSupport;
|
||||
|
||||
public KeywordSearchListsAbstract(String filePath) {
|
||||
this.filePath = filePath;
|
||||
theLists = new LinkedHashMap<String, KeywordSearchList>();
|
||||
changeSupport = new PropertyChangeSupport(this);
|
||||
}
|
||||
|
||||
//property support
|
||||
public enum ListsEvt {
|
||||
|
||||
LIST_ADDED, LIST_DELETED, LIST_UPDATED
|
||||
};
|
||||
|
||||
/**
|
||||
* get instance for managing the current keyword list of the application
|
||||
*/
|
||||
static KeywordSearchListsXML getCurrent() {
|
||||
if (currentInstance == null) {
|
||||
currentInstance = new KeywordSearchListsXML(CUR_LISTS_FILE);
|
||||
currentInstance.reload();
|
||||
}
|
||||
return currentInstance;
|
||||
}
|
||||
|
||||
void addPropertyChangeListener(PropertyChangeListener l) {
|
||||
changeSupport.addPropertyChangeListener(l);
|
||||
}
|
||||
|
||||
private void prepopulateLists() {
|
||||
//phone number
|
||||
List<Keyword> phones = new ArrayList<Keyword>();
|
||||
phones.add(new Keyword("[(]{0,1}\\d\\d\\d[)]{0,1}[\\.-]\\d\\d\\d[\\.-]\\d\\d\\d\\d", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER));
|
||||
//phones.add(new Keyword("\\d{8,10}", false));
|
||||
//IP address
|
||||
List<Keyword> ips = new ArrayList<Keyword>();
|
||||
ips.add(new Keyword("(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IP_ADDRESS));
|
||||
//email
|
||||
List<Keyword> emails = new ArrayList<Keyword>();
|
||||
emails.add(new Keyword("[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
|
||||
//URL
|
||||
List<Keyword> urls = new ArrayList<Keyword>();
|
||||
//urls.add(new Keyword("http://|https://|^www\\.", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||
urls.add(new Keyword("((((ht|f)tp(s?))\\://)|www\\.)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,5})(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\;\\?\\'\\\\+&%\\$#\\=~_\\-]+))*", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||
|
||||
//urls.add(new Keyword("ssh://", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||
|
||||
//disable messages for harcoded/locked lists
|
||||
addList("Phone Numbers", phones, false, false, true);
|
||||
addList("IP Addresses", ips, false, false, true);
|
||||
addList("Email Addresses", emails, true, false, true);
|
||||
addList("URLs", urls, true, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* load the file or create new
|
||||
*/
|
||||
public void reload() {
|
||||
boolean created = false;
|
||||
|
||||
theLists.clear();
|
||||
prepopulateLists();
|
||||
if (!this.listFileExists()) {
|
||||
//create new if it doesn't exist
|
||||
save();
|
||||
created = true;
|
||||
}
|
||||
|
||||
//load, if fails to laod create new
|
||||
if (!load() && !created) {
|
||||
//create new if failed to load
|
||||
save();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
List<KeywordSearchList> getListsL() {
|
||||
List<KeywordSearchList> ret = new ArrayList<KeywordSearchList>();
|
||||
for (KeywordSearchList list : theLists.values()) {
|
||||
ret.add(list);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
List<KeywordSearchList> getListsL(boolean locked) {
|
||||
List<KeywordSearchList> ret = new ArrayList<KeywordSearchList>();
|
||||
for (KeywordSearchList list : theLists.values()) {
|
||||
if(list.isLocked().equals(locked)) {
|
||||
ret.add(list);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list names of all loaded keyword list names
|
||||
* @return List of keyword list names
|
||||
*/
|
||||
List<String> getListNames() {
|
||||
return new ArrayList<String>(theLists.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list names of all locked or unlocked loaded keyword list names
|
||||
* @param locked true if look for locked lists, false otherwise
|
||||
* @return List of keyword list names
|
||||
*/
|
||||
List<String> getListNames(boolean locked) {
|
||||
ArrayList<String> lists = new ArrayList<String>();
|
||||
for (String listName : theLists.keySet()) {
|
||||
KeywordSearchList list = theLists.get(listName);
|
||||
if (locked == list.isLocked())
|
||||
lists.add(listName);
|
||||
}
|
||||
|
||||
return lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* return first list that contains the keyword
|
||||
* @param keyword
|
||||
* @return found list or null
|
||||
*/
|
||||
KeywordSearchList getListWithKeyword(Keyword keyword) {
|
||||
KeywordSearchList found = null;
|
||||
for (KeywordSearchList list : theLists.values()) {
|
||||
if (list.hasKeyword(keyword)) {
|
||||
found = list;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* return first list that contains the keyword
|
||||
* @param keyword
|
||||
* @return found list or null
|
||||
*/
|
||||
KeywordSearchList getListWithKeyword(String keyword) {
|
||||
KeywordSearchList found = null;
|
||||
for (KeywordSearchList list : theLists.values()) {
|
||||
if (list.hasKeyword(keyword)) {
|
||||
found = list;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* get number of lists currently stored
|
||||
* @return number of lists currently stored
|
||||
*/
|
||||
int getNumberLists() {
|
||||
return theLists.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* get number of unlocked or locked lists currently stored
|
||||
* @param locked true if look for locked lists, false otherwise
|
||||
* @return number of unlocked lists currently stored
|
||||
*/
|
||||
int getNumberLists(boolean locked) {
|
||||
int numLists = 0;
|
||||
for (String listName : theLists.keySet()) {
|
||||
KeywordSearchList list = theLists.get(listName);
|
||||
if (locked == list.isLocked())
|
||||
++ numLists;
|
||||
}
|
||||
return numLists;
|
||||
}
|
||||
|
||||
/**
|
||||
* get list by name or null
|
||||
* @param name id of the list
|
||||
* @return keyword list representation
|
||||
*/
|
||||
KeywordSearchList getList(String name) {
|
||||
return theLists.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if list with given name id exists
|
||||
* @param name id to check
|
||||
* @return true if list already exists or false otherwise
|
||||
*/
|
||||
boolean listExists(String name) {
|
||||
return getList(name) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the new word list using name id
|
||||
* replacing old one if exists with the same name
|
||||
* @param name the name of the new list or list to replace
|
||||
* @param newList list of keywords
|
||||
* @param useForIngest should this list be used for ingest
|
||||
* @return true if old list was replaced
|
||||
*/
|
||||
boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolean ingestMessages, boolean locked) {
|
||||
boolean replaced = false;
|
||||
KeywordSearchList curList = getList(name);
|
||||
final Date now = new Date();
|
||||
if (curList == null) {
|
||||
theLists.put(name, new KeywordSearchList(name, now, now, useForIngest, ingestMessages, newList, locked));
|
||||
if (!locked) {
|
||||
save();
|
||||
}
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, name);
|
||||
} else {
|
||||
theLists.put(name, new KeywordSearchList(name, curList.getDateCreated(), now, useForIngest, ingestMessages, newList, locked));
|
||||
if (!locked) {
|
||||
save();
|
||||
}
|
||||
replaced = true;
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, name);
|
||||
}
|
||||
|
||||
return replaced;
|
||||
}
|
||||
|
||||
boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolean ingestMessages) {
|
||||
return addList(name, newList, useForIngest, ingestMessages, false);
|
||||
}
|
||||
|
||||
boolean addList(String name, List<Keyword> newList) {
|
||||
return addList(name, newList, true, true);
|
||||
}
|
||||
|
||||
boolean addList(KeywordSearchList list) {
|
||||
return addList(list.getName(), list.getKeywords(), list.getUseForIngest(), list.getIngestMessages(), list.isLocked());
|
||||
}
|
||||
|
||||
/**
|
||||
* write out multiple lists
|
||||
* @param lists
|
||||
* @return
|
||||
*/
|
||||
boolean writeLists(List<KeywordSearchList> lists) {
|
||||
int oldSize = this.getNumberLists();
|
||||
|
||||
List<KeywordSearchList> overwritten = new ArrayList<KeywordSearchList>();
|
||||
List<KeywordSearchList> newLists = new ArrayList<KeywordSearchList>();
|
||||
for (KeywordSearchList list : lists) {
|
||||
if (this.listExists(list.getName())) {
|
||||
overwritten.add(list);
|
||||
} else {
|
||||
newLists.add(list);
|
||||
}
|
||||
theLists.put(list.getName(), list);
|
||||
}
|
||||
boolean saved = save();
|
||||
if (saved) {
|
||||
for (KeywordSearchList list : newLists) {
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, list.getName());
|
||||
}
|
||||
for (KeywordSearchList over : overwritten) {
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, over.getName());
|
||||
}
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete list if exists and save new list
|
||||
* @param name of list to delete
|
||||
* @return true if deleted
|
||||
*/
|
||||
boolean deleteList(String name) {
|
||||
boolean deleted = false;
|
||||
KeywordSearchList delList = getList(name);
|
||||
if (delList != null && !delList.isLocked()) {
|
||||
theLists.remove(name);
|
||||
deleted = save();
|
||||
}
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_DELETED.toString(), null, name);
|
||||
return deleted;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* writes out current list replacing the last lists file
|
||||
*/
|
||||
public abstract boolean save();
|
||||
|
||||
/**
|
||||
* load and parse List, then dispose
|
||||
*/
|
||||
public abstract boolean load();
|
||||
|
||||
private boolean listFileExists() {
|
||||
File f = new File(filePath);
|
||||
return f.exists() && f.canRead() && f.canWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* a representation of a single keyword list
|
||||
* created or loaded
|
||||
*/
|
||||
class KeywordSearchList {
|
||||
|
||||
private String name;
|
||||
private Date created;
|
||||
private Date modified;
|
||||
private Boolean useForIngest;
|
||||
private Boolean ingestMessages;
|
||||
private List<Keyword> keywords;
|
||||
private Boolean locked;
|
||||
|
||||
KeywordSearchList(String name, Date created, Date modified, Boolean useForIngest, Boolean ingestMessages, List<Keyword> keywords, boolean locked) {
|
||||
this.name = name;
|
||||
this.created = created;
|
||||
this.modified = modified;
|
||||
this.useForIngest = useForIngest;
|
||||
this.ingestMessages = ingestMessages;
|
||||
this.keywords = keywords;
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
KeywordSearchList(String name, Date created, Date modified, Boolean useForIngest, Boolean ingestMessages, List<Keyword> keywords) {
|
||||
this(name, created, modified, useForIngest, ingestMessages, keywords, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final KeywordSearchList other = (KeywordSearchList) obj;
|
||||
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
return hash;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
Date getDateCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
Date getDateModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
Boolean getUseForIngest() {
|
||||
return useForIngest;
|
||||
}
|
||||
|
||||
void setUseForIngest(boolean use) {
|
||||
this.useForIngest = use;
|
||||
}
|
||||
|
||||
Boolean getIngestMessages() {
|
||||
return ingestMessages;
|
||||
}
|
||||
|
||||
void setIngestMessages(boolean ingestMessages) {
|
||||
this.ingestMessages = ingestMessages;
|
||||
}
|
||||
|
||||
List<Keyword> getKeywords() {
|
||||
return keywords;
|
||||
}
|
||||
|
||||
boolean hasKeyword(Keyword keyword) {
|
||||
return keywords.contains(keyword);
|
||||
}
|
||||
|
||||
boolean hasKeyword(String keyword) {
|
||||
//note, this ignores isLiteral
|
||||
for (Keyword k : keywords) {
|
||||
if (k.getQuery().equals(keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Boolean isLocked() {
|
||||
return locked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* @author dfickling
|
||||
* KeywordSearchListsEncase adds support for Encase tab-delimited
|
||||
* keyword list exports to Autopsy.
|
||||
*
|
||||
* load() does the I/O operation, converting lines from the text file to
|
||||
* an unsorted list of EncaseFileEntrys
|
||||
* The next step is to recreate the original folder hierarchy,
|
||||
* and finally the EncaseFileEntries are converted to KeywordSearchLists
|
||||
*
|
||||
*/
|
||||
public class KeywordSearchListsEncase extends KeywordSearchListsAbstract{
|
||||
|
||||
ArrayList<EncaseFileEntry> entriesUnsorted;
|
||||
EncaseFileEntry rootEntry;
|
||||
|
||||
public KeywordSearchListsEncase(String encasePath) {
|
||||
super(encasePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow the EncaseFileEntry hierarchy starting with given entry
|
||||
* Create list for each Folder entry, add keyword for each Expression
|
||||
* @param entry
|
||||
* @param parentPath
|
||||
*/
|
||||
private void doCreateListsFromEntries(EncaseFileEntry entry, String parentPath) {
|
||||
String name;
|
||||
if(parentPath.isEmpty()) {
|
||||
name = entry.name;
|
||||
} else {
|
||||
name = parentPath + "/" + entry.name;
|
||||
}
|
||||
|
||||
List<Keyword> children = new ArrayList<Keyword>();
|
||||
for(EncaseFileEntry child : entry.children) {
|
||||
switch(child.type) {
|
||||
case Folder:
|
||||
doCreateListsFromEntries(child, name);
|
||||
break;
|
||||
case Expression:
|
||||
if(child.flags.contains(EncaseFlag.pg)) { // Skip GREP keywords
|
||||
break;
|
||||
}
|
||||
children.add(new Keyword(child.value, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Give each list a unique name
|
||||
if(theLists.containsKey(name)) {
|
||||
int i = 2;
|
||||
while(theLists.containsKey(name + "(" + i + ")")) {
|
||||
i+=1;
|
||||
}
|
||||
name = name + "(" + i + ")";
|
||||
}
|
||||
// Don't create lists if there are no keywords
|
||||
if (!children.isEmpty()) {
|
||||
KeywordSearchList newList = new KeywordSearchList(name, new Date(), new Date(),
|
||||
true, true, children);
|
||||
theLists.put(name, newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert entriesUnsorted (a list of childless and parentless EncaseFileEntries) into an EncaseFileEntry structure
|
||||
*/
|
||||
private void doCreateEntryStructure(EncaseFileEntry parent) {
|
||||
if (!parent.isFull()) {
|
||||
EncaseFileEntry child = entriesUnsorted.remove(0);
|
||||
child.hasParent = true;
|
||||
child.parent = parent;
|
||||
parent.addChild(child);
|
||||
if(!child.isFull()) {
|
||||
doCreateEntryStructure(child);
|
||||
}
|
||||
if (!parent.isFull()) {
|
||||
doCreateEntryStructure(parent);
|
||||
}
|
||||
}
|
||||
if (parent.hasParent) {
|
||||
doCreateEntryStructure(parent.parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load() {
|
||||
try {
|
||||
BufferedReader readBuffer = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "utf-16"));
|
||||
String structLine;
|
||||
String metaLine;
|
||||
entriesUnsorted = new ArrayList<EncaseFileEntry>();
|
||||
for(int line = 1; line < 6; line++) {
|
||||
readBuffer.readLine();
|
||||
}
|
||||
while ((structLine = readBuffer.readLine()) != null && (metaLine = readBuffer.readLine()) != null) {
|
||||
String[] structArr = structLine.split("\t");
|
||||
String[] metaArr = metaLine.split("\t");
|
||||
EncaseMetaType type = EncaseMetaType.getType(metaArr[0]);
|
||||
String childCount = structArr[1];
|
||||
String name = metaArr[1];
|
||||
String value = metaArr[2];
|
||||
ArrayList<EncaseFlag> flags = new ArrayList<EncaseFlag>();
|
||||
for(int i = 0; i < 17; i++) {
|
||||
if(metaArr.length < i+4) {
|
||||
continue;
|
||||
}
|
||||
if(!metaArr[i+3].equals("")) {
|
||||
flags.add(EncaseFlag.getFlag(i));
|
||||
}
|
||||
}
|
||||
entriesUnsorted.add(new EncaseFileEntry(name, value, Integer.parseInt(childCount), false, null, type, flags));
|
||||
}
|
||||
this.rootEntry = entriesUnsorted.remove(0);
|
||||
doCreateEntryStructure(this.rootEntry);
|
||||
doCreateListsFromEntries(this.rootEntry, "");
|
||||
return true;
|
||||
|
||||
} catch (FileNotFoundException ex) {
|
||||
logger.log(Level.INFO, "File at " + filePath + " does not exist!", ex);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.INFO, "Failed to read file at " + filePath, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private enum EncaseMetaType {
|
||||
Expression, Folder;
|
||||
|
||||
static EncaseMetaType getType(String type) {
|
||||
if(type.equals("5")) {
|
||||
return Folder;
|
||||
} else if(type.equals("")) {
|
||||
return Expression;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported EncaseMetaType: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Flags for EncaseFileEntries.
|
||||
* p8 = UTF-8
|
||||
* p7 = UTF-7
|
||||
* pg = GREP
|
||||
*/
|
||||
private enum EncaseFlag {
|
||||
pc, pu, pb, p8, p7, pg, an, ph, or, di, um, st, ww, pr, lo, ta, cp;
|
||||
|
||||
static EncaseFlag getFlag(int i) {
|
||||
return EncaseFlag.values()[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry in the Encase keyword list file.
|
||||
*/
|
||||
private class EncaseFileEntry {
|
||||
String name;
|
||||
String value;
|
||||
int childCount;
|
||||
List<EncaseFileEntry> children;
|
||||
EncaseFileEntry parent;
|
||||
EncaseMetaType type;
|
||||
boolean hasParent;
|
||||
ArrayList<EncaseFlag> flags;
|
||||
EncaseFileEntry(String name, String value, int childCount, boolean hasParent, EncaseFileEntry parent, EncaseMetaType type, ArrayList<EncaseFlag> flags) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.childCount = childCount;
|
||||
this.children = new ArrayList<EncaseFileEntry>();
|
||||
this.hasParent = hasParent;
|
||||
this.parent = parent;
|
||||
this.type = type;
|
||||
this.flags = flags;
|
||||
}
|
||||
boolean isFull() {
|
||||
return children.size() == childCount;
|
||||
}
|
||||
void addChild(EncaseFileEntry child) {
|
||||
children.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,27 +26,30 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="19" max="-2" attributes="0"/>
|
||||
<Component id="skipNSRLCheckBox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="18" max="-2" attributes="0"/>
|
||||
<Component id="newListButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="importButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane1" alignment="0" min="-2" max="-2" attributes="1"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="jScrollPane1" pref="274" max="32767" attributes="0"/>
|
||||
<Component id="jScrollPane1" pref="249" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="newListButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="importButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
|
||||
<Component id="skipNSRLCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@@ -75,14 +78,14 @@
|
||||
<TableHeader reorderingAllowed="false" resizingAllowed="true"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="keyPressed" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="listsTableKeyPressed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="newListButton">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="cc" green="cc" red="cc" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="KeywordSearchListsManagementPanel.newListButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
@@ -93,9 +96,6 @@
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="importButton">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="cc" green="cc" red="cc" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="KeywordSearchListsManagementPanel.importButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
@@ -104,5 +104,19 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="importButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="skipNSRLCheckBox">
|
||||
<Properties>
|
||||
<Property name="selected" type="boolean" value="true"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="KeywordSearchListsManagementPanel.skipNSRLCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="KeywordSearchListsManagementPanel.skipNSRLCheckBox.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="skipNSRLCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
||||
@@ -24,23 +24,18 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -98,11 +93,10 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
listsTable.getSelectionModel().setSelectionInterval(0, 0);
|
||||
else
|
||||
listsTable.getSelectionModel().clearSelection();
|
||||
} else if (evt.getPropertyName().equals(KeywordSearchListsXML.ListsEvt.LIST_UPDATED.toString())) {
|
||||
tableModel.resync(); //changed list name
|
||||
}
|
||||
}
|
||||
});
|
||||
this.skipNSRLCheckBox.setSelected(KeywordSearchIngestService.getDefault().getSkipKnown());
|
||||
|
||||
}
|
||||
|
||||
@@ -119,6 +113,7 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
listsTable = new javax.swing.JTable();
|
||||
newListButton = new javax.swing.JButton();
|
||||
importButton = new javax.swing.JButton();
|
||||
skipNSRLCheckBox = new javax.swing.JCheckBox();
|
||||
|
||||
setMinimumSize(new java.awt.Dimension(200, 0));
|
||||
setPreferredSize(new java.awt.Dimension(200, 297));
|
||||
@@ -129,9 +124,13 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
listsTable.setShowHorizontalLines(false);
|
||||
listsTable.setShowVerticalLines(false);
|
||||
listsTable.getTableHeader().setReorderingAllowed(false);
|
||||
listsTable.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
public void keyPressed(java.awt.event.KeyEvent evt) {
|
||||
listsTableKeyPressed(evt);
|
||||
}
|
||||
});
|
||||
jScrollPane1.setViewportView(listsTable);
|
||||
|
||||
newListButton.setBackground(new java.awt.Color(204, 204, 204));
|
||||
newListButton.setText(org.openide.util.NbBundle.getMessage(KeywordSearchListsManagementPanel.class, "KeywordSearchListsManagementPanel.newListButton.text")); // NOI18N
|
||||
newListButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
@@ -139,7 +138,6 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
importButton.setBackground(new java.awt.Color(204, 204, 204));
|
||||
importButton.setText(org.openide.util.NbBundle.getMessage(KeywordSearchListsManagementPanel.class, "KeywordSearchListsManagementPanel.importButton.text")); // NOI18N
|
||||
importButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
@@ -147,28 +145,40 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
skipNSRLCheckBox.setSelected(true);
|
||||
skipNSRLCheckBox.setText(org.openide.util.NbBundle.getMessage(KeywordSearchListsManagementPanel.class, "KeywordSearchListsManagementPanel.skipNSRLCheckBox.text")); // NOI18N
|
||||
skipNSRLCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(KeywordSearchListsManagementPanel.class, "KeywordSearchListsManagementPanel.skipNSRLCheckBox.toolTipText")); // NOI18N
|
||||
skipNSRLCheckBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
skipNSRLCheckBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(skipNSRLCheckBox)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(19, 19, 19)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(newListButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(importButton))
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 274, Short.MAX_VALUE)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 249, Short.MAX_VALUE)
|
||||
.addGap(0, 0, 0)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(newListButton)
|
||||
.addComponent(importButton)))
|
||||
.addComponent(importButton))
|
||||
.addGap(2, 2, 2)
|
||||
.addComponent(skipNSRLCheckBox))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@@ -180,9 +190,18 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
}
|
||||
boolean shouldAdd = false;
|
||||
if (writer.listExists(listName)) {
|
||||
boolean replace = KeywordSearchUtil.displayConfirmDialog("New Keyword List", "Keyword List <" + listName + "> already exists, do you want to replace it?", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN);
|
||||
if (replace) {
|
||||
shouldAdd = true;
|
||||
if (writer.getList(listName).isLocked() ) {
|
||||
boolean replace = KeywordSearchUtil.displayConfirmDialog("New Keyword List", "Keyword List <" + listName
|
||||
+ "> already exists as a read-only list. Do you want to replace it for the duration of the program (the change will not be persistent).", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN);
|
||||
if (replace) {
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
boolean replace = KeywordSearchUtil.displayConfirmDialog("New Keyword List", "Keyword List <" + listName + "> already exists, do you want to replace it?", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN);
|
||||
if (replace) {
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shouldAdd = true;
|
||||
@@ -198,12 +217,13 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
}//GEN-LAST:event_newListButtonActionPerformed
|
||||
|
||||
private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed
|
||||
|
||||
final String FEATURE_NAME = "Keyword List Import";
|
||||
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
final String EXTENSION = "xml";
|
||||
final String[] EXTENSION = new String[]{"xml", "txt"};
|
||||
FileNameExtensionFilter filter = new FileNameExtensionFilter(
|
||||
"Keyword List XML file", EXTENSION);
|
||||
"Keyword List File", EXTENSION);
|
||||
chooser.setFileFilter(filter);
|
||||
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||
|
||||
@@ -216,8 +236,15 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
|
||||
//force append extension if not given
|
||||
String fileAbs = selFile.getAbsolutePath();
|
||||
|
||||
final KeywordSearchListsXML reader = new KeywordSearchListsXML(fileAbs);
|
||||
|
||||
final KeywordSearchListsAbstract reader;
|
||||
|
||||
if(KeywordSearchUtil.isXMLList(fileAbs)) {
|
||||
reader = new KeywordSearchListsXML(fileAbs);
|
||||
} else {
|
||||
reader = new KeywordSearchListsEncase(fileAbs);
|
||||
}
|
||||
|
||||
if (!reader.load()) {
|
||||
KeywordSearchUtil.displayDialog(FEATURE_NAME, "Error importing keyword list from file " + fileAbs, KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR);
|
||||
return;
|
||||
@@ -266,11 +293,28 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
}
|
||||
}//GEN-LAST:event_importButtonActionPerformed
|
||||
|
||||
private void skipNSRLCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_skipNSRLCheckBoxActionPerformed
|
||||
KeywordSearchIngestService.getDefault().setSkipKnown(skipNSRLCheckBox.isSelected());
|
||||
}//GEN-LAST:event_skipNSRLCheckBoxActionPerformed
|
||||
|
||||
private void listsTableKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_listsTableKeyPressed
|
||||
if(evt.getKeyCode() == KeyEvent.VK_DELETE) {
|
||||
int[] selected = listsTable.getSelectedRows();
|
||||
if(selected.length == 0) {
|
||||
return;
|
||||
}
|
||||
KeywordSearchListsXML deleter = KeywordSearchListsXML.getCurrent();
|
||||
String listName = deleter.getListNames().get(selected[0]);
|
||||
KeywordSearchListsXML.getCurrent().deleteList(listName);
|
||||
}
|
||||
}//GEN-LAST:event_listsTableKeyPressed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton importButton;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
private javax.swing.JTable listsTable;
|
||||
private javax.swing.JButton newListButton;
|
||||
private javax.swing.JCheckBox skipNSRLCheckBox;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
|
||||
@@ -286,7 +330,7 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return listsHandle.getNumberLists();
|
||||
return listsHandle.getNumberLists(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -296,7 +340,7 @@ class KeywordSearchListsManagementPanel extends javax.swing.JPanel {
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
return listsHandle.getListNames().get(rowIndex);
|
||||
return listsHandle.getListNames(false).get(rowIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -158,12 +158,6 @@ class KeywordSearchListsViewerPanel extends AbstractKeywordSearchPerformer {
|
||||
}
|
||||
}
|
||||
});
|
||||
ingestListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
addToIngestAction(e);
|
||||
}
|
||||
};
|
||||
searchListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -329,12 +323,6 @@ class KeywordSearchListsViewerPanel extends AbstractKeywordSearchPerformer {
|
||||
}
|
||||
}
|
||||
|
||||
private void addToIngestAction(ActionEvent e) {
|
||||
for(KeywordSearchList list : listsTableModel.getSelectedListsL()){
|
||||
KeywordSearchIngestService.getDefault().addToKeywordLists(list.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Keyword> getQueryList() {
|
||||
List<Keyword> ret = new ArrayList<Keyword>();
|
||||
|
||||
@@ -59,7 +59,7 @@ import org.xml.sax.SAXException;
|
||||
* Manages reading and writing of keyword lists to user settings XML file keywords.xml
|
||||
* or to any file provided in constructor
|
||||
*/
|
||||
public class KeywordSearchListsXML {
|
||||
public class KeywordSearchListsXML extends KeywordSearchListsAbstract{
|
||||
|
||||
private static final String ROOT_EL = "keyword_lists";
|
||||
private static final String LIST_EL = "keyword_list";
|
||||
@@ -71,22 +71,12 @@ public class KeywordSearchListsXML {
|
||||
private static final String KEYWORD_EL = "keyword";
|
||||
private static final String KEYWORD_LITERAL_ATTR = "literal";
|
||||
private static final String KEYWORD_SELECTOR_ATTR = "selector";
|
||||
private static final String CUR_LISTS_FILE_NAME = "keywords.xml";
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
private static final String ENCODING = "UTF-8";
|
||||
private static String CUR_LISTS_FILE = AutopsyPropFile.getUserDirPath() + File.separator + CUR_LISTS_FILE_NAME;
|
||||
private static final Logger logger = Logger.getLogger(KeywordSearchListsXML.class.getName());
|
||||
Map<String, KeywordSearchList> theLists; //the keyword data
|
||||
static KeywordSearchListsXML currentInstance = null;
|
||||
private String xmlFile;
|
||||
private DateFormat dateFormatter;
|
||||
|
||||
//property support
|
||||
public enum ListsEvt {
|
||||
|
||||
LIST_ADDED, LIST_DELETED, LIST_UPDATED
|
||||
};
|
||||
private PropertyChangeSupport changeSupport;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor to obtain handle on other that the current keyword list
|
||||
@@ -94,237 +84,16 @@ public class KeywordSearchListsXML {
|
||||
* @param xmlFile xmlFile to obtain KeywordSearchListsXML handle on
|
||||
*/
|
||||
KeywordSearchListsXML(String xmlFile) {
|
||||
theLists = new LinkedHashMap<String, KeywordSearchList>();
|
||||
this.xmlFile = xmlFile;
|
||||
changeSupport = new PropertyChangeSupport(this);
|
||||
|
||||
super(xmlFile);
|
||||
dateFormatter = new SimpleDateFormat(DATE_FORMAT);
|
||||
}
|
||||
|
||||
private void prepopulateLists() {
|
||||
//phone number
|
||||
List<Keyword> phones = new ArrayList<Keyword>();
|
||||
phones.add(new Keyword("[(]{0,1}\\d\\d\\d[)]{0,1}[\\.-]\\d\\d\\d[\\.-]\\d\\d\\d\\d", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER));
|
||||
//phones.add(new Keyword("\\d{8,10}", false));
|
||||
//IP address
|
||||
List<Keyword> ips = new ArrayList<Keyword>();
|
||||
ips.add(new Keyword("(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IP_ADDRESS));
|
||||
//email
|
||||
List<Keyword> emails = new ArrayList<Keyword>();
|
||||
emails.add(new Keyword("[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
|
||||
//URL
|
||||
List<Keyword> urls = new ArrayList<Keyword>();
|
||||
//urls.add(new Keyword("http://|https://|^www\\.", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||
urls.add(new Keyword("((((ht|f)tp(s?))\\://)|www\\.)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,5})(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\;\\?\\'\\\\+&%\\$#\\=~_\\-]+))*", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||
|
||||
//urls.add(new Keyword("ssh://", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
|
||||
|
||||
//disable messages for harcoded/locked lists
|
||||
addList("Phone Numbers", phones, true, false, true);
|
||||
addList("IP Addresses", ips, true, false, true);
|
||||
addList("Email Addresses", emails, true, false, true);
|
||||
addList("URLs", urls, true, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* get instance for managing the current keyword list of the application
|
||||
*/
|
||||
static KeywordSearchListsXML getCurrent() {
|
||||
if (currentInstance == null) {
|
||||
currentInstance = new KeywordSearchListsXML(CUR_LISTS_FILE);
|
||||
currentInstance.reload();
|
||||
}
|
||||
return currentInstance;
|
||||
}
|
||||
|
||||
void addPropertyChangeListener(PropertyChangeListener l) {
|
||||
changeSupport.addPropertyChangeListener(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* load the file or create new
|
||||
*/
|
||||
public void reload() {
|
||||
boolean created = false;
|
||||
|
||||
theLists.clear();
|
||||
prepopulateLists();
|
||||
if (!this.listFileExists()) {
|
||||
//create new if it doesn't exist
|
||||
save();
|
||||
created = true;
|
||||
}
|
||||
|
||||
//load, if fails to laod create new
|
||||
if (!load() && !created) {
|
||||
//create new if failed to load
|
||||
save();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
List<KeywordSearchList> getListsL() {
|
||||
List<KeywordSearchList> ret = new ArrayList<KeywordSearchList>();
|
||||
for (KeywordSearchList list : theLists.values()) {
|
||||
ret.add(list);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* get list of all loaded keyword list names
|
||||
* @return List of keyword list names
|
||||
*/
|
||||
List<String> getListNames() {
|
||||
return new ArrayList<String>(theLists.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* return first list that contains the keyword
|
||||
* @param keyword
|
||||
* @return found list or null
|
||||
*/
|
||||
KeywordSearchList getListWithKeyword(Keyword keyword) {
|
||||
KeywordSearchList found = null;
|
||||
for (KeywordSearchList list : theLists.values()) {
|
||||
if (list.hasKeyword(keyword)) {
|
||||
found = list;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* return first list that contains the keyword
|
||||
* @param keyword
|
||||
* @return found list or null
|
||||
*/
|
||||
KeywordSearchList getListWithKeyword(String keyword) {
|
||||
KeywordSearchList found = null;
|
||||
for (KeywordSearchList list : theLists.values()) {
|
||||
if (list.hasKeyword(keyword)) {
|
||||
found = list;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* get number of lists currently stored
|
||||
* @return number of lists currently stored
|
||||
*/
|
||||
int getNumberLists() {
|
||||
return theLists.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* get list by name or null
|
||||
* @param name id of the list
|
||||
* @return keyword list representation
|
||||
*/
|
||||
KeywordSearchList getList(String name) {
|
||||
return theLists.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if list with given name id exists
|
||||
* @param name id to check
|
||||
* @return true if list already exists or false otherwise
|
||||
*/
|
||||
boolean listExists(String name) {
|
||||
return getList(name) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the new word list using name id
|
||||
* replacing old one if exists with the same name
|
||||
* @param name the name of the new list or list to replace
|
||||
* @param newList list of keywords
|
||||
* @param useForIngest should this list be used for ingest
|
||||
* @return true if old list was replaced
|
||||
*/
|
||||
boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolean ingestMessages, boolean locked) {
|
||||
boolean replaced = false;
|
||||
KeywordSearchList curList = getList(name);
|
||||
final Date now = new Date();
|
||||
if (curList == null) {
|
||||
theLists.put(name, new KeywordSearchList(name, now, now, useForIngest, ingestMessages, newList, locked));
|
||||
if(!locked)
|
||||
save();
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, name);
|
||||
} else {
|
||||
theLists.put(name, new KeywordSearchList(name, curList.getDateCreated(), now, useForIngest, ingestMessages, newList, locked));
|
||||
if(!locked)
|
||||
save();
|
||||
replaced = true;
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, name);
|
||||
}
|
||||
|
||||
return replaced;
|
||||
}
|
||||
|
||||
boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolean ingestMessages) {
|
||||
return addList(name, newList, useForIngest, ingestMessages, false);
|
||||
}
|
||||
|
||||
boolean addList(String name, List<Keyword> newList) {
|
||||
return addList(name, newList, true, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* write out multiple lists
|
||||
* @param lists
|
||||
* @return
|
||||
*/
|
||||
boolean writeLists(List<KeywordSearchList> lists) {
|
||||
int oldSize = this.getNumberLists();
|
||||
|
||||
List<KeywordSearchList> overwritten = new ArrayList<KeywordSearchList>();
|
||||
List<KeywordSearchList> newLists = new ArrayList<KeywordSearchList>();
|
||||
for (KeywordSearchList list : lists) {
|
||||
if (this.listExists(list.getName()))
|
||||
overwritten.add(list);
|
||||
else
|
||||
newLists.add(list);
|
||||
theLists.put(list.getName(), list);
|
||||
}
|
||||
boolean saved = save();
|
||||
if (saved) {
|
||||
for (KeywordSearchList list : newLists) {
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, list.getName());
|
||||
}
|
||||
for (KeywordSearchList over : overwritten) {
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, over.getName());
|
||||
}
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete list if exists and save new list
|
||||
* @param name of list to delete
|
||||
* @return true if deleted
|
||||
*/
|
||||
boolean deleteList(String name) {
|
||||
boolean deleted = false;
|
||||
KeywordSearchList delList = getList(name);
|
||||
if (delList != null) {
|
||||
theLists.remove(name);
|
||||
deleted = save();
|
||||
}
|
||||
changeSupport.firePropertyChange(ListsEvt.LIST_DELETED.toString(), null, name);
|
||||
return deleted;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* writes out current list replacing the last lists file
|
||||
*/
|
||||
private boolean save() {
|
||||
@Override
|
||||
public boolean save() {
|
||||
boolean success = false;
|
||||
|
||||
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
|
||||
@@ -378,6 +147,7 @@ public class KeywordSearchListsXML {
|
||||
/**
|
||||
* load and parse XML, then dispose
|
||||
*/
|
||||
@Override
|
||||
public boolean load() {
|
||||
final Document doc = loadDoc();
|
||||
if (doc == null) {
|
||||
@@ -433,11 +203,6 @@ public class KeywordSearchListsXML {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean listFileExists() {
|
||||
File f = new File(xmlFile);
|
||||
return f.exists() && f.canRead() && f.canWrite();
|
||||
}
|
||||
|
||||
private Document loadDoc() {
|
||||
DocumentBuilderFactory builderFactory =
|
||||
DocumentBuilderFactory.newInstance();
|
||||
@@ -448,7 +213,7 @@ public class KeywordSearchListsXML {
|
||||
try {
|
||||
DocumentBuilder builder = builderFactory.newDocumentBuilder();
|
||||
ret = builder.parse(
|
||||
new FileInputStream(xmlFile));
|
||||
new FileInputStream(filePath));
|
||||
} catch (ParserConfigurationException e) {
|
||||
logger.log(Level.SEVERE, "Error loading keyword list: can't initialize parser.", e);
|
||||
|
||||
@@ -475,7 +240,7 @@ public class KeywordSearchListsXML {
|
||||
xformer.setOutputProperty(OutputKeys.ENCODING, ENCODING);
|
||||
xformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
|
||||
xformer.setOutputProperty(OutputKeys.VERSION, "1.0");
|
||||
File file = new File(xmlFile);
|
||||
File file = new File(filePath);
|
||||
FileOutputStream stream = new FileOutputStream(file);
|
||||
Result out = new StreamResult(new OutputStreamWriter(stream, ENCODING));
|
||||
xformer.transform(new DOMSource(doc), out);
|
||||
@@ -490,110 +255,10 @@ public class KeywordSearchListsXML {
|
||||
} catch (TransformerException e) {
|
||||
logger.log(Level.SEVERE, "Error writing keyword lists XML", e);
|
||||
} catch (FileNotFoundException e) {
|
||||
logger.log(Level.SEVERE, "Error writing keyword lists XML: cannot write to file: " + xmlFile, e);
|
||||
logger.log(Level.SEVERE, "Error writing keyword lists XML: cannot write to file: " + filePath, e);
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Error writing keyword lists XML: cannot write to file: " + xmlFile, e);
|
||||
logger.log(Level.SEVERE, "Error writing keyword lists XML: cannot write to file: " + filePath, e);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* a representation of a single keyword list
|
||||
* created or loaded
|
||||
*/
|
||||
class KeywordSearchList {
|
||||
|
||||
private String name;
|
||||
private Date created;
|
||||
private Date modified;
|
||||
private Boolean useForIngest;
|
||||
private Boolean ingestMessages;
|
||||
private List<Keyword> keywords;
|
||||
private Boolean locked;
|
||||
|
||||
KeywordSearchList(String name, Date created, Date modified, Boolean useForIngest, Boolean ingestMessages, List<Keyword> keywords, boolean locked) {
|
||||
this.name = name;
|
||||
this.created = created;
|
||||
this.modified = modified;
|
||||
this.useForIngest = useForIngest;
|
||||
this.ingestMessages = ingestMessages;
|
||||
this.keywords = keywords;
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
KeywordSearchList(String name, Date created, Date modified, Boolean useForIngest, Boolean ingestMessages, List<Keyword> keywords) {
|
||||
this(name, created, modified, useForIngest, ingestMessages, keywords, false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final KeywordSearchList other = (KeywordSearchList) obj;
|
||||
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
return hash;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
Date getDateCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
Date getDateModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
Boolean getUseForIngest() {
|
||||
return useForIngest;
|
||||
}
|
||||
|
||||
void setUseForIngest(boolean use) {
|
||||
this.useForIngest = use;
|
||||
}
|
||||
|
||||
Boolean getIngestMessages() {
|
||||
return ingestMessages;
|
||||
}
|
||||
|
||||
void setIngestMessages(boolean ingestMessages) {
|
||||
this.ingestMessages = ingestMessages;
|
||||
}
|
||||
|
||||
List<Keyword> getKeywords() {
|
||||
return keywords;
|
||||
}
|
||||
|
||||
boolean hasKeyword(Keyword keyword) {
|
||||
return keywords.contains(keyword);
|
||||
}
|
||||
|
||||
boolean hasKeyword(String keyword) {
|
||||
//note, this ignores isLiteral
|
||||
for (Keyword k : keywords) {
|
||||
if (k.getQuery().equals(keyword))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Boolean isLocked() {
|
||||
return locked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@ public interface KeywordSearchQuery {
|
||||
*/
|
||||
public void setFilter(KeywordQueryFilter filter);
|
||||
|
||||
/**
|
||||
* Set an optional field to narrow down the search
|
||||
* @param field field to set on the query
|
||||
*/
|
||||
public void setField(String field);
|
||||
|
||||
|
||||
/**
|
||||
* escape the query string and use the escaped string in the query
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.SwingUtilities;
|
||||
@@ -46,9 +49,10 @@ import org.sleuthkit.autopsy.keywordsearch.KeywordSearchQueryManager.Presentatio
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.File;
|
||||
import org.sleuthkit.datamodel.FsContent;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -66,30 +70,31 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Keyword";
|
||||
return BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getDisplayName();
|
||||
}
|
||||
},
|
||||
REGEX {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Regex";
|
||||
return BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getDisplayName();
|
||||
}
|
||||
},
|
||||
MATCH {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Match";
|
||||
return "File Name";
|
||||
}
|
||||
},
|
||||
CONTEXT {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Context";
|
||||
return BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getDisplayName();
|
||||
}
|
||||
},}
|
||||
},
|
||||
}
|
||||
private Presentation presentation;
|
||||
private List<Keyword> queries;
|
||||
private Collection<KeyValueQuery> things;
|
||||
@@ -246,22 +251,55 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
Map<String, Object> resMap = new LinkedHashMap<String, Object>();
|
||||
setCommonProperty(resMap, CommonPropertyTypes.MATCH, f.getName());
|
||||
|
||||
if (true) {
|
||||
try {
|
||||
String snippet;
|
||||
//TODO reuse snippet in ResultWriter
|
||||
snippet = LuceneQuery.querySnippet(tcq.getEscapedQueryString(), f.getId(), previewChunk, !literal_query, true);
|
||||
setCommonProperty(resMap, CommonPropertyTypes.CONTEXT, snippet);
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.WARNING, "Could not perform the snippet query. ", ex);
|
||||
return false;
|
||||
try {
|
||||
String snippet;
|
||||
|
||||
String snippetQuery = null;
|
||||
|
||||
if (literal_query) {
|
||||
snippetQuery = tcq.getEscapedQueryString();
|
||||
} else {
|
||||
//in regex, to generate the preview snippet
|
||||
//just pick any term that hit that file (since we are compressing result view)
|
||||
String hit = null;
|
||||
//find the first hit for this file
|
||||
for (String hitKey : tcqRes.keySet()) {
|
||||
List<ContentHit> chits = tcqRes.get(hitKey);
|
||||
for (ContentHit chit : chits) {
|
||||
if (chit.getContent().equals(f)) {
|
||||
hit = hitKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hit != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hit != null) {
|
||||
snippetQuery = KeywordSearchUtil.escapeLuceneQuery(hit, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (snippetQuery != null) {
|
||||
snippet = LuceneQuery.querySnippet(snippetQuery, f.getId(), previewChunk, !literal_query, true);
|
||||
setCommonProperty(resMap, CommonPropertyTypes.CONTEXT, snippet);
|
||||
}
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.WARNING, "Could not perform the snippet query. ", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f.getType() == TSK_DB_FILES_TYPE_ENUM.FS) {
|
||||
AbstractFsContentNode.fillPropertyMap(resMap, (FsContent) f);
|
||||
}
|
||||
|
||||
final String highlightQueryEscaped = getHighlightQuery(tcq, literal_query, tcqRes, f);
|
||||
toPopulate.add(new KeyValueQueryContent(f.getName(), resMap, ++resID, f, highlightQueryEscaped, tcq, previewChunk, tcqRes));
|
||||
}
|
||||
//write to bb
|
||||
//cannot reuse snippet in ResultWriter
|
||||
//because for regex searches in UI we compress results by showing a file per regex once (even if multiple term hits)
|
||||
//whereas in bb we write every hit per file separately
|
||||
new ResultWriter(tcqRes, tcq, listName).execute();
|
||||
|
||||
|
||||
@@ -362,10 +400,7 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
}
|
||||
|
||||
/**
|
||||
* factory produces 2nd level child nodes showing files with *approximate* matches
|
||||
* since they rely on underlying Lucene query to get details
|
||||
* To implement exact regex match detail view, we need to extract files content
|
||||
* returned by Lucene and further narrow down by applying a Java regex
|
||||
* Child factory that produces 2nd level child nodes showing files with matches
|
||||
*/
|
||||
class ResultFilesChildFactory extends ChildFactory<KeyValueQuery> {
|
||||
|
||||
@@ -400,7 +435,9 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
for (final AbstractFile f : uniqueMatches.keySet()) {
|
||||
final int previewChunkId = uniqueMatches.get(f);
|
||||
Map<String, Object> resMap = new LinkedHashMap<String, Object>();
|
||||
AbstractFsContentNode.fillPropertyMap(resMap, (File) f);
|
||||
if (f.getType() == TSK_DB_FILES_TYPE_ENUM.FS) {
|
||||
AbstractFsContentNode.fillPropertyMap(resMap, (FsContent) f);
|
||||
}
|
||||
toPopulate.add(new KeyValueQueryContent(f.getName(), resMap, ++resID, f, keywordQuery, thing.getQuery(), previewChunkId, matchesRes));
|
||||
|
||||
}
|
||||
@@ -417,7 +454,7 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
final String query = thingContent.getQueryStr();
|
||||
final int previewChunk = thingContent.getPreviewChunk();
|
||||
final Map<String, List<ContentHit>> hits = thingContent.getHits();
|
||||
|
||||
|
||||
|
||||
Node kvNode = new KeyValueNode(thingContent, Children.LEAF, Lookups.singleton(content));
|
||||
//wrap in KeywordSearchFilterNode for the markup content
|
||||
@@ -449,7 +486,7 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
int getPreviewChunk() {
|
||||
return previewChunk;
|
||||
}
|
||||
|
||||
|
||||
Map<String, List<ContentHit>> getHits() {
|
||||
return hits;
|
||||
}
|
||||
@@ -470,6 +507,9 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
static class ResultWriter extends SwingWorker<Object, Void> {
|
||||
|
||||
private static List<ResultWriter> writers = new ArrayList<ResultWriter>();
|
||||
//lock utilized to enqueue writers and limit execution to 1 at a time
|
||||
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); //use fairness policy
|
||||
//private static final Lock writerLock = rwLock.writeLock();
|
||||
private ProgressHandle progress;
|
||||
private KeywordSearchQuery query;
|
||||
private String listName;
|
||||
@@ -484,11 +524,17 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
super.done();
|
||||
protected void finalizeWorker() {
|
||||
deregisterWriter(this);
|
||||
progress.finish();
|
||||
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
progress.finish();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (!this.isCancelled() && !na.isEmpty()) {
|
||||
IngestManager.fireServiceDataEvent(new ServiceDataEvent(KeywordSearchIngestService.MODULE_NAME, ARTIFACT_TYPE.TSK_KEYWORD_HIT, na));
|
||||
@@ -497,47 +543,55 @@ public class KeywordSearchResultFactory extends ChildFactory<KeyValueQuery> {
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
registerWriter(this);
|
||||
final String queryStr = query.getQueryString();
|
||||
final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr;
|
||||
progress = ProgressHandleFactory.createHandle("Saving results: " + queryDisp, new Cancellable() {
|
||||
registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return ResultWriter.this.cancel(true);
|
||||
}
|
||||
});
|
||||
//block until previous writer is done
|
||||
//writerLock.lock();
|
||||
try {
|
||||
final String queryStr = query.getQueryString();
|
||||
final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr;
|
||||
progress = ProgressHandleFactory.createHandle("Saving results: " + queryDisp, new Cancellable() {
|
||||
|
||||
progress.start(hits.keySet().size());
|
||||
int processedFiles = 0;
|
||||
for (final String hit : hits.keySet()) {
|
||||
progress.progress(hit, ++processedFiles);
|
||||
if (this.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Map<AbstractFile, Integer> flattened = ContentHit.flattenResults(hits.get(hit));
|
||||
for (AbstractFile f : flattened.keySet()) {
|
||||
int chunkId = flattened.get(f);
|
||||
final String snippetQuery = KeywordSearchUtil.escapeLuceneQuery(hit, true, false);
|
||||
String snippet = null;
|
||||
try {
|
||||
snippet = LuceneQuery.querySnippet(snippetQuery, f.getId(), chunkId, !query.isLiteral(), true);
|
||||
} catch (NoOpenCoreException e) {
|
||||
logger.log(Level.WARNING, "Error querying snippet: " + snippetQuery, e);
|
||||
//no reason to continie
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Error querying snippet: " + snippetQuery, e);
|
||||
continue;
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return ResultWriter.this.cancel(true);
|
||||
}
|
||||
if (snippet != null) {
|
||||
KeywordWriteResult written = query.writeToBlackBoard(hit, f, snippet, listName);
|
||||
if (written != null) {
|
||||
na.add(written.getArtifact());
|
||||
});
|
||||
|
||||
progress.start(hits.keySet().size());
|
||||
int processedFiles = 0;
|
||||
for (final String hit : hits.keySet()) {
|
||||
progress.progress(hit, ++processedFiles);
|
||||
if (this.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Map<AbstractFile, Integer> flattened = ContentHit.flattenResults(hits.get(hit));
|
||||
for (AbstractFile f : flattened.keySet()) {
|
||||
int chunkId = flattened.get(f);
|
||||
final String snippetQuery = KeywordSearchUtil.escapeLuceneQuery(hit, true, false);
|
||||
String snippet = null;
|
||||
try {
|
||||
snippet = LuceneQuery.querySnippet(snippetQuery, f.getId(), chunkId, !query.isLiteral(), true);
|
||||
} catch (NoOpenCoreException e) {
|
||||
logger.log(Level.WARNING, "Error querying snippet: " + snippetQuery, e);
|
||||
//no reason to continie
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Error querying snippet: " + snippetQuery, e);
|
||||
continue;
|
||||
}
|
||||
if (snippet != null) {
|
||||
KeywordWriteResult written = query.writeToBlackBoard(hit, f, snippet, listName);
|
||||
if (written != null) {
|
||||
na.add(written.getArtifact());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
//writerLock.unlock();
|
||||
finalizeWorker();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.logging.Level;
|
||||
@@ -59,6 +60,24 @@ public class KeywordSearchUtil {
|
||||
return dirName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a quoted version of the query if the original query is not quoted
|
||||
* @param query the query to check if it is quoted
|
||||
* @return quoted query
|
||||
*/
|
||||
public static String quoteQuery(String query) {
|
||||
//ensure a single pair of quotes around the query
|
||||
final int length = query.length();
|
||||
if (length > 1 && query.charAt(0) == '"'
|
||||
&& query.charAt(length - 1) == '"') {
|
||||
return query;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\"").append(query).append("\"");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform standard escaping / encoding into UTF-8 before sending over net
|
||||
* @param query to be encoded
|
||||
@@ -143,5 +162,14 @@ public class KeywordSearchUtil {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the Keyword Search list at absPath an XML list?
|
||||
* @param absPath
|
||||
* @return yes or no
|
||||
*/
|
||||
static boolean isXMLList(String absPath) {
|
||||
//TODO: make this more robust, if necessary
|
||||
return new File(absPath).getName().endsWith(".xml");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
private boolean isEscaped;
|
||||
private Keyword keywordQuery = null;
|
||||
private KeywordQueryFilter filter = null;
|
||||
private String field = null;
|
||||
//use different highlight Solr fields for regex and literal search
|
||||
static final String HIGHLIGHT_FIELD_LITERAL = Server.Schema.CONTENT.toString();
|
||||
static final String HIGHLIGHT_FIELD_REGEX = Server.Schema.CONTENT.toString();
|
||||
@@ -71,6 +72,11 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
public void setFilter(KeywordQueryFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setField(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void escape() {
|
||||
@@ -164,11 +170,11 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Just perform the query and return result without updating the GUI
|
||||
* This utility is used in this class, can be potentially reused by other classes
|
||||
* @param query
|
||||
* @return matches List
|
||||
* Perform the query and return result
|
||||
* @return list of ContentHit objects
|
||||
* @throws NoOpenCoreException
|
||||
*/
|
||||
private List<ContentHit> performLuceneQuery() throws NoOpenCoreException {
|
||||
|
||||
@@ -181,14 +187,23 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
|
||||
SolrQuery q = new SolrQuery();
|
||||
|
||||
q.setQuery(queryEscaped);
|
||||
//set query, force quotes/grouping around all literal queries
|
||||
final String groupedQuery = KeywordSearchUtil.quoteQuery(queryEscaped);
|
||||
String theQueryStr = groupedQuery;
|
||||
if (field != null) {
|
||||
//use the optional field
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(field).append(":").append(groupedQuery);
|
||||
theQueryStr = sb.toString();
|
||||
}
|
||||
|
||||
q.setQuery(theQueryStr);
|
||||
q.setRows(ROWS_PER_FETCH);
|
||||
q.setFields(Server.Schema.ID.toString());
|
||||
if (filter != null) {
|
||||
q.addFilterQuery(filter.toString());
|
||||
}
|
||||
|
||||
|
||||
for (int start = 0; !allMatchesFetched; start = start + ROWS_PER_FETCH) {
|
||||
q.setStart(start);
|
||||
|
||||
@@ -208,7 +223,7 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
for (SolrDocument resultDoc : resultList) {
|
||||
final String resultID = (String) resultDoc.getFieldValue(Server.Schema.ID.toString());
|
||||
|
||||
final int sepIndex = resultID.indexOf('_');
|
||||
final int sepIndex = resultID.indexOf(Server.ID_CHUNK_SEP);
|
||||
|
||||
if (sepIndex != -1) {
|
||||
//file chunk result
|
||||
@@ -247,7 +262,6 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
throw ex;
|
||||
} catch (SolrServerException ex) {
|
||||
logger.log(Level.WARNING, "Error executing Lucene Solr Query: " + query, ex);
|
||||
// TODO: handle bad query strings, among other issues
|
||||
}
|
||||
|
||||
}
|
||||
@@ -303,8 +317,8 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
q.setQuery(sb.toString());
|
||||
} else {
|
||||
//simplify query/escaping and use default field
|
||||
//quote only if user supplies quotes
|
||||
q.setQuery(query);
|
||||
//always force grouping/quotes
|
||||
q.setQuery(KeywordSearchUtil.quoteQuery(query));
|
||||
}
|
||||
|
||||
String contentIDStr = null;
|
||||
@@ -322,7 +336,7 @@ public class LuceneQuery implements KeywordSearchQuery {
|
||||
q.setHighlightSimplePost("»");
|
||||
q.setHighlightSnippets(1);
|
||||
q.setHighlightFragsize(SNIPPET_LENGTH);
|
||||
q.setParam("hl.maxAnalyzedChars", Server.HL_ANALYZE_CHARS_UNLIMITED); //analyze all content
|
||||
q.setParam("hl.maxAnalyzedChars", Server.HL_ANALYZE_CHARS_UNLIMITED); //analyze all content SLOW! consider lowering
|
||||
|
||||
try {
|
||||
QueryResponse response = solrServer.query(q);
|
||||
|
||||
@@ -132,7 +132,9 @@ class Server {
|
||||
private static final String DEFAULT_CORE_NAME = "coreCase";
|
||||
// TODO: DEFAULT_CORE_NAME needs to be replaced with unique names to support multiple open cases
|
||||
public static final String CORE_EVT = "CORE_EVT";
|
||||
public static final char ID_CHUNK_SEP = '_';
|
||||
private String javaPath = "java";
|
||||
private static final int MAX_SOLR_MEM_MB = 512; //TODO set dynamically based on avail. system resources
|
||||
private Process curSolrProcess = null;
|
||||
|
||||
public enum CORE_EVT_STATES {
|
||||
@@ -235,7 +237,10 @@ class Server {
|
||||
void start() {
|
||||
logger.log(Level.INFO, "Starting Solr server from: " + solrFolder.getAbsolutePath());
|
||||
try {
|
||||
curSolrProcess = Runtime.getRuntime().exec(javaPath + " -DSTOP.PORT=8079 -DSTOP.KEY=mysecret -jar start.jar", null, solrFolder);
|
||||
final String MAX_SOLR_MEM_MB_PAR = " -Xmx" + Integer.toString(MAX_SOLR_MEM_MB) + "m";
|
||||
final String SOLR_START_CMD = javaPath + MAX_SOLR_MEM_MB_PAR + " -DSTOP.PORT=8079 -DSTOP.KEY=mysecret -jar start.jar";
|
||||
logger.log(Level.INFO, "Starting Solr using: " + SOLR_START_CMD);
|
||||
curSolrProcess = Runtime.getRuntime().exec(SOLR_START_CMD, null, solrFolder);
|
||||
try {
|
||||
//block, give time to fully stary the process
|
||||
//so if it's restarted solr operations can be resumed seamlessly
|
||||
@@ -561,7 +566,7 @@ class Server {
|
||||
q.setQuery("*:*");
|
||||
String filterQuery = Schema.ID.toString() + ":" + contentID;
|
||||
if (chunkID != 0)
|
||||
filterQuery = filterQuery + "_" + chunkID;
|
||||
filterQuery = filterQuery + Server.ID_CHUNK_SEP + chunkID;
|
||||
q.addFilterQuery(filterQuery);
|
||||
q.setFields(Schema.CONTENT.toString());
|
||||
try {
|
||||
@@ -615,7 +620,8 @@ class Server {
|
||||
* @throws SolrServerException
|
||||
*/
|
||||
private int queryNumFileChunks(long contentID) throws SolrServerException {
|
||||
SolrQuery q = new SolrQuery("id:" + Long.toString(contentID) + "_*");
|
||||
final SolrQuery q =
|
||||
new SolrQuery(Server.Schema.ID + ":" + Long.toString(contentID) + Server.ID_CHUNK_SEP + "*");
|
||||
q.setRows(0);
|
||||
return (int) query(q).getResults().getNumFound();
|
||||
}
|
||||
|
||||
@@ -22,35 +22,22 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.apache.solr.client.solrj.SolrQuery;
|
||||
import org.apache.solr.client.solrj.SolrServerException;
|
||||
import org.apache.solr.client.solrj.response.TermsResponse;
|
||||
import org.apache.solr.client.solrj.response.TermsResponse.Term;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
|
||||
import org.sleuthkit.autopsy.keywordsearch.KeywordSearchQueryManager.Presentation;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.FsContent;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
public class TermComponentQuery implements KeywordSearchQuery {
|
||||
@@ -67,6 +54,7 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
private List<Term> terms;
|
||||
private Keyword keywordQuery = null;
|
||||
private KeywordQueryFilter filter = null;
|
||||
private String field = null;
|
||||
|
||||
public TermComponentQuery(Keyword keywordQuery) {
|
||||
this.keywordQuery = keywordQuery;
|
||||
@@ -76,12 +64,16 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
terms = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setFilter(KeywordQueryFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setField(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void escape() {
|
||||
queryEscaped = Pattern.quote(termsQuery);
|
||||
@@ -109,7 +101,7 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
public boolean isEscaped() {
|
||||
return isEscaped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isLiteral() {
|
||||
return false;
|
||||
@@ -170,10 +162,6 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
public KeywordWriteResult writeToBlackBoard(String termHit, AbstractFile newFsHit, String snippet, String listName) {
|
||||
final String MODULE_NAME = KeywordSearchIngestService.MODULE_NAME;
|
||||
|
||||
if (snippet == null || snippet.equals("")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//there is match actually in this file, create artifact only then
|
||||
BlackboardArtifact bba = null;
|
||||
KeywordWriteResult writeResult = null;
|
||||
@@ -196,7 +184,8 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), MODULE_NAME, "", listName));
|
||||
|
||||
//preview
|
||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID(), MODULE_NAME, "", snippet));
|
||||
if (snippet != null)
|
||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID(), MODULE_NAME, "", snippet));
|
||||
|
||||
//regex keyword
|
||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID(), MODULE_NAME, "", termsQuery));
|
||||
@@ -224,7 +213,7 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<ContentHit>> performQuery() throws NoOpenCoreException{
|
||||
public Map<String, List<ContentHit>> performQuery() throws NoOpenCoreException {
|
||||
Map<String, List<ContentHit>> results = new HashMap<String, List<ContentHit>>();
|
||||
|
||||
final SolrQuery q = createQuery();
|
||||
@@ -232,15 +221,13 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
|
||||
|
||||
for (Term term : terms) {
|
||||
final String termS = KeywordSearchUtil.escapeLuceneQuery(term.getTerm(), true, false);
|
||||
final String termStr = KeywordSearchUtil.escapeLuceneQuery(term.getTerm(), true, false);
|
||||
|
||||
StringBuilder filesQueryB = new StringBuilder();
|
||||
filesQueryB.append(TERMS_SEARCH_FIELD).append(":").append(termS);
|
||||
final String queryStr = filesQueryB.toString();
|
||||
|
||||
LuceneQuery filesQuery = new LuceneQuery(queryStr);
|
||||
if (filter != null)
|
||||
LuceneQuery filesQuery = new LuceneQuery(termStr);
|
||||
filesQuery.setField(TERMS_SEARCH_FIELD);
|
||||
if (filter != null) {
|
||||
filesQuery.setFilter(filter);
|
||||
}
|
||||
try {
|
||||
Map<String, List<ContentHit>> subResults = filesQuery.performQuery();
|
||||
Set<ContentHit> filesResults = new HashSet<ContentHit>();
|
||||
@@ -248,12 +235,10 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
filesResults.addAll(subResults.get(key));
|
||||
}
|
||||
results.put(term.getTerm(), new ArrayList<ContentHit>(filesResults));
|
||||
}
|
||||
catch (NoOpenCoreException e) {
|
||||
} catch (NoOpenCoreException e) {
|
||||
logger.log(Level.WARNING, "Error executing Solr query,", e);
|
||||
throw e;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
} catch (RuntimeException e) {
|
||||
logger.log(Level.WARNING, "Error executing Solr query,", e);
|
||||
}
|
||||
|
||||
@@ -262,5 +247,4 @@ public class TermComponentQuery implements KeywordSearchQuery {
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Keyword Search</title>
|
||||
<link rel="stylesheet" href="nbdocs:/org/netbeans/modules/usersguide/ide.css" type="text/css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Keyword Search</h2>
|
||||
<p>
|
||||
Autopsy contains a keyword search <a href="nbdocs:/org/sleuthkit/autopsy/ingest/docs/ingest-about.html">ingest module</a>
|
||||
that extracts and indexes strings from the files on the image being ingested. Search queries will not be executed until the
|
||||
ingest module has finished running.
|
||||
</p>
|
||||
<p>
|
||||
To see keyword search results in real-time while ingest is running, add keyword lists using the
|
||||
<a href="nbdocs:/org/sleuthkit/autopsy/keywordsearch/docs/keywordsearch-configuration.html">Keyword Search Configuration Dialog</a>
|
||||
and select the "Use during triage / ingest" check box. See <a href="nbdocs:/org/sleuthkit/autopsy/ingest/docs/ingest-about.html">(Ingest)</a>
|
||||
for more information on refresh speeds and ingest in general.
|
||||
</p>
|
||||
<p>
|
||||
Once ingest is finished and the index has been created, the <a href="nbdocs:/org/sleuthkit/autopsy/keywordsearch/docs/keywordsearch-bar.html">Keyword Search Bar</a>
|
||||
will be available for use.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
<!--
|
||||
Tip: to create a link which will open in an external web browser, try:
|
||||
<object classid="java:org.netbeans.modules.javahelp.BrowserDisplayer">
|
||||
<param name="content" value="http://www.netbeans.org/">
|
||||
<param name="text" value="<html><u>http://www.netbeans.org/</u></html>">
|
||||
<param name="textFontSize" value="medium">
|
||||
<param name="textColor" value="blue">
|
||||
</object>
|
||||
To create a link to a help set from another module, you need to know the code name base and path, e.g.:
|
||||
<a href="nbdocs://org.netbeans.modules.usersguide/org/netbeans/modules/usersguide/configure/configure_options.html">Using the Options Window</a>
|
||||
(This link will behave sanely if that module is disabled or missing.)
|
||||
-->
|
||||
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Keyword Search Bar</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Keyword Search Bar</h2>
|
||||
<p>
|
||||
The keyword search bar is used to search the index for matching words, phrases, lists, or regular expressions.
|
||||
It can also be used during ingest to add images to the search process.
|
||||
Enable regular expression mode by pressing the arrow to the left of the search box and selecting 'Use Regular Expressions'
|
||||
</p>
|
||||
<h2>Keyword List Search</h2>
|
||||
<p>
|
||||
Lists created using the <a href="nbdocs:/org/sleuthkit/autopsy/keywordsearch/docs/keywordsearch-configuration.html">Keyword Search Configuration Dialog</a>
|
||||
can be searched by pressing on the 'Keyword Lists' button, selecting the check boxes corresponding to the lists to be searched, and pressing the 'Search' button.
|
||||
</p>
|
||||
<h2>Search During Ingest</h2>
|
||||
<p>
|
||||
Searching during ingest is not supported. However, lists can be added to ingest by following the same procedure as above.
|
||||
</p>
|
||||
<img src="keywordsearch-bar.png" alt="Keyword Search Bar" />
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,49 @@
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Keyword Search Configuration</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Keyword Search Configuration Dialog</h2>
|
||||
<p>
|
||||
The keyword search configuration dialog is used to add, remove, and modify keyword search lists.
|
||||
</p>
|
||||
<p>
|
||||
To begin, select the 'New List' button and choose a name for the new Keyword List.
|
||||
Once the list has been created, keywords can be added to it. Regular expressions are supported using
|
||||
<a href="http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html">Java Regex Syntax</a>.
|
||||
Lists can be added to the keyword search ingest process; searches will happen at regular intervals as content is added to the index.
|
||||
</p>
|
||||
<h2>List Import and Export</h2>
|
||||
<p>
|
||||
Autopsy supports importing Encase tab-delimited lists as well as lists created previously with Autopsy.
|
||||
For Encase lists, folder structure and hierarchy is currently ignored. This will be fixed in a future version.
|
||||
There is currently no way to export lists for use with Encase. This will also be added in future releases.
|
||||
</p>
|
||||
<h2>NIST NSRL Support</h2>
|
||||
<p>
|
||||
The hash database ingest service can be configured to use the NIST NSRL hash database of known files.
|
||||
The keyword search configuration dialog contains an option to skip keyword indexing and search on files found in the NSRL.
|
||||
</p>
|
||||
<img src="keywordsearch-configuration.png" alt="Keyword Search Configuration Dialog" />
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE helpset PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN" "http://java.sun.com/products/javahelp/helpset_2_0.dtd">
|
||||
<helpset version="2.0">
|
||||
<title>KeywordSearch Help</title>
|
||||
<maps>
|
||||
<homeID>org.sleuthkit.autopsy.keywordsearch.about</homeID>
|
||||
<mapref location="keywordsearch-map.xml"/>
|
||||
</maps>
|
||||
<view mergetype="javax.help.AppendMerge">
|
||||
<name>TOC</name>
|
||||
<label>Table of Contents</label>
|
||||
<type>javax.help.TOCView</type>
|
||||
<data>keywordsearch-toc.xml</data>
|
||||
</view>
|
||||
<view mergetype="javax.help.AppendMerge">
|
||||
<name>Index</name>
|
||||
<label>Index</label>
|
||||
<type>javax.help.IndexView</type>
|
||||
<data>keywordsearch-idx.xml</data>
|
||||
</view>
|
||||
<view>
|
||||
<name>Search</name>
|
||||
<label>Search</label>
|
||||
<type>javax.help.SearchView</type>
|
||||
<data engine="com.sun.java.help.search.DefaultSearchEngine">JavaHelpSearch</data>
|
||||
</view>
|
||||
</helpset>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE index PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 2.0//EN" "http://java.sun.com/products/javahelp/index_2_0.dtd">
|
||||
<index version="2.0">
|
||||
<indexitem text="About KeywordSearch" target="org.sleuthkit.autopsy.keywordsearch.about"/>
|
||||
<indexitem text="Keyword Search Configuration" target="org.sleuthkit.autopsy.keywordsearch.configuration"/>
|
||||
<indexitem text="Keyword Search Bar" target="org.sleuthkit.autopsy.keywordsearch.bar"/>
|
||||
</index>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE map PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN" "http://java.sun.com/products/javahelp/map_2_0.dtd">
|
||||
<map version="2.0">
|
||||
<mapID target="org.sleuthkit.autopsy.keywordsearch.about" url="keywordsearch-about.html"/>
|
||||
<mapID target="org.sleuthkit.autopsy.keywordsearch.configuration" url="keywordsearch-configuration.html"/>
|
||||
<mapID target="org.sleuthkit.autopsy.keywordsearch.bar" url="keywordsearch-bar.html"/>
|
||||
</map>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2011 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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE toc PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 2.0//EN" "http://java.sun.com/products/javahelp/toc_2_0.dtd">
|
||||
<toc version="2.0">
|
||||
<tocitem text="Keyword Search">
|
||||
<tocitem text="About Keyword Search" target="org.sleuthkit.autopsy.keywordsearch.about"/>
|
||||
<tocitem text="Keyword Search Configuration Dialog" target="org.sleuthkit.autopsy.keywordsearch.configuration"/>
|
||||
<tocitem text="Keyword Search Bar" target="org.sleuthkit.autopsy.keywordsearch.bar"/>
|
||||
</tocitem>
|
||||
</toc>
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@HelpSetRegistration(helpSet = "keywordsearch-hs.xml", position = 3521)
|
||||
package org.sleuthkit.autopsy.keywordsearch.docs;
|
||||
|
||||
import org.netbeans.api.javahelp.HelpSetRegistration;
|
||||