diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 4e8fb1fe80..b9e71ccdf8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -40,6 +40,7 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JComboBox; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; @@ -53,32 +54,27 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.corecomponentinterfaces.FileTypeViewer; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** - * A file content viewer for SQLITE db files. - * + * A file content viewer for SQLite database files. */ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { + private static final long serialVersionUID = 1L; public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; - private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); - private Connection connection = null; - - private String tmpDBPathName = null; - private File tmpDBFile = null; - - private final Map dbTablesMap = new TreeMap<>(); - private static final int ROWS_PER_PAGE = 100; + private static final Logger logger = Logger.getLogger(FileViewer.class.getName()); + private final SQLiteTableView selectedTableView = new SQLiteTableView(); + private AbstractFile sqliteDbFile; + private File tmpDbFile; + private Connection connection; private int numRows; // num of rows in the selected table private int currPage = 0; // curr page of rows being displayed - - SQLiteTableView selectedTableView = new SQLiteTableView(); private SwingWorker worker; - /** - * Creates new form SQLiteViewer + * Constructs a file content viewer for SQLite database files. */ public SQLiteViewer() { initComponents(); @@ -217,7 +213,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { }// //GEN-END:initComponents private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed - currPage++; if (currPage * ROWS_PER_PAGE > numRows) { nextPageButton.setEnabled(false); @@ -231,7 +226,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { }//GEN-LAST:event_nextPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed - currPage--; if (currPage == 1) { prevPageButton.setEnabled(false); @@ -250,7 +244,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { if (null == tableName) { return; } - selectTable(tableName); }//GEN-LAST:event_tablesDropdownListActionPerformed @@ -276,7 +269,8 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public void setFile(AbstractFile file) { - processSQLiteFile(file); + sqliteDbFile = file; + processSQLiteFile(); } @Override @@ -286,9 +280,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public void resetComponent() { - - dbTablesMap.clear(); - tablesDropdownList.setEnabled(true); tablesDropdownList.removeAllItems(); numEntriesField.setText(""); @@ -299,75 +290,61 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { connection.close(); connection = null; } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS + logger.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS } } // delete last temp file - if (null != tmpDBFile) { - tmpDBFile.delete(); - tmpDBFile = null; + if (null != tmpDbFile) { + tmpDbFile.delete(); + tmpDbFile = null; } + + sqliteDbFile = null; } /** - * Process the given SQLite DB file - * - * @param sqliteFile - - * - * @return none + * Process the given SQLite DB file. */ - @NbBundle.Messages({"# {0} - fileName", - "SQLiteViewer.processSQLiteFile.errorMessage=Error opening SQLite file {0}" - }) - private void processSQLiteFile(AbstractFile sqliteFile) { - - tablesDropdownList.removeAllItems(); - - new SwingWorker() { + @NbBundle.Messages({ + "SQLiteViewer.comboBox.noTableEntry=No tables found", + "SQLiteViewer.errorMessage.interrupted=The processing of the file was interrupted.", + "SQLiteViewer.errorMessage.noCurrentCase=The case has been closed.", + "SQLiteViewer.errorMessage.failedToExtractFile=The file could not be extracted from the data source.", + "SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.", + "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.", + "# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",}) + private void processSQLiteFile() { + SwingUtilities.invokeLater(() -> { + tablesDropdownList.removeAllItems(); + }); + new SwingWorker, Void>() { @Override - protected Void doInBackground() throws IOException, SQLException, ClassNotFoundException { + protected Map doInBackground() throws NoCurrentCaseException, TskCoreException, IOException, SQLException, ClassNotFoundException { + // Copy the file to temp folder + String tmpDBPathName = Case.getOpenCase().getTempDirectory() + File.separator + sqliteDbFile.getName(); + tmpDbFile = new File(tmpDBPathName); + ContentUtils.writeToFile(sqliteDbFile, tmpDbFile); - try { - // Copy the file to temp folder - tmpDBPathName = Case.getOpenCase().getTempDirectory() + File.separator + sqliteFile.getName(); - tmpDBFile = new File(tmpDBPathName); - ContentUtils.writeToFile(sqliteFile, tmpDBFile); + // Look for any meta files associated with this DB - WAL, SHM, etc. + findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-wal"); + findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-shm"); - // 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 (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Failed to copy DB file " + sqliteFile.getName(), ex); //NON-NLS - throw ex; - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Failed to get tables from DB file " + sqliteFile.getName(), ex); //NON-NLS - throw ex; - } catch (ClassNotFoundException ex) { - LOGGER.log(Level.SEVERE, "Failed to initialize JDBC SQLite.", ex); //NON-NLS - throw ex; - } - return null; + // Load the SQLite JDBC driver, if necessary. + Class.forName("org.sqlite.JDBC"); //NON-NLS + connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS + + // Query the file for the table names and schemas. + return getTables(); } @Override protected void done() { super.done(); try { - - get(); + Map dbTablesMap = get(); if (dbTablesMap.isEmpty()) { - // Populate error message - tablesDropdownList.addItem("No tables found"); + tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry()); tablesDropdownList.setEnabled(false); } else { dbTablesMap.keySet().forEach((tableName) -> { @@ -375,97 +352,76 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { }); } } catch (InterruptedException ex) { - - LOGGER.log(Level.SEVERE, "Interrupted while opening DB file " + sqliteFile.getName(), ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - Bundle.SQLiteViewer_processSQLiteFile_errorMessage(sqliteFile.getName()), - JOptionPane.ERROR_MESSAGE); - } - catch (ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception while opening DB file " + sqliteFile.getName(), ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getCause().getMessage(), - Bundle.SQLiteViewer_processSQLiteFile_errorMessage(sqliteFile.getName()), - JOptionPane.ERROR_MESSAGE); + logger.log(Level.SEVERE, String.format("Interrupted while opening SQLite database file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_interrupted()); + } catch (ExecutionException ex) { + String errorMessage; + Throwable cause = ex.getCause(); + if (cause instanceof NoCurrentCaseException) { + logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS + errorMessage = Bundle.SQLiteViewer_errorMessage_noCurrentCase(); + } else if (cause instanceof TskCoreException || cause instanceof IOException) { + logger.log(Level.SEVERE, String.format("Failed to create temp copy of DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + errorMessage = Bundle.SQLiteViewer_errorMessage_failedToExtractFile(); + } else if (cause instanceof SQLException) { + logger.log(Level.SEVERE, String.format("Failed to get tables from DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + errorMessage = Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase(); + } else if (cause instanceof ClassNotFoundException) { + logger.log(Level.SEVERE, String.format("Failed to initialize JDBC SQLite '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + errorMessage = Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver(); + } else { + logger.log(Level.SEVERE, String.format("Unexpected exception while processing DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + errorMessage = Bundle.SQLiteViewer_errorMessage_unexpectedError(cause.getLocalizedMessage()); + } + MessageNotifyUtil.Message.error(errorMessage); } } }.execute(); - } /** - * Searches for a meta file associated with the give SQLite db - * If found, copies the file to the temp folder + * 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 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 ) { - Case openCase; - try { - openCase = Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return false; - } + private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile, String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException { + Case openCase = Case.getOpenCase(); SleuthkitCase sleuthkitCase = openCase.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; - } - + List metaFiles = fileManager.findFiles(sqliteFile.getDataSource(), metaFileName, sqliteFile.getParent().getName()); if (metaFiles != null) { - for (AbstractFile metaFile: metaFiles) { + for (AbstractFile metaFile : metaFiles) { String tmpMetafilePathName = openCase.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; - } + ContentUtils.writeToFile(metaFile, tmpMetafile); } } - - return true; } - /** - * Gets the table names and their schema from loaded SQLite db file - * - * @return true if success, false otherwise - */ - private void getTables() throws SQLException { + /** + * Gets the table names and schemas from the SQLite database file. + * + * @return A mapping of table names to SQL CREATE TABLE statements. + */ + private Map getTables() throws SQLException { + Map dbTablesMap = new TreeMap<>(); Statement statement = null; ResultSet resultSet = null; - try { - statement = connection.createStatement(); - resultSet = statement.executeQuery( - "SELECT name, sql FROM sqlite_master " - + " WHERE type= 'table' " - + " ORDER BY name;"); //NON-NLS - + statement = connection.createStatement(); + resultSet = statement.executeQuery( + "SELECT name, sql FROM sqlite_master " + + " WHERE type= 'table' " + + " ORDER BY name;"); //NON-NLS while (resultSet.next()) { String tableName = resultSet.getString("name"); //NON-NLS String tableSQL = resultSet.getString("sql"); //NON-NLS - dbTablesMap.put(tableName, tableSQL); } - } catch(SQLException ex) { - throw ex; - } - finally { + } finally { if (null != resultSet) { resultSet.close(); } @@ -473,6 +429,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { statement.close(); } } + return dbTablesMap; } @NbBundle.Messages({"# {0} - tableName", @@ -498,8 +455,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { return resultSet.getInt("count"); } catch (SQLException ex) { throw ex; - } - finally { + } finally { if (null != resultSet) { resultSet.close(); } @@ -523,7 +479,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { prevPageButton.setEnabled(false); - if (numRows > 0) { nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE))); readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); @@ -532,19 +487,18 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { selectedTableView.setupTable(Collections.emptyList()); } - } catch (InterruptedException ex ) { - LOGGER.log(Level.SEVERE, "Interrupted while getting row count from table " + tableName, ex); //NON-NLS + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Interrupted while getting row count from table " + tableName, ex); //NON-NLS JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getMessage(), Bundle.SQLiteViewer_selectTable_errorText(tableName), JOptionPane.ERROR_MESSAGE); - } - catch (ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception while getting row count from table " + tableName, ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getCause().getMessage(), - Bundle.SQLiteViewer_selectTable_errorText(tableName), - JOptionPane.ERROR_MESSAGE); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while getting row count from table " + tableName, ex); //NON-NLS + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + ex.getCause().getMessage(), + Bundle.SQLiteViewer_selectTable_errorText(tableName), + JOptionPane.ERROR_MESSAGE); } } }; @@ -563,7 +517,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { worker = new SwingWorker>, Void>() { @Override protected ArrayList> doInBackground() throws Exception { - + Statement statement = null; ResultSet resultSet = null; try { @@ -573,12 +527,11 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { + " LIMIT " + Integer.toString(numRowsToRead) + " OFFSET " + Integer.toString(startRow - 1) ); //NON-NLS - + return resultSetToArrayList(resultSet); } catch (SQLException ex) { throw ex; - } - finally { + } finally { if (null != resultSet) { resultSet.close(); } @@ -600,19 +553,18 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { ArrayList> rows = get(); if (Objects.nonNull(rows)) { selectedTableView.setupTable(rows); - }else{ + } else { selectedTableView.setupTable(Collections.emptyList()); } } catch (InterruptedException ex) { - LOGGER.log(Level.SEVERE, "Interrupted while reading table " + tableName, ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + logger.log(Level.SEVERE, "Interrupted while reading table " + tableName, ex); //NON-NLS + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getMessage(), Bundle.SQLiteViewer_readTable_errorText(tableName), JOptionPane.ERROR_MESSAGE); - } - catch (ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception while reading table " + tableName, ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while reading table " + tableName, ex); //NON-NLS + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getCause().getMessage(), Bundle.SQLiteViewer_readTable_errorText(tableName), JOptionPane.ERROR_MESSAGE);