Sleuth Kit Java Bindings (JNI)  4.6
Java bindings for using The Sleuth Kit
CaseDbAccessManager.java
Go to the documentation of this file.
1 /*
2  * Sleuth Kit Data Model
3  *
4  * Copyright 2018-2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.datamodel;
20 
21 import java.sql.PreparedStatement;
22 import java.sql.ResultSet;
23 import java.sql.SQLException;
24 import java.sql.Statement;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
29 import static org.sleuthkit.datamodel.SleuthkitCase.closeStatement;
31 
37 public final class CaseDbAccessManager {
38 
42  public interface CaseDbAccessQueryCallback {
43 
52  void process(ResultSet resultSet);
53 
54  }
55 
56 
57  private static final Logger logger = Logger.getLogger(CaseDbAccessManager.class.getName());
58 
59  private final SleuthkitCase tskDB;
60 
68  this.tskDB = skCase;
69  }
70 
80  public boolean columnExists(String tableName, String columnName) throws TskCoreException {
81 
82  boolean doesColumnExists = false;
83  CaseDbTransaction localTrans = tskDB.beginTransaction();
84  try {
85  doesColumnExists = columnExists(tableName, columnName, localTrans);
86  localTrans.commit();
87  localTrans = null;
88  }
89  finally {
90  if (null != localTrans) {
91  try {
92  localTrans.rollback();
93  } catch (TskCoreException ex) {
94  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
95  }
96  }
97  }
98 
99  return doesColumnExists;
100  }
101 
112  public boolean columnExists(String tableName, String columnName, CaseDbTransaction transaction) throws TskCoreException {
113 
114  boolean columnExists = false;
115  Statement statement = null;
116  ResultSet resultSet = null;
117  try {
118  CaseDbConnection connection = transaction.getConnection();
119  statement = connection.createStatement();
120  if (DbType.SQLITE == tskDB.getDatabaseType()) {
121  String tableInfoQuery = "PRAGMA table_info(%s)"; //NON-NLS
122  resultSet = statement.executeQuery(String.format(tableInfoQuery, tableName));
123  while (resultSet.next()) {
124  if (resultSet.getString("name").equalsIgnoreCase(columnName)) {
125  columnExists = true;
126  break;
127  }
128  }
129  }
130  else {
131  String tableInfoQueryTemplate = "SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='%s' AND column_name='%s')"; //NON-NLS
132  resultSet = statement.executeQuery(String.format(tableInfoQueryTemplate, tableName.toLowerCase(), columnName.toLowerCase()));
133  if (resultSet.next()) {
134  columnExists = resultSet.getBoolean(1);
135  }
136  }
137  }
138  catch (SQLException ex) {
139  throw new TskCoreException("Error checking if column " + columnName + "exists ", ex);
140  }
141  finally {
142  if (resultSet != null) {
143  try {
144  resultSet.close();
145  } catch (SQLException ex2) {
146  logger.log(Level.WARNING, "Failed to to close resultset after checking column", ex2);
147  }
148  }
149  closeStatement(statement);
150  }
151  return columnExists;
152  }
153 
162  public boolean tableExists(String tableName) throws TskCoreException {
163 
164  boolean doesTableExist = false;
165  CaseDbTransaction localTrans = tskDB.beginTransaction();
166  try {
167  doesTableExist = tableExists(tableName, localTrans);
168  localTrans.commit();
169  localTrans = null;
170  }
171  finally {
172  if (null != localTrans) {
173  try {
174  localTrans.rollback();
175  } catch (TskCoreException ex) {
176  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex); //NON-NLS
177  }
178  }
179  }
180 
181  return doesTableExist;
182  }
183 
193  public boolean tableExists(String tableName, CaseDbTransaction transaction) throws TskCoreException {
194 
195  boolean tableExists = false;
196  Statement statement = null;
197  ResultSet resultSet = null;
198  try {
199  CaseDbConnection connection = transaction.getConnection();
200  statement = connection.createStatement();
201  if (DbType.SQLITE == tskDB.getDatabaseType()) {
202  resultSet = statement.executeQuery("SELECT name FROM sqlite_master WHERE type='table'"); //NON-NLS
203  while (resultSet.next()) {
204  if (resultSet.getString("name").equalsIgnoreCase(tableName)) { //NON-NLS
205  tableExists = true;
206  break;
207  }
208  }
209  }
210  else {
211  String tableInfoQueryTemplate = "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='%s')"; //NON-NLS
212  resultSet = statement.executeQuery(String.format(tableInfoQueryTemplate, tableName.toLowerCase()));
213  if (resultSet.next()) {
214  tableExists = resultSet.getBoolean(1);
215  }
216  }
217  }
218  catch (SQLException ex) {
219  throw new TskCoreException("Error checking if table " + tableName + "exists ", ex);
220  } finally {
221  if (resultSet != null) {
222  try {
223  resultSet.close();
224  } catch (SQLException ex2) {
225  logger.log(Level.WARNING, "Failed to to close resultset after checking table", ex2);
226  }
227  }
228  closeStatement(statement);
229  }
230  return tableExists;
231  }
232 
246  public void createTable(final String tableName, final String tableSchema) throws TskCoreException {
247 
248  validateTableName(tableName);
249  validateSQL(tableSchema);
250 
251  CaseDbConnection connection = tskDB.getConnection();
253 
254  Statement statement = null;
255  String createSQL = "CREATE TABLE IF NOT EXISTS " + tableName + " " + tableSchema;
256  try {
257  statement = connection.createStatement();
258  statement.execute(createSQL);
259  } catch (SQLException ex) {
260  throw new TskCoreException("Error creating table " + tableName, ex);
261  } finally {
262  closeStatement(statement);
263  connection.close();
265  }
266 
267  }
268 
277  public void alterTable(final String tableName, final String alterSQL) throws TskCoreException {
278 
279  CaseDbTransaction localTrans = tskDB.beginTransaction();
280  try {
281  alterTable(tableName, alterSQL, localTrans);
282  localTrans.commit();
283  localTrans = null;
284  } finally {
285  if (null != localTrans) {
286  try {
287  localTrans.rollback();
288  } catch (TskCoreException ex) {
289  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
290  }
291  }
292  }
293  }
294 
304  public void alterTable(final String tableName, final String alterSQL, final CaseDbTransaction transaction) throws TskCoreException {
305 
306  validateTableName(tableName);
307  validateSQL(alterSQL);
308 
309  CaseDbConnection connection = transaction.getConnection();
310  transaction.acquireSingleUserCaseWriteLock();
311 
312  Statement statement = null;
313  String sql = "ALTER TABLE " + tableName + " " + alterSQL;
314 
315  try {
316  statement = connection.createStatement();
317  statement.execute(sql);
318  } catch (SQLException ex) {
319  // SQLite occasionally returns false for columnExists() if a table was just created with that column
320  // leading to "duplicate column name" exception.
321  // We ignore this exception
322  if (DbType.SQLITE == tskDB.getDatabaseType() &&
323  alterSQL.toLowerCase().contains("add column") &&
324  ex.getMessage().toLowerCase().contains("duplicate column name")) {
325  logger.log(Level.WARNING, String.format("Column being added by SQL = %s already exists in table %s", alterSQL, tableName));
326  return;
327  }
328  throw new TskCoreException(String.format("Error altering table %s with SQL = %s", tableName, sql), ex);
329  } finally {
330  closeStatement(statement);
331  // NOTE: write lock will be released by transaction
332  }
333  }
334 
349  public void createIndex(final String indexName, final String tableName, final String colsSQL) throws TskCoreException {
350 
351  validateTableName(tableName);
352  validateIndexName(indexName);
353  validateSQL(colsSQL);
354 
355  CaseDbConnection connection = tskDB.getConnection();
357 
358  Statement statement = null;
359  String indexSQL = "CREATE INDEX IF NOT EXISTS " + indexName + " ON " + tableName + " " + colsSQL; // NON-NLS
360  try {
361  statement = connection.createStatement();
362  statement.execute(indexSQL);
363  } catch (SQLException ex) {
364  throw new TskCoreException("Error creating index " + tableName, ex);
365  } finally {
366  closeStatement(statement);
367  connection.close();
369  }
370  }
371 
382  public long insert(final String tableName, final String sql) throws TskCoreException {
383 
384  CaseDbTransaction localTrans = tskDB.beginTransaction();
385  try {
386  long rowId = insert(tableName, sql, localTrans);
387  localTrans.commit();
388  localTrans = null;
389  return rowId;
390  } finally {
391  if (null != localTrans) {
392  try {
393  localTrans.rollback();
394  } catch (TskCoreException ex) {
395  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
396  }
397  }
398  }
399 
400  }
401 
418  public long insert(final String tableName, final String sql, final CaseDbTransaction transaction) throws TskCoreException {
419  long rowId = 0;
420 
421  validateTableName(tableName);
422  validateSQL(sql);
423 
424  CaseDbConnection connection = transaction.getConnection();
425  transaction.acquireSingleUserCaseWriteLock();
426 
427  PreparedStatement statement = null;
428  ResultSet resultSet;
429  String insertSQL = "INSERT";
430  if (DbType.SQLITE == tskDB.getDatabaseType()) {
431  insertSQL += " OR IGNORE";
432  }
433 
434  insertSQL = insertSQL+ " INTO " + tableName + " " + sql; // NON-NLS
435  try {
436  statement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
437  connection.executeUpdate(statement);
438 
439  resultSet = statement.getGeneratedKeys();
440  if (resultSet.next()) {
441  rowId = resultSet.getLong(1); //last_insert_rowid()
442  }
443  } catch (SQLException ex) {
444  throw new TskCoreException("Error inserting row in table " + tableName + " with sql = "+ insertSQL, ex);
445  } finally {
446  closeStatement(statement);
447  // NOTE: write lock will be released by transaction
448  }
449 
450  return rowId;
451  }
452 
467  public long insertOrUpdate(final String tableName, final String sql) throws TskCoreException {
468 
469  CaseDbTransaction localTrans = tskDB.beginTransaction();
470  try {
471  long rowId = insertOrUpdate(tableName, sql, localTrans);
472  localTrans.commit();
473  localTrans = null;
474  return rowId;
475  } finally {
476  if (null != localTrans) {
477  try {
478  localTrans.rollback();
479  } catch (TskCoreException ex) {
480  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
481  }
482  }
483  }
484 
485  }
486 
503  public long insertOrUpdate(final String tableName, final String sql, final CaseDbTransaction transaction) throws TskCoreException {
504  long rowId = 0;
505 
506  validateTableName(tableName);
507  validateSQL(sql);
508 
509  CaseDbConnection connection = transaction.getConnection();
510  transaction.acquireSingleUserCaseWriteLock();
511 
512  PreparedStatement statement = null;
513  ResultSet resultSet;
514  String insertSQL = "INSERT";
515  if (DbType.SQLITE == tskDB.getDatabaseType()) {
516  insertSQL += " OR REPLACE";
517  }
518 
519  insertSQL += " INTO " + tableName + " " + sql; // NON-NLS
520  try {
521  statement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
522  connection.executeUpdate(statement);
523 
524  resultSet = statement.getGeneratedKeys();
525  resultSet.next();
526  rowId = resultSet.getLong(1); //last_insert_rowid()
527  } catch (SQLException ex) {
528  throw new TskCoreException("Error inserting row in table " + tableName + " with sql = "+ insertSQL, ex);
529  } finally {
530  closeStatement(statement);
531  // NOTE: write lock will be released by transaction
532  }
533 
534  return rowId;
535  }
536 
545  public void update(final String tableName, final String sql) throws TskCoreException {
546  CaseDbTransaction localTrans = tskDB.beginTransaction();
547  try {
548  update(tableName, sql, localTrans);
549  localTrans.commit();
550  localTrans = null;
551  } finally {
552  if (null != localTrans) {
553  try {
554  localTrans.rollback();
555  } catch (TskCoreException ex) {
556  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
557  }
558  }
559  }
560  }
561 
562 
573  public void update(final String tableName, final String sql, CaseDbTransaction transaction ) throws TskCoreException {
574 
575  validateTableName(tableName);
576  validateSQL(sql);
577 
578  CaseDbConnection connection = transaction.getConnection();
579  transaction.acquireSingleUserCaseWriteLock();
580 
581  Statement statement = null;
582  String updateSQL = "UPDATE " + tableName + " " + sql; // NON-NLS
583 
584  try {
585  statement = connection.createStatement();
586  statement.executeUpdate(updateSQL);
587  } catch (SQLException ex) {
588  throw new TskCoreException("Error Updating table " + tableName, ex);
589  } finally {
590  closeStatement(statement);
591  // NOTE: write lock will be released by transaction
592  }
593  }
594 
603  public void select(final String sql, final CaseDbAccessQueryCallback queryCallback) throws TskCoreException {
604 
605  if (queryCallback == null) {
606  throw new TskCoreException("Callback is null");
607  }
608 
609  validateSQL(sql);
610 
611  CaseDbConnection connection = tskDB.getConnection();
613 
614  Statement statement = null;
615  ResultSet resultSet;
616  String selectSQL = "SELECT " + sql; // NON-NLS
617  try {
618  statement = connection.createStatement();
619  resultSet = statement.executeQuery(selectSQL);
620  queryCallback.process(resultSet);
621  } catch (SQLException ex) {
622  throw new TskCoreException("Error running SELECT query.", ex);
623  } finally {
624  closeStatement(statement);
625  connection.close();
627  }
628  }
629 
638  public void delete(final String tableName, final String sql ) throws TskCoreException {
639  validateTableName(tableName);
640  validateSQL(sql);
641 
642  CaseDbConnection connection = tskDB.getConnection();
644 
645  Statement statement = null;
646  String deleteSQL = "DELETE FROM " + tableName + " " + sql; // NON-NLS
647  try {
648  statement = connection.createStatement();
649  statement.executeUpdate(deleteSQL);
650  } catch (SQLException ex) {
651  throw new TskCoreException("Error deleting row from table " + tableName, ex);
652  } finally {
653  closeStatement(statement);
654  connection.close();
656  }
657  }
658 
667  private void validateTableName(String tableName) throws TskCoreException {
668 
669  if (SleuthkitCase.getCoreTableNames().contains(tableName.toLowerCase())) {
670  throw new TskCoreException("Attempt to modify a core TSK table " + tableName);
671  }
672  if (tableName.toLowerCase().startsWith("tsk_")) {
673  throw new TskCoreException("Modifying tables with tsk_ prefix is not allowed. ");
674  }
675  }
676 
685  private void validateIndexName(String indexName) throws TskCoreException {
686 
687  if (indexName.isEmpty()) {
688  throw new TskCoreException("Invalid index name " + indexName);
689  }
690 
691  if (SleuthkitCase.getCoreIndexNames().contains(indexName.toLowerCase())) {
692  throw new TskCoreException("Attempt to modify a core TSK index " + indexName);
693  }
694  }
695 
703  private void validateSQL(String sql) throws TskCoreException {
704  /*
705  * TODO (JIRA-5950): Need SQL injection defense in CaseDbAccessManager
706  */
707  }
708 
709 }
boolean tableExists(String tableName, CaseDbTransaction transaction)
long insertOrUpdate(final String tableName, final String sql)
void update(final String tableName, final String sql)
long insert(final String tableName, final String sql, final CaseDbTransaction transaction)
void alterTable(final String tableName, final String alterSQL, final CaseDbTransaction transaction)
void createIndex(final String indexName, final String tableName, final String colsSQL)
boolean columnExists(String tableName, String columnName)
void createTable(final String tableName, final String tableSchema)
long insertOrUpdate(final String tableName, final String sql, final CaseDbTransaction transaction)
void alterTable(final String tableName, final String alterSQL)
void select(final String sql, final CaseDbAccessQueryCallback queryCallback)
void update(final String tableName, final String sql, CaseDbTransaction transaction)
long insert(final String tableName, final String sql)
boolean columnExists(String tableName, String columnName, CaseDbTransaction transaction)

Copyright © 2011-2018 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.