mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-02 23:39:50 +00:00
Merge pull request #3428 from raman-bt/sqliteviewer
3457: SQLiteViewer should handle databases with WAL files
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,9 +18,12 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javax.swing.Action;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.ChildFactory;
|
||||
import org.openide.nodes.Children;
|
||||
@@ -28,12 +31,17 @@ import org.openide.nodes.Node;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
|
||||
/**
|
||||
* Factory class to generate nodes for SQLite table rows
|
||||
*/
|
||||
public class SQLiteTableRowFactory extends ChildFactory<Integer> {
|
||||
|
||||
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.colActions = actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,37 +60,53 @@ public class SQLiteTableRowFactory extends ChildFactory<Integer> {
|
||||
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 {
|
||||
|
||||
private final Map<String, Object> row;
|
||||
|
||||
SQLiteTableRowNode(Map<String, Object> row) {
|
||||
private final List<Action> nodeActions;
|
||||
|
||||
SQLiteTableRowNode(Map<String, Object> row, List<Action> actions) {
|
||||
super(Children.LEAF);
|
||||
this.row = row;
|
||||
this.nodeActions = actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
|
||||
Sheet s = super.createSheet();
|
||||
Sheet.Set properties = s.get(Sheet.PROPERTIES);
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
|
||||
if (properties == null) {
|
||||
properties = Sheet.createPropertiesSet();
|
||||
s.put(properties);
|
||||
sheet.put(properties);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> col : row.entrySet()) {
|
||||
String colName = col.getKey();
|
||||
String colVal = col.getValue().toString();
|
||||
|
||||
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.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import org.netbeans.swing.etable.ETableColumn;
|
||||
import org.netbeans.swing.etable.ETableColumnModel;
|
||||
@@ -36,7 +43,12 @@ 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.openide.util.actions.Presenter;
|
||||
|
||||
/**
|
||||
* Panel to display a SQLite table
|
||||
*/
|
||||
class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
||||
|
||||
private final org.openide.explorer.view.OutlineView outlineView;
|
||||
@@ -63,6 +75,7 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
||||
outline.setRowSelectionAllowed(false);
|
||||
outline.setRootVisible(false);
|
||||
|
||||
outline.setCellSelectionEnabled(true);
|
||||
explorerManager = new ExplorerManager();
|
||||
}
|
||||
|
||||
@@ -71,6 +84,10 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
||||
*
|
||||
* @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) {
|
||||
|
||||
|
||||
@@ -103,7 +120,11 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -160,4 +181,79 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider {
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN: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 org.openide.util.NbBundle;
|
||||
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.datamodel.ContentUtils;
|
||||
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 static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"};
|
||||
@@ -62,8 +70,8 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
private int currPage = 0; // curr page of rows being displayed
|
||||
|
||||
SQLiteTableView selectedTableView = new SQLiteTableView();
|
||||
|
||||
private SwingWorker<? extends Object, ? extends Object> worker;
|
||||
|
||||
|
||||
/**
|
||||
* Creates new form SQLiteViewer
|
||||
@@ -308,21 +316,25 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
private void processSQLiteFile(AbstractFile sqliteFile) {
|
||||
|
||||
tablesDropdownList.removeAllItems();
|
||||
|
||||
|
||||
new SwingWorker<Boolean, Void>() {
|
||||
@Override
|
||||
protected Boolean doInBackground() throws Exception {
|
||||
|
||||
try {
|
||||
// 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);
|
||||
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
|
||||
Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS
|
||||
|
||||
|
||||
// Read all table names and schema
|
||||
return getTables();
|
||||
} catch (IOException ex) {
|
||||
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user