mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #4005 from dannysmyda/4092-factor-out-sqlite-reader
4092: Refactored SQLite parsing functionality out of SQLiteViewer and into SQLiteReader
This commit is contained in:
commit
b8364e91be
@ -22,23 +22,17 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
@ -48,14 +42,11 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
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;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.sqlitereader.SQLiteReader;
|
||||
|
||||
/**
|
||||
* A file content viewer for SQLite database files.
|
||||
@ -70,7 +61,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
private final SQLiteTableView selectedTableView = new SQLiteTableView();
|
||||
private AbstractFile sqliteDbFile;
|
||||
private File tmpDbFile;
|
||||
private Connection connection;
|
||||
private SQLiteReader sqliteReader;
|
||||
private int numRows; // num of rows in the selected table
|
||||
private int currPage = 0; // curr page of rows being displayed
|
||||
|
||||
@ -347,10 +338,10 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
numEntriesField.setText("");
|
||||
|
||||
// close DB connection to file
|
||||
if (null != connection) {
|
||||
if (null != sqliteReader) {
|
||||
try {
|
||||
connection.close();
|
||||
connection = null;
|
||||
sqliteReader.close();
|
||||
sqliteReader = null;
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS
|
||||
}
|
||||
@ -370,41 +361,15 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
"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() {
|
||||
|
||||
private void processSQLiteFile() {
|
||||
tablesDropdownList.removeAllItems();
|
||||
|
||||
// Copy the file to temp folder
|
||||
String tmpDBPathName;
|
||||
try {
|
||||
tmpDBPathName = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + sqliteDbFile.getName();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_noCurrentCase());
|
||||
return;
|
||||
}
|
||||
|
||||
tmpDbFile = new File(tmpDBPathName);
|
||||
if (! tmpDbFile.exists()) {
|
||||
try {
|
||||
ContentUtils.writeToFile(sqliteDbFile, tmpDbFile);
|
||||
|
||||
// Look for any meta files associated with this DB - WAL, SHM, etc.
|
||||
findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-wal");
|
||||
findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-shm");
|
||||
} catch (IOException | NoCurrentCaseException | TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to create temp copy of DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToExtractFile());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the SQLite JDBC driver, if necessary.
|
||||
Class.forName("org.sqlite.JDBC"); //NON-NLS
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS
|
||||
|
||||
Map<String, String> dbTablesMap = getTables();
|
||||
String localDiskPath = Case.getCurrentCaseThrows().getTempDirectory() +
|
||||
File.separator + sqliteDbFile.getName();
|
||||
sqliteReader = new SQLiteReader(sqliteDbFile, localDiskPath);
|
||||
|
||||
Map<String, String> dbTablesMap = sqliteReader.getTableSchemas();
|
||||
|
||||
if (dbTablesMap.isEmpty()) {
|
||||
tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
|
||||
tablesDropdownList.setEnabled(false);
|
||||
@ -413,78 +378,36 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
tablesDropdownList.addItem(tableName);
|
||||
});
|
||||
}
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_noCurrentCase());
|
||||
} catch (IOException | TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format(
|
||||
"Failed to create temp copy of DB file '%s' (objId=%d)", //NON-NLS
|
||||
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
|
||||
MessageNotifyUtil.Message.error(
|
||||
Bundle.SQLiteViewer_errorMessage_failedToExtractFile());
|
||||
} catch (ClassNotFoundException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to initialize JDBC SQLite '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver());
|
||||
logger.log(Level.SEVERE, String.format(
|
||||
"Failed to initialize JDBC SQLite '%s' (objId=%d)", //NON-NLS
|
||||
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
|
||||
MessageNotifyUtil.Message.error(
|
||||
Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver());
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to get tables from DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase());
|
||||
logger.log(Level.SEVERE, String.format(
|
||||
"Failed to get tables from DB file '%s' (objId=%d)", //NON-NLS
|
||||
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
|
||||
MessageNotifyUtil.Message.error(
|
||||
Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile, String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
|
||||
Case openCase = Case.getCurrentCaseThrows();
|
||||
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
|
||||
Services services = new Services(sleuthkitCase);
|
||||
FileManager fileManager = services.getFileManager();
|
||||
List<AbstractFile> metaFiles = fileManager.findFiles(sqliteFile.getDataSource(), metaFileName, sqliteFile.getParent().getName());
|
||||
if (metaFiles != null) {
|
||||
for (AbstractFile metaFile : metaFiles) {
|
||||
String tmpMetafilePathName = openCase.getTempDirectory() + File.separator + metaFile.getName();
|
||||
File tmpMetafile = new File(tmpMetafilePathName);
|
||||
ContentUtils.writeToFile(metaFile, tmpMetafile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the table names and schemas from the SQLite database file.
|
||||
*
|
||||
* @return A mapping of table names to SQL CREATE TABLE statements.
|
||||
*/
|
||||
private Map<String, String> getTables() throws SQLException {
|
||||
Map<String, String> 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
|
||||
while (resultSet.next()) {
|
||||
String tableName = resultSet.getString("name"); //NON-NLS
|
||||
String tableSQL = resultSet.getString("sql"); //NON-NLS
|
||||
dbTablesMap.put(tableName, tableSQL);
|
||||
}
|
||||
} finally {
|
||||
if (null != resultSet) {
|
||||
resultSet.close();
|
||||
}
|
||||
if (null != statement) {
|
||||
statement.close();
|
||||
}
|
||||
}
|
||||
return dbTablesMap;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"# {0} - tableName",
|
||||
"SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}"
|
||||
})
|
||||
private void selectTable(String tableName) {
|
||||
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT count (*) as count FROM " + tableName)) { //NON-NLS{
|
||||
|
||||
numRows = resultSet.getInt("count");
|
||||
try {
|
||||
numRows = sqliteReader.getTableRowCount(tableName);
|
||||
numEntriesField.setText(numRows + " entries");
|
||||
|
||||
currPage = 1;
|
||||
@ -504,8 +427,11 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to load table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName));
|
||||
logger.log(Level.SEVERE, String.format(
|
||||
"Failed to load table %s from DB file '%s' (objId=%d)", tableName, //NON-NLS
|
||||
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
|
||||
MessageNotifyUtil.Message.error(
|
||||
Bundle.SQLiteViewer_selectTable_errorText(tableName));
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,109 +439,108 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
|
||||
"SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
|
||||
private void readTable(String tableName, int startRow, int numRowsToRead) {
|
||||
|
||||
try (
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT * FROM " + tableName
|
||||
+ " LIMIT " + Integer.toString(numRowsToRead)
|
||||
+ " OFFSET " + Integer.toString(startRow - 1))) {
|
||||
|
||||
ArrayList<Map<String, Object>> rows = resultSetToArrayList(resultSet);
|
||||
try {
|
||||
List<Map<String, Object>> rows = sqliteReader.getRowsFromTable(
|
||||
tableName, startRow, numRowsToRead);
|
||||
if (Objects.nonNull(rows)) {
|
||||
selectedTableView.setupTable(rows);
|
||||
} else {
|
||||
selectedTableView.setupTable(Collections.emptyList());
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
|
||||
logger.log(Level.SEVERE, String.format(
|
||||
"Failed to read table %s from DB file '%s' (objId=%d)", tableName, //NON-NLS
|
||||
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
|
||||
MessageNotifyUtil.Message.error(
|
||||
Bundle.SQLiteViewer_readTable_errorText(tableName));
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages("SQLiteViewer.BlobNotShown.message=BLOB Data not shown")
|
||||
private ArrayList<Map<String, Object>> resultSetToArrayList(ResultSet rs) throws SQLException {
|
||||
ResultSetMetaData metaData = rs.getMetaData();
|
||||
int columns = metaData.getColumnCount();
|
||||
ArrayList<Map<String, Object>> rowlist = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
Map<String, Object> row = new LinkedHashMap<>(columns);
|
||||
for (int i = 1; i <= columns; ++i) {
|
||||
if (rs.getObject(i) == null) {
|
||||
row.put(metaData.getColumnName(i), "");
|
||||
} else {
|
||||
if (metaData.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) {
|
||||
row.put(metaData.getColumnName(i), Bundle.SQLiteViewer_BlobNotShown_message());
|
||||
} else {
|
||||
row.put(metaData.getColumnName(i), rs.getObject(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
rowlist.add(row);
|
||||
}
|
||||
|
||||
return rowlist;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
|
||||
"SQLiteViewer.exportTableToCsv.FileName=File name: ",
|
||||
"SQLiteViewer.exportTableToCsv.TableName=Table name: "
|
||||
/**
|
||||
* Converts a sqlite table into a CSV file.
|
||||
*
|
||||
* @param file
|
||||
* @param tableName
|
||||
* @param rowMap -- A list of rows in the table, where each row is represented as a column-value
|
||||
* map.
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"SQLiteViewer.exportTableToCsv.FileName=File name: ",
|
||||
"SQLiteViewer.exportTableToCsv.TableName=Table name: "
|
||||
})
|
||||
public void exportTableToCSV(File file, String tableName,
|
||||
List<Map<String, Object>> rowMap) throws FileNotFoundException, IOException{
|
||||
|
||||
File csvFile;
|
||||
String fileName = file.getName();
|
||||
if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) {
|
||||
csvFile = file;
|
||||
} else {
|
||||
csvFile = new File(file.toString() + ".csv");
|
||||
}
|
||||
|
||||
try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
|
||||
|
||||
out.write((Bundle.SQLiteViewer_exportTableToCsv_FileName() + csvFile.getName() + "\n").getBytes());
|
||||
out.write((Bundle.SQLiteViewer_exportTableToCsv_TableName() + tableName + "\n").getBytes());
|
||||
|
||||
String header = createColumnHeader(rowMap.get(0)).concat("\n");
|
||||
out.write(header.getBytes());
|
||||
|
||||
for (Map<String, Object> maps : rowMap) {
|
||||
String row = maps.values()
|
||||
.stream()
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.joining(","))
|
||||
.concat("\n");
|
||||
out.write(row.getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
|
||||
})
|
||||
private void exportTableToCsv(File file) {
|
||||
String tableName = (String) this.tablesDropdownList.getSelectedItem();
|
||||
try (
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT * FROM " + tableName)) {
|
||||
List<Map<String, Object>> currentTableRows = resultSetToArrayList(resultSet);
|
||||
try {
|
||||
List<Map<String, Object>> currentTableRows =
|
||||
sqliteReader.getRowsFromTable(tableName);
|
||||
|
||||
if (Objects.isNull(currentTableRows) || currentTableRows.isEmpty()) {
|
||||
logger.log(Level.INFO, String.format("The table %s is empty. (objId=%d)", tableName, sqliteDbFile.getId())); //NON-NLS
|
||||
logger.log(Level.INFO, String.format(
|
||||
"The table %s is empty. (objId=%d)", tableName, //NON-NLS
|
||||
sqliteDbFile.getId()));
|
||||
} else {
|
||||
File csvFile;
|
||||
String fileName = file.getName();
|
||||
if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) {
|
||||
csvFile = file;
|
||||
} else {
|
||||
csvFile = new File(file.toString() + ".csv");
|
||||
}
|
||||
|
||||
try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
|
||||
|
||||
out.write((Bundle.SQLiteViewer_exportTableToCsv_FileName() + csvFile.getName() + "\n").getBytes());
|
||||
out.write((Bundle.SQLiteViewer_exportTableToCsv_TableName() + tableName + "\n").getBytes());
|
||||
// Set up the column names
|
||||
Map<String, Object> row = currentTableRows.get(0);
|
||||
StringBuffer header = new StringBuffer();
|
||||
for (Map.Entry<String, Object> col : row.entrySet()) {
|
||||
String colName = col.getKey();
|
||||
if (header.length() > 0) {
|
||||
header.append(',').append(colName);
|
||||
} else {
|
||||
header.append(colName);
|
||||
}
|
||||
}
|
||||
out.write(header.append('\n').toString().getBytes());
|
||||
|
||||
for (Map<String, Object> maps : currentTableRows) {
|
||||
StringBuffer valueLine = new StringBuffer();
|
||||
maps.values().forEach((value) -> {
|
||||
if (valueLine.length() > 0) {
|
||||
valueLine.append(',').append(value.toString());
|
||||
} else {
|
||||
valueLine.append(value.toString());
|
||||
}
|
||||
});
|
||||
out.write(valueLine.append('\n').toString().getBytes());
|
||||
}
|
||||
}
|
||||
exportTableToCSV(file, tableName, currentTableRows);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
|
||||
logger.log(Level.SEVERE, String.format(
|
||||
"Failed to read table %s from DB file '%s' (objId=%d)", //NON-NLS
|
||||
tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
|
||||
MessageNotifyUtil.Message.error(
|
||||
Bundle.SQLiteViewer_readTable_errorText(tableName));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to export table %s to file '%s'", tableName, file.getName()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_exportTableToCsv_write_errText());
|
||||
logger.log(Level.SEVERE, String.format(
|
||||
"Failed to export table %s to file '%s'", tableName, file.getName()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Message.error(
|
||||
Bundle.SQLiteViewer_exportTableToCsv_write_errText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a comma seperated header string from the keys of the column
|
||||
* row map.
|
||||
*
|
||||
* @param row -- column header row map
|
||||
* @return -- comma seperated header string
|
||||
*/
|
||||
private String createColumnHeader(Map<String, Object> row) {
|
||||
return row.entrySet()
|
||||
.stream()
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
}
|
||||
|
272
Core/src/org/sleuthkit/autopsy/sqlitereader/SQLiteReader.java
Executable file
272
Core/src/org/sleuthkit/autopsy/sqlitereader/SQLiteReader.java
Executable file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-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.sqlitereader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Reads rows from SQLite tables and returns results in a list collection.
|
||||
*/
|
||||
public class SQLiteReader implements AutoCloseable {
|
||||
|
||||
private final Connection connection;
|
||||
|
||||
/**
|
||||
* Writes data source file contents to local disk and opens a sqlite JDBC
|
||||
* connection.
|
||||
*
|
||||
* @param sqliteDbFile Data source abstract file
|
||||
* @param localDiskPath Location for database contents to be copied to
|
||||
* @throws ClassNotFoundException missing SQLite JDBC class
|
||||
* @throws SQLException Exception opening JDBC connection
|
||||
* @throws IOException Exception writing file contents
|
||||
* @throws NoCurrentCaseException Current case closed during file copying
|
||||
* @throws TskCoreException Exception finding files from abstract file
|
||||
*/
|
||||
public SQLiteReader(AbstractFile sqliteDbFile, String localDiskPath) throws ClassNotFoundException,
|
||||
SQLException, IOException, NoCurrentCaseException, TskCoreException{
|
||||
|
||||
writeDataSourceToLocalDisk(sqliteDbFile, localDiskPath);
|
||||
connection = getDatabaseConnection(localDiskPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the data source file contents to local drive for processing.
|
||||
*
|
||||
* @param file AbstractFile from the data source
|
||||
* @param localDiskPath Local drive path to copy AbstractFile contents
|
||||
* @throws IOException Exception writing file contents
|
||||
* @throws NoCurrentCaseException Current case closed during file copying
|
||||
* @throws TskCoreException Exception finding files from abstract file
|
||||
*/
|
||||
private void writeDataSourceToLocalDisk(AbstractFile file, String localDiskPath)
|
||||
throws IOException, NoCurrentCaseException, TskCoreException {
|
||||
|
||||
File localDatabaseFile = new File(localDiskPath);
|
||||
if (!localDatabaseFile.exists()) {
|
||||
ContentUtils.writeToFile(file, localDatabaseFile);
|
||||
|
||||
// Look for any meta files associated with this DB - WAL, SHM, etc.
|
||||
findAndCopySQLiteMetaFile(file, file.getName() + "-wal");
|
||||
findAndCopySQLiteMetaFile(file, file.getName() + "-shm");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a meta file associated with the give SQLite database. If found,
|
||||
* copies the file to the local disk folder
|
||||
*
|
||||
* @param sqliteFile SQLIte db file being processed
|
||||
* @param metaFileName name of meta file to look for
|
||||
* @throws NoCurrentCaseException Case has been closed.
|
||||
* @throws TskCoreException fileManager cannot find AbstractFile files.
|
||||
* @throws IOException Issue during writing to file.
|
||||
*/
|
||||
private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile,
|
||||
String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
|
||||
|
||||
Case openCase = Case.getCurrentCaseThrows();
|
||||
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
|
||||
Services services = new Services(sleuthkitCase);
|
||||
FileManager fileManager = services.getFileManager();
|
||||
|
||||
List<AbstractFile> metaFiles = fileManager.findFiles(
|
||||
sqliteFile.getDataSource(), metaFileName,
|
||||
sqliteFile.getParent().getName());
|
||||
|
||||
if (metaFiles != null) {
|
||||
for (AbstractFile metaFile : metaFiles) {
|
||||
String tmpMetafilePathName = openCase.getTempDirectory() +
|
||||
File.separator + metaFile.getName();
|
||||
File tmpMetafile = new File(tmpMetafilePathName);
|
||||
ContentUtils.writeToFile(metaFile, tmpMetafile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a JDBC connection to the sqlite database specified by the path
|
||||
* parameter.
|
||||
*
|
||||
* @param databasePath Local path of sqlite database
|
||||
* @return Connection JDBC connection, to be maintained and closed by the reader
|
||||
* @throws ClassNotFoundException missing SQLite JDBC class
|
||||
* @throws SQLException Exception during opening database connection
|
||||
*/
|
||||
private Connection getDatabaseConnection(String databasePath)
|
||||
throws ClassNotFoundException, SQLException {
|
||||
|
||||
// Load the SQLite JDBC driver, if necessary.
|
||||
Class.forName("org.sqlite.JDBC"); //NON-NLS
|
||||
return DriverManager.getConnection(
|
||||
"jdbc:sqlite:" + databasePath); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a map view of table names to table schemas (in the form of
|
||||
* CREATE TABLE statments).
|
||||
*
|
||||
* @return A map of table names to table schemas
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Map<String, String> getTableSchemas()
|
||||
throws SQLException {
|
||||
|
||||
Map<String, String> dbTablesMap = new TreeMap<>();
|
||||
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT name, sql FROM sqlite_master " //NON-NLS
|
||||
+ " WHERE type= 'table' " //NON-NLS
|
||||
+ " 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);
|
||||
}
|
||||
}
|
||||
|
||||
return dbTablesMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the total number of rows from a table in the SQLite database.
|
||||
*
|
||||
* @param tableName
|
||||
* @return Row count from tableName
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Integer getTableRowCount(String tableName) throws SQLException {
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT count (*) as count FROM " + tableName)){ //NON-NLS
|
||||
return resultSet.getInt("count"); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all rows from a given table in the SQLite database. If only a
|
||||
* subset of rows are desired, see the overloaded function below.
|
||||
*
|
||||
* @param tableName
|
||||
* @return List of rows, where each row is
|
||||
* represented as a column-value map.
|
||||
* @throws SQLException
|
||||
*/
|
||||
public List<Map<String, Object>> getRowsFromTable(String tableName) throws SQLException {
|
||||
|
||||
//This method does not directly call its overloaded counterpart
|
||||
//since the second parameter would need to be retreived from a call to
|
||||
//getTableRowCount().
|
||||
try(Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT * FROM " + tableName)) { //NON-NLS
|
||||
return resultSetToList(resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a subset of the rows from a given table in the SQLite database.
|
||||
*
|
||||
* @param tableName
|
||||
* @param startRow Desired start index (rows begin at 1)
|
||||
* @param numRowsToRead Number of rows past the start index
|
||||
* @return List of rows, where each row is
|
||||
* represented as a column-value map.
|
||||
* @throws SQLException
|
||||
*/
|
||||
public List<Map<String, Object>> getRowsFromTable(String tableName,
|
||||
int startRow, int numRowsToRead) throws SQLException{
|
||||
|
||||
try(Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(
|
||||
"SELECT * FROM " + tableName //NON-NLS
|
||||
+ " LIMIT " + Integer.toString(numRowsToRead) //NON-NLS
|
||||
+ " OFFSET " + Integer.toString(startRow - 1))) { //NON-NLS
|
||||
return resultSetToList(resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a ResultSet (row results from a table read) into a list.
|
||||
*
|
||||
* @param resultSet row results from a table read
|
||||
* @return List of rows, where each row is
|
||||
* represented as a column-value map.
|
||||
* @throws SQLException occurs if ResultSet is closed while attempting to
|
||||
* access it's data.
|
||||
*/
|
||||
@NbBundle.Messages("SQLiteReader.BlobNotShown.message=BLOB Data not shown")
|
||||
private List<Map<String, Object>> resultSetToList(ResultSet resultSet) throws SQLException {
|
||||
|
||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||
int columns = metaData.getColumnCount();
|
||||
List<Map<String, Object>> rowMap = new ArrayList<>();
|
||||
while (resultSet.next()) {
|
||||
Map<String, Object> row = new LinkedHashMap<>(columns);
|
||||
for (int i = 1; i <= columns; ++i) {
|
||||
if (resultSet.getObject(i) == null) {
|
||||
row.put(metaData.getColumnName(i), "");
|
||||
} else {
|
||||
if (metaData.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) {
|
||||
row.put(metaData.getColumnName(i), Bundle.SQLiteReader_BlobNotShown_message());
|
||||
} else {
|
||||
row.put(metaData.getColumnName(i), resultSet.getObject(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
rowMap.add(row);
|
||||
}
|
||||
|
||||
return rowMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes underlying JDBC connection.
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Override
|
||||
public void close() throws SQLException {
|
||||
connection.close();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user