diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java new file mode 100644 index 0000000000..723c1ef0f3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/EpochTimeCellRenderer.java @@ -0,0 +1,105 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java index 633f40260c..02a08f7037 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java @@ -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 { private final List> rows; + private final List colActions; - public SQLiteTableRowFactory(List> rows) { + SQLiteTableRowFactory(List> rows, List actions ) { this.rows = rows; + this.colActions = actions; } @Override @@ -52,37 +60,53 @@ public class SQLiteTableRowFactory extends ChildFactory { 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 row; - - SQLiteTableRowNode(Map row) { + private final List nodeActions; + + SQLiteTableRowNode(Map row, List 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 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 actions = new ArrayList<>(); + + actions.addAll(Arrays.asList(super.getActions(context))); + actions.addAll(nodeActions); + + return actions.toArray(new Action[actions.size()]); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index 7ca873e13c..77c0814c55 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -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> 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 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); + } + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 627d30e87c..49d6231435 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -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 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() { @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 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 *