mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-19 11:07:43 +00:00
Merge remote-tracking branch 'upstream/develop' into 3284_DiskImageBlockSize
This commit is contained in:
commit
eeec005160
@ -23,5 +23,7 @@
|
|||||||
<dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
|
<dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
|
||||||
<dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
|
<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="org.jsoup" name="jsoup" rev="1.10.3"/>
|
||||||
|
<dependency conf="core->default" org="com.googlecode.plist" name="dd-plist" rev="1.20"/>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</ivy-module>
|
</ivy-module>
|
||||||
|
@ -3,6 +3,7 @@ file.reference.c3p0-0.9.5.jar=release/modules/ext/c3p0-0.9.5.jar
|
|||||||
file.reference.commons-compress-1.14.jar=release/modules/ext/commons-compress-1.14.jar
|
file.reference.commons-compress-1.14.jar=release/modules/ext/commons-compress-1.14.jar
|
||||||
file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1.1.jar
|
file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1.1.jar
|
||||||
file.reference.commons-pool2-2.4.2.jar=release\\modules\\ext\\commons-pool2-2.4.2.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.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar
|
file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar
|
||||||
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
|
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
|
||||||
file.reference.jsoup-1.10.3.jar=release/modules/ext/jsoup-1.10.3.jar
|
file.reference.jsoup-1.10.3.jar=release/modules/ext/jsoup-1.10.3.jar
|
||||||
|
@ -405,6 +405,10 @@
|
|||||||
<runtime-relative-path>ext/Rejistry-1.0-SNAPSHOT.jar</runtime-relative-path>
|
<runtime-relative-path>ext/Rejistry-1.0-SNAPSHOT.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/Rejistry-1.0-SNAPSHOT.jar</binary-origin>
|
<binary-origin>release/modules/ext/Rejistry-1.0-SNAPSHOT.jar</binary-origin>
|
||||||
</class-path-extension>
|
</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>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/sevenzipjbinding-AllPlatforms.jar</runtime-relative-path>
|
<runtime-relative-path>ext/sevenzipjbinding-AllPlatforms.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/sevenzipjbinding-AllPlatforms.jar</binary-origin>
|
<binary-origin>release/modules/ext/sevenzipjbinding-AllPlatforms.jar</binary-origin>
|
||||||
|
@ -43,3 +43,4 @@ SQLiteViewer.currPageLabel.text=x
|
|||||||
SQLiteViewer.jLabel2.text=Page
|
SQLiteViewer.jLabel2.text=Page
|
||||||
SQLiteViewer.numEntriesField.text=num Entries
|
SQLiteViewer.numEntriesField.text=num Entries
|
||||||
SQLiteViewer.jLabel1.text=Table
|
SQLiteViewer.jLabel1.text=Table
|
||||||
|
PListViewer.exportButton.text=Export
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2018 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.Font;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
import org.openide.nodes.Node;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Cell renderer to display a SQLite column cell as readable Epoch date/time
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class EpochTimeCellRenderer extends DefaultTableCellRenderer {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName());
|
||||||
|
|
||||||
|
private static final String FORMAT_STRING = "yyyy/MM/dd HH:mm:ss"; //NON-NLS
|
||||||
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(FORMAT_STRING);
|
||||||
|
|
||||||
|
private final boolean renderAsEpoch;
|
||||||
|
|
||||||
|
EpochTimeCellRenderer(boolean renderAsEpoch) {
|
||||||
|
this.renderAsEpoch = renderAsEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
|
||||||
|
// Set the forceground/background so its obvious when the cell is selected.
|
||||||
|
if (isSelected) {
|
||||||
|
super.setForeground(table.getSelectionForeground());
|
||||||
|
super.setBackground(table.getSelectionBackground());
|
||||||
|
} else {
|
||||||
|
super.setForeground( table.getForeground());
|
||||||
|
super.setBackground( table.getBackground());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
setText("");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String textStr = "";
|
||||||
|
try {
|
||||||
|
// get the col property value
|
||||||
|
if (value instanceof Node.Property<?>) {
|
||||||
|
Node.Property<?> nodeProp = (Node.Property)value;
|
||||||
|
textStr = nodeProp.getValue().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderAsEpoch) {
|
||||||
|
long epochTime = Long.parseUnsignedLong(textStr);
|
||||||
|
if (epochTime > 0 ) {
|
||||||
|
Font font = getFont();
|
||||||
|
setFont(font.deriveFont(font.getStyle() | Font.ITALIC));
|
||||||
|
setText(DATE_FORMAT.format(new Date(epochTime)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setText(textStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // Display raw data
|
||||||
|
setText(textStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
setText(textStr);
|
||||||
|
LOGGER.log(Level.INFO, "Error converting column value to number.", e); //NON-NLS
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||||
|
setText("");
|
||||||
|
LOGGER.log(Level.SEVERE, "Error in getting column value.", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isRenderingAsEpoch() {
|
||||||
|
return this.renderAsEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -46,7 +46,8 @@ public class FileViewer extends javax.swing.JPanel implements DataContentViewer
|
|||||||
// TBD: This hardcoded list of viewers should be replaced with a dynamic lookup
|
// TBD: This hardcoded list of viewers should be replaced with a dynamic lookup
|
||||||
private static final FileTypeViewer[] KNOWN_VIEWERS = new FileTypeViewer[]{
|
private static final FileTypeViewer[] KNOWN_VIEWERS = new FileTypeViewer[]{
|
||||||
// new JPEGViewerDummy(), // this if for testing only
|
// new JPEGViewerDummy(), // this if for testing only
|
||||||
new SQLiteViewer()
|
new SQLiteViewer(),
|
||||||
|
new PListViewer()
|
||||||
};
|
};
|
||||||
|
|
||||||
private FileTypeViewer lastViewer;
|
private FileTypeViewer lastViewer;
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2018 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.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.openide.nodes.AbstractNode;
|
||||||
|
import org.openide.nodes.ChildFactory;
|
||||||
|
import org.openide.nodes.Children;
|
||||||
|
import org.openide.nodes.Node;
|
||||||
|
import org.openide.nodes.Sheet;
|
||||||
|
import org.sleuthkit.autopsy.contentviewers.PListViewer.PropKeyValue;
|
||||||
|
import org.sleuthkit.autopsy.contentviewers.PListViewer.PropertyType;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class to create nodes for Plist table view
|
||||||
|
*/
|
||||||
|
public class PListRowFactory extends ChildFactory<Integer> {
|
||||||
|
|
||||||
|
private final List<PropKeyValue> rows;
|
||||||
|
|
||||||
|
PListRowFactory(final List<PropKeyValue> rows) {
|
||||||
|
this.rows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates keys
|
||||||
|
*
|
||||||
|
* @param keys
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean createKeys(final List<Integer> keys) {
|
||||||
|
if (rows != null) {
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
keys.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates node for the given key
|
||||||
|
* @param key
|
||||||
|
* @return node for the given key, null if the key is invalid or node doesn't exist
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Node createNodeForKey(final Integer key) {
|
||||||
|
if (Objects.isNull(rows) || rows.isEmpty() || key >= rows.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new PListNode(rows.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node for a Plist key
|
||||||
|
*/
|
||||||
|
class PListNode extends AbstractNode {
|
||||||
|
|
||||||
|
private final PropKeyValue propKeyVal;
|
||||||
|
|
||||||
|
PListNode(final PropKeyValue propKeyVal) {
|
||||||
|
|
||||||
|
super(propKeyVal.getChildren() == null ? Children.LEAF : new PListNodeChildren(propKeyVal.getChildren()));
|
||||||
|
|
||||||
|
this.propKeyVal = propKeyVal;
|
||||||
|
|
||||||
|
super.setName(propKeyVal.getKey());
|
||||||
|
super.setDisplayName(propKeyVal.getKey());
|
||||||
|
if (propKeyVal.getType() == PropertyType.ARRAY) {
|
||||||
|
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keychain-16.png");
|
||||||
|
} else if (propKeyVal.getType() == PropertyType.DICTIONARY) {
|
||||||
|
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keys-dict-16.png");
|
||||||
|
} else {
|
||||||
|
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/key-16.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates property sheet for the node
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Sheet createSheet() {
|
||||||
|
|
||||||
|
final Sheet sheet = super.createSheet();
|
||||||
|
Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
|
||||||
|
if (properties == null) {
|
||||||
|
properties = Sheet.createPropertiesSet();
|
||||||
|
sheet.put(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
properties.put(new NodeProperty<>(Bundle.PListNode_TypeCol(),
|
||||||
|
Bundle.PListNode_TypeCol(),
|
||||||
|
Bundle.PListNode_TypeCol(),
|
||||||
|
propKeyVal.getType().name())); // NON-NLS
|
||||||
|
|
||||||
|
properties.put(new NodeProperty<>(Bundle.PListNode_ValueCol(),
|
||||||
|
Bundle.PListNode_ValueCol(),
|
||||||
|
Bundle.PListNode_ValueCol(),
|
||||||
|
(propKeyVal.getChildren() == null) ? propKeyVal.getValue() : "")); // NON-NLS
|
||||||
|
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates children nodes for a compound PList key
|
||||||
|
*/
|
||||||
|
private static class PListNodeChildren extends Children.Keys<PropKeyValue> {
|
||||||
|
|
||||||
|
private final List<PropKeyValue> children;
|
||||||
|
|
||||||
|
PListNodeChildren(final PropKeyValue... children) {
|
||||||
|
super();
|
||||||
|
this.children = Arrays.asList(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addNotify() {
|
||||||
|
super.setKeys(this.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Node[] createNodes(final PropKeyValue propKeyVal) {
|
||||||
|
return new Node[]{new PListNode(propKeyVal)};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<Form version="1.4" 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">
|
||||||
|
<Component id="hdrPanel" max="32767" attributes="0"/>
|
||||||
|
<EmptySpace min="5" pref="5" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
<Component id="jPanel1" max="32767" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
</DimensionLayout>
|
||||||
|
<DimensionLayout dim="1">
|
||||||
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
|
<Group type="102" attributes="0">
|
||||||
|
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
|
||||||
|
<Component id="hdrPanel" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||||
|
<Component id="jPanel1" pref="249" max="32767" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</DimensionLayout>
|
||||||
|
</Layout>
|
||||||
|
<SubComponents>
|
||||||
|
<Container class="javax.swing.JPanel" name="jPanel1">
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||||
|
<SubComponents>
|
||||||
|
<Container class="javax.swing.JScrollPane" name="plistTableScrollPane">
|
||||||
|
<Properties>
|
||||||
|
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||||
|
<Border info="null"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
|
||||||
|
<Property name="verticalScrollBarPolicy" type="int" value="21"/>
|
||||||
|
</Properties>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||||
|
<BorderConstraints direction="Center"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||||
|
</Container>
|
||||||
|
</SubComponents>
|
||||||
|
</Container>
|
||||||
|
<Container class="javax.swing.JPanel" name="hdrPanel">
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<DimensionLayout dim="0">
|
||||||
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
|
<Group type="102" alignment="1" attributes="0">
|
||||||
|
<EmptySpace pref="320" max="32767" attributes="0"/>
|
||||||
|
<Component id="exportButton" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</DimensionLayout>
|
||||||
|
<DimensionLayout dim="1">
|
||||||
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
|
<Group type="102" alignment="1" attributes="0">
|
||||||
|
<EmptySpace min="0" pref="6" max="32767" attributes="0"/>
|
||||||
|
<Component id="exportButton" min="-2" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</DimensionLayout>
|
||||||
|
</Layout>
|
||||||
|
<SubComponents>
|
||||||
|
<Component class="javax.swing.JButton" name="exportButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="PListViewer.exportButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="exportButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</Component>
|
||||||
|
</SubComponents>
|
||||||
|
</Container>
|
||||||
|
</SubComponents>
|
||||||
|
</Form>
|
498
Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java
Normal file
498
Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2018 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.util.List;
|
||||||
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import com.dd.plist.NSDictionary;
|
||||||
|
import com.dd.plist.PropertyListParser;
|
||||||
|
import com.dd.plist.NSObject;
|
||||||
|
import com.dd.plist.NSArray;
|
||||||
|
import com.dd.plist.NSDate;
|
||||||
|
import com.dd.plist.NSString;
|
||||||
|
import com.dd.plist.NSNumber;
|
||||||
|
import com.dd.plist.NSData;
|
||||||
|
import com.dd.plist.PropertyListFormatException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.ListSelectionModel;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
import javax.swing.table.TableCellRenderer;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import org.netbeans.swing.outline.DefaultOutlineModel;
|
||||||
|
import org.netbeans.swing.outline.Outline;
|
||||||
|
import org.openide.explorer.ExplorerManager;
|
||||||
|
import org.openide.nodes.AbstractNode;
|
||||||
|
import org.openide.nodes.Children;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PListViewer - a file viewer for binary plist files.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class PListViewer extends javax.swing.JPanel implements FileTypeViewer, ExplorerManager.Provider {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final String[] MIMETYPES = new String[]{"application/x-bplist"};
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(PListViewer.class.getName());
|
||||||
|
|
||||||
|
private final org.openide.explorer.view.OutlineView outlineView;
|
||||||
|
private final Outline outline;
|
||||||
|
private ExplorerManager explorerManager;
|
||||||
|
|
||||||
|
private NSDictionary rootDict;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new form PListViewer
|
||||||
|
*/
|
||||||
|
public PListViewer() {
|
||||||
|
|
||||||
|
// Create an Outlineview and add to the panel
|
||||||
|
outlineView = new org.openide.explorer.view.OutlineView();
|
||||||
|
|
||||||
|
initComponents();
|
||||||
|
|
||||||
|
outline = outlineView.getOutline();
|
||||||
|
|
||||||
|
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel("Key");
|
||||||
|
|
||||||
|
outlineView.setPropertyColumns(
|
||||||
|
"Type", Bundle.PListNode_TypeCol(),
|
||||||
|
"Value", Bundle.PListNode_ValueCol());
|
||||||
|
|
||||||
|
customize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({"PListNode.KeyCol=Key",
|
||||||
|
"PListNode.TypeCol=Type",
|
||||||
|
"PListNode.ValueCol=Value"})
|
||||||
|
|
||||||
|
private void customize() {
|
||||||
|
|
||||||
|
outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
|
||||||
|
outline.setRootVisible(false);
|
||||||
|
if (null == explorerManager) {
|
||||||
|
explorerManager = new ExplorerManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
//outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||||
|
plistTableScrollPane.setViewportView(outlineView);
|
||||||
|
|
||||||
|
outline.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
|
||||||
|
|
||||||
|
this.setVisible(true);
|
||||||
|
outline.setRowSelectionAllowed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
|
||||||
|
jPanel1 = new javax.swing.JPanel();
|
||||||
|
plistTableScrollPane = new javax.swing.JScrollPane();
|
||||||
|
hdrPanel = new javax.swing.JPanel();
|
||||||
|
exportButton = new javax.swing.JButton();
|
||||||
|
|
||||||
|
jPanel1.setLayout(new java.awt.BorderLayout());
|
||||||
|
|
||||||
|
plistTableScrollPane.setBorder(null);
|
||||||
|
plistTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
plistTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
|
||||||
|
jPanel1.add(plistTableScrollPane, java.awt.BorderLayout.CENTER);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(exportButton, org.openide.util.NbBundle.getMessage(PListViewer.class, "PListViewer.exportButton.text")); // NOI18N
|
||||||
|
exportButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
exportButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
javax.swing.GroupLayout hdrPanelLayout = new javax.swing.GroupLayout(hdrPanel);
|
||||||
|
hdrPanel.setLayout(hdrPanelLayout);
|
||||||
|
hdrPanelLayout.setHorizontalGroup(
|
||||||
|
hdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, hdrPanelLayout.createSequentialGroup()
|
||||||
|
.addContainerGap(320, Short.MAX_VALUE)
|
||||||
|
.addComponent(exportButton)
|
||||||
|
.addContainerGap())
|
||||||
|
);
|
||||||
|
hdrPanelLayout.setVerticalGroup(
|
||||||
|
hdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, hdrPanelLayout.createSequentialGroup()
|
||||||
|
.addGap(0, 6, Short.MAX_VALUE)
|
||||||
|
.addComponent(exportButton))
|
||||||
|
);
|
||||||
|
|
||||||
|
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||||
|
this.setLayout(layout);
|
||||||
|
layout.setHorizontalGroup(
|
||||||
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addComponent(hdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
.addGap(5, 5, 5))
|
||||||
|
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
);
|
||||||
|
layout.setVerticalGroup(
|
||||||
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addGap(3, 3, 3)
|
||||||
|
.addComponent(hdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||||
|
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 249, Short.MAX_VALUE))
|
||||||
|
);
|
||||||
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
|
@NbBundle.Messages({"PListViewer.ExportSuccess.message=Plist file exported successfully",
|
||||||
|
"PListViewer.ExportFailed.message=Plist file export failed.",})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the Export button pressed action
|
||||||
|
*/
|
||||||
|
private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportButtonActionPerformed
|
||||||
|
|
||||||
|
final JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setCurrentDirectory(new File(Case.getCurrentCase().getExportDirectory()));
|
||||||
|
fileChooser.setFileFilter(new FileNameExtensionFilter("XML file", "xml"));
|
||||||
|
|
||||||
|
final int returnVal = fileChooser.showSaveDialog(this);
|
||||||
|
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||||
|
|
||||||
|
File selectedFile = fileChooser.getSelectedFile();
|
||||||
|
if (!selectedFile.getName().endsWith(".xml")) { // NON-NLS
|
||||||
|
selectedFile = new File(selectedFile.toString() + ".xml"); // NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Save the propery list as XML
|
||||||
|
PropertyListParser.saveAsXML(this.rootDict, selectedFile);
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
String.format("Plist file exported successfully to %s ", selectedFile.getName()),
|
||||||
|
Bundle.PListViewer_ExportSuccess_message(),
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
String.format("Failed to export plist file to %s ", selectedFile.getName()),
|
||||||
|
Bundle.PListViewer_ExportFailed_message(),
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
|
||||||
|
LOGGER.log(Level.SEVERE, "Error exporting plist to XML file " + selectedFile.getName(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}//GEN-LAST:event_exportButtonActionPerformed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns mime types supported by this viewer
|
||||||
|
*
|
||||||
|
* @return list of supported mime types
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<String> getSupportedMIMETypes() {
|
||||||
|
return Arrays.asList(MIMETYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the file to be displayed in the viewer
|
||||||
|
*
|
||||||
|
* @param file file to display
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFile(final AbstractFile file) {
|
||||||
|
processPlist(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the viewer component
|
||||||
|
*
|
||||||
|
* @return the viewer component
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Component getComponent() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the viewer component
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void resetComponent() {
|
||||||
|
rootDict = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the given Plist file
|
||||||
|
*
|
||||||
|
* @param plistFile -
|
||||||
|
*
|
||||||
|
* @return none
|
||||||
|
*/
|
||||||
|
private void processPlist(final AbstractFile plistFile) {
|
||||||
|
|
||||||
|
final byte[] plistFileBuf = new byte[(int) plistFile.getSize()];
|
||||||
|
try {
|
||||||
|
plistFile.read(plistFileBuf, 0, plistFile.getSize());
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Error reading bytes of plist file.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PropKeyValue> plist;
|
||||||
|
try {
|
||||||
|
plist = parsePList(plistFileBuf);
|
||||||
|
new SwingWorker<Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() {
|
||||||
|
setupTable(plist);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
super.done();
|
||||||
|
setColumnWidths();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
} catch (IOException | PropertyListFormatException | ParseException | ParserConfigurationException | SAXException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, String.format("Error parsing plist for file (obj_id = %d)", plistFile.getId()), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the columns in the display table
|
||||||
|
*
|
||||||
|
* @param tableRows
|
||||||
|
*/
|
||||||
|
private void setupTable(final List<PropKeyValue> tableRows) {
|
||||||
|
explorerManager.setRootContext(new AbstractNode(Children.create(new PListRowFactory(tableRows), true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the column widths
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private void setColumnWidths() {
|
||||||
|
final int margin = 4;
|
||||||
|
final int padding = 8;
|
||||||
|
|
||||||
|
// find the maximum width needed to fit the values for the first N rows, at most
|
||||||
|
final int rows = Math.min(20, outline.getRowCount());
|
||||||
|
for (int col = 0; col < outline.getColumnCount(); col++) {
|
||||||
|
final int columnWidthLimit = 2000;
|
||||||
|
int columnWidth = 0;
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
final TableCellRenderer renderer = outline.getCellRenderer(row, col);
|
||||||
|
final Component comp = outline.prepareRenderer(renderer, row, col);
|
||||||
|
|
||||||
|
columnWidth = Math.max(comp.getPreferredSize().width, columnWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
columnWidth += 2 * margin + padding; // add margin and regular padding
|
||||||
|
columnWidth = Math.min(columnWidth, columnWidthLimit);
|
||||||
|
outline.getColumnModel().getColumn(col).setPreferredWidth(columnWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given plist key/value
|
||||||
|
*/
|
||||||
|
@NbBundle.Messages({"PListViewer.DataType.message=Binary Data value not shown"})
|
||||||
|
private PropKeyValue parseProperty(final String key, final NSObject value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
} else if (value instanceof NSString) {
|
||||||
|
return new PropKeyValue(key, PropertyType.STRING, value.toString());
|
||||||
|
} else if (value instanceof NSNumber) {
|
||||||
|
final NSNumber number = (NSNumber) value;
|
||||||
|
if (number.isInteger()) {
|
||||||
|
return new PropKeyValue(key, PropertyType.NUMBER, number.longValue());
|
||||||
|
} else if (number.isBoolean()) {
|
||||||
|
return new PropKeyValue(key, PropertyType.BOOLEAN, number.boolValue());
|
||||||
|
} else {
|
||||||
|
return new PropKeyValue(key, PropertyType.NUMBER, number.floatValue());
|
||||||
|
}
|
||||||
|
} else if (value instanceof NSDate) {
|
||||||
|
final NSDate date = (NSDate) value;
|
||||||
|
return new PropKeyValue(key, PropertyType.DATE, date.toString());
|
||||||
|
} else if (value instanceof NSData) {
|
||||||
|
return new PropKeyValue(key, PropertyType.DATA, Bundle.PListViewer_DataType_message());
|
||||||
|
} else if (value instanceof NSArray) {
|
||||||
|
final List<PropKeyValue> children = new ArrayList<>();
|
||||||
|
final NSArray array = (NSArray) value;
|
||||||
|
|
||||||
|
final PropKeyValue pkv = new PropKeyValue(key, PropertyType.ARRAY, array);
|
||||||
|
for (int i = 0; i < array.count(); i++) {
|
||||||
|
children.add(parseProperty("", array.objectAtIndex(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pkv.setChildren(children.toArray(new PropKeyValue[children.size()]));
|
||||||
|
return pkv;
|
||||||
|
} else if (value instanceof NSDictionary) {
|
||||||
|
final List<PropKeyValue> children = new ArrayList<>();
|
||||||
|
final NSDictionary dict = (NSDictionary) value;
|
||||||
|
|
||||||
|
final PropKeyValue pkv = new PropKeyValue(key, PropertyType.DICTIONARY, dict);
|
||||||
|
for (final String key2 : ((NSDictionary) value).allKeys()) {
|
||||||
|
final NSObject obj = ((NSDictionary) value).objectForKey(key2);
|
||||||
|
children.add(parseProperty(key2, obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
pkv.setChildren(children.toArray(new PropKeyValue[children.size()]));
|
||||||
|
return pkv;
|
||||||
|
} else {
|
||||||
|
LOGGER.log(Level.SEVERE, "Can''t parse Plist for key = {0} value of type {1}", new Object[]{key, value.getClass()});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses given binary stream and extracts Plist key/value
|
||||||
|
*
|
||||||
|
* @param plistbytes
|
||||||
|
*
|
||||||
|
* @return list of PropKeyValue
|
||||||
|
*/
|
||||||
|
private List<PropKeyValue> parsePList(final byte[] plistbytes) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
|
||||||
|
|
||||||
|
final List<PropKeyValue> plist = new ArrayList<>();
|
||||||
|
rootDict = (NSDictionary) PropertyListParser.parse(plistbytes);
|
||||||
|
|
||||||
|
final String[] keys = rootDict.allKeys();
|
||||||
|
for (final String key : keys) {
|
||||||
|
final PropKeyValue pkv = parseProperty(key, rootDict.objectForKey(key));
|
||||||
|
if (null != pkv) {
|
||||||
|
plist.add(pkv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return plist;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExplorerManager getExplorerManager() {
|
||||||
|
return explorerManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plist property type
|
||||||
|
*/
|
||||||
|
enum PropertyType {
|
||||||
|
STRING,
|
||||||
|
NUMBER,
|
||||||
|
BOOLEAN,
|
||||||
|
DATE,
|
||||||
|
DATA,
|
||||||
|
ARRAY,
|
||||||
|
DICTIONARY
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates a Plist property
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final static class PropKeyValue {
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
private final PropertyType type;
|
||||||
|
private final Object value;
|
||||||
|
|
||||||
|
private PropKeyValue[] children;
|
||||||
|
|
||||||
|
PropKeyValue(String key, PropertyType type, Object value) {
|
||||||
|
this.key = key;
|
||||||
|
this.type = type;
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
this.children = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor
|
||||||
|
*/
|
||||||
|
PropKeyValue(PropKeyValue other) {
|
||||||
|
this.key = other.getKey();
|
||||||
|
this.type = other.getType();
|
||||||
|
this.value = other.getValue();
|
||||||
|
|
||||||
|
this.setChildren(other.getChildren());
|
||||||
|
}
|
||||||
|
|
||||||
|
String getKey() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyType getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of children, if any.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
PropKeyValue[] getChildren() {
|
||||||
|
if (children == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a copy
|
||||||
|
return Arrays.stream(children)
|
||||||
|
.map(child -> new PropKeyValue(child))
|
||||||
|
.toArray(PropKeyValue[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setChildren(final PropKeyValue... children) {
|
||||||
|
this.children = Arrays.stream(children)
|
||||||
|
.map(child -> new PropKeyValue(child))
|
||||||
|
.toArray(PropKeyValue[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
private javax.swing.JButton exportButton;
|
||||||
|
private javax.swing.JPanel hdrPanel;
|
||||||
|
private javax.swing.JPanel jPanel1;
|
||||||
|
private javax.swing.JScrollPane plistTableScrollPane;
|
||||||
|
// End of variables declaration//GEN-END:variables
|
||||||
|
}
|
@ -18,9 +18,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.contentviewers;
|
package org.sleuthkit.autopsy.contentviewers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import javax.swing.Action;
|
||||||
import org.openide.nodes.AbstractNode;
|
import org.openide.nodes.AbstractNode;
|
||||||
import org.openide.nodes.ChildFactory;
|
import org.openide.nodes.ChildFactory;
|
||||||
import org.openide.nodes.Children;
|
import org.openide.nodes.Children;
|
||||||
@ -28,12 +31,17 @@ import org.openide.nodes.Node;
|
|||||||
import org.openide.nodes.Sheet;
|
import org.openide.nodes.Sheet;
|
||||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class to generate nodes for SQLite table rows
|
||||||
|
*/
|
||||||
public class SQLiteTableRowFactory extends ChildFactory<Integer> {
|
public class SQLiteTableRowFactory extends ChildFactory<Integer> {
|
||||||
|
|
||||||
private final List<Map<String, Object>> rows;
|
private final List<Map<String, Object>> rows;
|
||||||
|
private final List<Action> colActions;
|
||||||
|
|
||||||
public SQLiteTableRowFactory(List<Map<String, Object>> rows) {
|
SQLiteTableRowFactory(List<Map<String, Object>> rows, List<Action> actions ) {
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
|
this.colActions = actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -52,37 +60,53 @@ public class SQLiteTableRowFactory extends ChildFactory<Integer> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SQLiteTableRowNode(rows.get(key));
|
return new SQLiteTableRowNode(rows.get(key), this.colActions );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Node for SQLite table row
|
||||||
|
*/
|
||||||
class SQLiteTableRowNode extends AbstractNode {
|
class SQLiteTableRowNode extends AbstractNode {
|
||||||
|
|
||||||
private final Map<String, Object> row;
|
private final Map<String, Object> row;
|
||||||
|
private final List<Action> nodeActions;
|
||||||
|
|
||||||
SQLiteTableRowNode(Map<String, Object> row) {
|
SQLiteTableRowNode(Map<String, Object> row, List<Action> actions) {
|
||||||
super(Children.LEAF);
|
super(Children.LEAF);
|
||||||
this.row = row;
|
this.row = row;
|
||||||
|
this.nodeActions = actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Sheet createSheet() {
|
protected Sheet createSheet() {
|
||||||
|
|
||||||
Sheet s = super.createSheet();
|
Sheet sheet = super.createSheet();
|
||||||
Sheet.Set properties = s.get(Sheet.PROPERTIES);
|
Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
|
||||||
if (properties == null) {
|
if (properties == null) {
|
||||||
properties = Sheet.createPropertiesSet();
|
properties = Sheet.createPropertiesSet();
|
||||||
s.put(properties);
|
sheet.put(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, Object> col : row.entrySet()) {
|
for (Map.Entry<String, Object> col : row.entrySet()) {
|
||||||
String colName = col.getKey();
|
String colName = col.getKey();
|
||||||
String colVal = col.getValue().toString();
|
String colVal = col.getValue().toString();
|
||||||
|
|
||||||
properties.put(new NodeProperty<>(colName, colName, colName, colVal)); // NON-NLS
|
properties.put(new NodeProperty<>(colName, colName, colName, colVal)); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return sheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Action[] getActions(boolean context) {
|
||||||
|
List<Action> actions = new ArrayList<>();
|
||||||
|
|
||||||
|
actions.addAll(Arrays.asList(super.getActions(context)));
|
||||||
|
actions.addAll(nodeActions);
|
||||||
|
|
||||||
|
return actions.toArray(new Action[actions.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,15 +20,22 @@ package org.sleuthkit.autopsy.contentviewers;
|
|||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.JMenu;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
import javax.swing.ListSelectionModel;
|
import javax.swing.ListSelectionModel;
|
||||||
import javax.swing.ScrollPaneConstants;
|
import javax.swing.ScrollPaneConstants;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.table.TableCellRenderer;
|
import javax.swing.table.TableCellRenderer;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
import javax.swing.table.TableColumnModel;
|
import javax.swing.table.TableColumnModel;
|
||||||
import org.netbeans.swing.etable.ETableColumn;
|
import org.netbeans.swing.etable.ETableColumn;
|
||||||
import org.netbeans.swing.etable.ETableColumnModel;
|
import org.netbeans.swing.etable.ETableColumnModel;
|
||||||
@ -36,7 +43,12 @@ import org.netbeans.swing.outline.Outline;
|
|||||||
import org.openide.explorer.ExplorerManager;
|
import org.openide.explorer.ExplorerManager;
|
||||||
import org.openide.nodes.AbstractNode;
|
import org.openide.nodes.AbstractNode;
|
||||||
import org.openide.nodes.Children;
|
import org.openide.nodes.Children;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.openide.util.actions.Presenter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Panel to display a SQLite table
|
||||||
|
*/
|
||||||
class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
||||||
|
|
||||||
private final org.openide.explorer.view.OutlineView outlineView;
|
private final org.openide.explorer.view.OutlineView outlineView;
|
||||||
@ -63,6 +75,7 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
|||||||
outline.setRowSelectionAllowed(false);
|
outline.setRowSelectionAllowed(false);
|
||||||
outline.setRootVisible(false);
|
outline.setRootVisible(false);
|
||||||
|
|
||||||
|
outline.setCellSelectionEnabled(true);
|
||||||
explorerManager = new ExplorerManager();
|
explorerManager = new ExplorerManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +84,10 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
|||||||
*
|
*
|
||||||
* @param tableRows
|
* @param tableRows
|
||||||
*/
|
*/
|
||||||
|
@NbBundle.Messages({"SQLiteTableView.DisplayAs.text=Display as",
|
||||||
|
"SQLiteTableView.DisplayAsMenuItem.Date=Date",
|
||||||
|
"SQLiteTableView.DisplayAsMenuItem.RawData=Raw Data"
|
||||||
|
})
|
||||||
void setupTable(List<Map<String, Object>> tableRows) {
|
void setupTable(List<Map<String, Object>> tableRows) {
|
||||||
|
|
||||||
|
|
||||||
@ -103,7 +120,11 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground() throws Exception {
|
protected Boolean doInBackground() throws Exception {
|
||||||
|
|
||||||
explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true)));
|
List<Action> nodeActions = new ArrayList<>();
|
||||||
|
|
||||||
|
nodeActions.add(new ParseColAction(Bundle.SQLiteTableView_DisplayAs_text(), outline) );
|
||||||
|
|
||||||
|
explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows, nodeActions), true)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,4 +181,79 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
|||||||
|
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
// End of variables declaration//GEN-END:variables
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to handle "Display as" menu item.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private class ParseColAction extends AbstractAction implements Presenter.Popup {
|
||||||
|
private final Outline outline;
|
||||||
|
private final String displayName;
|
||||||
|
|
||||||
|
ParseColAction(String displayName, Outline outline ) {
|
||||||
|
super(displayName);
|
||||||
|
this.outline = outline;
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JMenuItem getPopupPresenter() {
|
||||||
|
return new DisplayColAsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to SubMenu for "Display As" menu
|
||||||
|
*/
|
||||||
|
private class DisplayColAsMenu extends JMenu {
|
||||||
|
|
||||||
|
DisplayColAsMenu() {
|
||||||
|
super(displayName);
|
||||||
|
initMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
final void initMenu() {
|
||||||
|
|
||||||
|
int selCol = outline.getSelectedColumn();
|
||||||
|
if (selCol < 0 ) {
|
||||||
|
selCol = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableColumnModel columnModel = outline.getColumnModel();
|
||||||
|
TableColumn column = columnModel.getColumn(selCol);
|
||||||
|
|
||||||
|
JMenuItem parseAsEpochItem = new JMenuItem(Bundle.SQLiteTableView_DisplayAsMenuItem_Date());
|
||||||
|
parseAsEpochItem.addActionListener((ActionEvent evt) -> {
|
||||||
|
column.setCellRenderer(new EpochTimeCellRenderer(true));
|
||||||
|
});
|
||||||
|
parseAsEpochItem.setEnabled(false);
|
||||||
|
add(parseAsEpochItem);
|
||||||
|
|
||||||
|
JMenuItem parseAsOriginalItem = new JMenuItem(Bundle.SQLiteTableView_DisplayAsMenuItem_RawData());
|
||||||
|
parseAsOriginalItem.addActionListener((ActionEvent evt) -> {
|
||||||
|
column.setCellRenderer(new EpochTimeCellRenderer(false));
|
||||||
|
});
|
||||||
|
parseAsOriginalItem.setEnabled(false);
|
||||||
|
add(parseAsOriginalItem);
|
||||||
|
|
||||||
|
// Enable the relevant menuitem based on the current display state of the column
|
||||||
|
TableCellRenderer currRenderer = column.getCellRenderer();
|
||||||
|
if (currRenderer instanceof EpochTimeCellRenderer) {
|
||||||
|
if (((EpochTimeCellRenderer) currRenderer).isRenderingAsEpoch()) {
|
||||||
|
parseAsOriginalItem.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
parseAsEpochItem.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parseAsEpochItem.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,10 +42,18 @@ import javax.swing.JComboBox;
|
|||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file content viewer for SQLITE db files.
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||||
|
|
||||||
public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"};
|
public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"};
|
||||||
@ -62,9 +70,9 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
private int currPage = 0; // curr page of rows being displayed
|
private int currPage = 0; // curr page of rows being displayed
|
||||||
|
|
||||||
SQLiteTableView selectedTableView = new SQLiteTableView();
|
SQLiteTableView selectedTableView = new SQLiteTableView();
|
||||||
|
|
||||||
private SwingWorker<? extends Object, ? extends Object> worker;
|
private SwingWorker<? extends Object, ? extends Object> worker;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form SQLiteViewer
|
* Creates new form SQLiteViewer
|
||||||
*/
|
*/
|
||||||
@ -315,10 +323,14 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Copy the file to temp folder
|
// Copy the file to temp folder
|
||||||
tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName() + "-" + sqliteFile.getId();
|
tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName();
|
||||||
tmpDBFile = new File(tmpDBPathName);
|
tmpDBFile = new File(tmpDBPathName);
|
||||||
ContentUtils.writeToFile(sqliteFile, tmpDBFile);
|
ContentUtils.writeToFile(sqliteFile, tmpDBFile);
|
||||||
|
|
||||||
|
// look for any meta files associated with this DB - WAL, SHM
|
||||||
|
findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-wal");
|
||||||
|
findAndCopySQLiteMetaFile(sqliteFile, sqliteFile.getName() + "-shm");
|
||||||
|
|
||||||
// Open copy using JDBC
|
// Open copy using JDBC
|
||||||
Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver
|
Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver
|
||||||
connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS
|
connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS
|
||||||
@ -357,6 +369,45 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a meta file associated with the give SQLite db
|
||||||
|
* If found, copies the file to the temp folder
|
||||||
|
*
|
||||||
|
* @param sqliteFile - SQLIte db file being processed
|
||||||
|
* @param metaFileName name of meta file to look for
|
||||||
|
*
|
||||||
|
* @return true if the meta file is found and copied successfully, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean findAndCopySQLiteMetaFile(AbstractFile sqliteFile, String metaFileName ) {
|
||||||
|
|
||||||
|
SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||||
|
Services services = new Services(sleuthkitCase);
|
||||||
|
FileManager fileManager = services.getFileManager();
|
||||||
|
|
||||||
|
List<AbstractFile> metaFiles = null;
|
||||||
|
try {
|
||||||
|
metaFiles = fileManager.findFiles(sqliteFile.getDataSource(), metaFileName, sqliteFile.getParent().getName() );
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Unexpected exception while searching SQLite meta file = " + metaFileName , ex); //NON-NLS
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metaFiles != null) {
|
||||||
|
for (AbstractFile metaFile: metaFiles) {
|
||||||
|
String tmpMetafilePathName = Case.getCurrentCase().getTempDirectory() + File.separator + metaFile.getName();
|
||||||
|
|
||||||
|
File tmpMetafile = new File(tmpMetafilePathName);
|
||||||
|
try {
|
||||||
|
ContentUtils.writeToFile(metaFile, tmpMetafile);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Unexpected exception while copying SQLite meta file = " + metaFileName , ex); //NON-NLS
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Gets the table names and their schema from loaded SQLite db file
|
* Gets the table names and their schema from loaded SQLite db file
|
||||||
*
|
*
|
||||||
|
BIN
Core/src/org/sleuthkit/autopsy/images/key-16.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/key-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 223 B |
BIN
Core/src/org/sleuthkit/autopsy/images/keychain-16.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/keychain-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 700 B |
BIN
Core/src/org/sleuthkit/autopsy/images/keys-dict-16.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/keys-dict-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 470 B |
@ -6,8 +6,8 @@ app.name=${branding.token}
|
|||||||
### if left unset, version will default to today's date
|
### if left unset, version will default to today's date
|
||||||
app.version=4.6.0
|
app.version=4.6.0
|
||||||
### build.type must be one of: DEVELOPMENT, RELEASE
|
### build.type must be one of: DEVELOPMENT, RELEASE
|
||||||
build.type=RELEASE
|
#build.type=RELEASE
|
||||||
#build.type=DEVELOPMENT
|
build.type=DEVELOPMENT
|
||||||
|
|
||||||
project.org.netbeans.progress=org-netbeans-api-progress
|
project.org.netbeans.progress=org-netbeans-api-progress
|
||||||
project.org.sleuthkit.autopsy.experimental=Experimental
|
project.org.sleuthkit.autopsy.experimental=Experimental
|
||||||
|
Loading…
x
Reference in New Issue
Block a user