diff --git a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java index 4d7d0adaa0c6a508520ddfc80f22086f323d2028..cf79c881a0a384e37a78eaa1216b250a86619e5a 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java @@ -21,6 +21,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.ref.SoftReference; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; @@ -37,6 +38,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; +import org.sleuthkit.datamodel.TskData.CollectedStatus; import org.sleuthkit.datamodel.TskData.FileKnown; import org.sleuthkit.datamodel.TskData.TSK_FS_META_FLAG_ENUM; import org.sleuthkit.datamodel.TskData.TSK_FS_META_TYPE_ENUM; @@ -116,6 +118,10 @@ public abstract class AbstractFile extends AbstractContent { private volatile String uniquePath; private volatile FileSystem parentFileSystem; + + private final boolean tryContentProviderStream; + private Object contentProviderStreamLock = new Object(); + private SoftReference<ContentProviderStream> contentProviderStreamRef = null; /** * Initializes common fields used by AbstactFile implementations (objects in @@ -227,6 +233,9 @@ public abstract class AbstractFile extends AbstractContent { this.ownerUid = ownerUid; this.osAccountObjId = osAccountObjectId; this.collected = collected; + // any item that is marked as YES_REPO and there is a custom content provider for the db will attempt to use the content provider to provide data + // this will be flipped to false if there is no content provider stream from the content provider for this file + this.tryContentProviderStream = collected == CollectedStatus.YES_REPO && db.getContentProvider() != null; if (Objects.nonNull(fileAttributes) && !fileAttributes.isEmpty()) { this.fileAttributesCache.addAll(fileAttributes); loadedAttributesCacheFromDb = true; @@ -1062,12 +1071,48 @@ void removeMetaFlag(TSK_FS_META_FLAG_ENUM metaFlag) { short getMetaFlagsAsInt() { return TSK_FS_META_FLAG_ENUM.toInt(metaFlags); } + + /** + * Attempts to get cached or load the content provider stream for this file. + * If none exists, returns null. + * + * NOTE: Does not check the value for tryContentProviderStream before + * attempting. + * + * @return The content stream for this file or null if none exists. + * + * @throws TskCoreException + */ + private ContentProviderStream getContentProviderStream() throws TskCoreException { + synchronized (contentProviderStreamLock) { + // try to get soft reference content provider stream + ContentProviderStream contentProviderStream = contentProviderStreamRef == null ? null : contentProviderStreamRef.get(); + // load if not cached and then cache if present + if (contentProviderStream == null) { + ContentStreamProvider provider = getSleuthkitCase().getContentProvider(); + contentProviderStream = provider == null ? null : provider.getContentStream(this).orElse(null); + + if (contentProviderStream == null) { + throw new TskCoreException(MessageFormat.format("Could not get content provider string for file with obj id: {0}, path: {1}", + getId(), + getUniquePath())); + } + + this.contentProviderStreamRef = new SoftReference<>(contentProviderStream); + } + return contentProviderStream; + } + } + @Override public final int read(byte[] buf, long offset, long len) throws TskCoreException { - //template method - //if localPath is set, use local, otherwise, use readCustom() supplied by derived class - if (localPathSet) { + // try to use content provider stream if should use + if (tryContentProviderStream) { + ContentProviderStream contentProviderStream = getContentProviderStream(); + return contentProviderStream.read(buf, offset, len); + } else if (localPathSet) { + //if localPath is set, use local, otherwise, use readCustom() supplied by derived class return readLocal(buf, offset, len); } else { return readInt(buf, offset, len); @@ -1233,13 +1278,14 @@ final void setEncodingType(TskData.EncodingType encodingType) { } /** - * Check if the file exists. If non-local always true, if local, checks if - * actual local path exists + * Check if the file exists. If non-local or file is marked with YES_REPO + * and there is a content provider always true, if local, checks if actual + * local path exists * * @return true if the file exists, false otherwise */ public boolean exists() { - if (!localPathSet) { + if (tryContentProviderStream || !localPathSet) { return true; } else { try { @@ -1254,13 +1300,13 @@ public boolean exists() { /** * Check if the file exists and is readable. If non-local (e.g. within an - * image), always true, if local, checks if actual local path exists and is - * readable + * image) or file is marked with YES_REPO and there is a content provider, + * always true, if local, checks if actual local path exists and is readable * * @return true if the file is readable */ public boolean canRead() { - if (!localPathSet) { + if (tryContentProviderStream || !localPathSet) { return true; } else { try { diff --git a/bindings/java/src/org/sleuthkit/datamodel/ContentProviderStream.java b/bindings/java/src/org/sleuthkit/datamodel/ContentProviderStream.java new file mode 100644 index 0000000000000000000000000000000000000000..5617362036bb1a9f2c55991bd7d39072c5e44552 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/ContentProviderStream.java @@ -0,0 +1,41 @@ +/* + * SleuthKit Java Bindings + * + * Copyright 2023 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.datamodel; + +/** + * Custom provider for content bytes. + */ +@SuppressWarnings("try") +public interface ContentProviderStream extends AutoCloseable { + + /** + * Reads data that this content object is associated with (file contents, + * volume contents, etc.). + * + * @param buf a character array of data (in bytes) to copy read data to + * @param offset byte offset in the content to start reading from + * @param len number of bytes to read into buf. + * + * @return num of bytes read, or -1 on error + * + * @throws TskCoreException if critical error occurred during read in the + * tsk core + */ + public int read(byte[] buf, long offset, long len) throws TskCoreException; +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/ContentStreamProvider.java b/bindings/java/src/org/sleuthkit/datamodel/ContentStreamProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..c7b09bd2bd46e803ee7d64511d871569bd65efe8 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/ContentStreamProvider.java @@ -0,0 +1,38 @@ +/* + * SleuthKit Java Bindings + * + * Copyright 2023 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.datamodel; + +import java.util.Optional; + +/** + * Custom provider for bytes of an abstract file. + */ +public interface ContentStreamProvider { + + /** + * Provides a content stream for a content object or empty if this provider + * has none to provide. + * + * @param content The content. + * + * @return The content stream or empty if no stream can be provided for this + * content. + */ + Optional<ContentProviderStream> getContentStream(Content content) throws TskCoreException; +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java index 7cdfd79903e6f99f3656877a3823784c9e25951e..d76f85de22ea6a8b8af4e45669868167a3a8d2de 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -82,6 +82,7 @@ import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType; import org.sleuthkit.datamodel.IngestModuleInfo.IngestModuleType; import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess; +import org.sleuthkit.datamodel.TimelineManager.TimelineEventAddedEvent; import org.sleuthkit.datamodel.TskData.DbType; import org.sleuthkit.datamodel.TskData.FileKnown; import org.sleuthkit.datamodel.TskData.ObjectType; @@ -203,7 +204,9 @@ public class SleuthkitCase { private final Map<RootDirectoryKey, Long> rootDirectoryMap = new HashMap<>(); private final Cache<Long, Boolean> isRootDirectoryCache = CacheBuilder.newBuilder().maximumSize(200000).expireAfterAccess(5, TimeUnit.MINUTES).build(); - + // custom provider for file bytes (can be null) + private final ContentStreamProvider contentProvider; + /* * First parameter is used to specify the SparseBitSet to use, as object IDs * can be larger than the max size of a SparseBitSet @@ -332,10 +335,11 @@ public static void tryConnect(CaseDbConnectionInfo info) throws TskCoreException * @param caseHandle A handle to a case database object in the native code * SleuthKit layer. * @param dbType The type of database we're dealing with + * @param contentProvider Custom provider for file content (can be null). * * @throws Exception */ - private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbType dbType) throws Exception { + private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbType dbType, ContentStreamProvider contentProvider) throws Exception { Class.forName("org.sqlite.JDBC"); this.dbPath = dbPath; this.dbType = dbType; @@ -345,6 +349,7 @@ private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbTyp this.connections = new SQLiteConnections(dbPath); this.caseHandle = caseHandle; this.caseHandleIdentifier = caseHandle.getCaseDbIdentifier(); + this.contentProvider = contentProvider; init(); logSQLiteJDBCDriverInfo(); } @@ -363,10 +368,11 @@ private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbTyp * @param dbType The type of database we're dealing with SleuthKit * layer. * @param caseDirPath The path to the root case directory. + * @param contentProvider Custom provider for file content (can be null). * * @throws Exception */ - private SleuthkitCase(String host, int port, String dbName, String userName, String password, SleuthkitJNI.CaseDbHandle caseHandle, String caseDirPath, DbType dbType) throws Exception { + private SleuthkitCase(String host, int port, String dbName, String userName, String password, SleuthkitJNI.CaseDbHandle caseHandle, String caseDirPath, DbType dbType, ContentStreamProvider contentProvider) throws Exception { this.dbPath = ""; this.databaseName = dbName; this.dbType = dbType; @@ -374,6 +380,7 @@ private SleuthkitCase(String host, int port, String dbName, String userName, Str this.connections = new PostgreSQLConnections(host, port, dbName, userName, password); this.caseHandle = caseHandle; this.caseHandleIdentifier = caseHandle.getCaseDbIdentifier(); + this.contentProvider = contentProvider; init(); } @@ -406,6 +413,17 @@ private void init() throws Exception { personManager = new PersonManager(this); hostAddressManager = new HostAddressManager(this); } + + /** + * Returns the custom content provider for this case if one exists. + * Otherwise, returns null. + * + * @return The custom content provider for this case if one exists. + * Otherwise, returns null. + */ + ContentStreamProvider getContentProvider() { + return this.contentProvider; + } /** * Returns a set of core table names in the SleuthKit Case database. @@ -2970,9 +2988,24 @@ public void releaseSingleUserCaseReadLock() { * @throws org.sleuthkit.datamodel.TskCoreException */ public static SleuthkitCase openCase(String dbPath) throws TskCoreException { + return openCase(dbPath, null); + } + + /** + * Open an existing case database. + * + * @param dbPath Path to SQLite case database. + * @param contentProvider Custom provider for file content bytes (can be null). + * + * @return Case database object. + * + * @throws org.sleuthkit.datamodel.TskCoreException + */ + @Beta + public static SleuthkitCase openCase(String dbPath, ContentStreamProvider provider) throws TskCoreException { try { final SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(dbPath); - return new SleuthkitCase(dbPath, caseHandle, DbType.SQLITE); + return new SleuthkitCase(dbPath, caseHandle, DbType.SQLITE, provider); } catch (TskUnsupportedSchemaVersionException ex) { //don't wrap in new TskCoreException throw ex; @@ -2993,6 +3026,23 @@ public static SleuthkitCase openCase(String dbPath) throws TskCoreException { * @throws TskCoreException If there is a problem opening the database. */ public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo info, String caseDir) throws TskCoreException { + return openCase(databaseName, info, caseDir, null); + } + + /** + * Open an existing multi-user case database. + * + * @param databaseName The name of the database. + * @param info Connection information for the the database. + * @param caseDir The folder where the case metadata fils is stored. + * @param contentProvider Custom provider for file content bytes (can be null). + * + * @return A case database object. + * + * @throws TskCoreException If there is a problem opening the database. + */ + @Beta + public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo info, String caseDir, ContentStreamProvider contentProvider) throws TskCoreException { try { /* * The flow of this method involves trying to open case and if @@ -3007,7 +3057,7 @@ public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo i * are able, but do not lose any information if unable. */ final SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(databaseName, info); - return new SleuthkitCase(info.getHost(), Integer.parseInt(info.getPort()), databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDir, info.getDbType()); + return new SleuthkitCase(info.getHost(), Integer.parseInt(info.getPort()), databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDir, info.getDbType(), contentProvider); } catch (PropertyVetoException exp) { // In this case, the JDBC driver doesn't support PostgreSQL. Use the generic message here. throw new TskCoreException(exp.getMessage(), exp); @@ -3030,12 +3080,27 @@ public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo i * @throws org.sleuthkit.datamodel.TskCoreException */ public static SleuthkitCase newCase(String dbPath) throws TskCoreException { + return newCase(dbPath, null); + } + + /** + * Creates a new SQLite case database. + * + * @param dbPath Path to where SQlite case database should be created. + * @param contentProvider Custom provider for file bytes (can be null). + * + * @return A case database object. + * + * @throws org.sleuthkit.datamodel.TskCoreException + */ + @Beta + public static SleuthkitCase newCase(String dbPath, ContentStreamProvider contentProvider) throws TskCoreException { try { CaseDatabaseFactory factory = new CaseDatabaseFactory(dbPath); factory.createCaseDatabase(); SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(dbPath); - return new SleuthkitCase(dbPath, caseHandle, DbType.SQLITE); + return new SleuthkitCase(dbPath, caseHandle, DbType.SQLITE, contentProvider); } catch (Exception ex) { throw new TskCoreException("Failed to create case database at " + dbPath, ex); } @@ -3057,6 +3122,28 @@ public static SleuthkitCase newCase(String dbPath) throws TskCoreException { * @throws org.sleuthkit.datamodel.TskCoreException */ public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info, String caseDirPath) throws TskCoreException { + return newCase(caseName, info, caseDirPath, null); + } + + + /** + * Creates a new PostgreSQL case database. + * + * @param caseName The name of the case. It will be used to create a case + * database name that can be safely used in SQL commands + * and will not be subject to name collisions on the case + * database server. Use getDatabaseName to get the + * created name. + * @param info The information to connect to the database. + * @param caseDirPath The case directory path. + * @param contentProvider Custom provider for file bytes (can be null). + * + * @return A case database object. + * + * @throws org.sleuthkit.datamodel.TskCoreException + */ + @Beta + public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info, String caseDirPath, ContentStreamProvider contentProvider) throws TskCoreException { String databaseName = createCaseDataBaseName(caseName); try { /** @@ -3076,7 +3163,7 @@ public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info, final SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(databaseName, info); return new SleuthkitCase(info.getHost(), Integer.parseInt(info.getPort()), - databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDirPath, info.getDbType()); + databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDirPath, info.getDbType(), contentProvider); } catch (PropertyVetoException exp) { // In this case, the JDBC driver doesn't support PostgreSQL. Use the generic message here. throw new TskCoreException(exp.getMessage(), exp); @@ -9979,34 +10066,50 @@ public List<Image> getImages() throws TskCoreException { * within tsk core and the update fails */ public void setImagePaths(long obj_id, List<String> paths) throws TskCoreException { - CaseDbConnection connection = null; - acquireSingleUserCaseWriteLock(); - PreparedStatement statement; + CaseDbTransaction transaction = beginTransaction(); try { - connection = connections.getConnection(); - connection.beginTransaction(); - statement = connection.getPreparedStatement(PREPARED_STATEMENT.DELETE_IMAGE_NAME); + setImagePaths(obj_id, paths, transaction); + transaction.commit(); + transaction = null; + } finally { + if (transaction != null) { + transaction.rollback(); + } + } + } + + /** + * Set the file paths for the image given by obj_id + * + * @param obj_id the ID of the image to update + * @param paths the fully qualified path to the files that make up the + * image + * @param trans The case database transaction. + * + * @throws TskCoreException exception thrown when critical error occurs + * within tsk core and the update fails + */ + @Beta + public void setImagePaths(long obj_id, List<String> paths, CaseDbTransaction trans) throws TskCoreException { + try { + PreparedStatement statement = trans.getConnection().getPreparedStatement(PREPARED_STATEMENT.DELETE_IMAGE_NAME); statement.clearParameters(); statement.setLong(1, obj_id); - connection.executeUpdate(statement); + trans.getConnection().executeUpdate(statement); for (int i = 0; i < paths.size(); i++) { - statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_NAME); + statement = trans.getConnection().getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_NAME); statement.clearParameters(); statement.setLong(1, obj_id); statement.setString(2, paths.get(i)); statement.setLong(3, i); - connection.executeUpdate(statement); + trans.getConnection().executeUpdate(statement); } - connection.commitTransaction(); } catch (SQLException ex) { - rollbackTransaction(connection); throw new TskCoreException("Error updating image paths.", ex); - } finally { - closeConnection(connection); - releaseSingleUserCaseWriteLock(); - } + } } - + + /** * Deletes a datasource from the open case, the database has foreign keys * with a delete cascade so that all the tables that have a datasource @@ -13862,6 +13965,7 @@ public static final class CaseDbTransaction { // Score changes are stored as a map keyed by objId to prevent duplicates. private Map<Long, ScoreChange> scoreChangeMap = new HashMap<>(); private List<Host> hostsAdded = new ArrayList<>(); + private List<TimelineEventAddedEvent> timelineEvents = new ArrayList<>(); private List<OsAccount> accountsChanged = new ArrayList<>(); private List<OsAccount> accountsAdded = new ArrayList<>(); private List<TskEvent.MergedAccountsPair> accountsMerged = new ArrayList<>(); @@ -13909,6 +14013,16 @@ CaseDbConnection getConnection() { void registerScoreChange(ScoreChange scoreChange) { scoreChangeMap.put(scoreChange.getObjectId(), scoreChange); } + + /** + * Register timeline event to be fired when transaction finishes. + * @param timelineEvent The timeline event. + */ + void registerTimelineEvent(TimelineEventAddedEvent timelineEvent) { + if (timelineEvent != null) { + timelineEvents.add(timelineEvent); + } + } /** * Saves a host that has been added as a part of this transaction. @@ -14029,6 +14143,11 @@ public void commit() throws TskCoreException { if(!deletedBlackboardArtifactTagIds.isEmpty()) { sleuthkitCase.fireTSKEvent(new TskEvent.BlackboardArtifactTagsDeletedTskEvent(deletedBlackboardArtifactTagIds)); } + if (!timelineEvents.isEmpty()) { + for (TimelineEventAddedEvent evt : timelineEvents) { + sleuthkitCase.fireTSKEvent(evt); + } + } if (!hostsAdded.isEmpty()) { sleuthkitCase.fireTSKEvent(new TskEvent.HostsAddedTskEvent(hostsAdded)); } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java index cf560705d3ad91e16ead90f9087e334191a62ec2..0b215868d60e5d045037766c16c7dee15ea31f7f 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java @@ -26,6 +26,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; +import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; @@ -48,6 +49,7 @@ import static org.sleuthkit.datamodel.CollectionUtils.isNotEmpty; import static org.sleuthkit.datamodel.CommManagerSqlStringUtils.buildCSVString; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import static org.sleuthkit.datamodel.SleuthkitCase.escapeSingleQuotes; /** @@ -790,6 +792,58 @@ private Optional<TimelineEvent> addOtherEventDesc(BlackboardArtifact artifact) t return addArtifactEvent(evtWDesc, evtType, artifact); } + + + /** + * Adds a timeline event to the database in a transaction. + * @param eventType The event type. + * @param shortDesc The short description. + * @param medDesc The medium description. + * @param longDesc The long description. + * @param dataSourceId The data source id of the event. + * @param contentId The content id of the event. + * @param artifactId The artifact id of the event (can be null). + * @param time Unix epoch offset time of the event in seconds. + * @param hashHit True if a hash hit. + * @param tagged True if tagged. + * @param trans The transaction. + * @return The added event. + * @throws TskCoreException + */ + @Beta + public TimelineEvent addTimelineEvent( + TimelineEventType eventType, String shortDesc, String medDesc, String longDesc, + long dataSourceId, long contentId, Long artifactId, long time, + boolean hashHit, boolean tagged, + CaseDbTransaction trans + ) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try { + Long descriptionID = addEventDescription(dataSourceId, contentId, artifactId, + longDesc, medDesc, shortDesc, hashHit, tagged, trans.getConnection()); + + if (descriptionID == null) { + descriptionID = getEventDescription(dataSourceId, contentId, artifactId, longDesc, trans.getConnection()); + } + if (descriptionID != null) { + long eventID = addEventWithExistingDescription(time, eventType, descriptionID, trans.getConnection()); + TimelineEvent timelineEvt = new TimelineEvent(eventID, descriptionID, contentId, artifactId, time, eventType, + longDesc, medDesc, shortDesc, hashHit, tagged); + + trans.registerTimelineEvent(new TimelineEventAddedEvent(timelineEvt)); + return timelineEvt; + } else { + throw new TskCoreException(MessageFormat.format( + "Failed to get event description for [shortDesc: {0}, dataSourceId: {1}, contentId: {2}, artifactId: {3}]", + shortDesc, dataSourceId, contentId, artifactId == null ? "<null>" : artifactId)); + } + } catch (DuplicateException dupEx) { + logger.log(Level.WARNING, "Attempt to make duplicate", dupEx); + return null; + } finally { + caseDB.releaseSingleUserCaseWriteLock(); + } + } /** * Add an event of the given type from the given artifact to the database. diff --git a/db_diff/tskdbdiff.py b/db_diff/tskdbdiff.py index ea649cd85a52ed8ffa6b07a4c6a8f8410adb665a..f3b874e9280be75a3e84a7f2b33b985ca7de8a4e 100644 --- a/db_diff/tskdbdiff.py +++ b/db_diff/tskdbdiff.py @@ -272,6 +272,7 @@ def _dump_output_db_bb(db_file, bb_dump_file, isMultiUser, pgSettings, id_obj_pa msg = "There were inconsistent sources for artifact with id #" + str(row["artifact_id"]) + ".\n" try: + attr_value_as_string = None if attr["value_type"] == 0: attr_value_as_string = str(attr["value_text"]) elif attr["value_type"] == 1: @@ -285,10 +286,15 @@ def _dump_output_db_bb(db_file, bb_dump_file, isMultiUser, pgSettings, id_obj_pa elif attr["value_type"] == 4: attr_value_as_string = "bytes" elif attr["value_type"] == 5: - attr_value_as_string = str(attr["value_int64"]) + attr_value_as_string = str(attr["value_int64"]) + elif attr["value_type"] == 6: + attr_value_as_string = str(attr["value_text"]) if attr["display_name"] == "Associated Artifact": attr_value_as_string = TskDbDiff._get_associated_artifact_type(attribute_cursor, attr_value_as_string, isMultiUser) patrn = re.compile("[\n\0\a\b\r\f]") + if attr_value_as_string is None: + print(f'Could not determine attribute value for value type: {attr["value_type"]}, display name: {attr["display_name"]}') + attr_value_as_string = re.sub(patrn, ' ', attr_value_as_string) if attr["source"] == "Keyword Search" and attr["display_name"] == "Keyword Preview": attr_value_as_string = "<Keyword Preview placeholder>" @@ -652,6 +658,9 @@ def index_of(lst, search_item) -> int: Returns: The index in the list of the item or -1. """ + if lst is None: + return -1 + for idx, item in enumerate(lst): if item == search_item: return idx @@ -1006,7 +1015,6 @@ def normalize_tsk_objects_path(guid_util: TskGuidUtils, objid: int, path_parts = ['ModuleOutput', leapp_module, 'index.html'] is_leapp = True break - if not is_leapp: for idx in range(0, len(path_parts) - 1): if path_parts[idx].lower() == "reports" and \ @@ -1020,7 +1028,8 @@ def normalize_tsk_objects_path(guid_util: TskGuidUtils, objid: int, path_parts = ["Reports", "html-report.html"] break - path = os.path.join(*path_parts) if len(path_parts) > 0 else '/' + + path = os.path.join(*path_parts) if (path_parts is not None and len(path_parts) > 0) else '/' return path