Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 4626-CrDataSourcesUniqueConstraint

This commit is contained in:
William Schaefer
2019-03-05 12:55:43 -05:00
57 changed files with 5063 additions and 672 deletions
-1
View File
@@ -30,7 +30,6 @@
<dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
<dependency conf="core->default" org="org.jsoup" name="jsoup" rev="1.10.3"/>
<dependency conf="core->default" org="com.googlecode.plist" name="dd-plist" rev="1.20"/>
<dependency conf="core->default" org="com.fasterxml.jackson.core" name="jackson-core" rev="2.9.7"/>
-1
View File
@@ -12,7 +12,6 @@ file.reference.commons-compress-1.14.jar=release/modules/ext/commons-compress-1.
file.reference.commons-dbcp2-2.1.1.jar=release/modules/ext/commons-dbcp2-2.1.1.jar
file.reference.commons-io-2.5.jar=release/modules/ext/commons-io-2.5.jar
file.reference.commons-pool2-2.4.2.jar=release/modules/ext/commons-pool2-2.4.2.jar
file.reference.dd-plist-1.20.jar=release/modules/ext/dd-plist-1.20.jar
file.reference.geoapi-3.0.0.jar=release/modules/ext/geoapi-3.0.0.jar
file.reference.grib-4.5.5.jar=release/modules/ext/grib-4.5.5.jar
file.reference.httpservices-4.5.5.jar=release/modules/ext/httpservices-4.5.5.jar
-4
View File
@@ -629,10 +629,6 @@
<runtime-relative-path>ext/Rejistry-1.0-SNAPSHOT.jar</runtime-relative-path>
<binary-origin>release/modules/ext/Rejistry-1.0-SNAPSHOT.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/dd-plist-1.20.jar</runtime-relative-path>
<binary-origin>release/modules/ext/dd-plist-1.20.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/rome-1.5.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/rome-1.5.1.jar</binary-origin>
@@ -6,3 +6,4 @@ DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency
DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date
DataContentViewerOtherCases.earliestCaseLabel.toolTipText=
DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date:
DataContentViewerOtherCases.foundInLabel.text=
@@ -10,6 +10,7 @@ DataContentViewerOtherCases.correlatedArtifacts.failed=Failed to get frequency d
DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.
DataContentViewerOtherCases.correlatedArtifacts.title=Attribute Frequency
DataContentViewerOtherCases.earliestCaseNotAvailable=\ Not Enabled.
DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources.
DataContentViewerOtherCases.noOpenCase.errMsg=No open case available.
DataContentViewerOtherCases.selectAllMenuItem.text=Select All
DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details
@@ -22,6 +23,7 @@ DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency
DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date
DataContentViewerOtherCases.earliestCaseLabel.toolTipText=
DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date:
DataContentViewerOtherCases.foundInLabel.text=
DataContentViewerOtherCases.title=Other Occurrences
DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.
DataContentViewerOtherCasesTableModel.attribute=Matched Attribute
@@ -73,7 +73,7 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="otherCasesPanel" pref="58" max="32767" attributes="0"/>
<Component id="otherCasesPanel" pref="53" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
@@ -96,10 +96,10 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="483" max="32767" attributes="0"/>
<EmptySpace min="0" pref="61" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" pref="58" max="32767" attributes="0"/>
<Component id="tableContainerPanel" pref="53" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
@@ -117,31 +117,28 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="tableStatusPanel" pref="1282" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="218" max="-2" attributes="0"/>
</Group>
<Component id="tableScrollPane" alignment="0" max="32767" attributes="0"/>
<Component id="tableScrollPane" alignment="0" pref="1508" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="earliestCaseLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="earliestCaseDate" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace min="-2" pref="66" max="-2" attributes="0"/>
<Component id="foundInLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="1157" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="tableScrollPane" pref="27" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
<Component id="tableScrollPane" pref="71" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="earliestCaseLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="earliestCaseDate" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="foundInLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="tableStatusPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@@ -198,26 +195,13 @@
</Property>
</Properties>
</Component>
<Container class="javax.swing.JPanel" name="tableStatusPanel">
<Component class="javax.swing.JLabel" name="foundInLabel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1500, 16]"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.foundInLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="16" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Container>
</Component>
</SubComponents>
</Container>
</SubComponents>
@@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2018 Basis Technology Corp.
* Copyright 2015-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,10 +33,12 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JFileChooser;
@@ -323,6 +325,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
tableModel.clearTable();
correlationAttributes.clear();
earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable());
foundInLabel.setText("");
}
@Override
@@ -354,6 +357,40 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
public int isPreferred(Node node) {
return 1;
}
/**
* Set the number of unique cases and data sources.
*/
@Messages({
"DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources."
})
private void setOccurrenceCounts() {
DataContentViewerOtherCasesTableModel model = (DataContentViewerOtherCasesTableModel) otherCasesTable.getModel();
int caseColumnIndex = DataContentViewerOtherCasesTableModel.TableColumns.CASE_NAME.ordinal();
int deviceColumnIndex = DataContentViewerOtherCasesTableModel.TableColumns.DEVICE.ordinal();
/*
* We need a unique set of data sources. We rely on device ID for this.
* To mitigate edge cases where a device ID could be duplicated in the
* same case (e.g. "report.xml"), we put the device ID and case name in
* a key-value pair.
*
* Note: Relying on the case name isn't a fool-proof way of determining
* a case to be unique. We should improve this in the future.
*/
Set<String> cases = new HashSet<>();
Map<String, String> devices = new HashMap();
for (int i=0; i < model.getRowCount(); i++) {
String caseName = (String) model.getValueAt(i, caseColumnIndex);
String deviceId = (String) model.getValueAt(i, deviceColumnIndex);
cases.add(caseName);
devices.put(deviceId, caseName);
}
foundInLabel.setText(String.format(Bundle.DataContentViewerOtherCases_foundIn_text(), model.getRowCount(), cases.size(), devices.size()));
}
/**
* Get the associated BlackboardArtifact from a node, if it exists.
@@ -737,7 +774,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
} else {
setColumnWidths();
}
setEarliestCaseDate();
setOccurrenceCounts();
}
/**
@@ -789,7 +828,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
otherCasesTable = new javax.swing.JTable();
earliestCaseLabel = new javax.swing.JLabel();
earliestCaseDate = new javax.swing.JLabel();
tableStatusPanel = new javax.swing.JPanel();
foundInLabel = new javax.swing.JLabel();
rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
@@ -835,44 +874,31 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N
tableStatusPanel.setPreferredSize(new java.awt.Dimension(1500, 16));
javax.swing.GroupLayout tableStatusPanelLayout = new javax.swing.GroupLayout(tableStatusPanel);
tableStatusPanel.setLayout(tableStatusPanelLayout);
tableStatusPanelLayout.setHorizontalGroup(
tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
tableStatusPanelLayout.setVerticalGroup(
tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 16, Short.MAX_VALUE)
);
org.openide.awt.Mnemonics.setLocalizedText(foundInLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.foundInLabel.text")); // NOI18N
javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel);
tableContainerPanel.setLayout(tableContainerPanelLayout);
tableContainerPanelLayout.setHorizontalGroup(
tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup()
.addComponent(tableStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1282, Short.MAX_VALUE)
.addGap(218, 218, 218))
.addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1508, Short.MAX_VALUE)
.addGroup(tableContainerPanelLayout.createSequentialGroup()
.addComponent(earliestCaseLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(earliestCaseDate)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGap(66, 66, 66)
.addComponent(foundInLabel)
.addGap(0, 1157, Short.MAX_VALUE))
);
tableContainerPanelLayout.setVerticalGroup(
tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup()
.addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 27, Short.MAX_VALUE)
.addGap(2, 2, 2)
.addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(earliestCaseLabel)
.addComponent(earliestCaseDate))
.addGap(0, 0, 0)
.addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0))
.addComponent(earliestCaseDate)
.addComponent(foundInLabel))
.addGap(6, 6, 6))
);
javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel);
@@ -885,10 +911,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
);
otherCasesPanelLayout.setVerticalGroup(
otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 483, Short.MAX_VALUE)
.addGap(0, 61, Short.MAX_VALUE)
.addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(otherCasesPanelLayout.createSequentialGroup()
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 59, Short.MAX_VALUE)
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 53, Short.MAX_VALUE)
.addGap(0, 0, 0)))
);
@@ -900,7 +926,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 59, Short.MAX_VALUE)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 53, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
@@ -924,6 +950,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
private javax.swing.JLabel earliestCaseDate;
private javax.swing.JLabel earliestCaseLabel;
private javax.swing.JMenuItem exportToCSVMenuItem;
private javax.swing.JLabel foundInLabel;
private javax.swing.JPanel otherCasesPanel;
private javax.swing.JTable otherCasesTable;
private javax.swing.JPopupMenu rightClickPopupMenu;
@@ -932,7 +959,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
private javax.swing.JMenuItem showCommonalityMenuItem;
private javax.swing.JPanel tableContainerPanel;
private javax.swing.JScrollPane tableScrollPane;
private javax.swing.JPanel tableStatusPanel;
// End of variables declaration//GEN-END:variables
/**
@@ -34,6 +34,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -1057,30 +1058,43 @@ abstract class AbstractSqlEamDb implements EamDb {
}
}
/**
* Retrieves eamArtifact instances from the database that are associated
* with the eamArtifactType and eamArtifactValue of the given eamArtifact.
*
* @param aType The type of the artifact
* @param value The correlation value
*
* @return List of artifact instances for a given type/value
*
* @throws EamDbException
*/
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
return getArtifactInstancesByTypeValues(aType, Arrays.asList(value));
}
String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value);
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValues(CorrelationAttributeInstance.Type aType, List<String> values) throws EamDbException, CorrelationAttributeNormalizationException {
return getArtifactInstances(prepareGetInstancesSql(aType, values), aType);
}
Connection conn = connect();
List<CorrelationAttributeInstance> artifactInstances = new ArrayList<>();
CorrelationAttributeInstance artifactInstance;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValuesAndCases(CorrelationAttributeInstance.Type aType, List<String> values, List<Integer> caseIds) throws EamDbException, CorrelationAttributeNormalizationException {
String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType);
String sql
= " and "
+ tableName
+ ".case_id in ('";
StringBuilder inValuesBuilder = new StringBuilder(prepareGetInstancesSql(aType, values));
inValuesBuilder.append(sql);
inValuesBuilder.append(caseIds.stream().map(String::valueOf).collect(Collectors.joining("', '")));
inValuesBuilder.append("')");
return getArtifactInstances(inValuesBuilder.toString(), aType);
}
/**
* Get the select statement for retrieving correlation attribute instances
* from the CR for a given type with values matching the specified values
*
* @param aType The type of the artifact
* @param values The list of correlation values to get
* CorrelationAttributeInstances for
*
* @return the select statement as a String
*
* @throws CorrelationAttributeNormalizationException
*/
private String prepareGetInstancesSql(CorrelationAttributeInstance.Type aType, List<String> values) throws CorrelationAttributeNormalizationException {
String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType);
String sql
= "SELECT "
@@ -1098,11 +1112,42 @@ abstract class AbstractSqlEamDb implements EamDb {
+ " LEFT JOIN data_sources ON "
+ tableName
+ ".data_source_id=data_sources.id"
+ " WHERE value=?";
+ " WHERE value IN (";
StringBuilder inValuesBuilder = new StringBuilder(sql);
for (String value : values) {
if (value != null) {
inValuesBuilder.append("'");
inValuesBuilder.append(CorrelationAttributeNormalizer.normalize(aType, value));
inValuesBuilder.append("',");
}
}
inValuesBuilder.deleteCharAt(inValuesBuilder.length() - 1); //delete last comma
inValuesBuilder.append(")");
return inValuesBuilder.toString();
}
/**
* Retrieves eamArtifact instances from the database that are associated
* with the eamArtifactType and eamArtifactValues of the given eamArtifact.
*
* @param aType The type of the artifact
* @param values The list of correlation values to get
* CorrelationAttributeInstances for
*
* @return List of artifact instances for a given type with the specified
* values
*
* @throws CorrelationAttributeNormalizationException
* @throws EamDbException
*/
private List<CorrelationAttributeInstance> getArtifactInstances(String sql, CorrelationAttributeInstance.Type aType) throws CorrelationAttributeNormalizationException, EamDbException {
Connection conn = connect();
List<CorrelationAttributeInstance> artifactInstances = new ArrayList<>();
CorrelationAttributeInstance artifactInstance;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, normalizedValue);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
artifactInstance = getEamArtifactInstanceFromResultSet(resultSet, aType);
@@ -1115,7 +1160,6 @@ abstract class AbstractSqlEamDb implements EamDb {
EamDbUtil.closeResultSet(resultSet);
EamDbUtil.closeConnection(conn);
}
return artifactInstances;
}
@@ -24,7 +24,6 @@ import java.util.Set;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
/**
* Main interface for interacting with the database
@@ -200,27 +199,29 @@ public interface EamDb {
* Creates new Data Source in the database
*
* @param eamDataSource the data source to add
*
* @return - A CorrelationDataSource object with data source's central repository id
*
* @return - A CorrelationDataSource object with data source's central
* repository id
*/
CorrelationDataSource newDataSource(CorrelationDataSource eamDataSource) throws EamDbException;
/**
* Updates the MD5 hash value in an existing data source in the database.
*
* @param eamDataSource The data source to update
*/
void updateDataSourceMd5Hash(CorrelationDataSource eamDataSource) throws EamDbException;
/**
* Updates the SHA-1 hash value in an existing data source in the database.
*
* @param eamDataSource The data source to update
*/
void updateDataSourceSha1Hash(CorrelationDataSource eamDataSource) throws EamDbException;
/**
* Updates the SHA-256 hash value in an existing data source in the database.
* Updates the SHA-256 hash value in an existing data source in the
* database.
*
* @param eamDataSource The data source to update
*/
@@ -257,14 +258,14 @@ public interface EamDb {
/**
* Changes the name of a data source in the DB
*
* @param eamDataSource The data source
* @param newName The new name
*
* @throws EamDbException
*
* @param eamDataSource The data source
* @param newName The new name
*
* @throws EamDbException
*/
void updateDataSourceName(CorrelationDataSource eamDataSource, String newName) throws EamDbException;
/**
* Inserts new Artifact(s) into the database. Should add associated Case and
* Data Source first.
@@ -273,17 +274,55 @@ public interface EamDb {
*/
void addArtifactInstance(CorrelationAttributeInstance eamArtifact) throws EamDbException;
/**
* Retrieves eamArtifact instances from the database that are associated
* with the eamArtifactType and eamArtifactValues of the given eamArtifact.
*
* @param aType EamArtifact.Type to search for
* @param values The list of correlation values to get
* CorrelationAttributeInstances for
*
* @return List of artifact instances for a given type with the specified
* values
*
* @throws CorrelationAttributeNormalizationException
* @throws EamDbException
*/
List<CorrelationAttributeInstance> getArtifactInstancesByTypeValues(CorrelationAttributeInstance.Type aType, List<String> values) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Retrieves eamArtifact instances from the database that are associated
* with the eamArtifactType and eamArtifactValue of the given eamArtifact.
*
* @param aType EamArtifact.Type to search for
* @param value Value to search for
* @param aType The type of the artifact
* @param value The correlation value
*
* @return List of artifact instances for a given type/value
*
* @throws CorrelationAttributeNormalizationException
* @throws EamDbException
*/
List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Retrieves eamArtifact instances from the database that are associated
* with the eamArtifactType and eamArtifactValues of the given eamArtifact
* for the specified cases.
*
* @param aType The type of the artifact
* @param values The list of correlation values to get
* CorrelationAttributeInstances for
* @param caseIds The list of central repository case ids to get
* CorrelationAttributeInstances for
*
* @return List of artifact instances for a given type with the specified
* values for the specified cases
*
* @throws CorrelationAttributeNormalizationException
* @throws EamDbException
*/
List<CorrelationAttributeInstance> getArtifactInstancesByTypeValuesAndCases(CorrelationAttributeInstance.Type aType, List<String> values, List<Integer> caseIds) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Retrieves eamArtifact instances from the database that are associated
* with the aType and filePath
@@ -341,7 +380,7 @@ public interface EamDb {
* Retrieves number of eamArtifact instances in the database that are
* associated with the given data source.
*
* @param correlationDataSource Data source to search for
* @param correlationDataSource Data source to search for
*
* @return Number of artifact instances having caseDisplayName and
* dataSource
@@ -530,15 +530,6 @@ final class SqliteEamDb extends AbstractSqlEamDb {
}
}
/**
* Retrieves eamArtifact instances from the database that are associated
* with the eamArtifactType and eamArtifactValue of the given eamArtifact.
*
* @param aType The type of the artifact
* @param value The correlation value
*
* @return List of artifact instances for a given type/value
*/
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
@@ -549,6 +540,26 @@ final class SqliteEamDb extends AbstractSqlEamDb {
}
}
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValues(CorrelationAttributeInstance.Type aType, List<String> values) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getArtifactInstancesByTypeValues(aType, values);
} finally {
releaseSharedLock();
}
}
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValuesAndCases(CorrelationAttributeInstance.Type aType, List<String> values, List<Integer> caseIds) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getArtifactInstancesByTypeValuesAndCases(aType, values, caseIds);
} finally {
releaseSharedLock();
}
}
/**
* Retrieves eamArtifact instances from the database that are associated
* with the aType and filePath
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -56,7 +56,6 @@ public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttribut
@Override
public CommonAttributeCountSearchResults findMatchesByCount() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(corAttrType);
Map<Integer, CommonAttributeValueList> interCaseCommonFiles = eamDbAttrInst.findInterCaseValuesByCount(Case.getCurrentCase());
Set<String> mimeTypesToFilterOn = new HashSet<>();
if (isFilterByMedia()) {
mimeTypesToFilterOn.addAll(MEDIA_PICS_VIDEO_MIME_TYPES);
@@ -64,13 +63,13 @@ public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttribut
if (isFilterByDoc()) {
mimeTypesToFilterOn.addAll(TEXT_FILES_MIME_TYPES);
}
return new CommonAttributeCountSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType, mimeTypesToFilterOn);
Map<Integer, CommonAttributeValueList> interCaseCommonFiles = eamDbAttrInst.findInterCaseValuesByCount(Case.getCurrentCase(), mimeTypesToFilterOn);
return new CommonAttributeCountSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType);
}
@Override
public CommonAttributeCaseSearchResults findMatchesByCase() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(corAttrType);
Map<String, Map<String, CommonAttributeValueList>> interCaseCommonFiles = eamDbAttrInst.findInterCaseValuesByCase(Case.getCurrentCase());
Set<String> mimeTypesToFilterOn = new HashSet<>();
if (isFilterByMedia()) {
mimeTypesToFilterOn.addAll(MEDIA_PICS_VIDEO_MIME_TYPES);
@@ -78,7 +77,8 @@ public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttribut
if (isFilterByDoc()) {
mimeTypesToFilterOn.addAll(TEXT_FILES_MIME_TYPES);
}
return new CommonAttributeCaseSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType, mimeTypesToFilterOn);
Map<String, Map<String, CommonAttributeValueList>> interCaseCommonFiles = eamDbAttrInst.findInterCaseValuesByCase(Case.getCurrentCase(), mimeTypesToFilterOn);
return new CommonAttributeCaseSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType);
}
@NbBundle.Messages({
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,7 +33,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNor
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Stores the results from the various types of common attribute searching
@@ -55,11 +54,9 @@ final public class CommonAttributeCaseSearchResults {
* common, value of 0 is disabled
* @param resultType The type of Correlation Attribute being
* searched for
* @param mimeTypesToFilterOn Set of mime types to include for intercase
* searches
*/
CommonAttributeCaseSearchResults(Map<String, Map<String, CommonAttributeValueList>> metadata, int percentageThreshold, CorrelationAttributeInstance.Type resultType, Set<String> mimeTypesToFilterOn) {
this.caseNameToDataSources = filterMetadata(metadata, percentageThreshold, resultType.getId(), mimeTypesToFilterOn);
CommonAttributeCaseSearchResults(Map<String, Map<String, CommonAttributeValueList>> metadata, int percentageThreshold, CorrelationAttributeInstance.Type resultType) {
this.caseNameToDataSources = filterMetadata(metadata, percentageThreshold, resultType.getId());
}
/**
@@ -71,7 +68,7 @@ final public class CommonAttributeCaseSearchResults {
* common, value of 0 is disabled
*/
CommonAttributeCaseSearchResults(Map<String, Map<String, CommonAttributeValueList>> metadata, int percentageThreshold) {
this.caseNameToDataSources = filterMetadata(metadata, percentageThreshold, CorrelationAttributeInstance.FILES_TYPE_ID, new HashSet<>());
this.caseNameToDataSources = filterMetadata(metadata, percentageThreshold, CorrelationAttributeInstance.FILES_TYPE_ID);
}
/**
@@ -110,11 +107,10 @@ final public class CommonAttributeCaseSearchResults {
* not be more common than
* @param resultTypeId the ID of the result type contained in the
* metadata
* @param mimeTypesToFilterOn the mimetypes to include in our results
*
* @return metadata
*/
private Map<String, Map<String, CommonAttributeValueList>> filterMetadata(Map<String, Map<String, CommonAttributeValueList>> metadata, int percentageThreshold, int resultTypeId, Set<String> mimeTypesToFilterOn) {
private Map<String, Map<String, CommonAttributeValueList>> filterMetadata(Map<String, Map<String, CommonAttributeValueList>> metadata, int percentageThreshold, int resultTypeId) {
try {
final String currentCaseName;
try {
@@ -123,8 +119,9 @@ final public class CommonAttributeCaseSearchResults {
throw new EamDbException("Unable to get current case while performing filtering", ex);
}
Map<String, CommonAttributeValueList> currentCaseDataSourceMap = metadata.get(currentCaseName);
if (currentCaseDataSourceMap == null) {
throw new EamDbException("No data for current case found in results, indicating there are no results and nothing will be filtered");
Map<String, Map<String, CommonAttributeValueList>> filteredCaseNameToDataSourcesTree = new HashMap<>();
if (currentCaseDataSourceMap == null) { //there are no results
return filteredCaseNameToDataSourcesTree;
}
CorrelationAttributeInstance.Type attributeType = CorrelationAttributeInstance
.getDefaultCorrelationTypes()
@@ -133,13 +130,14 @@ final public class CommonAttributeCaseSearchResults {
.findFirst().get();
//Call countUniqueDataSources once to reduce the number of DB queries needed to get the frequencyPercentage
Double uniqueCaseDataSourceTuples = EamDb.getInstance().getCountUniqueDataSources().doubleValue();
Map<String, Map<String, CommonAttributeValueList>> filteredCaseNameToDataSourcesTree = new HashMap<>();
Map<String, CommonAttributeValue> valuesToKeepCurrentCase = getValuesToKeepFromCurrentCase(currentCaseDataSourceMap, attributeType, percentageThreshold, uniqueCaseDataSourceTuples, mimeTypesToFilterOn);
Map<String, CommonAttributeValue> valuesToKeepCurrentCase = getValuesToKeepFromCurrentCase(currentCaseDataSourceMap, attributeType, percentageThreshold, uniqueCaseDataSourceTuples);
for (Entry<String, Map<String, CommonAttributeValueList>> mapOfDataSources : Collections.unmodifiableMap(metadata).entrySet()) {
if (!mapOfDataSources.getKey().equals(currentCaseName)) {
//rebuild the metadata structure with items from the current case substituted for their matches in other cases results we want to filter out removed
Map<String, CommonAttributeValueList> newTreeForCase = createTreeForCase(valuesToKeepCurrentCase, mapOfDataSources.getValue());
filteredCaseNameToDataSourcesTree.put(mapOfDataSources.getKey(), newTreeForCase);
if (!newTreeForCase.isEmpty()) {
filteredCaseNameToDataSourcesTree.put(mapOfDataSources.getKey(), newTreeForCase);
}
}
}
return filteredCaseNameToDataSourcesTree;
@@ -162,21 +160,20 @@ final public class CommonAttributeCaseSearchResults {
* should not be more common than
* @param uniqueCaseDataSourceTuples the number of unique data sources in
* the CR
* @param mimeTypesToFilterOn the mimetypes to include in our results
*
* @return a map of correlation value to CommonAttributeValue for results
* from the current case
*
* @throws EamDbException
*/
private Map<String, CommonAttributeValue> getValuesToKeepFromCurrentCase(Map<String, CommonAttributeValueList> dataSourceToValueList, CorrelationAttributeInstance.Type attributeType, int maximumPercentageThreshold, Double uniqueCaseDataSourceTuples, Set<String> mimeTypesToFilterOn) throws EamDbException {
private Map<String, CommonAttributeValue> getValuesToKeepFromCurrentCase(Map<String, CommonAttributeValueList> dataSourceToValueList, CorrelationAttributeInstance.Type attributeType, int maximumPercentageThreshold, Double uniqueCaseDataSourceTuples) throws EamDbException {
Map<String, CommonAttributeValue> valuesToKeep = new HashMap<>();
Set<String> valuesToRemove = new HashSet<>();
for (Entry<String, CommonAttributeValueList> mapOfValueLists : Collections.unmodifiableMap(dataSourceToValueList).entrySet()) {
for (CommonAttributeValue value : mapOfValueLists.getValue().getDelayedMetadataList()) {
for (CommonAttributeValue value : mapOfValueLists.getValue().getDelayedMetadataSet()) {
if (valuesToRemove.contains(value.getValue())) {
//do nothing this value will not be added
} else if (filterValue(attributeType, value, maximumPercentageThreshold, uniqueCaseDataSourceTuples, mimeTypesToFilterOn)) {
} else if (filterValue(attributeType, value, maximumPercentageThreshold, uniqueCaseDataSourceTuples)) {
valuesToRemove.add(value.getValue());
} else {
valuesToKeep.put(value.getValue(), value);
@@ -202,7 +199,7 @@ final public class CommonAttributeCaseSearchResults {
private Map<String, CommonAttributeValueList> createTreeForCase(Map<String, CommonAttributeValue> valuesToKeepCurrentCase, Map<String, CommonAttributeValueList> dataSourceToValueList) throws EamDbException {
Map<String, CommonAttributeValueList> treeForCase = new HashMap<>();
for (Entry<String, CommonAttributeValueList> mapOfValueLists : Collections.unmodifiableMap(dataSourceToValueList).entrySet()) {
for (CommonAttributeValue value : mapOfValueLists.getValue().getDelayedMetadataList()) {
for (CommonAttributeValue value : mapOfValueLists.getValue().getDelayedMetadataSet()) {
if (valuesToKeepCurrentCase.containsKey(value.getValue())) {
if (!treeForCase.containsKey(mapOfValueLists.getKey())) {
treeForCase.put(mapOfValueLists.getKey(), new CommonAttributeValueList());
@@ -226,7 +223,6 @@ final public class CommonAttributeCaseSearchResults {
* should not be more common than
* @param uniqueCaseDataSourceTuples the number of unique data sources in
* the CR
* @param mimeTypesToInclude the mimetypes to include in our results
*
* @return true if the value should be filtered and removed from what is
* shown to the user, false if the value should not be removed and
@@ -234,20 +230,7 @@ final public class CommonAttributeCaseSearchResults {
*
* @throws EamDbException
*/
private boolean filterValue(CorrelationAttributeInstance.Type attributeType, CommonAttributeValue value, int maximumPercentageThreshold, Double uniqueCaseDataSourceTuples, Set<String> mimeTypesToInclude) throws EamDbException {
//Intracase common attribute searches will have been created with an empty mimeTypesToInclude list
//because when performing intra case search this filtering will have been done during the query of the case database
if (!mimeTypesToInclude.isEmpty()) { //only do the mime type filtering when mime types aren't empty
for (AbstractCommonAttributeInstance commonAttr : value.getInstances()) {
AbstractFile abstractFile = commonAttr.getAbstractFile();
if (abstractFile != null) {
String mimeType = abstractFile.getMIMEType();
if (mimeType != null && !mimeTypesToInclude.contains(mimeType)) {
return true;
}
}
}
}
private boolean filterValue(CorrelationAttributeInstance.Type attributeType, CommonAttributeValue value, int maximumPercentageThreshold, Double uniqueCaseDataSourceTuples) throws EamDbException {
if (maximumPercentageThreshold != 0) { //only do the frequency filtering when a max % was set
try {
Double uniqueTypeValueTuples = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,18 +22,15 @@ package org.sleuthkit.autopsy.commonpropertiessearch;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Stores the results from the various types of common attribute searching
@@ -45,7 +42,6 @@ final public class CommonAttributeCountSearchResults {
// maps instance count to list of attribute values.
private final Map<Integer, CommonAttributeValueList> instanceCountToAttributeValues;
private final Set<String> mimeTypesToInclude;
private final int percentageThreshold;
private final int resultTypeId;
@@ -58,15 +54,13 @@ final public class CommonAttributeCountSearchResults {
* common, value of 0 is disabled
* @param resultType The type of Correlation Attribute being
* searched for
* @param mimeTypesToFilterOn Set of mime types to include for intercase
* searches
*
*/
CommonAttributeCountSearchResults(Map<Integer, CommonAttributeValueList> metadata, int percentageThreshold, CorrelationAttributeInstance.Type resultType, Set<String> mimeTypesToFilterOn) {
CommonAttributeCountSearchResults(Map<Integer, CommonAttributeValueList> metadata, int percentageThreshold, CorrelationAttributeInstance.Type resultType) {
//wrap in a new object in case any client code has used an unmodifiable collection
this.instanceCountToAttributeValues = new HashMap<>(metadata);
this.percentageThreshold = percentageThreshold;
this.resultTypeId = resultType.getId();
this.mimeTypesToInclude = mimeTypesToFilterOn;
}
/**
@@ -82,7 +76,6 @@ final public class CommonAttributeCountSearchResults {
this.instanceCountToAttributeValues = new HashMap<>(metadata);
this.percentageThreshold = percentageThreshold;
this.resultTypeId = CorrelationAttributeInstance.FILES_TYPE_ID;
this.mimeTypesToInclude = new HashSet<>(); //don't filter on mimetypes
}
/**
@@ -152,36 +145,8 @@ final public class CommonAttributeCountSearchResults {
final Integer key = listOfValues.getKey();
final CommonAttributeValueList values = listOfValues.getValue();
for (CommonAttributeValue value : values.getDelayedMetadataList()) { // Need the real metadata
//Intracase common attribute searches will have been created with an empty mimeTypesToInclude list
//because when performing intra case search this filtering will have been done during the query of the case database
boolean mimeTypeToRemove = false; //allow code to be more efficient by not attempting to remove the same value multiple times
if (!mimeTypesToInclude.isEmpty()) { //only do the mime type filtering when mime types aren't empty
for (AbstractCommonAttributeInstance commonAttr : value.getInstances()) {
AbstractFile abstractFile = commonAttr.getAbstractFile();
if (abstractFile != null) {
String mimeType = commonAttr.getAbstractFile().getMIMEType();
if (mimeType != null && !mimeTypesToInclude.contains(mimeType)) {
if (itemsToRemove.containsKey(key)) {
itemsToRemove.get(key).add(value);
} else {
List<CommonAttributeValue> toRemove = new ArrayList<>();
toRemove.add(value);
itemsToRemove.put(key, toRemove);
}
//value will be removed as the mime type existed and was not in the set to be included
//because value is removed this value does not need to be checked further
mimeTypeToRemove = true;
break;
}
}
if (mimeTypeToRemove) {
break;
}
}
}
if (!mimeTypeToRemove && maximumPercentageThreshold != 0) { //only do the frequency filtering when a max % was set
for (CommonAttributeValue value : values.getDelayedMetadataSet()) { // Need the real metadata
if (maximumPercentageThreshold != 0) { //only do the frequency filtering when a max % was set
try {
Double uniqueTypeValueTuples = eamDb.getCountUniqueCaseDataSourceTuplesHavingTypeValue(
attributeType, value.getValue()).doubleValue();
@@ -209,7 +174,7 @@ final public class CommonAttributeCountSearchResults {
final CommonAttributeValueList instanceCountValue = this.instanceCountToAttributeValues.get(key);
if (instanceCountValue != null) {
instanceCountValue.removeMetaData(value);
if (instanceCountValue.getDelayedMetadataList().isEmpty()) { // Check the real metadata
if (instanceCountValue.getDelayedMetadataSet().isEmpty()) { // Check the real metadata
this.instanceCountToAttributeValues.remove(key);
}
}
@@ -226,7 +191,7 @@ final public class CommonAttributeCountSearchResults {
int count = 0;
for (CommonAttributeValueList data : this.instanceCountToAttributeValues.values()) {
for (CommonAttributeValue md5 : data.getDelayedMetadataList()) {
for (CommonAttributeValue md5 : data.getDelayedMetadataSet()) {
count += md5.getInstanceCount();
}
}
@@ -1,16 +1,16 @@
/*
*
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
*
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.commonpropertiessearch;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Utility and wrapper model around data required for Common Files Search
@@ -34,10 +36,10 @@ final public class CommonAttributeValueList {
* The list of value nodes, which begins empty.
*/
private final List<CommonAttributeValue> metadataList;
/**
* The backing list of value nodes, which will be dynamically loaded
* when requested.
* The backing list of value nodes, which will be dynamically loaded when
* requested.
*/
private final List<CommonAttributeValue> delayedMetadataList;
@@ -58,36 +60,38 @@ final public class CommonAttributeValueList {
}
/**
* Get the list of value nodes. Will be empty if
* displayDelayedMetadata() has not been called for the
* parent InstanceCountNode
* Get the list of value nodes. Will be empty if displayDelayedMetadata()
* has not been called for the parent InstanceCountNode
*
* @return metadataList the list of nodes
*/
public List<CommonAttributeValue> getMetadataList() {
return Collections.unmodifiableList(this.metadataList);
}
/**
* Get the delayed list of value nodes. Only use for
* determining how many CommonAttributeValues
* actually exist in the list.
* @return metadataList the list of nodes
* Get the delayed set of value nodes. Only use for determining which values
* and how many CommonAttributeValues actually exist in the list.
*
* @return metadataList the set of nodes
*/
List<CommonAttributeValue> getDelayedMetadataList() {
return Collections.unmodifiableList(this.delayedMetadataList);
Set<CommonAttributeValue> getDelayedMetadataSet() {
//Allows nodes to be de-duped
return new HashSet<>(this.delayedMetadataList);
}
void removeMetaData(CommonAttributeValue commonVal) {
this.delayedMetadataList.remove(commonVal);
}
/**
* Return the size of the backing list, in case
* displayDelayedMetadata() has not be called yet.
* Return the size of the backing list, in case displayDelayedMetadata() has
* not be called yet.
*
* @return int the number of matches for this value
*/
int getCommonAttributeListSize() {
return this.delayedMetadataList.size();
return this.delayedMetadataList.size();
}
/**
@@ -103,6 +107,7 @@ final public class CommonAttributeValueList {
/**
* A a value node to the list, to be loaded later.
*
* @param metadata the node to add
*/
void addMetadataToList(CommonAttributeValue metadata) {
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -110,8 +110,7 @@ public final class InstanceDataSourceNode extends DisplayableItemNode {
}
/**
* ChildFactory which builds DisplayableItem from the metadata data
* sources.
* ChildFactory which builds DisplayableItem from the metadata data sources.
*/
static class FileInstanceNodeFactory extends ChildFactory<AbstractCommonAttributeInstance> {
@@ -123,7 +122,7 @@ public final class InstanceDataSourceNode extends DisplayableItemNode {
@Override
protected boolean createKeys(List<AbstractCommonAttributeInstance> list) {
for (CommonAttributeValue value : descendants.getDelayedMetadataList()) {
for (CommonAttributeValue value : descendants.getDelayedMetadataSet()) {
// This is a bit of a hack to ensure that the AbstractFile instance
// has been created before createNodesForKey() is called. Constructing
// the AbstractFile in createNodesForKey() was resulting in UI lockups.
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,10 +18,16 @@
*/
package org.sleuthkit.autopsy.commonpropertiessearch;
import com.google.common.collect.Iterables;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -32,12 +38,13 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.InstanceTableCallback;
import org.sleuthkit.autopsy.commonpropertiessearch.AbstractCommonAttributeInstance.NODE_TYPE;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.HashUtility;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Used to process and return CorrelationCase values from the EamDB for
@@ -45,23 +52,13 @@ import org.sleuthkit.datamodel.HashUtility;
*/
final class InterCaseSearchResultsProcessor {
private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName());
private static final String INTER_CASE_WHERE_CLAUSE = "case_id=%s AND (known_status !=%s OR known_status IS NULL)"; //NON-NLS
/**
* The CorrelationAttributeInstance.Type this Processor will query on
*/
private final Type correlationType;
private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName());
/**
* The initial CorrelationAttributeInstance ids lookup query.
*/
private final String interCaseWhereClause;
/**
* The single CorrelationAttributeInstance object retrieval query
*/
private final String singleInterCaseWhereClause;
/**
* Used in the InterCaseCommonAttributeSearchers to find common attribute
* instances and generate nodes at the UI level.
@@ -71,32 +68,6 @@ final class InterCaseSearchResultsProcessor {
*/
InterCaseSearchResultsProcessor(CorrelationAttributeInstance.Type theType) {
this.correlationType = theType;
interCaseWhereClause = getInterCaseWhereClause();
singleInterCaseWhereClause = getSingleInterCaseWhereClause();
}
private String getInterCaseWhereClause() {
String tableName = EamDbUtil.correlationTypeToInstanceTableName(correlationType);
StringBuilder sqlString = new StringBuilder(250);
sqlString.append("value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value)")
.append(" GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value");
return sqlString.toString();
}
private String getSingleInterCaseWhereClause() {
String tableName = EamDbUtil.correlationTypeToInstanceTableName(correlationType);
StringBuilder sqlString = new StringBuilder(250);
sqlString.append("value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value)")
.append(" AND (case_id=%s OR case_id=%s) GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value");
return sqlString.toString();
}
/**
@@ -122,30 +93,53 @@ final class InterCaseSearchResultsProcessor {
return null;
}
/**
* Get the portion of the select query which will get md5 values for files
* from the current case which are potentially being correlated on.
*
* @param mimeTypesToFilterOn the set of mime types to filter on
*
* @return the portion of a query which follows the SELECT keyword for
* finding MD5s which we are correlating on
*
* @throws EamDbException
*/
private String getFileQuery(Set<String> mimeTypesToFilterOn) throws EamDbException {
String query;
query = "md5 AS value FROM tsk_files WHERE known!=" + TskData.FileKnown.KNOWN.getFileKnownValue() + " AND md5 IS NOT NULL"; //NON-NLS
if (!mimeTypesToFilterOn.isEmpty()) {
query = query + " AND mime_type IS NOT NULL AND mime_type IN ('" + String.join("', '", mimeTypesToFilterOn) + "')"; //NON-NLS
}
return query;
}
/**
* Given the current case, fins all intercase common files from the EamDb
* and builds maps of case name to maps of data source name to
* CommonAttributeValueList.
*
* @param currentCase The current TSK Case.
* @param currentCase The current TSK Case.
* @param mimeTypesToFilterOn the set of mime types to filter on
*
* @return map of Case name to Maps of Datasources and their
* CommonAttributeValueLists
*/
Map<String, Map<String, CommonAttributeValueList>> findInterCaseValuesByCase(Case currentCase) {
Map<String, Map<String, CommonAttributeValueList>> findInterCaseValuesByCase(Case currentCase, Set<String> mimeTypesToFilterOn) {
try {
InterCaseByCaseCallback instancetableCallback = new InterCaseByCaseCallback();
EamDb dbManager = EamDb.getInstance();
int caseId = dbManager.getCase(currentCase).getID();
dbManager.processInstanceTableWhere(correlationType, String.format(interCaseWhereClause, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue()),
instancetableCallback);
InterCaseByCaseCallback instancetableCallback = new InterCaseByCaseCallback(caseId);
if (correlationType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
currentCase.getSleuthkitCase().getCaseDbAccessManager().select(getFileQuery(mimeTypesToFilterOn), instancetableCallback);
} else {
dbManager.processInstanceTableWhere(correlationType, String.format(INTER_CASE_WHERE_CLAUSE, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue()),
instancetableCallback);
}
return instancetableCallback.getInstanceCollatedCommonFiles();
} catch (EamDbException ex) {
} catch (EamDbException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex);
}
return new HashMap<>();
@@ -153,24 +147,30 @@ final class InterCaseSearchResultsProcessor {
/**
* Given the current case, fins all intercase common files from the EamDb
* and builds maps of obj id to md5 and case.
* and builds maps of obj id to value and case.
*
* @param currentCase The current TSK Case.
* @param currentCase The current TSK Case.
* @param mimeTypesToFilterOn the set of mime types to filter on
*
* @return map of number of instances to CommonAttributeValueLists
*/
Map<Integer, CommonAttributeValueList> findInterCaseValuesByCount(Case currentCase) {
Map<Integer, CommonAttributeValueList> findInterCaseValuesByCount(Case currentCase, Set<String> mimeTypesToFilterOn) {
try {
InterCaseByCountCallback instancetableCallback = new InterCaseByCountCallback();
EamDb dbManager = EamDb.getInstance();
int caseId = dbManager.getCase(currentCase).getID();
dbManager.processInstanceTableWhere(correlationType, String.format(interCaseWhereClause, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue()),
instancetableCallback);
InterCaseByCountCallback instancetableCallback = new InterCaseByCountCallback(caseId);
if (correlationType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
currentCase.getSleuthkitCase().getCaseDbAccessManager().select(getFileQuery(mimeTypesToFilterOn), instancetableCallback);
} else {
dbManager.processInstanceTableWhere(correlationType, String.format(INTER_CASE_WHERE_CLAUSE, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue()),
instancetableCallback);
}
return instancetableCallback.getInstanceCollatedCommonFiles();
} catch (EamDbException ex) {
} catch (EamDbException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex);
}
return new HashMap<>();
@@ -179,21 +179,30 @@ final class InterCaseSearchResultsProcessor {
/**
* Given the current case, and a specific case of interest, finds common
* files which exist between cases from the EamDb. Builds maps of obj id to
* md5 and case.
* value and case.
*
* @param currentCase The current TSK Case.
* @param singleCase The case of interest. Matches must exist in this case.
* @param currentCase The current TSK Case.
* @param mimeTypesToFilterOn the set of mime types to filter on
* @param singleCase The case of interest. Matches must exist in
* this case.
*
* @return map of number of instances to CommonAttributeValueLists
*/
Map<Integer, CommonAttributeValueList> findSingleInterCaseValuesByCount(Case currentCase, CorrelationCase singleCase) {
Map<Integer, CommonAttributeValueList> findSingleInterCaseValuesByCount(Case currentCase, Set<String> mimeTypesToFilterOn, CorrelationCase singleCase) {
try {
InterCaseByCountCallback instancetableCallback = new InterCaseByCountCallback();
EamDb dbManager = EamDb.getInstance();
int caseId = dbManager.getCase(currentCase).getID();
int targetCaseId = singleCase.getID();
dbManager.processInstanceTableWhere(correlationType, String.format(singleInterCaseWhereClause, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue(), caseId, targetCaseId), instancetableCallback);
InterCaseByCountCallback instancetableCallback = new InterCaseByCountCallback(caseId, targetCaseId);
if (correlationType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
currentCase.getSleuthkitCase().getCaseDbAccessManager().select(getFileQuery(mimeTypesToFilterOn), instancetableCallback);
} else {
dbManager.processInstanceTableWhere(correlationType, String.format(INTER_CASE_WHERE_CLAUSE, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue()),
instancetableCallback);
}
return instancetableCallback.getInstanceCollatedCommonFiles();
} catch (EamDbException ex) {
} catch (EamDbException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex);
}
return new HashMap<>();
@@ -204,24 +213,30 @@ final class InterCaseSearchResultsProcessor {
* files which exist between cases from the EamDb. Builds map of case name
* to maps of data source name to CommonAttributeValueList.
*
* @param currentCase The current TSK Case.
* @param currentCase The current TSK Case.
* @param mimeTypesToFilterOn the set of mime types to filter on
* @param singleCase The case of interest. Matches must exist in
* this case.
*
* @return map of Case name to Maps of Datasources and their
* CommonAttributeValueLists
*
* @param currentCase The current TSK Case.
* @param singleCase The case of interest. Matches must exist in this case.
*/
Map<String, Map<String, CommonAttributeValueList>> findSingleInterCaseValuesByCase(Case currentCase, CorrelationCase singleCase) {
Map<String, Map<String, CommonAttributeValueList>> findSingleInterCaseValuesByCase(Case currentCase, Set<String> mimeTypesToFilterOn, CorrelationCase singleCase) {
try {
InterCaseByCaseCallback instancetableCallback = new InterCaseByCaseCallback();
EamDb dbManager = EamDb.getInstance();
int caseId = dbManager.getCase(currentCase).getID();
int targetCaseId = singleCase.getID();
dbManager.processInstanceTableWhere(correlationType, String.format(singleInterCaseWhereClause, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue(), caseId, targetCaseId), instancetableCallback);
InterCaseByCaseCallback instancetableCallback = new InterCaseByCaseCallback(caseId, targetCaseId);
if (correlationType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
currentCase.getSleuthkitCase().getCaseDbAccessManager().select(getFileQuery(mimeTypesToFilterOn), instancetableCallback);
} else {
dbManager.processInstanceTableWhere(correlationType, String.format(INTER_CASE_WHERE_CLAUSE, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue()),
instancetableCallback);
}
return instancetableCallback.getInstanceCollatedCommonFiles();
} catch (EamDbException ex) {
} catch (EamDbException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex);
}
return new HashMap<>();
@@ -229,128 +244,146 @@ final class InterCaseSearchResultsProcessor {
/**
* Callback to use with findInterCaseValuesByCount which generates a list of
* md5s for common files search
* values for common property search
*/
private class InterCaseByCountCallback implements InstanceTableCallback {
private class InterCaseByCountCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback, InstanceTableCallback {
final Map<Integer, CommonAttributeValueList> instanceCollatedCommonFiles = new HashMap<>();
private final Map<Integer, CommonAttributeValueList> instanceCollatedCommonFiles = new HashMap<>();
private final int caseID;
private final int targetCase;
private CommonAttributeValue commonAttributeValue = null;
private String previousRowMd5 = "";
private InterCaseByCountCallback(int caseId) {
this(caseId, 0);
}
private InterCaseByCountCallback(int caseId, int targetCase) {
this.caseID = caseId;
this.targetCase = targetCase;
}
@Override
public void process(ResultSet resultSet) {
try {
Set<String> values = new HashSet<>();
List<Integer> targetCases = new ArrayList<>();
if (targetCase != 0) {
targetCases.add(caseID);
targetCases.add(targetCase);
}
while (resultSet.next()) {
int resultId = InstanceTableCallback.getId(resultSet);
String corValue = InstanceTableCallback.getValue(resultSet);
if (previousRowMd5.isEmpty()) {
previousRowMd5 = corValue;
}
if (corValue == null || HashUtility.isNoDataMd5(corValue)) {
continue;
}
countAndAddCommonAttributes(corValue, resultId);
values.add(corValue);
}
//Add the final instance(s)
if (commonAttributeValue != null) {
int size = commonAttributeValue.getInstanceCount();
if (instanceCollatedCommonFiles.containsKey(size)) {
instanceCollatedCommonFiles.get(size).addMetadataToList(commonAttributeValue);
for (String corValue : values) {
List<CorrelationAttributeInstance> instances;
if (targetCases.isEmpty()) {
instances = EamDb.getInstance().getArtifactInstancesByTypeValues(correlationType, Arrays.asList(corValue));
} else {
CommonAttributeValueList value = new CommonAttributeValueList();
value.addMetadataToList(commonAttributeValue);
instanceCollatedCommonFiles.put(size, value);
instances = EamDb.getInstance().getArtifactInstancesByTypeValuesAndCases(correlationType, Arrays.asList(corValue), targetCases);
}
int size = instances.size();
if (size > 1) {
CommonAttributeValue commonAttributeValue = new CommonAttributeValue(corValue);
boolean anotherCase = false;
for (CorrelationAttributeInstance instance : instances) {
CentralRepoCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(instance.getID(), correlationType, NODE_TYPE.COUNT_NODE);
searchResult.setCurrentAttributeInst(instance);
commonAttributeValue.addInstance(searchResult);
anotherCase = anotherCase || instance.getCorrelationCase().getID() != caseID;
}
if (anotherCase) {
if (instanceCollatedCommonFiles.containsKey(size)) {
instanceCollatedCommonFiles.get(size).addMetadataToList(commonAttributeValue);
} else {
CommonAttributeValueList value = new CommonAttributeValueList();
value.addMetadataToList(commonAttributeValue);
instanceCollatedCommonFiles.put(size, value);
}
}
}
}
} catch (SQLException ex) {
} catch (SQLException | EamDbException | CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.WARNING, "Error getting artifact instances from database.", ex); // NON-NLS
}
}
/**
* Add a resultId to the list of matches for a given corValue, which
* counts to number of instances of that match, determining which
* InstanceCountNode the match will be added to.
*
* @param corValue the value which matches
* @param resultId the CorrelationAttributeInstance id to be retrieved
* later.
*/
private void countAndAddCommonAttributes(String corValue, int resultId) {
if (commonAttributeValue == null) {
commonAttributeValue = new CommonAttributeValue(corValue);
}
if (!corValue.equals(previousRowMd5)) {
int size = commonAttributeValue.getInstanceCount();
if (instanceCollatedCommonFiles.containsKey(size)) {
instanceCollatedCommonFiles.get(size).addMetadataToList(commonAttributeValue);
} else {
CommonAttributeValueList value = new CommonAttributeValueList();
value.addMetadataToList(commonAttributeValue);
instanceCollatedCommonFiles.put(size, value);
}
commonAttributeValue = new CommonAttributeValue(corValue);
previousRowMd5 = corValue;
}
// we don't *have* all the information for the rows in the CR,
// so we need to consult the present case via the SleuthkitCase object
// Later, when the FileInstanceNode is built. Therefore, build node generators for now.
CentralRepoCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(resultId, correlationType, NODE_TYPE.COUNT_NODE);
CorrelationAttributeInstance corrAttr = findSingleCorrelationAttribute(resultId);
searchResult.setCurrentAttributeInst(corrAttr);
commonAttributeValue.addInstance(searchResult);
}
Map<Integer, CommonAttributeValueList> getInstanceCollatedCommonFiles() {
return Collections.unmodifiableMap(instanceCollatedCommonFiles);
}
}
/**
* Callback to use with findInterCaseValuesByCount which generates a list of
* md5s for common files search
* Callback to use with findInterCaseValuesByCase which generates a map of
* maps of values for common property search
*/
private class InterCaseByCaseCallback implements InstanceTableCallback {
private class InterCaseByCaseCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback, InstanceTableCallback {
final Map<String, Map<String, CommonAttributeValueList>> caseCollatedDataSourceCollections = new HashMap<>();
private static final int VALUE_BATCH_SIZE = 500;
private final Map<String, Map<String, CommonAttributeValueList>> caseCollatedDataSourceCollections = new HashMap<>();
private final int caseID;
private final int targetCase;
private InterCaseByCaseCallback(int caseId) {
this(caseId, 0);
}
private InterCaseByCaseCallback(int caseId, int targetCase) {
this.caseID = caseId;
this.targetCase = targetCase;
}
@Override
public void process(ResultSet resultSet) {
try {
List<Integer> targetCases = new ArrayList<>();
if (targetCase != 0) {
targetCases.add(caseID);
targetCases.add(targetCase);
}
Set<String> values = new HashSet<>();
while (resultSet.next()) {
int resultId = InstanceTableCallback.getId(resultSet);
String corValue = InstanceTableCallback.getValue(resultSet);
if (corValue == null || HashUtility.isNoDataMd5(corValue)) {
continue;
}
CorrelationCase correlationCase = EamDb.getInstance().getCaseById(InstanceTableCallback.getCaseId(resultSet));
String caseName = correlationCase.getDisplayName();
CorrelationDataSource correlationDatasource = EamDb.getInstance().getDataSourceById(correlationCase, InstanceTableCallback.getDataSourceId(resultSet));
//label datasource with it's id for uniqueness done in same manner as ImageGallery does in the DataSourceCell class
String dataSourceNameKey = correlationDatasource.getName() + " (Id: " + correlationDatasource.getDataSourceObjectID() + ")";
if (!caseCollatedDataSourceCollections.containsKey(caseName)) {
caseCollatedDataSourceCollections.put(caseName, new HashMap<String, CommonAttributeValueList>());
}
Map<String, CommonAttributeValueList> dataSourceToFile = caseCollatedDataSourceCollections.get(caseName);
if (!dataSourceToFile.containsKey(dataSourceNameKey)) {
dataSourceToFile.put(dataSourceNameKey, new CommonAttributeValueList());
}
CommonAttributeValueList valueList = dataSourceToFile.get(dataSourceNameKey);
CentralRepoCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(resultId, correlationType, NODE_TYPE.CASE_NODE);
CorrelationAttributeInstance corrAttr = findSingleCorrelationAttribute(resultId);
searchResult.setCurrentAttributeInst(corrAttr);
CommonAttributeValue commonAttributeValue = new CommonAttributeValue(corValue);
commonAttributeValue.addInstance(searchResult);
valueList.addMetadataToList(commonAttributeValue);
dataSourceToFile.put(dataSourceNameKey, valueList);
caseCollatedDataSourceCollections.put(caseName, dataSourceToFile);
values.add(corValue);
}
} catch (EamDbException | SQLException ex) {
for (List<String> valuesChunk : Iterables.partition(values, VALUE_BATCH_SIZE)) {
List<CorrelationAttributeInstance> instances;
if (targetCases.isEmpty()) {
instances = EamDb.getInstance().getArtifactInstancesByTypeValues(correlationType, valuesChunk);
} else {
instances = EamDb.getInstance().getArtifactInstancesByTypeValuesAndCases(correlationType, valuesChunk, targetCases);
}
if (instances.size() > 1) {
for (CorrelationAttributeInstance instance : instances) {
CorrelationCase correlationCase = instance.getCorrelationCase();
String caseName = correlationCase.getDisplayName();
CorrelationDataSource correlationDatasource = instance.getCorrelationDataSource();
//label datasource with it's id for uniqueness done in same manner as ImageGallery does in the DataSourceCell class
String dataSourceNameKey = correlationDatasource.getName() + " (Id: " + correlationDatasource.getDataSourceObjectID() + ")";
if (!caseCollatedDataSourceCollections.containsKey(caseName)) {
caseCollatedDataSourceCollections.put(caseName, new HashMap<>());
}
Map<String, CommonAttributeValueList> dataSourceToFile = caseCollatedDataSourceCollections.get(caseName);
if (!dataSourceToFile.containsKey(dataSourceNameKey)) {
dataSourceToFile.put(dataSourceNameKey, new CommonAttributeValueList());
}
CommonAttributeValueList valueList = dataSourceToFile.get(dataSourceNameKey);
CentralRepoCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(instance.getID(), correlationType, NODE_TYPE.CASE_NODE);
searchResult.setCurrentAttributeInst(instance);
CommonAttributeValue commonAttributeValue = new CommonAttributeValue(instance.getCorrelationValue());
commonAttributeValue.addInstance(searchResult);
valueList.addMetadataToList(commonAttributeValue);
dataSourceToFile.put(dataSourceNameKey, valueList);
caseCollatedDataSourceCollections.put(caseName, dataSourceToFile);
}
}
}
} catch (EamDbException | SQLException | CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.WARNING, "Error getting artifact instances from database.", ex); // NON-NLS
}
}
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -76,7 +76,6 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri
CorrelationCase correlationCase = this.getCorrelationCaseFromId(this.corrleationCaseId);
this.correlationCaseName = correlationCase.getDisplayName();
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.corAttrType);
Map<Integer, CommonAttributeValueList> interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseValuesByCount(Case.getCurrentCase(), correlationCase);
Set<String> mimeTypesToFilterOn = new HashSet<>();
if (isFilterByMedia()) {
mimeTypesToFilterOn.addAll(MEDIA_PICS_VIDEO_MIME_TYPES);
@@ -84,7 +83,9 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri
if (isFilterByDoc()) {
mimeTypesToFilterOn.addAll(TEXT_FILES_MIME_TYPES);
}
return new CommonAttributeCountSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType, mimeTypesToFilterOn);
Map<Integer, CommonAttributeValueList> interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseValuesByCount(Case.getCurrentCase(), mimeTypesToFilterOn, correlationCase);
return new CommonAttributeCountSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType);
}
/**
@@ -104,7 +105,6 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri
CorrelationCase correlationCase = this.getCorrelationCaseFromId(this.corrleationCaseId);
this.correlationCaseName = correlationCase.getDisplayName();
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.corAttrType);
Map<String, Map<String, CommonAttributeValueList>> interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseValuesByCase(Case.getCurrentCase(), correlationCase);
Set<String> mimeTypesToFilterOn = new HashSet<>();
if (isFilterByMedia()) {
mimeTypesToFilterOn.addAll(MEDIA_PICS_VIDEO_MIME_TYPES);
@@ -112,7 +112,9 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri
if (isFilterByDoc()) {
mimeTypesToFilterOn.addAll(TEXT_FILES_MIME_TYPES);
}
return new CommonAttributeCaseSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType, mimeTypesToFilterOn);
Map<String, Map<String, CommonAttributeValueList>> interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseValuesByCase(Case.getCurrentCase(), mimeTypesToFilterOn, correlationCase);
return new CommonAttributeCaseSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType);
}
@NbBundle.Messages({
@@ -83,3 +83,4 @@ MediaViewImagePanel.zoomResetButton.text=Reset
MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
HtmlPanel.showImagesToggleButton.text=Show Images
@@ -32,6 +32,8 @@ GstVideoPanel.progress.buffering=Buffering...
GstVideoPanel.progressLabel.bufferingErr=Error buffering file
GstVideoPanel.progress.infoLabel.updateErr=Error updating video progress: {0}
GstVideoPanel.ExtractMedia.progress.buffering=Buffering {0}
HtmlPanel_showImagesToggleButton_hide=Hide Images
HtmlPanel_showImagesToggleButton_show=Show Images
MediaFileViewer.AccessibleContext.accessibleDescription=
MediaFileViewer.title=Media
MediaFileViewer.toolTip=Displays supported multimedia files (images, videos, audio)
@@ -44,8 +46,6 @@ MediaViewVideoPanel.infoLabel.text=info
MediaViewImagePanel.imgFileTooLarge.msg=Could not load image file (too large): {0}
MessageContentViewer.AtrachmentsPanel.title=Attachments
MessageContentViewer.showImagesToggleButton.hide.text=Hide Images
MessageContentViewer.showImagesToggleButton.text=Show Images
MessageContentViewer.title=Message
MessageContentViewer.toolTip=Displays messages.
Metadata.nodeText.none=None
@@ -53,6 +53,7 @@ Metadata.nodeText.truncated=(results truncated)
Metadata.nodeText.unknown=Unknown
Metadata.tableRowTitle.acquisitionDetails=Acquisition Details
Metadata.tableRowTitle.deviceId=Device ID
Metadata.tableRowTitle.downloadSource=Downloaded From
Metadata.tableRowTitle.imageType=Type
Metadata.tableRowTitle.mimeType=MIME Type
Metadata.tableRowTitle.name=Name
@@ -139,6 +140,7 @@ MediaViewImagePanel.zoomResetButton.text=Reset
MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
HtmlPanel.showImagesToggleButton.text=Show Images
# {0} - tableName
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
# {0} - tableName
@@ -11,6 +11,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<LayoutCode>
<CodeStatement>
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -49,7 +49,8 @@ public class FileViewer extends javax.swing.JPanel implements DataContentViewer
private final FileTypeViewer[] KNOWN_VIEWERS = new FileTypeViewer[]{
new SQLiteViewer(),
new PListViewer(),
new MediaFileViewer()
new MediaFileViewer(),
new HtmlViewer()
};
private FileTypeViewer lastViewer;
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" 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"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="htmlScrollPane" pref="300" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="htmlScrollPane" pref="71" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="htmlScrollPane">
<Properties>
<Property name="verticalScrollBarPolicy" type="int" value="22"/>
</Properties>
<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.JTextPane" name="htmlbodyTextPane">
<Properties>
<Property name="editable" type="boolean" value="false"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JToggleButton" name="showImagesToggleButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="HtmlPanel.showImagesToggleButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="showImagesToggleButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>
@@ -0,0 +1,165 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.contentviewers;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.openide.util.NbBundle.Messages;
/**
* A file content viewer for HTML files.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class HtmlPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private String htmlText;
/**
* Creates new form HtmlViewerPanel
*/
HtmlPanel() {
initComponents();
Utilities.configureTextPaneAsHtml(htmlbodyTextPane);
}
/**
* Set the text pane's HTML text and refresh the view to display it.
*
* @param htmlText The HTML text to be applied to the text pane.
*/
void setHtmlText(String htmlText) {
this.htmlText = htmlText;
refresh();
}
/**
* Clear the HTML in the text pane and disable the show/hide button.
*/
void reset() {
htmlbodyTextPane.setText("");
showImagesToggleButton.setEnabled(false);
}
/**
* Guarantee the HTML text has 'html' and 'body' tags.
*
* @param htmlText The HTML text
*
* @return The HTML text with the 'html' and 'body' tags applied.
*/
private String wrapInHtmlBody(String htmlText) {
return "<html><body>" + htmlText + "</body></html>";
}
/**
* Cleans out input HTML string
*
* @param htmlInString The HTML string to cleanse
*
* @return The cleansed HTML String
*/
private String cleanseHTML(String htmlInString) {
Document doc = Jsoup.parse(htmlInString);
// Update all 'img' tags.
doc.select("img[src]").forEach(img -> img.attr("src", ""));
return doc.html();
}
/**
* Refresh the panel to reflect the current show/hide images setting.
*/
@Messages({
"HtmlPanel_showImagesToggleButton_show=Show Images",
"HtmlPanel_showImagesToggleButton_hide=Hide Images"
})
private void refresh() {
if (false == htmlText.isEmpty()) {
if (showImagesToggleButton.isSelected()) {
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_hide());
this.htmlbodyTextPane.setText(wrapInHtmlBody(htmlText));
} else {
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_show());
this.htmlbodyTextPane.setText(wrapInHtmlBody(cleanseHTML(htmlText)));
}
htmlbodyTextPane.setCaretPosition(0);
showImagesToggleButton.setEnabled(true);
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
htmlScrollPane = new javax.swing.JScrollPane();
htmlbodyTextPane = new javax.swing.JTextPane();
showImagesToggleButton = new javax.swing.JToggleButton();
htmlScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
htmlbodyTextPane.setEditable(false);
htmlScrollPane.setViewportView(htmlbodyTextPane);
org.openide.awt.Mnemonics.setLocalizedText(showImagesToggleButton, org.openide.util.NbBundle.getMessage(HtmlPanel.class, "HtmlPanel.showImagesToggleButton.text")); // NOI18N
showImagesToggleButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
showImagesToggleButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton)
.addGap(0, 0, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void showImagesToggleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showImagesToggleButtonActionPerformed
refresh();
}//GEN-LAST:event_showImagesToggleButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane htmlScrollPane;
private javax.swing.JTextPane htmlbodyTextPane;
private javax.swing.JToggleButton showImagesToggleButton;
// End of variables declaration//GEN-END:variables
}
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" 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"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="htmlPanel" 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="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="htmlPanel" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="org.sleuthkit.autopsy.contentviewers.HtmlPanel" name="htmlPanel">
</Component>
</SubComponents>
</Form>
@@ -0,0 +1,128 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.contentviewers;
import java.awt.Component;
import java.awt.Cursor;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A file content viewer for HTML files.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class HtmlViewer extends javax.swing.JPanel implements FileTypeViewer {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(HtmlViewer.class.getName());
private static final String[] SUPPORTED_MIMETYPES = new String[]{
"text/html",
"application/xhtml+xml"
};
/**
* Creates new form HtmlViewerPanel
*/
HtmlViewer() {
initComponents();
}
/**
* Retrieve the HTML text content from the supplied file.
*
* @param abstractFile The file to read.
*
* @return The text content of the file.
*/
private String getHtmlText(AbstractFile abstractFile) {
try {
int fileSize = (int) abstractFile.getSize();
byte[] buffer = new byte[fileSize];
abstractFile.read(buffer, 0, fileSize);
return new String(buffer);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Unable to read from file '%s' (id=%d).",
abstractFile.getName(), abstractFile.getId()), ex);
}
return null;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
htmlPanel = new org.sleuthkit.autopsy.contentviewers.HtmlPanel();
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(htmlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(htmlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.contentviewers.HtmlPanel htmlPanel;
// End of variables declaration//GEN-END:variables
@Override
public List<String> getSupportedMIMETypes() {
return Arrays.asList(SUPPORTED_MIMETYPES);
}
@Override
public void setFile(AbstractFile file) {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
htmlPanel.setHtmlText(getHtmlText(file));
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
@Override
public Component getComponent() {
return this;
}
@Override
public void resetComponent() {
htmlPanel.reset();
}
}
@@ -126,7 +126,6 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm()); //NOI18N
fxPanel.setScene(scene);
//bind size of image to that of scene, while keeping proportions
fxImageView.setSmooth(true);
fxImageView.setCache(true);
@@ -142,11 +141,13 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
}
/**
* clear the displayed image
* Clear the displayed image
*/
public void reset() {
Platform.runLater(() -> {
fxImageView.setViewport(new Rectangle2D(0, 0, 0, 0));
fxImageView.setImage(null);
scrollPane.setContent(null);
scrollPane.setContent(fxImageView);
});
@@ -274,50 +274,17 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="htmlScrollPane" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<EmptySpace pref="533" max="32767" attributes="0"/>
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
</Group>
<Component id="htmlPanel" alignment="0" pref="647" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="htmlScrollPane" pref="333" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
</Group>
<Component id="htmlPanel" alignment="0" pref="362" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="htmlScrollPane">
<Properties>
<Property name="verticalScrollBarPolicy" type="int" value="22"/>
</Properties>
<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.JTextPane" name="htmlbodyTextPane">
<Properties>
<Property name="editable" type="boolean" value="false"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JToggleButton" name="showImagesToggleButton">
<Properties>
<Property name="text" type="java.lang.String" value="Show Images"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="showImagesToggleButtonActionPerformed"/>
</Events>
<Component class="org.sleuthkit.autopsy.contentviewers.HtmlPanel" name="htmlPanel">
</Component>
</SubComponents>
</Container>
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2018 Basis Technology Corp.
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -106,9 +106,12 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
attachmentsScrollPane.setViewportView(drp);
msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true);
textAreas = Arrays.asList(headersTextArea, textbodyTextArea, htmlbodyTextPane, rtfbodyTextPane);
/*
* HTML tab uses the HtmlPanel instead of an internal text pane, so we
* use 'null' for that index.
*/
textAreas = Arrays.asList(headersTextArea, textbodyTextArea, null, rtfbodyTextPane);
Utilities.configureTextPaneAsHtml(htmlbodyTextPane);
Utilities.configureTextPaneAsRtf(rtfbodyTextPane);
resetComponent();
@@ -150,9 +153,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
textbodyScrollPane = new javax.swing.JScrollPane();
textbodyTextArea = new javax.swing.JTextArea();
htmlPane = new javax.swing.JPanel();
htmlScrollPane = new javax.swing.JScrollPane();
htmlbodyTextPane = new javax.swing.JTextPane();
showImagesToggleButton = new javax.swing.JToggleButton();
htmlPanel = new org.sleuthkit.autopsy.contentviewers.HtmlPanel();
rtfbodyScrollPane = new javax.swing.JScrollPane();
rtfbodyTextPane = new javax.swing.JTextPane();
attachmentsPanel = new javax.swing.JPanel();
@@ -265,35 +266,15 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.textbodyScrollPane.TabConstraints.tabTitle"), textbodyScrollPane); // NOI18N
htmlScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
htmlbodyTextPane.setEditable(false);
htmlScrollPane.setViewportView(htmlbodyTextPane);
org.openide.awt.Mnemonics.setLocalizedText(showImagesToggleButton, "Show Images");
showImagesToggleButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
showImagesToggleButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout htmlPaneLayout = new javax.swing.GroupLayout(htmlPane);
htmlPane.setLayout(htmlPaneLayout);
htmlPaneLayout.setHorizontalGroup(
htmlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(htmlScrollPane)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, htmlPaneLayout.createSequentialGroup()
.addContainerGap(533, Short.MAX_VALUE)
.addComponent(showImagesToggleButton)
.addGap(3, 3, 3))
.addComponent(htmlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 647, Short.MAX_VALUE)
);
htmlPaneLayout.setVerticalGroup(
htmlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(htmlPaneLayout.createSequentialGroup()
.addComponent(showImagesToggleButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 333, Short.MAX_VALUE)
.addGap(0, 0, 0))
.addComponent(htmlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 362, Short.MAX_VALUE)
);
msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.htmlPane.TabConstraints.tabTitle"), htmlPane); // NOI18N
@@ -358,26 +339,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
);
}// </editor-fold>//GEN-END:initComponents
@NbBundle.Messages({
"MessageContentViewer.showImagesToggleButton.hide.text=Hide Images",
"MessageContentViewer.showImagesToggleButton.text=Show Images"})
private void showImagesToggleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showImagesToggleButtonActionPerformed
try {
String htmlText = getAttributeValueSafe(artifact, TSK_EMAIL_CONTENT_HTML);
if (false == htmlText.isEmpty()) {
if (showImagesToggleButton.isSelected()) {
showImagesToggleButton.setText(Bundle.MessageContentViewer_showImagesToggleButton_hide_text());
this.htmlbodyTextPane.setText(wrapInHtmlBody(htmlText));
} else {
showImagesToggleButton.setText(Bundle.MessageContentViewer_showImagesToggleButton_text());
this.htmlbodyTextPane.setText(wrapInHtmlBody(cleanseHTML(htmlText)));
}
}
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "Failed to get attributes for email message.", ex); //NON-NLS
}
}//GEN-LAST:event_showImagesToggleButtonActionPerformed
private void viewInNewWindowButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewInNewWindowButtonActionPerformed
new NewWindowViewAction("View in new window", drpExplorerManager.getSelectedNodes()[0]).actionPerformed(evt);
}//GEN-LAST:event_viewInNewWindowButtonActionPerformed
@@ -396,12 +357,10 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
private javax.swing.JScrollPane headersScrollPane;
private javax.swing.JTextArea headersTextArea;
private javax.swing.JPanel htmlPane;
private javax.swing.JScrollPane htmlScrollPane;
private javax.swing.JTextPane htmlbodyTextPane;
private org.sleuthkit.autopsy.contentviewers.HtmlPanel htmlPanel;
private javax.swing.JTabbedPane msgbodyTabbedPane;
private javax.swing.JScrollPane rtfbodyScrollPane;
private javax.swing.JTextPane rtfbodyTextPane;
private javax.swing.JToggleButton showImagesToggleButton;
private javax.swing.JLabel subjectLabel;
private javax.swing.JLabel subjectText;
private javax.swing.JScrollPane textbodyScrollPane;
@@ -505,9 +464,8 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
headersTextArea.setText("");
rtfbodyTextPane.setText("");
htmlbodyTextPane.setText("");
htmlPanel.reset();
textbodyTextArea.setText("");
showImagesToggleButton.setEnabled(false);
msgbodyTabbedPane.setEnabled(false);
}
@@ -567,12 +525,15 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
String attributeText = getAttributeValueSafe(artifact, type);
if (index == HTML_TAB_INDEX && StringUtils.isNotBlank(attributeText)) {
//special case for HTML, we need to 'cleanse' it
attributeText = wrapInHtmlBody(cleanseHTML(attributeText));
htmlPanel.setHtmlText(attributeText);
} else {
JTextComponent textComponent = textAreas.get(index);
if (textComponent != null) {
textComponent.setText(attributeText);
textComponent.setCaretPosition(0); //make sure we start at the top
}
}
JTextComponent textComponent = textAreas.get(index);
textComponent.setText(attributeText);
textComponent.setCaretPosition(0); //make sure we start at the top
final boolean hasText = attributeText.length() > 0;
msgbodyTabbedPane.setEnabledAt(index, hasText);
@@ -613,10 +574,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
directionText.setEnabled(false);
ccLabel.setEnabled(true);
showImagesToggleButton.setEnabled(true);
showImagesToggleButton.setText("Show Images");
showImagesToggleButton.setSelected(false);
try {
this.fromText.setText(getAttributeValueSafe(artifact, TSK_EMAIL_FROM));
this.fromText.setToolTipText(getAttributeValueSafe(artifact, TSK_EMAIL_FROM));
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.contentviewers;
import java.awt.Component;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Node;
@@ -29,6 +30,10 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
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.DataSource;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.FsContent;
@@ -138,6 +143,7 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
"Metadata.tableRowTitle.timezone=Time Zone",
"Metadata.tableRowTitle.deviceId=Device ID",
"Metadata.tableRowTitle.acquisitionDetails=Acquisition Details",
"Metadata.tableRowTitle.downloadSource=Downloaded From",
"Metadata.nodeText.unknown=Unknown",
"Metadata.nodeText.none=None"})
@Override
@@ -184,6 +190,19 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"), file.getLocalAbsPath());
}
try {
List<BlackboardArtifact> sourceArtifacts = file.getArtifacts(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
if (!sourceArtifacts.isEmpty()) {
BlackboardArtifact artifact = sourceArtifacts.get(0);
BlackboardAttribute urlAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL));
if (urlAttr != null) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.downloadSource"), urlAttr.getValueString());
}
}
} catch (TskCoreException ex) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
endTable(sb);
/*
@@ -50,6 +50,7 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHS
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
@@ -236,6 +237,7 @@ public class ExtractedContent implements AutopsyVisitableItem {
doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT));
doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT));
doNotShow.add(new BlackboardArtifact.Type(TSK_DATA_SOURCE_USAGE));
doNotShow.add(new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE) );
}
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
@@ -302,8 +302,9 @@ class MSOfficeEmbeddedContentExtractor {
}
List<ExtractedFile> listOfExtractedImages = new ArrayList<>();
byte[] data = null;
int pictureNumber = 0; //added to ensure uniqueness in cases where suggestFullFileName returns duplicates
for (Picture picture : listOfAllPictures) {
String fileName = picture.suggestFullFileName();
String fileName = UNKNOWN_IMAGE_NAME_PREFIX +pictureNumber +"."+ picture.suggestFileExtension();
try {
data = picture.getContent();
} catch (Exception ex) {
@@ -312,6 +313,7 @@ class MSOfficeEmbeddedContentExtractor {
writeExtractedImage(Paths.get(outputFolderPath, fileName).toString(), data);
// TODO Extract more info from the Picture viz ctime, crtime, atime, mtime
listOfExtractedImages.add(new ExtractedFile(fileName, getFileRelativePath(fileName), picture.getSize()));
pictureNumber++;
}
return listOfExtractedImages;
@@ -1,5 +1,5 @@
# {0} - File name
CreatePortableCaseModule.addFilesToPortableCase.copyingFile=Copying file {0}
CreatePortableCaseModule.copyContentToPortableCase.copyingFile=Copying file {0}
# {0} - case folder
CreatePortableCaseModule.createCase.caseDirExists=Case folder {0} already exists
CreatePortableCaseModule.createCase.errorCreatingCase=Error creating case
@@ -7,11 +7,16 @@ CreatePortableCaseModule.createCase.errorCreatingCase=Error creating case
CreatePortableCaseModule.createCase.errorCreatingFolder=Error creating folder {0}
CreatePortableCaseModule.generateReport.caseClosed=Current case has been closed
# {0} - tag name
CreatePortableCaseModule.generateReport.copyingArtifacts=Copying artifacts tagged as {0}...
# {0} - tag name
CreatePortableCaseModule.generateReport.copyingFiles=Copying files tagged as {0}...
CreatePortableCaseModule.generateReport.copyingTags=Copying tags...
CreatePortableCaseModule.generateReport.creatingCase=Creating portable case database...
CreatePortableCaseModule.generateReport.errorCopyingArtifacts=Error copying tagged artifacts
CreatePortableCaseModule.generateReport.errorCopyingFiles=Error copying tagged files
CreatePortableCaseModule.generateReport.errorCopyingTags=Error copying tags
# {0} - attribute type name
CreatePortableCaseModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}
CreatePortableCaseModule.generateReport.noTagsSelected=No tags selected for export.
# {0} - output folder
CreatePortableCaseModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist
+1 -1
View File
@@ -69,7 +69,7 @@
<dependency conf="autopsy_core->default" org="com.twelvemonkeys.imageio" name="imageio-thumbsdb" rev="3.2" />
<dependency conf="autopsy_core->default" org="com.twelvemonkeys.imageio" name="imageio-core" rev="3.2" />
<dependency conf="autopsy_core->default" org="com.twelvemonkeys.imageio" name="imageio-metadata" rev="3.2" />
<dependency conf="autopsy_core->default" org="com.googlecode.plist" name="dd-plist" rev="1.20"/>
<!-- conflict resolutions for multiple JAR versions -->
<conflict org="net.java.dev.jna" module="jna" rev="3.4.0"/>
<conflict org="net.java.dev.jna" module="platform" rev="3.4.0"/>
+1
View File
@@ -75,6 +75,7 @@ file.reference.slf4j-simple-1.6.1.jar=release/modules/ext/slf4j-simple-1.6.1.jar
file.reference.stax-api-1.0.1.jar=release/modules/ext/stax-api-1.0.1.jar
file.reference.xml-apis-1.0.b2.jar=release/modules/ext/xml-apis-1.0.b2.jar
file.reference.xmlbeans-2.6.0.jar=release/modules/ext/xmlbeans-2.6.0.jar
file.reference.dd-plist-1.20.jar=release/modules/ext/dd-plist-1.20.jar
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
javadoc.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4-javadoc.jar
+13 -8
View File
@@ -37,6 +37,7 @@
<package>com.apple.eawt</package>
<package>com.apple.eawt.event</package>
<package>com.apple.eio</package>
<package>com.dd.plist</package>
<package>com.github.lgooddatepicker.components</package>
<package>com.github.lgooddatepicker.optionalusertools</package>
<package>com.github.lgooddatepicker.zinternaltools</package>
@@ -742,18 +743,10 @@
<runtime-relative-path>ext/jna-3.4.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jna-3.4.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/gson-2.8.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/gson-2.8.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jfxtras-common-8.0-r4.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jfxtras-common-8.0-r4.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/opencv-248.jar</runtime-relative-path>
<binary-origin>release/modules/ext/opencv-248.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jsr305-1.3.9.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jsr305-1.3.9.jar</binary-origin>
@@ -874,6 +867,14 @@
<runtime-relative-path>ext/commons-codec-1.10.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-codec-1.10.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/gson-2.8.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/gson-2.8.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/opencv-248.jar</runtime-relative-path>
<binary-origin>release/modules/ext/opencv-248.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/slf4j-simple-1.6.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/slf4j-simple-1.6.1.jar</binary-origin>
@@ -970,6 +971,10 @@
<runtime-relative-path>ext/gst1-java-core-0.9.3.jar</runtime-relative-path>
<binary-origin>release/modules/ext/gst1-java-core-0.9.3.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/dd-plist-1.20.jar</runtime-relative-path>
<binary-origin>release/modules/ext/dd-plist-1.20.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/dom4j-1.6.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/dom4j-1.6.1.jar</binary-origin>
@@ -183,7 +183,7 @@ OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File
# {0} - caseErrorMessage
OpenCaseAction.errorMsg=Failed to open case: {0}
OpenCaseAction.menuItemText=Open
OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change. \n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it.
OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change.\n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it.
OpenIDE-Module-Name=Experimental
OpenIDE-Module-Short-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution.
DisplayLogDialog.cannotOpenLog=Unable to open the selected case log file
@@ -34,7 +34,7 @@ KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get sc
KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found.
KeywordSearchResultFactory.query.exception.msg=Could not perform the query
OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time. \nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\n\The module indexes files found in the disk image at ingest time.\n\It then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
OpenIDE-Module-Name=KeywordSearch
OptionsCategory_Name_KeywordSearchOptions=Keyword Search
OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search
@@ -128,7 +128,7 @@ KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl=View in New Window
KeywordSearchIngestModule.init.noKwInLstMsg=No keywords in keyword list.
KeywordSearchIngestModule.init.onlyIdxKwSkipMsg=Only indexing will be done and keyword search will be skipped (you can still add keyword lists using the Keyword Lists - Add to Ingest).
KeywordSearchIngestModule.doInBackGround.displayName=Periodic Keyword Search
KeywordSearchIngestModule.doInBackGround.finalizeMsg=- Finalizing
KeywordSearchIngestModule.doInBackGround.finalizeMsg=Finalizing
KeywordSearchIngestModule.doInBackGround.pendingMsg=(Pending)
RawText.FileText=File Text
RawText.ResultText=Result Text
@@ -224,7 +224,7 @@ Server.start.exception.cantStartSolr.msg=Could not start Solr server process
Server.start.exception.cantStartSolr.msg2=Could not start Solr server process
Server.isRunning.exception.errCheckSolrRunning.msg=Error checking if Solr server is running
Server.isRunning.exception.errCheckSolrRunning.msg2=Error checking if Solr server is running
Server.openCore.exception.alreadyOpen.msg=Already an open Core! Explicitely close Core first.
Server.openCore.exception.alreadyOpen.msg=There is an already open Solr core. Explicitly close the core first.
Server.queryNumIdxFiles.exception.msg=Error querying number of indexed files,
Server.queryNumIdxChunks.exception.msg=Error querying number of indexed chunks,
Server.queryNumIdxDocs.exception.msg=Error querying number of indexed documents,
+3
View File
@@ -19,6 +19,9 @@
<copy todir="${basedir}/release/rr-full" >
<fileset dir="${thirdparty.dir}/rr-full/" />
</copy>
<copy todir="${basedir}/release/ESEDatabaseView" >
<fileset dir="${thirdparty.dir}/ESEDatabaseView/" />
</copy>
</target>
+3
View File
@@ -6,4 +6,7 @@
<conf name="recent-activity"/>
</configurations>
<dependencies>
<dependency conf="recent-activity->default" org="com.googlecode.plist" name="dd-plist" rev="1.20"/>
</dependencies>
</ivy-module>
@@ -1,3 +1,4 @@
file.reference.dd-plist-1.20.jar=release/modules/ext/dd-plist-1.20.jar
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt
+4
View File
@@ -74,6 +74,10 @@
</dependency>
</module-dependencies>
<public-packages/>
<class-path-extension>
<runtime-relative-path>ext/dd-plist-1.20.jar</runtime-relative-path>
<binary-origin>release/modules/ext/dd-plist-1.20.jar</binary-origin>
</class-path-extension>
</data>
</configuration>
</project>
@@ -0,0 +1,434 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
*
* 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.recentactivity;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie;
/**
* The binary cookie reader encapsulates all the knowledge of how to read the mac
* .binarycookie files into one class.
*
* The binarycookie file has a header which describes how many pages of cookies
* and where they are located. Each cookie page has a header and a list of
* cookies.
*
*/
public final class BinaryCookieReader implements Iterable<Cookie> {
private static final int MAGIC_SIZE = 4;
private static final int SIZEOF_INT_BYTES = 4;
private static final int PAGE_HEADER_VALUE = 256;
private static final String COOKIE_MAGIC = "cook"; //NON-NLS
private static final int MAC_EPOC_FIX = 978307200;
private final int[] pageSizeArray;
private final File cookieFile;
private static final Logger LOG = Logger.getLogger(BinaryCookieReader.class.getName());
/**
* The binary cookie reader encapsulates all the knowledge of how to read the mac
* .binarycookie files into one class.
*
*/
private BinaryCookieReader(File cookieFile, int[] pageSizeArray) {
this.cookieFile = cookieFile;
this.pageSizeArray = pageSizeArray;
}
/**
* initalizeReader opens the given file, reads the header and checks that
* the file is a binarycookie file. This function does not keep the file
* open.
*
* @param file binarycookie file
* @return An instance of the reader
* @throws FileNotFoundException
* @throws IOException
*/
public static BinaryCookieReader initalizeReader(File cookieFile) throws FileNotFoundException, IOException {
BinaryCookieReader reader = null;
try (DataInputStream dataStream = new DataInputStream(new FileInputStream(cookieFile))) {
byte[] magic = new byte[MAGIC_SIZE];
if (dataStream.read(magic) != MAGIC_SIZE) {
throw new IOException("Failed to read header, invalid file size (" + cookieFile.getName() + ")"); //NON-NLS
}
if (!(new String(magic)).equals(COOKIE_MAGIC)) {
throw new IOException(cookieFile.getName() + " is not a cookie file"); //NON-NLS
}
int[] sizeArray = null;
int pageCount = dataStream.readInt();
if (pageCount != 0) {
sizeArray = new int[pageCount];
for (int cnt = 0; cnt < pageCount; cnt++) {
sizeArray[cnt] = dataStream.readInt();
}
LOG.log(Level.INFO, "No cookies found in {0}", cookieFile.getName()); //NON-NLS
}
reader = new BinaryCookieReader(cookieFile, sizeArray);
}
return reader;
}
/**
* Creates and returns a instance of CookiePageIterator.
*
* @return CookiePageIterator
*/
@Override
public Iterator<Cookie> iterator() {
return new CookiePageIterator();
}
/**
* The cookiePageIterator iterates the binarycookie file by page.
*/
private class CookiePageIterator implements Iterator<Cookie> {
int pageIndex = 0;
CookiePage currentPage = null;
Iterator<Cookie> currentIterator = null;
DataInputStream dataStream = null;
/**
* The cookiePageIterator iterates the binarycookie file by page.
*/
CookiePageIterator() {
if(pageSizeArray == null || pageSizeArray.length == 0) {
return;
}
try {
dataStream = new DataInputStream(new FileInputStream(cookieFile));
// skip to the first page
dataStream.skipBytes((2 * SIZEOF_INT_BYTES) + (pageSizeArray.length * SIZEOF_INT_BYTES));
} catch (IOException ex) {
String errorMessage = String.format("An error occurred creating an input stream for %s", cookieFile.getName());
LOG.log(Level.WARNING, errorMessage, ex); //NON-NLS
closeStream(); // Just incase the error was from skip
}
}
/**
* Returns true if there are more cookies in the binarycookie file.
*
* @return True if there are more cookies
*/
@Override
public boolean hasNext() {
if (dataStream == null) {
return false;
}
if (currentIterator == null || !currentIterator.hasNext()) {
try {
if (pageIndex < pageSizeArray.length) {
byte[] nextPage = new byte[pageSizeArray[pageIndex]];
dataStream.read(nextPage);
currentPage = new CookiePage(nextPage);
currentIterator = currentPage.iterator();
} else {
closeStream();
return false;
}
pageIndex++;
} catch (IOException ex) {
closeStream();
String errorMessage = String.format("A read error occured for file %s (pageIndex = %d)", cookieFile.getName(), pageIndex);
LOG.log(Level.WARNING, errorMessage, ex); //NON-NLS
return false;
}
}
return currentIterator.hasNext();
}
/**
* Get the next cookie from the current CookieIterator.
*
* @return The next cookie
*/
@Override
public Cookie next() {
// Just in case someone uses next without hasNext, this check will
// make sure there are more elements and that we iterate properly
// through the pages.
if (!hasNext()) {
throw new NoSuchElementException();
}
return currentIterator.next();
}
/**
* Close the DataInputStream
*/
private void closeStream() {
if (dataStream != null) {
try {
dataStream.close();
dataStream = null;
} catch (IOException ex) {
String errorMessage = String.format("An error occurred trying to close stream for file %s", cookieFile.getName());
LOG.log(Level.WARNING, errorMessage, ex); //NON-NLS
}
}
}
}
/**
* Wrapper class for an instance of a CookiePage in the binarycookie file.
*/
private class CookiePage implements Iterable<Cookie> {
int[] cookieOffSets;
ByteBuffer pageBuffer;
/**
* Setup the CookiePage object. Calidates that the page bytes are in the
* correct format by checking for the header value of 0x0100.
*
* @param page byte array representing a cookie page
* @throws IOException
*/
CookiePage(byte[] page) throws IOException {
if (page == null || page.length == 0) {
throw new IllegalArgumentException("Invalid value for page passed to CookiePage constructor"); //NON-NLS
}
pageBuffer = ByteBuffer.wrap(page);
if (pageBuffer.getInt() != PAGE_HEADER_VALUE) {
pageBuffer = null;
throw new IOException("Invalid file format, bad page head value found"); //NON-NLS
}
pageBuffer.order(ByteOrder.LITTLE_ENDIAN);
int count = pageBuffer.getInt();
cookieOffSets = new int[count];
for (int cnt = 0; cnt < count; cnt++) {
cookieOffSets[cnt] = pageBuffer.getInt();
}
pageBuffer.getInt(); // All 0, not needed
}
/**
* Returns an instance of a CookieIterator.
*
* @return CookieIterator
*/
@Override
public Iterator<Cookie> iterator() {
return new CookieIterator();
}
/**
* Implements Iterator to iterate over the cookies in the page.
*/
private class CookieIterator implements Iterator<Cookie> {
int index = 0;
/**
* Checks to see if there are more cookies.
*
* @return True if there are more cookies, false if there are not
*/
@Override
public boolean hasNext() {
if (pageBuffer == null) {
return false;
}
return index < cookieOffSets.length;
}
/**
* Gets the next cookie from the page.
*
* @return Next cookie
*/
@Override
public Cookie next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
int offset = cookieOffSets[index];
int size = pageBuffer.getInt(offset);
byte[] cookieBytes = new byte[size];
pageBuffer.get(cookieBytes, 0, size);
index++;
return new Cookie(cookieBytes);
}
}
}
/**
* Represents an instance of a cookie from the binarycookie file.
*/
public class Cookie {
private final static int COOKIE_HEAD_SKIP = 16;
private final double expirationDate;
private final double creationDate;
private final String name;
private final String url;
private final String path;
private final String value;
/**
* Creates a cookie object from the given array of bytes.
*
* @param cookieBytes Byte array for the cookie
*/
protected Cookie(byte[] cookieBytes) {
if (cookieBytes == null || cookieBytes.length == 0) {
throw new IllegalArgumentException("Invalid value for cookieBytes passed to Cookie constructor"); //NON-NLS
}
ByteBuffer byteBuffer = ByteBuffer.wrap(cookieBytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
// Skip past the four int values that we are not interested in
byteBuffer.position(byteBuffer.position() + COOKIE_HEAD_SKIP);
int urlOffset = byteBuffer.getInt();
int nameOffset = byteBuffer.getInt();
int pathOffset = byteBuffer.getInt();
int valueOffset = byteBuffer.getInt();
byteBuffer.getLong(); // 8 bytes of not needed
expirationDate = byteBuffer.getDouble();
creationDate = byteBuffer.getDouble();
url = decodeString(cookieBytes, urlOffset);
name = decodeString(cookieBytes, nameOffset);
path = decodeString(cookieBytes, pathOffset);
value = decodeString(cookieBytes, valueOffset);
}
/**
* Returns the expiration date of the cookie represented by this cookie
* object.
*
* @return Cookie expiration date in milliseconds with java epoch
*/
public final Long getExpirationDate() {
return ((long)expirationDate) + MAC_EPOC_FIX;
}
/**
* Returns the creation date of the cookie represented by this cookie
* object.
*
* @return Cookie creation date in milliseconds with java epoch
*/
public final Long getCreationDate() {
return ((long)creationDate) + MAC_EPOC_FIX;
}
/**
* Returns the url of the cookie represented by this cookie object.
*
* @return the cookie URL
*/
public final String getURL() {
return url;
}
/**
* Returns the name of the cookie represented by this cookie object.
*
* @return The cookie name
*/
public final String getName() {
return name;
}
/**
* Returns the path of the cookie represented by this cookie object.
*
* @return The cookie path
*/
public final String getPath() {
return path;
}
/**
* Returns the value of the cookie represented by this cookie object.
*
* @return The cookie value
*/
public final String getValue() {
return value;
}
/**
* Creates an ascii string from the bytes in byteArray starting at
* offset ending at the first null terminator found.
*
* @param byteArray Array of bytes
* @param offset starting offset in the array
* @return String with bytes converted to ascii
*/
private String decodeString(byte[] byteArray, int offset) {
byte[] stringBytes = new byte[byteArray.length - offset];
for (int index = 0; index < stringBytes.length; index++) {
byte nibble = byteArray[offset + index];
if (nibble != '\0') { //NON-NLS
stringBytes[index] = nibble;
} else {
break;
}
}
return new String(stringBytes);
}
}
}
@@ -1,11 +1,18 @@
cannotBuildXmlParser=Unable to build XML parser:
cannotLoadSEUQA=Unable to load Search Engine URL Query Analyzer settings file, SEUQAMappings.xml:
cannotParseXml=Unable to parse XML file:
ChromeCacheExtractor.moduleName=ChromeCacheExtractor
# {0} - OS name
DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0})
DataSourceUsageAnalyzer.parentModuleName=Recent Activity
Extract.indexError.message=Failed to index artifact for keyword search.
Extract.noOpenCase.errMsg=No open case available.
ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history
ExtractEdge_Module_Name=Microsoft Edge
ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file
ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file
ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer
ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file
ExtractOs.androidOs.label=Android
ExtractOs.androidVolume.label=OS Drive (Android)
ExtractOs.debianLinuxOs.label=Linux (Debian)
@@ -36,6 +43,10 @@ ExtractOs.unitedLinuxVolume.label=OS Drive (Linux United Linux)
ExtractOs.windowsVolume.label=OS Drive (Windows)
ExtractOs.yellowDogLinuxOs.label=Linux (Yellow Dog)
ExtractOs.yellowDogLinuxVolume.label=OS Drive (Linux Yellow Dog)
ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files.
ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files
ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files
ExtractSafari_Module_Name=Safari
OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy.
OpenIDE-Module-Name=RecentActivity
@@ -97,6 +97,9 @@ class Chrome extends Extract {
this.getLogins();
this.getAutofill();
this.getDownload();
ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context);
chromeCacheExtractor.getCaches();
}
/**
@@ -164,22 +167,22 @@ class Chrome extends Extract {
for (HashMap<String, Object> result : tempList) {
Collection<BlackboardAttribute> bbattributes = new ArrayList<BlackboardAttribute>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("last_visit_time").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REFERRER,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("from_visit").toString() != null) ? result.get("from_visit").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
(NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY, historyFile, bbattributes);
@@ -309,21 +312,16 @@ class Chrome extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
//TODO Revisit usage of deprecated constructor as per TSK-583
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"Chrome.parentModuleName"), url));
RecentActivityExtracterModuleFactory.getModuleName(), url));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
NbBundle.getMessage(this.getClass(),
"Chrome.parentModuleName"), name));
RecentActivityExtracterModuleFactory.getModuleName(), name));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(),
"Chrome.parentModuleName"), (date / 1000000) - Long.valueOf("11644473600")));
RecentActivityExtracterModuleFactory.getModuleName(), (date / 1000000) - Long.valueOf("11644473600")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"Chrome.parentModuleName"), domain));
RecentActivityExtracterModuleFactory.getModuleName(), domain));
bbart.addAttributes(bbattributes);
// index the artifact for keyword search
@@ -400,25 +398,25 @@ class Chrome extends Extract {
for (HashMap<String, Object> result : tempList) {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("host_key").toString() != null) ? result.get("host_key").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("last_access_utc").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
String domain = result.get("host_key").toString(); //NON-NLS
domain = domain.replaceFirst("^\\.+(?!$)", "");
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), domain));
RecentActivityExtracterModuleFactory.getModuleName(), domain));
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_COOKIE, cookiesFile, bbattributes);
if (bbart != null) {
@@ -496,7 +494,7 @@ class Chrome extends Extract {
for (HashMap<String, Object> result : tempList) {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), (result.get("full_path").toString()))); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(), (result.get("full_path").toString()))); //NON-NLS
long pathID = Util.findID(dataSource, (result.get("full_path").toString())); //NON-NLS
if (pathID != -1) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
@@ -504,7 +502,7 @@ class Chrome extends Extract {
"Chrome.parentModuleName"), pathID));
}
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "Recent Activity", ((result.get("url").toString() != null) ? EscapeUtil.decodeURL(result.get("url").toString()) : "")));
Long time = (Long.valueOf(result.get("start_time").toString()) / 1000000) - Long.valueOf("11644473600"); //NON-NLS
@@ -512,12 +510,12 @@ class Chrome extends Extract {
//TODO Revisit usage of deprecated constructor as per TSK-583
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "Recent Activity", "Last Visited", time));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), time));
RecentActivityExtracterModuleFactory.getModuleName(), time));
String domain = NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : ""); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), domain));
RecentActivityExtracterModuleFactory.getModuleName(), domain));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes);
@@ -590,24 +588,24 @@ class Chrome extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("origin_url").toString() != null) ? result.get("origin_url").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("date_created").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
(NetworkUtils.extractDomain((result.get("origin_url").toString() != null) ? result.get("origin_url").toString() : "")))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("username_value").toString() != null) ? result.get("username_value").toString().replaceAll("'", "''") : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("signon_realm").toString() != null) ? result.get("signon_realm").toString() : ""))); //NON-NLS
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, loginDataFile, bbattributes);
@@ -721,21 +719,21 @@ class Chrome extends Extract {
((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Integer.valueOf(result.get("count").toString())))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
Long.valueOf(result.get("date_created").toString()))); //NON-NLS
// get schema version specific attributes
if (isSchemaV8X) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
Long.valueOf(result.get("date_last_used").toString()))); //NON-NLS
}
@@ -830,37 +828,37 @@ class Chrome extends Extract {
full_name = String.join(" ", first_name, middle_name, last_name);
}
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME_PERSON,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
full_name)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
email_Addr)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
phone_number)); //NON-NLS
String locationAddress = String.join(", ", street_address, city, state, zipcode, country_code);
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LOCATION,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
locationAddress)); //NON-NLS
if (date_modified > 0) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
date_modified)); //NON-NLS
}
if (use_count > 0 ){
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
use_count)); //NON-NLS
}
if (use_date > 0) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
use_date)); //NON-NLS
}
@@ -22,10 +22,18 @@
*/
package org.sleuthkit.autopsy.recentactivity;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
@@ -35,9 +43,17 @@ import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
import org.sleuthkit.datamodel.*;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
abstract class Extract {
@@ -201,7 +217,222 @@ abstract class Extract {
return moduleName;
}
/**
* Returns the state of foundData
* @return
*/
public boolean foundData() {
return dataFound;
}
/**
* Sets the value of foundData
* @param foundData
*/
protected void setFoundData(boolean foundData){
dataFound = foundData;
}
/**
* Returns the current case instance
* @return Current case instance
*/
protected Case getCurrentCase(){
return this.currentCase;
}
/**
* Creates a list of attributes for a history artifact.
*
* @param url
* @param accessTime Time url was accessed
* @param referrer referred url
* @param title title of the page
* @param programName module name
* @param domain domain of the url
* @param user user that accessed url
* @return List of BlackboardAttributes for giving attributes
* @throws TskCoreException
*/
protected Collection<BlackboardAttribute> createHistoryAttribute(String url, Long accessTime,
String referrer, String title, String programName, String domain, String user) throws TskCoreException {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
RecentActivityExtracterModuleFactory.getModuleName(),
(url != null) ? url : "")); //NON-NLS
if (accessTime != null) {
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
RecentActivityExtracterModuleFactory.getModuleName(), accessTime));
}
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REFERRER,
RecentActivityExtracterModuleFactory.getModuleName(),
(referrer != null) ? referrer : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE,
RecentActivityExtracterModuleFactory.getModuleName(),
(title != null) ? title : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(),
(programName != null) ? programName : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
RecentActivityExtracterModuleFactory.getModuleName(),
(domain != null) ? domain : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME,
RecentActivityExtracterModuleFactory.getModuleName(),
(user != null) ? user : "")); //NON-NLS
return bbattributes;
}
/**
* Creates a list of attributes for a cookie.
*
* @param url cookie url
* @param creationTime cookie creation time
* @param name cookie name
* @param value cookie value
* @param programName Name of the module creating the attribute
* @param domain Domain of the URL
* @return List of BlackboarAttributes for the passed in attributes
*/
protected Collection<BlackboardAttribute> createCookieAttributes(String url,
Long creationTime, String name, String value, String programName, String domain) {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
RecentActivityExtracterModuleFactory.getModuleName(),
(url != null) ? url : "")); //NON-NLS
if (creationTime != null) {
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME,
RecentActivityExtracterModuleFactory.getModuleName(), creationTime));
}
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME,
RecentActivityExtracterModuleFactory.getModuleName(),
(name != null) ? name : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE,
RecentActivityExtracterModuleFactory.getModuleName(),
(value != null) ? value : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(),
(programName != null) ? programName : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
RecentActivityExtracterModuleFactory.getModuleName(),
(domain != null) ? domain : "")); //NON-NLS
return bbattributes;
}
/**
* Creates a list of bookmark attributes from the passed in parameters.
*
* @param url Bookmark url
* @param title Title of the bookmarked page
* @param creationTime Date & time at which the bookmark was created
* @param programName Name of the module creating the attribute
* @param domain The domain of the bookmark's url
* @return A collection of bookmark attributes
*/
protected Collection<BlackboardAttribute> createBookmarkAttributes(String url, String title, Long creationTime, String programName, String domain) {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
RecentActivityExtracterModuleFactory.getModuleName(),
(url != null) ? url : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE,
RecentActivityExtracterModuleFactory.getModuleName(),
(title != null) ? title : "")); //NON-NLS
if (creationTime != null) {
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
RecentActivityExtracterModuleFactory.getModuleName(), creationTime));
}
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(),
(programName != null) ? programName : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
RecentActivityExtracterModuleFactory.getModuleName(),
(domain != null) ? domain : "")); //NON-NLS
return bbattributes;
}
/**
* Creates a list of the attributes of a downloaded file
*
* @param path
* @param url URL of the downloaded file
* @param accessTime Time the download occurred
* @param domain Domain of the URL
* @param programName Name of the module creating the attribute
* @return A collection of attributed of a downloaded file
*/
protected Collection<BlackboardAttribute> createDownloadAttributes(String path, Long pathID, String url, Long accessTime, String domain, String programName) {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
RecentActivityExtracterModuleFactory.getModuleName(),
(path != null) ? path : "")); //NON-NLS
if (pathID != null && pathID != -1) {
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
RecentActivityExtracterModuleFactory.getModuleName(),
pathID));
}
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
RecentActivityExtracterModuleFactory.getModuleName(),
(url != null) ? url : "")); //NON-NLS
if (accessTime != null) {
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
RecentActivityExtracterModuleFactory.getModuleName(), accessTime));
}
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
RecentActivityExtracterModuleFactory.getModuleName(),
(domain != null) ? domain : "")); //NON-NLS
bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(),
(programName != null) ? programName : "")); //NON-NLS
return bbattributes;
}
/**
* Create temporary file for the given AbstractFile. The new file will be
* created in the temp directory for the module with a unique file name.
*
* @param context
* @param file
* @return Newly created copy of the AbstractFile
* @throws IOException
*/
protected File createTemporaryFile(IngestJobContext context, AbstractFile file) throws IOException{
Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(
getCurrentCase(), getName()), file.getName() + file.getId() + file.getNameExtension());
java.io.File tempFile = tempFilePath.toFile();
try {
ContentUtils.writeToFile(file, tempFile, context::dataSourceIngestIsCancelled);
} catch (IOException ex) {
throw new IOException("Error writingToFile: " + file, ex); //NON-NLS
}
return tempFile;
}
}
@@ -0,0 +1,858 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
*
* 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.recentactivity;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Level;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.ExecUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Extract the bookmarks, cookies, downloads and history from Microsoft Edge
*/
final class ExtractEdge extends Extract {
private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName());
private final IngestServices services = IngestServices.getInstance();
private final Path moduleTempResultPath;
private Content dataSource;
private IngestJobContext context;
private HashMap<String, ArrayList<String>> containersTable;
private static final String EDGE = "Edge"; //NON-NLS
private static final String EDGE_KEYWORD_VISIT = "Visited:"; //NON-NLS
private static final String IGNORE_COMMA_IN_QUOTES_REGEX = ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"; //NON-NLS
private static final String EDGE_TABLE_TYPE_DOWNLOAD = "iedownload"; //NON-NLS
private static final String EDGE_TABLE_TYPE_HISTORY = "History"; //NON-NLS
private static final String EDGE_TABLE_TYPE_COOKIE = "cookie"; //NON-NLS
private static final String EDGE_HEAD_URL = "url"; //NON-NLS
private static final String EDGE_HEAD_ACCESSTIME = "accessedtime"; //NON-NLS
private static final String EDGE_HEAD_NAME = "name"; //NON-NLS
private static final String EDGE_HEAD_CONTAINER_ID = "containerid"; //NON-NLS
private static final String EDGE_HEAD_RESPONSEHEAD = "responseheaders"; //NON-NLS
private static final String EDGE_HEAD_TITLE = "title"; //NON-NLS
private static final String EDGE_HEAD_RDOMAIN = "rdomain"; //NON-NLS
private static final String EDGE_HEAD_VALUE = "value"; //NON-NLS
private static final String EDGE_HEAD_LASTMOD = "lastmodified"; //NON-NLS
private static final String EDGE_WEBCACHE_PREFIX = "WebCacheV01"; //NON-NLS
private static final String EDGE_CONTAINER_FILE_PREFIX = "Container_"; //NON-NLS
private static final String EDGE_CONTAINER_FILE_EXT = ".csv"; //NON-NLS
private static final String EDGE_WEBCACHE_EXT = ".dat"; //NON-NLS
private static final String ESE_TOOL_NAME = "ESEDatabaseView.exe"; //NON-NLS
private static final String EDGE_WEBCACHE_NAME = "WebCacheV01.dat"; //NON-NLS
private static final String EDGE_SPARTAN_NAME = "Spartan.edb"; //NON-NLS
private static final String EDGE_CONTAINTERS_FILE_NAME = "Containers.csv"; //NON-NLS
private static final String EDGE_FAVORITE_FILE_NAME = "Favorites.csv"; //NON-NLS
private static final String EDGE_OUTPUT_FILE_NAME = "Output.txt"; //NON-NLS
private static final String EDGE_ERROR_FILE_NAME = "File.txt"; //NON-NLS
private static final String ESE_TOOL_FOLDER = "ESEDatabaseView"; //NON-NLS
private static final String EDGE_RESULT_FOLDER_NAME = "results"; //NON-NLS
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a");
@Messages({
"ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer",
"ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file",
"ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file",
"ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file",
"ExtractEdge_Module_Name=Microsoft Edge",
"ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history",
})
/**
* Extract the bookmarks, cookies, downloads and history from Microsoft Edge
*/
ExtractEdge() throws NoCurrentCaseException {
moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), EDGE_RESULT_FOLDER_NAME);
}
@Override
protected String getName() {
return Bundle.ExtractEdge_Module_Name();
}
@Override
void process(Content dataSource, IngestJobContext context) {
this.dataSource = dataSource;
this.context = context;
this.setFoundData(false);
List<AbstractFile> webCacheFiles = null;
List<AbstractFile> spartanFiles = null;
try {
webCacheFiles = fetchWebCacheDBFiles();
} catch (TskCoreException ex) {
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles());
LOG.log(Level.SEVERE, "Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex); //NON-NLS
}
try {
spartanFiles = fetchSpartanDBFiles(); // For later use with bookmarks
} catch (TskCoreException ex) {
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
LOG.log(Level.SEVERE, "Error fetching 'spartan.edb' files for Microsoft Edge", ex); //NON-NLS
}
// No edge files found
if (webCacheFiles == null && spartanFiles == null) {
return;
}
this.setFoundData(true);
if (!PlatformUtil.isWindowsOS()) {
LOG.log(Level.WARNING, "Microsoft Edge files found, unable to parse on Non-Windows system"); //NON-NLS
return;
}
final String esedumper = getPathForESEDumper();
if (esedumper == null) {
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer());
LOG.log(Level.SEVERE, "Error finding ESEDatabaseViewer program"); //NON-NLS
return; //If we cannot find the ESEDatabaseView we cannot proceed
}
try {
this.processWebCacheDbFile(esedumper, webCacheFiles);
} catch (IOException | TskCoreException ex) {
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
LOG.log(Level.SEVERE, "Error returned from processWebCacheDbFile", ex); // NON-NLS
}
try {
this.processSpartanDbFile(esedumper, spartanFiles);
} catch (IOException | TskCoreException ex) {
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
LOG.log(Level.SEVERE, "Error returned from processSpartanDbFile", ex); // NON-NLS
}
}
/**
* Dump the tables from WebCacheV01.dat and look for the data contained with
* in those files including downloads, cookies and history.
*
* @param eseDumperPath Path to ESEDatabaseView.exe
* @param webCacheFiles List of case WebCacheV01.dat files
* @throws IOException
* @throws TskCoreException
*/
void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles) throws IOException, TskCoreException {
for (AbstractFile webCacheFile : webCacheFiles) {
if (context.dataSourceIngestIsCancelled()) {
return;
}
clearContainerTable();
//Run the dumper
String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
+ Integer.toString((int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT; //NON-NLS
File tempWebCacheFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempWebCacheFileName);
try {
ContentUtils.writeToFile(webCacheFile, tempWebCacheFile,
context::dataSourceIngestIsCancelled);
} catch (IOException ex) {
throw new IOException("Error writingToFile: " + webCacheFile, ex); //NON-NLS
}
File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) webCacheFile.getId()));
resultsDir.mkdirs();
try {
executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(),
resultsDir.getAbsolutePath());
if (context.dataSourceIngestIsCancelled()) {
return;
}
this.getHistory(webCacheFile, resultsDir);
if (context.dataSourceIngestIsCancelled()) {
return;
}
this.getCookies(webCacheFile, resultsDir);
// if (context.dataSourceIngestIsCancelled()) {
// return;
// }
// Putting downloads on hold
// this.getDownload(webCacheFile, resultsDir);
} finally {
tempWebCacheFile.delete();
resultsDir.delete();
}
}
}
/**
* Creates a temp version of the database and runs the ESEDatabaseView tool
* to dump each of the database tables into a temporary folder.
*
* @param eseDumperPath Path to ESEDatabaseViewer
* @param spartanFiles List of the case spartan.edb files
* @throws IOException
* @throws TskCoreException
*/
void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles) throws IOException, TskCoreException {
for (AbstractFile spartanFile : spartanFiles) {
if (context.dataSourceIngestIsCancelled()) {
return;
}
//Run the dumper
String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
+ Integer.toString((int) spartanFile.getId()) + EDGE_WEBCACHE_EXT;
File tempSpartanFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempSpartanFileName);
try {
ContentUtils.writeToFile(spartanFile, tempSpartanFile,
context::dataSourceIngestIsCancelled);
} catch (IOException ex) {
throw new IOException("Error writingToFile: " + spartanFile, ex); //NON-NLS
}
File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) spartanFile.getId()));
resultsDir.mkdirs();
try {
executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
resultsDir.getAbsolutePath());
if (context.dataSourceIngestIsCancelled()) {
return;
}
this.getBookmarks(spartanFile, resultsDir);
} finally {
tempSpartanFile.delete();
resultsDir.delete();
}
}
}
/**
* getHistory searches the files with "container" in the file name for lines
* with the text "Visited" in them. Note that not all of the container
* files, if fact most of them do not, have the browser history in them.
* @param origFile Original case file
* @param resultDir Output directory of ESEDatabaseViewer
* @throws TskCoreException
* @throws FileNotFoundException
*/
private void getHistory(AbstractFile origFile, File resultDir) throws TskCoreException, FileNotFoundException {
ArrayList<File> historyFiles = getHistoryFiles(resultDir);
if (historyFiles == null) {
return;
}
for (File file : historyFiles) {
Scanner fileScanner;
try {
fileScanner = new Scanner(new FileInputStream(file.toString()));
} catch (FileNotFoundException ex) {
LOG.log(Level.WARNING, "Unable to find the ESEDatabaseView file at " + file.getPath(), ex); //NON-NLS
continue; // If we couldn't open this file, continue to the next file
}
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
try {
List<String> headers = null;
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
if (headers == null) {
headers = Arrays.asList(line.toLowerCase().split(","));
continue;
}
if (line.contains(EDGE_KEYWORD_VISIT)) {
BlackboardArtifact ba = getHistoryArtifact(origFile, headers, line);
if (ba != null) {
bbartifacts.add(ba);
this.indexArtifact(ba);
}
}
}
} finally {
fileScanner.close();
}
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
}
}
}
/**
* Search for bookmark files and make artifacts.
*
* @param origFile Original case file
* @param resultDir Output directory of ESEDatabaseViewer
* @throws TskCoreException
* @throws FileNotFoundException
*/
private void getBookmarks(AbstractFile origFile, File resultDir) throws TskCoreException {
Scanner fileScanner;
File favoriteFile = new File(resultDir, EDGE_FAVORITE_FILE_NAME);
try {
fileScanner = new Scanner(new FileInputStream(favoriteFile));
} catch (FileNotFoundException ex) {
// This is a non-fatal error, if the favorites file is not found
// there might have not been any favorites\bookmarks
return;
}
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
try {
List<String> headers = null;
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
if (headers == null) {
headers = Arrays.asList(line.toLowerCase().split(","));
continue;
}
BlackboardArtifact ba = getBookmarkArtifact(origFile, headers, line);
if (ba != null) {
bbartifacts.add(ba);
this.indexArtifact(ba);
}
}
} finally {
fileScanner.close();
}
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
}
}
/**
* Queries for cookie files and adds artifacts
*
* @param origFile Original case file
* @param resultDir Output directory of ESEDatabaseViewer
* @throws TskCoreException
*/
private void getCookies(AbstractFile origFile, File resultDir) throws TskCoreException {
File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains(EDGE_TABLE_TYPE_COOKIE));
if (containerFiles == null) {
return;
}
for (File file : containerFiles) {
Scanner fileScanner;
try {
fileScanner = new Scanner(new FileInputStream(file.toString()));
} catch (FileNotFoundException ex) {
LOG.log(Level.WARNING, "Unable to find the ESEDatabaseView file at " + file.getPath(), ex); //NON-NLS
continue; // If we couldn't open this file, continue to the next file
}
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
try {
List<String> headers = null;
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
if (headers == null) {
headers = Arrays.asList(line.toLowerCase().split(","));
continue;
}
BlackboardArtifact ba = getCookieArtifact(origFile, headers, line);
if (ba != null) {
bbartifacts.add(ba);
this.indexArtifact(ba);
}
}
} finally {
fileScanner.close();
}
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
}
}
}
/**
* Queries for download files and adds artifacts
*
* @param origFile Original case file
* @param resultDir Output directory of ESEDatabaseViewer
* @throws TskCoreException
* @throws FileNotFoundException
*/
private void getDownloads(AbstractFile origFile, File resultDir) throws TskCoreException, FileNotFoundException {
ArrayList<File> downloadFiles = getDownloadFiles(resultDir);
if (downloadFiles == null) {
return;
}
for (File file : downloadFiles) {
Scanner fileScanner;
try {
fileScanner = new Scanner(new FileInputStream(file.toString()));
} catch (FileNotFoundException ex) {
LOG.log(Level.WARNING, "Unable to find the ESEDatabaseView file at " + file.getPath(), ex); //NON-NLS
continue; // If we couldn't open this file, continue to the next file
}
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
try {
List<String> headers = null;
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
if (headers == null) {
headers = Arrays.asList(line.toLowerCase().split(","));
continue;
}
if (line.contains(EDGE_TABLE_TYPE_DOWNLOAD)) {
BlackboardArtifact ba = getDownloadArtifact(origFile, headers, line);
if (ba != null) {
bbartifacts.add(ba);
this.indexArtifact(ba);
}
}
}
} finally {
fileScanner.close();
}
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
}
}
}
/**
* Find the location of ESEDatabaseViewer.exe
*
* @return Absolute path to ESEDatabaseViewer.exe or null if the file is not found
*/
private String getPathForESEDumper() {
Path path = Paths.get(ESE_TOOL_FOLDER, ESE_TOOL_NAME);
File eseToolFile = InstalledFileLocator.getDefault().locate(path.toString(),
ExtractEdge.class.getPackage().getName(), false);
if (eseToolFile != null) {
return eseToolFile.getAbsolutePath();
}
return null;
}
/**
* Finds all of the WebCacheV01.dat files in the case
*
* @return A list of WebCacheV01.dat files, possibly empty if none are found
* @throws TskCoreException
*/
private List<AbstractFile> fetchWebCacheDBFiles() throws TskCoreException {
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager
= currentCase.getServices().getFileManager();
return fileManager.findFiles(dataSource, EDGE_WEBCACHE_NAME, "WebCache"); //NON-NLS
}
/**
* Finds all of the spartan.edb files in the case
*
* @return A list of spartan files, possibly empty if none are found
* @throws TskCoreException
*/
private List<AbstractFile> fetchSpartanDBFiles() throws TskCoreException {
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager
= currentCase.getServices().getFileManager();
return fileManager.findFiles(dataSource, EDGE_SPARTAN_NAME, "MicrosoftEdge"); //NON-NLS
}
/**
* Executes the ESEViewDumper on the given inputFile.
*
* Each table in the ese database will be dumped as a comma separated file
* named <tableName>.csv
*
* @param dumperPath Path to ESEDatabaseView.exe
* @param inputFilePath Path to ese database file to be dumped
* @param outputDir Output directory for dumper
* @throws IOException
*/
private void executeDumper(String dumperPath, String inputFilePath,
String outputDir) throws IOException {
final Path outputFilePath = Paths.get(outputDir, EDGE_OUTPUT_FILE_NAME);
final Path errFilePath = Paths.get(outputDir, EDGE_ERROR_FILE_NAME);
LOG.log(Level.INFO, "Writing ESEDatabaseViewer results to: {0}", outputDir); //NON-NLS
List<String> commandLine = new ArrayList<>();
commandLine.add(dumperPath);
commandLine.add("/table"); //NON-NLS
commandLine.add(inputFilePath);
commandLine.add("*"); //NON-NLS
commandLine.add("/scomma"); //NON-NLS
commandLine.add(outputDir + "\\" + "*.csv"); //NON-NLS
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
processBuilder.redirectOutput(outputFilePath.toFile());
processBuilder.redirectError(errFilePath.toFile());
ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context));
}
/**
* Create a BlackboardArtifact for the given row from the Edge history
* table.
*
* @param origFile Original case file
* @param headers List of table headers
* @param line CSV string representing a row of history table
* @return BlackboardArtifact representing one history table entry
* @throws TskCoreException
*/
private BlackboardArtifact getHistoryArtifact(AbstractFile origFile, List<String> headers, String line) throws TskCoreException {
String[] rowSplit = line.split(",");
int index = headers.indexOf(EDGE_HEAD_URL);
String urlUserStr = rowSplit[index];
String[] str = urlUserStr.split("@");
String user = (str[0].replace(EDGE_KEYWORD_VISIT, "")).trim();
String url = str[1];
index = headers.indexOf(EDGE_HEAD_ACCESSTIME);
String accessTime = rowSplit[index].trim();
Long ftime = null;
try {
Long epochtime = DATE_FORMATTER.parse(accessTime).getTime();
ftime = epochtime / 1000;
} catch (ParseException ex) {
LOG.log(Level.WARNING, "The Accessed Time format in history file seems invalid " + accessTime, ex); //NON-NLS
}
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
bbart.addAttributes(createHistoryAttribute(url, ftime,
null, null,
this.getName(),
NetworkUtils.extractDomain(url), user));
return bbart;
}
/**
* Create a BlackboardArtifact for the given row from the Edge cookie table.
*
* @param origFile Original case file
* @param headers List of table headers
* @param line CSV string representing a row of cookie table
* @return BlackboardArtifact representing one cookie table entry
* @throws TskCoreException
*/
private BlackboardArtifact getCookieArtifact(AbstractFile origFile, List<String> headers, String line) throws TskCoreException {
String[] lineSplit = line.split(",");
String accessTime = lineSplit[headers.indexOf(EDGE_HEAD_LASTMOD)].trim();
Long ftime = null;
try {
Long epochtime = DATE_FORMATTER.parse(accessTime).getTime();
ftime = epochtime / 1000;
} catch (ParseException ex) {
LOG.log(Level.WARNING, "The Accessed Time format in history file seems invalid " + accessTime, ex); //NON-NLS
}
String domain = lineSplit[headers.indexOf(EDGE_HEAD_RDOMAIN)].trim();
String name = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_NAME)].trim());
String value = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_VALUE)].trim());
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE);
bbart.addAttributes(createCookieAttributes(null, ftime, name, value, this.getName(), flipDomain(domain)));
return bbart;
}
/**
* Create a BlackboardArtifact for the given row from the Edge cookie table.
*
* This function is on hold for the moment. All of the information need
* seems to be in decodedheader, but its not currently obvious how to pull
* it apart.
*
* @param origFile Original case file
* @param headers List of table headers
* @param line CSV string representing a row of download table
* @return BlackboardArtifact representing one download table entry
* @throws TskCoreException
*/
private BlackboardArtifact getDownloadArtifact(AbstractFile origFile, List<String> headers, String line) throws TskCoreException {
// String[] lineSplit = line.split(",");
//
// String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
//
// String rheader = lineSplit[headers.indexOf(EDGE_HEAD_RESPONSEHEAD)];
//
// String decodedheader = this.hexToASCII(rheader);
// BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD);
// bbart.addAttributes(createDownloadAttributes(decodedheader, "Test2", null, "microsoft.com", this.getName()));
// return bbart;
return null;
}
/**
* Parse the comma separated row of information from the "Favorites" table
* of the spartan database.
*
* Note: The "Favorites" table does not have a "Creation Time"
*
* @param origFile File the table came from ie spartan.edb
* @param headers List of table column headers
* @param line The line or row of the table to parse
* @return BlackboardArtifact representation of the passed in line\table row or null if no Bookmark is found
* @throws TskCoreException
*/
private BlackboardArtifact getBookmarkArtifact(AbstractFile origFile, List<String> headers, String line) throws TskCoreException {
// split on all commas as long as they are not inbetween quotes
String[] lineSplit = line.split(IGNORE_COMMA_IN_QUOTES_REGEX, -1);
String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
String title = lineSplit[headers.indexOf(EDGE_HEAD_TITLE)].replace("\"", "");
if (url.isEmpty()) {
return null;
}
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
bbart.addAttributes(createBookmarkAttributes(url, title, null,
this.getName(), NetworkUtils.extractDomain(url)));
return bbart;
}
/**
* Converts a space separated string of hex values to ascii characters.
*
* @param hexString
* @return "decoded" string or null if a non-hex value was found
*/
private String hexToChar(String hexString) {
String[] hexValues = hexString.split(" ");
StringBuilder output = new StringBuilder();
for (String str : hexValues) {
try {
int value = Integer.parseInt(str, 16);
if (value > 31) { // Ignore non-print characters
output.append((char) value);
}
} catch (NumberFormatException ex) {
return null;
}
}
return output.toString();
}
/**
* The RDomain in the WebCacheV01.data cookies tables are backwards, this
* function corrects them.
*
* Values in the RDomain appear as either com.microsoft.www or com.microsoft
* but for some reason there could also be "junk". the length checks are
* there to weed out the "junk".
*
* @param domain
* @return Correct domain string
*/
private String flipDomain(String domain) {
if (domain == null || domain.isEmpty()) {
return null;
}
String[] tokens = domain.split("\\.");
if (tokens.length < 2 || tokens.length > 3) {
return domain; // don't know what to do, just send it back as is
}
StringBuilder buf = new StringBuilder();
if (tokens.length > 2) {
buf.append(tokens[2]);
buf.append(".");
}
buf.append(tokens[1]);
buf.append(".");
buf.append(tokens[0]);
return buf.toString();
}
/**
* Returns a list the container files that have download information in
* them.
*
* @param resultDir Path to ESEDatabaseViewer output
* @return List of download table files
*/
private ArrayList<File> getDownloadFiles(File resultDir) throws FileNotFoundException {
return getContainerFiles(resultDir, EDGE_TABLE_TYPE_DOWNLOAD);
}
/**
* Returns a list the container files that have history information in them.
*
* @param resultDir Path to ESEDatabaseViewer output
* @return List of history table files
* @throws FileNotFoundException
*/
private ArrayList<File> getHistoryFiles(File resultDir) throws FileNotFoundException {
return getContainerFiles(resultDir, EDGE_TABLE_TYPE_HISTORY);
}
/**
* Returns a list of the containers files that are of the given type string
*
* @param resultDir Path to ESEDatabaseViewer output
* @param type Type of table files
* @return List of table files returns null if no files of that type are found
* @throws FileNotFoundException
*/
private ArrayList<File> getContainerFiles(File resultDir, String type) throws FileNotFoundException {
HashMap<String, ArrayList<String>> idTable = getContainerIDTable(resultDir);
ArrayList<String> idList = idTable.get(type);
if (idList == null) {
return null;
}
ArrayList<File> fileList = new ArrayList<>();
for (String str : idList) {
String fileName = EDGE_CONTAINER_FILE_PREFIX + str + EDGE_CONTAINER_FILE_EXT;
fileList.add(new File(resultDir, fileName));
}
return fileList;
}
/**
* Opens and reads the Containers table to create a table of information
* about which of the Continer_xx files contain which type of information.
*
* Each row of the "Containers" table describes one of the Container_xx
* files.
*
* @param resultDir Path to ESEDatabaseViewer output
* @return Hashmap with Key representing the table type, the value is a list of table ids for that type
*/
private HashMap<String, ArrayList<String>> getContainerIDTable(File resultDir) throws FileNotFoundException {
if (containersTable == null) {
File containerFile = new File(resultDir, EDGE_CONTAINTERS_FILE_NAME);
try (Scanner fileScanner = new Scanner(new FileInputStream(containerFile))) {
List<String> headers = null;
containersTable = new HashMap<>();
int nameIdx = 0;
int idIdx = 0;
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
if (headers == null) {
headers = Arrays.asList(line.toLowerCase().split(","));
nameIdx = headers.indexOf(EDGE_HEAD_NAME);
idIdx = headers.indexOf(EDGE_HEAD_CONTAINER_ID);
} else {
String[] row = line.split(",");
String name = row[nameIdx];
String id = row[idIdx];
ArrayList<String> idList = containersTable.get(name);
if (idList == null) {
idList = new ArrayList<>();
containersTable.put(name, idList);
}
idList.add(id);
}
}
}
}
return containersTable;
}
/**
* Clears the containerTable
*/
private void clearContainerTable(){
containersTable = null;
}
}
@@ -130,22 +130,17 @@ class ExtractIE extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), url));
RecentActivityExtracterModuleFactory.getModuleName(), url));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), name));
RecentActivityExtracterModuleFactory.getModuleName(), name));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), datetime));
RecentActivityExtracterModuleFactory.getModuleName(), datetime));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "ExtractIE.moduleName.text")));
if (domain != null && domain.isEmpty() == false) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), domain));
RecentActivityExtracterModuleFactory.getModuleName(), domain));
}
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK, fav, bbattributes);
@@ -245,25 +240,19 @@ class ExtractIE extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), url));
RecentActivityExtracterModuleFactory.getModuleName(), url));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), datetime));
RecentActivityExtracterModuleFactory.getModuleName(), datetime));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), (name != null) ? name : ""));
RecentActivityExtracterModuleFactory.getModuleName(), (name != null) ? name : ""));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), value));
RecentActivityExtracterModuleFactory.getModuleName(), value));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "ExtractIE.moduleName.text")));
if (domain != null && domain.isEmpty() == false) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), domain));
RecentActivityExtracterModuleFactory.getModuleName(), domain));
}
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_COOKIE, cookiesFile, bbattributes);
if (bbart != null) {
@@ -534,30 +523,24 @@ class ExtractIE extends Extract {
BlackboardArtifact bbart = origFile.newArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY);
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), realurl));
RecentActivityExtracterModuleFactory.getModuleName(), realurl));
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", EscapeUtil.decodeURL(realurl)));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), ftime));
RecentActivityExtracterModuleFactory.getModuleName(), ftime));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REFERRER,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), ""));
RecentActivityExtracterModuleFactory.getModuleName(), ""));
// @@@ NOte that other browser modules are adding TITLE in hre for the title
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"ExtractIE.moduleName.text")));
if (domain != null && domain.isEmpty() == false) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), domain));
RecentActivityExtracterModuleFactory.getModuleName(), domain));
}
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
NbBundle.getMessage(this.getClass(),
"ExtractIE.parentModuleName.noSpace"), user));
RecentActivityExtracterModuleFactory.getModuleName(), user));
bbart.addAttributes(bbattributes);
// index the artifact for keyword search
@@ -415,7 +415,7 @@ class ExtractRegistry extends Extract {
Element artroot = (Element) artroots.item(0);
NodeList myartlist = artroot.getChildNodes();
String parentModuleName = NbBundle.getMessage(this.getClass(), "ExtractRegistry.parentModuleName.noSpace");
String parentModuleName = RecentActivityExtracterModuleFactory.getModuleName();
String winver = "";
// If all artifact nodes should really go under one Blackboard artifact, need to process it differently
@@ -834,7 +834,7 @@ class ExtractRegistry extends Extract {
*/
private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstractFile) {
File regfile = new File(regFilePath);
String parentModuleName = NbBundle.getMessage(this.getClass(), "ExtractRegistry.parentModuleName.noSpace");
String parentModuleName = RecentActivityExtracterModuleFactory.getModuleName();
SimpleDateFormat regRipperTimeFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy 'Z'");
regRipperTimeFormat.setTimeZone(getTimeZone("GMT"));
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(regfile))) {
@@ -0,0 +1,633 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
*
* 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.recentactivity;
import com.dd.plist.NSArray;
import com.dd.plist.NSDate;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.dd.plist.NSString;
import com.dd.plist.PropertyListFormatException;
import com.dd.plist.PropertyListParser;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import javax.xml.parsers.ParserConfigurationException;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.xml.sax.SAXException;
/**
* Extract the bookmarks, cookies, downloads and history from Safari
*
*/
final class ExtractSafari extends Extract {
private final IngestServices services = IngestServices.getInstance();
// visit_time uses an epoch of Jan 1, 2001 thus the addition of 978307200
private static final String HISTORY_QUERY = "SELECT url, title, visit_time + 978307200 as time FROM 'history_items' JOIN history_visits ON history_item = history_items.id;"; //NON-NLS
private static final String HISTORY_FILE_NAME = "History.db"; //NON-NLS
private static final String BOOKMARK_FILE_NAME = "Bookmarks.plist"; //NON-NLS
private static final String DOWNLOAD_FILE_NAME = "Downloads.plist"; //NON-NLS
private static final String COOKIE_FILE_NAME = "Cookies.binarycookies"; //NON-NLS
private static final String COOKIE_FOLDER = "Cookies";
private static final String SAFARI_FOLDER = "Safari";
private static final String HEAD_URL = "url"; //NON-NLS
private static final String HEAD_TITLE = "title"; //NON-NLS
private static final String HEAD_TIME = "time"; //NON-NLS
private static final String PLIST_KEY_CHILDREN = "Children"; //NON-NLS
private static final String PLIST_KEY_URL = "URLString"; //NON-NLS
private static final String PLIST_KEY_URI = "URIDictionary"; //NON-NLS
private static final String PLIST_KEY_TITLE = "title"; //NON-NLS
private static final String PLIST_KEY_DOWNLOAD_URL = "DownloadEntryURL"; //NON-NLS
private static final String PLIST_KEY_DOWNLOAD_DATE = "DownloadEntryDateAddedKey"; //NON-NLS
private static final String PLIST_KEY_DOWNLOAD_PATH = "DownloadEntryPath"; //NON-NLS
private static final String PLIST_KEY_DOWNLOAD_HISTORY = "DownloadHistory"; //NON-NLS
private static final Logger LOG = Logger.getLogger(ExtractSafari.class.getName());
@Messages({
"ExtractSafari_Module_Name=Safari",
"ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files.",
"ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files",
"ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files",
})
/**
* Extract the bookmarks, cookies, downloads and history from Safari.
*
*/
ExtractSafari() {
}
@Override
protected String getName() {
return Bundle.ExtractSafari_Module_Name();
}
@Override
void process(Content dataSource, IngestJobContext context) {
setFoundData(false);
try {
processHistoryDB(dataSource, context);
} catch (IOException | TskCoreException ex) {
this.addErrorMessage(Bundle.ExtractSafari_Error_Getting_History());
LOG.log(Level.SEVERE, "Exception thrown while processing history file: {0}", ex); //NON-NLS
}
try {
processBookmarkPList(dataSource, context);
} catch (IOException | TskCoreException | SAXException | PropertyListFormatException | ParseException | ParserConfigurationException ex) {
this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Bookmark());
LOG.log(Level.SEVERE, "Exception thrown while parsing Safari Bookmarks file: {0}", ex); //NON-NLS
}
try {
processDownloadsPList(dataSource, context);
} catch (IOException | TskCoreException | SAXException | PropertyListFormatException | ParseException | ParserConfigurationException ex) {
this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Bookmark());
LOG.log(Level.SEVERE, "Exception thrown while parsing Safari Download.plist file: {0}", ex); //NON-NLS
}
try {
processBinaryCookieFile(dataSource, context);
} catch (IOException | TskCoreException ex) {
this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Cookies());
LOG.log(Level.SEVERE, "Exception thrown while processing Safari cookies file: {0}", ex); //NON-NLS
}
}
/**
* Finds the all of the history.db files in the case looping through them to
* find all of the history artifacts.
*
* @throws TskCoreException
* @throws IOException
*/
private void processHistoryDB(Content dataSource, IngestJobContext context) throws TskCoreException, IOException {
FileManager fileManager = getCurrentCase().getServices().getFileManager();
List<AbstractFile> historyFiles = fileManager.findFiles(dataSource, HISTORY_FILE_NAME, SAFARI_FOLDER);
if (historyFiles == null || historyFiles.isEmpty()) {
return;
}
setFoundData(true);
for (AbstractFile historyFile : historyFiles) {
if (context.dataSourceIngestIsCancelled()) {
break;
}
getHistory(context, historyFile);
}
}
/**
* Finds all Bookmark.plist files and looks for bookmark entries.
* @param dataSource
* @param context
* @throws TskCoreException
* @throws IOException
* @throws SAXException
* @throws PropertyListFormatException
* @throws ParseException
* @throws ParserConfigurationException
*/
private void processBookmarkPList(Content dataSource, IngestJobContext context) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
FileManager fileManager = getCurrentCase().getServices().getFileManager();
List<AbstractFile> files = fileManager.findFiles(dataSource, BOOKMARK_FILE_NAME, SAFARI_FOLDER);
if (files == null || files.isEmpty()) {
return;
}
setFoundData(true);
for (AbstractFile file : files) {
if (context.dataSourceIngestIsCancelled()) {
break;
}
getBookmarks(context, file);
}
}
/**
* Process the safari download.plist file.
*
* @param dataSource
* @param context
* @throws TskCoreException
* @throws IOException
* @throws SAXException
* @throws PropertyListFormatException
* @throws ParseException
* @throws ParserConfigurationException
*/
private void processDownloadsPList(Content dataSource, IngestJobContext context) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
FileManager fileManager = getCurrentCase().getServices().getFileManager();
List<AbstractFile> files = fileManager.findFiles(dataSource, DOWNLOAD_FILE_NAME, SAFARI_FOLDER);
if (files == null || files.isEmpty()) {
return;
}
setFoundData(true);
for (AbstractFile file : files) {
if (context.dataSourceIngestIsCancelled()) {
break;
}
getDownloads(dataSource, context, file);
}
}
/**
* Process the Safari Cookie file.
* @param dataSource
* @param context
* @throws TskCoreException
* @throws IOException
*/
private void processBinaryCookieFile(Content dataSource, IngestJobContext context) throws TskCoreException, IOException {
FileManager fileManager = getCurrentCase().getServices().getFileManager();
List<AbstractFile> files = fileManager.findFiles(dataSource, COOKIE_FILE_NAME, COOKIE_FOLDER);
if (files == null || files.isEmpty()) {
return;
}
setFoundData(true);
for (AbstractFile file : files) {
if (context.dataSourceIngestIsCancelled()) {
break;
}
getCookies(context, file);
}
}
/**
* Creates a temporary copy of historyFile and creates a list of
* BlackboardArtifacts for the history information in the file.
*
* @param historyFile AbstractFile version of the history file from the case
* @throws TskCoreException
* @throws IOException
*/
private void getHistory(IngestJobContext context, AbstractFile historyFile) throws TskCoreException, IOException {
if (historyFile.getSize() == 0) {
return;
}
File tempHistoryFile = createTemporaryFile(context, historyFile);
try {
ContentUtils.writeToFile(historyFile, tempHistoryFile, context::dataSourceIngestIsCancelled);
} catch (IOException ex) {
throw new IOException("Error writingToFile: " + historyFile, ex); //NON-NLS
}
try {
Collection<BlackboardArtifact> bbartifacts = getHistoryArtifacts(historyFile, tempHistoryFile.toPath());
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
}
} finally {
tempHistoryFile.delete();
}
}
/**
* Creates a temporary bookmark file from the AbstractFile and creates
* BlackboardArtifacts for the any bookmarks found.
*
* @param context IngestJobContext object
* @param file AbstractFile from case
* @throws TskCoreException
* @throws IOException
* @throws SAXException
* @throws PropertyListFormatException
* @throws ParseException
* @throws ParserConfigurationException
*/
private void getBookmarks(IngestJobContext context, AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
if (file.getSize() == 0) {
return;
}
File tempFile = createTemporaryFile(context, file);
try {
Collection<BlackboardArtifact> bbartifacts = getBookmarkArtifacts(file, tempFile);
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bbartifacts));
}
} finally {
tempFile.delete();
}
}
/**
* Creates a temporary downloads file from the AbstractFile and creates
* BlackboardArtifacts for the any downloads found.
*
* @param context IngestJobContext object
* @param file AbstractFile from case
* @throws TskCoreException
* @throws IOException
* @throws SAXException
* @throws PropertyListFormatException
* @throws ParseException
* @throws ParserConfigurationException
*/
private void getDownloads(Content dataSource, IngestJobContext context, AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
if (file.getSize() == 0) {
return;
}
File tempFile = createTemporaryFile(context, file);
try {
Collection<BlackboardArtifact> bbartifacts = getDownloadArtifacts(dataSource, file, tempFile);
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
}
} finally {
if (tempFile != null) {
tempFile.delete();
}
}
}
/**
* Creates a temporary copy of the Cookie file and creates a list of cookie
* BlackboardArtifacts.
*
* @param context IngetstJobContext
* @param file Original Cookie file from the case
* @throws TskCoreException
* @throws IOException
*/
private void getCookies(IngestJobContext context, AbstractFile file) throws TskCoreException, IOException {
if (file.getSize() == 0) {
return;
}
File tempFile = null;
try {
tempFile = createTemporaryFile(context, file);
Collection<BlackboardArtifact> bbartifacts = getCookieArtifacts(file, tempFile);
if (!bbartifacts.isEmpty()) {
services.fireModuleDataEvent(new ModuleDataEvent(
RecentActivityExtracterModuleFactory.getModuleName(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, bbartifacts));
}
} finally {
if (tempFile != null) {
tempFile.delete();
}
}
}
/**
* Queries the history db for the history information creating a list of
* BlackBoardArtifact for each row returned from the db.
*
* @param origFile AbstractFile of the history file from the case
* @param tempFilePath Path to temporary copy of the history db
* @return Blackboard Artifacts for the history db or null if there are no
* history artifacts
* @throws TskCoreException
*/
private Collection<BlackboardArtifact> getHistoryArtifacts(AbstractFile origFile, Path tempFilePath) throws TskCoreException {
List<HashMap<String, Object>> historyList = this.dbConnect(tempFilePath.toString(), HISTORY_QUERY);
if (historyList == null || historyList.isEmpty()) {
return null;
}
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
for (HashMap<String, Object> row : historyList) {
String url = row.get(HEAD_URL).toString();
String title = row.get(HEAD_TITLE).toString();
Long time = (Double.valueOf(row.get(HEAD_TIME).toString())).longValue();
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
bbart.addAttributes(createHistoryAttribute(url, time, null, title,
this.getName(), NetworkUtils.extractDomain(url), null));
bbartifacts.add(bbart);
}
return bbartifacts;
}
/**
* Parses the temporary version of bookmarks.plist and creates
*
* @param origFile The origFile Bookmark.plist file from the case
* @param tempFile The temporary local version of Bookmark.plist
* @return Collection of BlackboardArtifacts for the bookmarks in origFile
* @throws IOException
* @throws PropertyListFormatException
* @throws ParseException
* @throws ParserConfigurationException
* @throws SAXException
* @throws TskCoreException
*/
private Collection<BlackboardArtifact> getBookmarkArtifacts(AbstractFile origFile, File tempFile) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
try {
NSDictionary root = (NSDictionary) PropertyListParser.parse(tempFile);
parseBookmarkDictionary(bbartifacts, origFile, root);
} catch (PropertyListFormatException ex) {
PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
plfe.setStackTrace(ex.getStackTrace());
throw plfe;
} catch (ParseException ex) {
ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
pe.setStackTrace(ex.getStackTrace());
throw pe;
} catch (ParserConfigurationException ex) {
ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
pce.setStackTrace(ex.getStackTrace());
throw pce;
} catch (SAXException ex) {
SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
se.setStackTrace(ex.getStackTrace());
throw se;
}
return bbartifacts;
}
/**
* Finds the download entries in the tempFile and creates a list of artifacts from them.
*
* @param origFile Download.plist file from case
* @param tempFile Temporary copy of download.plist file
* @return Collection of BlackboardArtifacts for the downloads in origFile
* @throws IOException
* @throws PropertyListFormatException
* @throws ParseException
* @throws ParserConfigurationException
* @throws SAXException
* @throws TskCoreException
*/
private Collection<BlackboardArtifact> getDownloadArtifacts(Content dataSource, AbstractFile origFile, File tempFile)throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
Collection<BlackboardArtifact> bbartifacts = null;
try {
while(true){
NSDictionary root = (NSDictionary)PropertyListParser.parse(tempFile);
if(root == null)
break;
NSArray nsArray = (NSArray)root.get(PLIST_KEY_DOWNLOAD_HISTORY);
if(nsArray == null)
break;
NSObject[] objectArray = nsArray.getArray();
bbartifacts = new ArrayList<>();
for(NSObject obj: objectArray){
if(obj instanceof NSDictionary){
bbartifacts.add(parseDownloadDictionary(dataSource, origFile, (NSDictionary)obj));
}
}
break;
}
} catch (PropertyListFormatException ex) {
PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
plfe.setStackTrace(ex.getStackTrace());
throw plfe;
} catch (ParseException ex) {
ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
pe.setStackTrace(ex.getStackTrace());
throw pe;
} catch (ParserConfigurationException ex) {
ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
pce.setStackTrace(ex.getStackTrace());
throw pce;
} catch (SAXException ex) {
SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
se.setStackTrace(ex.getStackTrace());
throw se;
}
return bbartifacts;
}
/**
* Finds the cookies in the tempFile creating a list of BlackboardArtifacts
* each representing one cookie.
*
* @param origFile Original Cookies.binarycookie file from case
* @param tempFile Temporary copy of the cookies file
* @return List of Blackboard Artifacts, one for each cookie
* @throws TskCoreException
* @throws IOException
*/
private Collection<BlackboardArtifact> getCookieArtifacts(AbstractFile origFile, File tempFile) throws TskCoreException, IOException {
Collection<BlackboardArtifact> bbartifacts = null;
BinaryCookieReader reader = BinaryCookieReader.initalizeReader(tempFile);
if (reader != null) {
bbartifacts = new ArrayList<>();
Iterator<Cookie> iter = reader.iterator();
while (iter.hasNext()) {
Cookie cookie = iter.next();
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE);
bbart.addAttributes(createCookieAttributes(cookie.getURL(), cookie.getCreationDate(), cookie.getName(), cookie.getValue(), this.getName(), NetworkUtils.extractDomain(cookie.getURL())));
bbartifacts.add(bbart);
}
}
return bbartifacts;
}
/**
* Parses the plist object to find the bookmark child objects, then creates
* an artifact with the bookmark information.
*
* @param bbartifacts BlackboardArtifact list to add new the artifacts to
* @param origFile The origFile Bookmark.plist file from the case
* @param root NSDictionary object to parse
* @throws TskCoreException
*/
private void parseBookmarkDictionary(Collection<BlackboardArtifact> bbartifacts, AbstractFile origFile, NSDictionary root) throws TskCoreException {
if (root.containsKey(PLIST_KEY_CHILDREN)) {
NSArray children = (NSArray) root.objectForKey(PLIST_KEY_CHILDREN);
if (children != null) {
for (NSObject obj : children.getArray()) {
parseBookmarkDictionary(bbartifacts, origFile, (NSDictionary) obj);
}
}
} else if (root.containsKey(PLIST_KEY_URL)) {
String url = null;
String title = null;
NSString nsstr = (NSString) root.objectForKey(PLIST_KEY_URL);
if (nsstr != null) {
url = nsstr.toString();
}
NSDictionary dic = (NSDictionary) root.get(PLIST_KEY_URI);
nsstr = (NSString) root.objectForKey(PLIST_KEY_TITLE);
if (nsstr != null) {
title = ((NSString) dic.get(PLIST_KEY_TITLE)).toString();
}
if (url != null || title != null) {
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
bbart.addAttributes(createBookmarkAttributes(url, title, null, getName(), NetworkUtils.extractDomain(url)));
bbartifacts.add(bbart);
}
}
}
/**
* Parse the NSDictionary object that represents one download.
*
* @param origFile Download.plist file from the case
* @param entry One NSDictionary Object that represents one download
* instance
* @return a Blackboard Artifact for the download.
* @throws TskCoreException
*/
private BlackboardArtifact parseDownloadDictionary(Content dataSource, AbstractFile origFile, NSDictionary entry) throws TskCoreException {
String url = null;
String path = null;
Long time = null;
Long pathID = null;
NSString nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_URL);
if (nsstring != null) {
url = nsstring.toString();
}
nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_PATH);
if (nsstring != null) {
path = nsstring.toString();
pathID = Util.findID(dataSource, path);
}
NSDate date = (NSDate) entry.get(PLIST_KEY_DOWNLOAD_DATE);
if (date != null) {
time = date.getDate().getTime();
}
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD);
bbart.addAttributes(this.createDownloadAttributes(path, pathID, url, time, NetworkUtils.extractDomain(url), getName()));
return bbart;
}
}
@@ -164,31 +164,25 @@ class Firefox extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((url != null) ? url : ""))); //NON-NLS
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("url").toString() != null) ? EscapeUtil.decodeURL(result.get("url").toString()) : "")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("visit_date").toString())))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REFERRER,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("ref").toString() != null) ? result.get("ref").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
String domain = extractDomain(url);
if (domain != null && domain.isEmpty() == false) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"), domain)); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(), domain)); //NON-NLS
}
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY, historyFile, bbattributes);
@@ -263,29 +257,23 @@ class Firefox extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((url != null) ? url : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
if (Long.valueOf(result.get("dateAdded").toString()) > 0) { //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("dateAdded").toString())))); //NON-NLS
}
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
String domain = extractDomain(url);
if (domain != null && domain.isEmpty() == false) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
domain)); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(), domain)); //NON-NLS
}
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bookmarkFile, bbattributes);
@@ -368,38 +356,31 @@ class Firefox extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((host != null) ? host : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("lastAccessed").toString())))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
if (checkColumn == true) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
(Long.valueOf(result.get("creationTime").toString())))); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("creationTime").toString())))); //NON-NLS
}
String domain = extractDomain(host);
if (domain != null && domain.isEmpty() == false) {
domain = domain.replaceFirst("^\\.+(?!$)", "");
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"), domain));
RecentActivityExtracterModuleFactory.getModuleName(), domain));
}
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_COOKIE, cookiesFile, bbattributes);
@@ -487,13 +468,11 @@ class Firefox extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
source)); //NON-NLS
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : "")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("startTime").toString())))); //NON-NLS
String target = result.get("target").toString(); //NON-NLS
@@ -502,14 +481,12 @@ class Firefox extends Extract {
try {
String decodedTarget = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
decodedTarget));
long pathID = Util.findID(dataSource, decodedTarget);
if (pathID != -1) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
pathID));
}
} catch (UnsupportedEncodingException ex) {
@@ -519,14 +496,12 @@ class Firefox extends Extract {
}
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
String domain = extractDomain(source);
if (domain != null && domain.isEmpty() == false) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
domain)); //NON-NLS
}
@@ -614,8 +589,7 @@ class Firefox extends Extract {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
url)); //NON-NLS
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : "")));
//TODO Revisit usage of deprecated constructor as per TSK-583
@@ -626,14 +600,12 @@ class Firefox extends Extract {
try {
String decodedTarget = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
decodedTarget));
long pathID = Util.findID(dataSource, decodedTarget);
if (pathID != -1) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
pathID));
}
} catch (UnsupportedEncodingException ex) {
@@ -642,19 +614,15 @@ class Firefox extends Extract {
}
}
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
Long.valueOf(result.get("lastModified").toString()))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
RecentActivityExtracterModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
String domain = extractDomain(url);
if (domain != null && domain.isEmpty() == false) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
NbBundle.getMessage(this.getClass(),
"Firefox.parentModuleName.noSpace"),
domain)); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(), domain)); //NON-NLS
}
BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
@@ -754,26 +722,26 @@ class Firefox extends Extract {
}
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
fieldName)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
// Newer versions of firefox have additional columns
if (isFirefoxV64) {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
(Long.valueOf(result.get("firstUsed").toString()) / 1000000))); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("firstUsed").toString()) / 1000000))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
(Long.valueOf(result.get("lastUsed").toString()) / 1000000))); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(),
(Long.valueOf(result.get("lastUsed").toString()) / 1000000))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
(Integer.valueOf(result.get("timesUsed").toString())))); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(),
(Integer.valueOf(result.get("timesUsed").toString())))); //NON-NLS
}
// Add artifact
@@ -919,32 +887,32 @@ class Firefox extends Extract {
try {
Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME_PERSON,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
name)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
email)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
phoneNumber)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LOCATION,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
RecentActivityExtracterModuleFactory.getModuleName(),
mailingAddress)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
datetimeCreated)); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(),
datetimeCreated)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
datetimeLastUsed)); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(),
datetimeLastUsed)); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
timesUsed)); //NON-NLS
RecentActivityExtracterModuleFactory.getModuleName(),
timesUsed)); //NON-NLS
BlackboardArtifact bbart = profileFile.newArtifact(ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS);
@@ -23,6 +23,7 @@
package org.sleuthkit.autopsy.recentactivity;
import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
@@ -59,8 +60,10 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
this.context = context;
Extract iexplore;
Extract edge;
try {
iexplore = new ExtractIE();
edge = new ExtractEdge();
} catch (NoCurrentCaseException ex) {
throw new IngestModuleException(ex.getMessage(), ex);
}
@@ -72,10 +75,13 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
Extract SEUQA = new SearchEngineURLQueryAnalyzer();
Extract osExtract = new ExtractOs();
Extract dataSourceAnalyzer = new DataSourceUsageAnalyzer();
Extract safari = new ExtractSafari();
extractors.add(chrome);
extractors.add(firefox);
extractors.add(iexplore);
extractors.add(edge);
extractors.add(safari);
extractors.add(recentDocuments);
extractors.add(SEUQA); // this needs to run after the web browser modules
extractors.add(registry); // this should run after quicker modules like the browser modules and needs to run before the DataSourceUsageAnalyzer
@@ -85,6 +91,8 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
browserExtractors.add(chrome);
browserExtractors.add(firefox);
browserExtractors.add(iexplore);
browserExtractors.add(edge);
browserExtractors.add(safari);
for (Extract extractor : extractors) {
extractor.init();
@@ -227,4 +235,15 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
}
return tmpDir;
}
/**
* Get relative path for module output folder.
*
* @throws NoCurrentCaseException if there is no open case.
* @return the relative path of the module output folder
*/
static String getRelModuleOutputPath() throws NoCurrentCaseException {
return Paths.get(Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath(),
"RecentActivity").normalize().toString() ; //NON-NLS
}
}
+33
View File
@@ -0,0 +1,33 @@
[General]
AutoSizeColumnsOnTableChange=1
MarkOddEvenRows=0
ShowGridLines=0
SaveFilterIndex=0
ShowInfoTip=1
AutoDetectDateTime=1
ConvertGMTToLocalTime=0
BinaryURLAsString=0
DetectBinaryUTF16=0
DetectBinaryAscii=0
SaveFileEncoeding=0
AlignNumbersToRight=0
UseQuickFilter=0
QuickFilterString=
QuickFilterColumnsMode=1
QuickFilterFindMode=1
QuickFilterShowHide=1
TrayIcon=0
WinPos=2C 00 00 00 00 00 00 00 01 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 22 02 00 00 9D FC FF FF 2A 07 00 00 4A FF FF FF
Columns=
Sort=0
[RecentFiles]
0=
1=
2=
3=
4=
5=
6=
7=
8=
9=
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+392
View File
@@ -0,0 +1,392 @@
ESEDatabaseView v1.62
Copyright (c) 2013 - 2018 Nir Sofer
Web site: http://www.nirsoft.net
Description
===========
ESEDatabaseView is a simple utility that reads and displays the data
stored inside Extensible Storage Engine (ESE) database (Also known as
'Jet Blue' or .edb file). It displays a list of all tables available in
the opened database file, allows you to choose the desired table to view,
and then when you choose a table, it displays all records found in the
selected table. ESEDatabaseView also allows you to easily choose one or
more records, and then export them into
comma-delimited/tab-delimited/html/xml file, or copy the records to the
clipboard (Ctrl+C) and then paste them into Excel or other spreadsheet
application.
System Requirements
===================
This utility works on any version of Windows, starting from Windows 2000
and up to Windows 10. Both 32-bit and 64-bit systems are supported.
esent.dll (The dll file of Extensible Storage Engine) is not required to
read the database.
Versions History
================
* Version 1.62:
o Fixed to sort date/time columns properly.
* Version 1.61:
o Added 'Run As Administrator' option (Ctrl+F11).
* Version 1.60:
o Fixed bug: On some tables ESEDatabaseView failed to read properly
some of the fields.
o Added 'Detect Ascii Strings In Binary Data' option. When it's
turned on, ESEDatabaseView displays binary data as string if it
detects that the binary data is Ascii string. This option is useful
for cookies names and values (CookieEntryEx_XX tables) in the
database of MS-Edge browser (WebCacheV01.dat).
o Added 'Put Icon On Tray' option.
* Version 1.54:
o Added new quick filter options: 'Find records with all words
(space-delimited list)' and 'Find records with all strings
(comma-delimited list)'
o Added new quick filter combo-box: 'Show only items match the
filter' and 'Hide items that match the filter'.
* Version 1.53:
o Added 'Open spartan.edb Database' which automatically opens the
spartan.edb database of IE11. This file stores the Favorites of IE11
and the full path of this file is
%LOCALAPPDATA%\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\Micros
oftEdge\User\Default\DataStore\Data\nouser1\120712-0049\DBStore\spartan
.edb)
o Made the display of binary data a little faster.
* Version 1.52:
o Added 'Detect UTF-16 Strings In Binary Data'. When it's turned
on, ESEDatabaseView displays binary data as string if it detects that
the binary data is UTF-16 string. (e.g: 'Key' field in MSysLocales
table)
o Fixed bug: 'Copy Selected Items' worked improperly when setting
the 'Unicode/Ascii Save Mode' to 'Always UTF-8'.
* Version 1.51:
o Fixed the 'Show Binary URL As String' feature to work properly
when the URL string starts in different position.
* Version 1.50:
o Added 'Quick Filter' feature (View -> Use Quick Filter or
Ctrl+Q). When it's turned on, you can type a string in the text-box
added under the tables combo-box and ESEDatabaseView will instantly
filter the ESE database records, showing only lines that contain the
string you typed.
* Version 1.43:
o Added 'Save All Items' (Shift+Ctrl+S).
* Version 1.42:
o Fixed bug: ESEDatabaseView crashed when using the find option
while the last item was selected.
* Version 1.41:
o Added 'Align Numeric Columns To Right' option.
* Version 1.40:
o Fixed bug: On some databases/tables (like Recipient table in
store.vol or tbUpdateLocalizedProps table in DataStore.edb)
ESEDatabaseView omitted the first 4 characters of a string.
o Added 'Select All' and 'Deselect All' buttons to the 'Choose
Column' window.
* Version 1.37:
o You can now choose the desired encoding (ANSI, UTF-8, UTF-16) to
save the csv/xml/text/html files. (Under the Options menu)
* Version 1.36:
o Added 'New ESEDatabaseView Instance' under the File menu, for
opening a new window of ESEDatabaseView.
* Version 1.35:
o When 'Auto Detect 64-bit Date/Time Value' option is turned on,
ESEDatabaseView now detects the Modified field of tbFiles table
inside DataStore.edb
o The properties window is now resizable.
* Version 1.33:
o Fixed issue: ESEDatabaseView failed to display dates earlier than
01/01/1986.
* Version 1.32:
o Added 'Show Binary URL As String'. When it's turned on, the Urls
field of tbFiles table inside DataStore.edb is displayed as string.
* Version 1.31:
o Fixed the 'Open Locked IE10/IE11 Database' option to work with
the latest build of Windows 10/IE11.
* Version 1.30:
o Added option to export to JSON file.
o Fixed bug: ESEDatabaseView failed to load records on some
tables/databases.
o Fixed bug: ESEDatabaseView crashed when trying to load a very
large binary value.
* Version 1.25:
o Fixed bug: ESEDatabaseView displayed incorrect values in
date/time fields.
* Version 1.24:
o Fixed bug: ESEDatabaseView failed to remember the last
size/position of the main window if it was not located in the primary
monitor.
* Version 1.23:
o You can now specify an empty string ("") in order to send the
data to stdout, for example:
ESEDatabaseView.exe /table "c:\temp\contacts.edb"
"SimpleContact-v081111-0122-1303" /scomma ""
* Version 1.22:
o Added 'Copy Sorted Column Data' option, which copies to the
clipboard the text of all selected items, but only the column that is
currently sorted.
* Version 1.21:
o Fixed to find the correct item when typing the string you want to
search into the main List View.
* Version 1.20:
o Added option to export all tables from command-line (Each table
in a separated file), for example:
ESEDatabaseView.exe /table "C:\temp\WebCacheV01.dat" * /scomma
"C:\Temp\export\webcache_*.csv"
* Version 1.18:
o Fixed to display local date/time values according to daylight
saving time settings.
* Version 1.17:
o Added 'Open SoftwareDistribution Database' option, which opens
the database file containing information about installed Winodws
updates (C:\WINDOWS\SoftwareDistribution\DataStore\DataStore.edb)
* Version 1.16:
o Added 'Clear Recent Files List' option.
* Version 1.15:
o Added 'Open Recent File' menu, which allows you to easily open
the last 10 database files that you previously opened.
* Version 1.10:
o Added 'Open Locked IE10 Database' option, which copies the locked
database file of Internet Explorer 10 (WebCacheV01.dat or
WebCacheV24.dat) into a temporary filename, and then opens the
temporary filename in ESEDatabaseView. You can use this option to
easily view the cache/history/cookies information stored by IE10.
* Version 1.07:
o Fixed the flickering appeared while scrolling the database
records.
* Version 1.06:
o Added 'Convert Date/Time From GMT To Local Time' option.
* Version 1.05:
o Added command-line support
* Version 1.00 - First release.
Known Limitations
=================
* Currently, ESEDatabaseView is somewhat a Beta version. It generally
reads the ESE databases properly, but in tables with complex data
structure, you may experience the following problems:
o Some fields in some of the records may display incorrect value or
display empty string while it actually contains some data.
o ESEDatabaseView may hang/stop responding when loading a table
with large amount of data.
Example for ESE Databases
=========================
ESE Databases are used by many Microsoft products. Usually, the file
extension of ESE database is .edb, but in some products the file
extension is different.
Here's some examples for .edb files used by Microsoft products:
* contacts.edb - Stores contacts information in Microsoft live products.
* WLCalendarStore.edb - Stores calendar information in Microsoft
Windows Live Mail.
* Mail.MSMessageStore - Stores messages information in Microsoft
Windows Live Mail.
* WebCacheV24.dat and WebCacheV01.dat - Stores cache, history, and
cookies information in Internet Explorer 10.
* Mailbox Database.edb and Public Folder Database.edb - Stores mail
data in Microsoft Exchange Server.
* Windows.edb - Stores index information (for Windows search) by
Windows operating system.
* DataStore.edb - Stores Windows updates information (Located under
C:\windows\SoftwareDistribution\DataStore )
* spartan.edb - Stores the Favorites of Internet Explorer 10/11.
(Stored under
%LOCALAPPDATA%\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\Microsof
tEdge\User\Default\DataStore\Data\nouser1\120712-0049)
Start Using ESEDatabaseView
===========================
ESEDatabaseView doesn't require any installation process or additional
dll files. In order to start using it, simple run the executable file
(ESEDatabaseView.exe) and then use the 'Open ESE Database File' option
(Ctrl+O) to open the desired .edb file. You can also drag the database
file from Explorer window into the window of ESEDatabaseView.
After opening the desired database file, the combo-box located below the
toolbar is filled with the list of all tables found in the database. By
default, MSysObjects table is selected and displayed in the main window
of ESEDatabaseView. MSysObjects is a system table available in all ESE
databases which provides the list of all tables and fields stored in the
database.
In order to view the content of another table, simply choose the desired
table in the combo-box located below the toolbar.
By default, the table is sorted according to the first column, but you
can sort by another field, simply by clicking the desired column header.
The sorting is made according to the type of the field, so... for
example, if the field is an integer value, then ESEDatabaseView will use
a numeric comparison in order to sort the column properly.
You can select one or more records (or select all records with Ctrl+A)
and then export them into text/csv/tab-delimited/html/xml file, by using
the 'Save Selected Items' option. You can also copy the selected records
into the clipboard (Ctrl+C) and then paste them (Ctrl+V) into Excel or
other spreadsheet application.
Command-Line Options
====================
/table <Database Filename> <Table Name>
Specifies the database and table to open. If the <Table Name> is "*" ,
all tables will be exported, each table in a separated file.
/stext <Filename>
Save the database table into a regular text file.
/stab <Filename>
Save the database table into a tab-delimited text file.
/scomma <Filename>
Save the database table into a comma-delimited text file (csv).
/stabular <Filename>
Save the database table into a tabular text file.
/shtml <Filename>
Save the database table into HTML file (Horizontal).
/sverhtml <Filename>
Save the database table into HTML file (Vertical).
/sxml <Filename>
Save the database table into XML file.
/sjson <Filename>
Save the database table into JSON file.
/sort <column>
This command-line option can be used with other save options for sorting
by the desired column. The <column> parameter can specify the column
index (0 for the first column, 1 for the second column, and so on) or the
name of the column, like "StatusState" and "CalculatedBuddyIdentifier".
You can specify the '~' prefix character (e.g:
"~CalculatedBuddyIdentifier") if you want to sort in descending order.
You can put multiple /sort in the command-line if you want to sort by
multiple columns.
Examples:
ESEDatabaseView.exe /table "c:\temp\contacts.edb"
"SimpleContact-v081111-0122-1303" /scomma c:\temp\1.csv
ESEDatabaseView.exe /table "c:\files\contacts.edb"
"SimpleContact-v081111-0777-1111" /shtml c:\files\1.html /Sort
"CalculatedBuddyIdentifier"
Example for exporting all tables: (Each table is exported into a
separated file)
ESEDatabaseView.exe /table "C:\temp\WebCacheV01.dat" * /scomma
"C:\Temp\export\webcache_*.csv"
The table name will replace the '*' character specified in the export
filename. For example, if the table name is Container1, then the exported
filename will be webcache_Container1.csv
Translating ESEDatabaseView to other languages
==============================================
In order to translate ESEDatabaseView to other language, follow the
instructions below:
1. Run ESEDatabaseView with /savelangfile parameter:
ESEDatabaseView.exe /savelangfile
A file named ESEDatabaseView_lng.ini will be created in the folder of
ESEDatabaseView utility.
2. Open the created language file in Notepad or in any other text
editor.
3. Translate all string entries to the desired language. Optionally,
you can also add your name and/or a link to your Web site.
(TranslatorName and TranslatorURL values) If you add this information,
it'll be used in the 'About' window.
4. After you finish the translation, Run ESEDatabaseView, and all
translated strings will be loaded from the language file.
If you want to run ESEDatabaseView without the translation, simply
rename the language file, or move it to another folder.
License
=======
This utility is released as freeware. You are allowed to freely
distribute this utility via floppy disk, CD-ROM, Internet, or in any
other way, as long as you don't charge anything for this and you don't
sell it or distribute it as a part of commercial product. If you
distribute this utility, you must include all files in the distribution
package, without any modification !
Disclaimer
==========
The software is provided "AS IS" without any warranty, either expressed
or implied, including, but not limited to, the implied warranties of
merchantability and fitness for a particular purpose. The author will not
be liable for any special, incidental, consequential or indirect damages
due to loss of data or any other reason.
Feedback
========
If you have any problem, suggestion, comment, or you found a bug in my
utility, you can send a message to nirsofer@yahoo.com
@@ -1,6 +1,6 @@
MboxParser.handleAttch.noOpenCase.errMsg=Exception while getting open case.
OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Email Parser ingest module.\n\nThe module extracts MBOX and PST e-mail files and posts the results to the blackboard. \nIt knows about the Thunderbird folder structure for MBOX files.
OpenIDE-Module-Long-Description=Email Parser ingest module.\n\nThe module extracts MBOX and PST e-mail files and posts the results to the blackboard.\nIt knows about the Thunderbird folder structure for MBOX files.
OpenIDE-Module-Name=Email Parser
OpenIDE-Module-Short-Description=Parses MBOX and PST files
MboxParser.parse.errMsg.failedToReadFile=Failed to read mbox file from disk.