Sleuth Kit Java Bindings (JNI)  4.10.2
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 
252  String createSQL = "CREATE TABLE IF NOT EXISTS " + tableName + " " + tableSchema;
253  try (CaseDbConnection connection = tskDB.getConnection();
254  Statement statement = connection.createStatement();) {
255  statement.execute(createSQL);
256  } catch (SQLException ex) {
257  throw new TskCoreException("Error creating table " + tableName, ex);
258  } finally {
260  }
261  }
262 
271  public void alterTable(final String tableName, final String alterSQL) throws TskCoreException {
272 
273  CaseDbTransaction localTrans = tskDB.beginTransaction();
274  try {
275  alterTable(tableName, alterSQL, localTrans);
276  localTrans.commit();
277  localTrans = null;
278  } finally {
279  if (null != localTrans) {
280  try {
281  localTrans.rollback();
282  } catch (TskCoreException ex) {
283  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
284  }
285  }
286  }
287  }
288 
298  public void alterTable(final String tableName, final String alterSQL, final CaseDbTransaction transaction) throws TskCoreException {
299 
300  validateTableName(tableName);
301  validateSQL(alterSQL);
302 
303  CaseDbConnection connection = transaction.getConnection();
304 
305  Statement statement = null;
306  String sql = "ALTER TABLE " + tableName + " " + alterSQL;
307 
308  try {
309  statement = connection.createStatement();
310  statement.execute(sql);
311  } catch (SQLException ex) {
312  // SQLite occasionally returns false for columnExists() if a table was just created with that column
313  // leading to "duplicate column name" exception.
314  // We ignore this exception
315  if (DbType.SQLITE == tskDB.getDatabaseType() &&
316  alterSQL.toLowerCase().contains("add column") &&
317  ex.getMessage().toLowerCase().contains("duplicate column name")) {
318  logger.log(Level.WARNING, String.format("Column being added by SQL = %s already exists in table %s", alterSQL, tableName));
319  return;
320  }
321  throw new TskCoreException(String.format("Error altering table %s with SQL = %s", tableName, sql), ex);
322  } finally {
323  closeStatement(statement);
324  }
325  }
326 
341  public void createIndex(final String indexName, final String tableName, final String colsSQL) throws TskCoreException {
342 
343  validateTableName(tableName);
344  validateIndexName(indexName);
345  validateSQL(colsSQL);
346 
348  String indexSQL = "CREATE INDEX IF NOT EXISTS " + indexName + " ON " + tableName + " " + colsSQL; // NON-NLS
349  try (CaseDbConnection connection = tskDB.getConnection();
350  Statement statement = connection.createStatement(); ) {
351  statement.execute(indexSQL);
352  } catch (SQLException ex) {
353  throw new TskCoreException("Error creating index " + tableName, ex);
354  } finally {
356  }
357  }
358 
369  public long insert(final String tableName, final String sql) throws TskCoreException {
370 
371  CaseDbTransaction localTrans = tskDB.beginTransaction();
372  try {
373  long rowId = insert(tableName, sql, localTrans);
374  localTrans.commit();
375  localTrans = null;
376  return rowId;
377  } finally {
378  if (null != localTrans) {
379  try {
380  localTrans.rollback();
381  } catch (TskCoreException ex) {
382  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
383  }
384  }
385  }
386 
387  }
388 
405  public long insert(final String tableName, final String sql, final CaseDbTransaction transaction) throws TskCoreException {
406  long rowId = 0;
407 
408  validateTableName(tableName);
409  validateSQL(sql);
410 
411  CaseDbConnection connection = transaction.getConnection();
412 
413  PreparedStatement statement = null;
414  ResultSet resultSet;
415  String insertSQL = "INSERT";
416  if (DbType.SQLITE == tskDB.getDatabaseType()) {
417  insertSQL += " OR IGNORE";
418  }
419 
420  insertSQL = insertSQL+ " INTO " + tableName + " " + sql; // NON-NLS
421  try {
422  statement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
423  connection.executeUpdate(statement);
424 
425  resultSet = statement.getGeneratedKeys();
426  if (resultSet.next()) {
427  rowId = resultSet.getLong(1); //last_insert_rowid()
428  }
429  } catch (SQLException ex) {
430  throw new TskCoreException("Error inserting row in table " + tableName + " with sql = "+ insertSQL, ex);
431  } finally {
432  closeStatement(statement);
433  }
434 
435  return rowId;
436  }
437 
452  public long insertOrUpdate(final String tableName, final String sql) throws TskCoreException {
453 
454  CaseDbTransaction localTrans = tskDB.beginTransaction();
455  try {
456  long rowId = insertOrUpdate(tableName, sql, localTrans);
457  localTrans.commit();
458  localTrans = null;
459  return rowId;
460  } finally {
461  if (null != localTrans) {
462  try {
463  localTrans.rollback();
464  } catch (TskCoreException ex) {
465  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
466  }
467  }
468  }
469 
470  }
471 
488  public long insertOrUpdate(final String tableName, final String sql, final CaseDbTransaction transaction) throws TskCoreException {
489  long rowId = 0;
490 
491  validateTableName(tableName);
492  validateSQL(sql);
493 
494  CaseDbConnection connection = transaction.getConnection();
495 
496  PreparedStatement statement = null;
497  ResultSet resultSet;
498  String insertSQL = "INSERT";
499  if (DbType.SQLITE == tskDB.getDatabaseType()) {
500  insertSQL += " OR REPLACE";
501  }
502 
503  insertSQL += " INTO " + tableName + " " + sql; // NON-NLS
504  try {
505  statement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
506  connection.executeUpdate(statement);
507 
508  resultSet = statement.getGeneratedKeys();
509  resultSet.next();
510  rowId = resultSet.getLong(1); //last_insert_rowid()
511  } catch (SQLException ex) {
512  throw new TskCoreException("Error inserting row in table " + tableName + " with sql = "+ insertSQL, ex);
513  } finally {
514  closeStatement(statement);
515  }
516 
517  return rowId;
518  }
519 
528  public void update(final String tableName, final String sql) throws TskCoreException {
529  CaseDbTransaction localTrans = tskDB.beginTransaction();
530  try {
531  update(tableName, sql, localTrans);
532  localTrans.commit();
533  localTrans = null;
534  } finally {
535  if (null != localTrans) {
536  try {
537  localTrans.rollback();
538  } catch (TskCoreException ex) {
539  logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex);
540  }
541  }
542  }
543  }
544 
545 
556  public void update(final String tableName, final String sql, CaseDbTransaction transaction ) throws TskCoreException {
557 
558  validateTableName(tableName);
559  validateSQL(sql);
560 
561  CaseDbConnection connection = transaction.getConnection();
562 
563  Statement statement = null;
564  String updateSQL = "UPDATE " + tableName + " " + sql; // NON-NLS
565 
566  try {
567  statement = connection.createStatement();
568  statement.executeUpdate(updateSQL);
569  } catch (SQLException ex) {
570  throw new TskCoreException("Error Updating table " + tableName, ex);
571  } finally {
572  closeStatement(statement);
573  }
574  }
575 
584  public void select(final String sql, final CaseDbAccessQueryCallback queryCallback) throws TskCoreException {
585 
586  if (queryCallback == null) {
587  throw new TskCoreException("Callback is null");
588  }
589 
590  validateSQL(sql);
591 
593  String selectSQL = "SELECT " + sql; // NON-NLS
594  try (CaseDbConnection connection = tskDB.getConnection();
595  Statement statement = connection.createStatement();
596  ResultSet resultSet = statement.executeQuery(selectSQL)) {
597  queryCallback.process(resultSet);
598  } catch (SQLException ex) {
599  throw new TskCoreException("Error running SELECT query.", ex);
600  } finally {
602  }
603  }
604 
613  public void delete(final String tableName, final String sql ) throws TskCoreException {
614  validateTableName(tableName);
615  validateSQL(sql);
616 
618  String deleteSQL = "DELETE FROM " + tableName + " " + sql; // NON-NLS
619  try (CaseDbConnection connection = tskDB.getConnection();
620  Statement statement = connection.createStatement();) {
621  statement.executeUpdate(deleteSQL);
622  } catch (SQLException ex) {
623  throw new TskCoreException("Error deleting row from table " + tableName, ex);
624  } finally {
626  }
627  }
628 
637  private void validateTableName(String tableName) throws TskCoreException {
638 
639  if (SleuthkitCase.getCoreTableNames().contains(tableName.toLowerCase())) {
640  throw new TskCoreException("Attempt to modify a core TSK table " + tableName);
641  }
642  if (tableName.toLowerCase().startsWith("tsk_")) {
643  throw new TskCoreException("Modifying tables with tsk_ prefix is not allowed. ");
644  }
645  }
646 
655  private void validateIndexName(String indexName) throws TskCoreException {
656 
657  if (indexName.isEmpty()) {
658  throw new TskCoreException("Invalid index name " + indexName);
659  }
660 
661  if (SleuthkitCase.getCoreIndexNames().contains(indexName.toLowerCase())) {
662  throw new TskCoreException("Attempt to modify a core TSK index " + indexName);
663  }
664  }
665 
673  private void validateSQL(String sql) throws TskCoreException {
674  /*
675  * TODO (JIRA-5950): Need SQL injection defense in CaseDbAccessManager
676  */
677  }
678 
679 }
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-2021 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.