diff --git a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java index db07ed5d4d6a8188fd687f8ff55f33dbbba5f76a..cf79c881a0a384e37a78eaa1216b250a86619e5a 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java @@ -119,9 +119,9 @@ public abstract class AbstractFile extends AbstractContent { private volatile String uniquePath; private volatile FileSystem parentFileSystem; - private boolean tryContentStream; - private Object contentStreamLock = new Object(); - private SoftReference<ContentStream> contentStreamRef = null; + private final boolean tryContentProviderStream; + private Object contentProviderStreamLock = new Object(); + private SoftReference<ContentProviderStream> contentProviderStreamRef = null; /** * Initializes common fields used by AbstactFile implementations (objects in @@ -234,8 +234,8 @@ public abstract class AbstractFile extends AbstractContent { 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 stream from the content provider for this file - this.tryContentStream = collected == CollectedStatus.YES_REPO && db.getContentProvider() != null; + // 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; @@ -1072,58 +1072,47 @@ short getMetaFlagsAsInt() { return TSK_FS_META_FLAG_ENUM.toInt(metaFlags); } - - - private boolean hasContentStream() { - ContentStream contentStream = null; - try { - contentStream = getContentStream(); - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "An error occurred while loading content stream for file with id: " + getId(), ex); - } - return contentStream != null; - } - /** - * Attempts to load the content stream for this file. If none exists, returns null. - * @return The content stream for this file or null if none exists. - * @throws TskCoreException + * 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 ContentStream getContentStream() throws TskCoreException { - ContentStream contentStream = null; - if (tryContentStream) { - try { - synchronized (contentStreamLock) { - // try to get soft reference content stream - contentStream = contentStreamRef == null ? null : contentStreamRef.get(); - // load if not cached and then cache if present - if (contentStream == null) { - contentStream = getSleuthkitCase().getContentProvider().getContentStream(this).orElse(null); - if (contentStream != null) { - this.contentStreamRef = new SoftReference<>(contentStream); - } - } + 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())); } - } finally { - if (contentStream == null) { - // don't try to load the content stream again if it fails to load (either through exception or not existing) - tryContentStream = false; - } + + this.contentProviderStreamRef = new SoftReference<>(contentProviderStream); } + + return contentProviderStream; } - return contentStream; } @Override public final int read(byte[] buf, long offset, long len) throws TskCoreException { - // try to use content stream if present - ContentStream contentStream = getContentStream(); - if (contentStream != null) { - return contentStream.read(buf, offset, len); - } - - //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); @@ -1289,17 +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 (hasContentStream()) { - return true; - } - - if (!localPathSet) { + if (tryContentProviderStream || !localPathSet) { return true; } else { try { @@ -1314,17 +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 (hasContentStream()) { - return true; - } - - if (!localPathSet) { + if (tryContentProviderStream || !localPathSet) { return true; } else { try { diff --git a/bindings/java/src/org/sleuthkit/datamodel/ContentStream.java b/bindings/java/src/org/sleuthkit/datamodel/ContentProviderStream.java similarity index 71% rename from bindings/java/src/org/sleuthkit/datamodel/ContentStream.java rename to bindings/java/src/org/sleuthkit/datamodel/ContentProviderStream.java index 00055d0a78188f1dada7745736c29747fdeecccb..5617362036bb1a9f2c55991bd7d39072c5e44552 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/ContentStream.java +++ b/bindings/java/src/org/sleuthkit/datamodel/ContentProviderStream.java @@ -18,12 +18,11 @@ */ package org.sleuthkit.datamodel; -import java.util.Optional; - /** * Custom provider for content bytes. */ -public interface ContentStream extends AutoCloseable { +@SuppressWarnings("try") +public interface ContentProviderStream extends AutoCloseable { /** * Reads data that this content object is associated with (file contents, @@ -39,21 +38,4 @@ public interface ContentStream extends AutoCloseable { * tsk core */ public int read(byte[] buf, long offset, long len) throws TskCoreException; - - /** - * Custom provider for bytes of an abstract file. - */ - public interface ContentProvider { - - /** - * 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<ContentStream> getContentStream(Content content) 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 5f6f482dca0caa05c452974e2ba691879ebdc6bd..57b86d06429adf024d0f423524c997ddb2771216 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -94,7 +94,6 @@ import org.sqlite.SQLiteConfig; import org.sqlite.SQLiteDataSource; import org.sqlite.SQLiteJDBCLoader; -import org.sleuthkit.datamodel.ContentStream.ContentProvider; /** * Represents the case database with methods that provide abstractions for @@ -205,7 +204,7 @@ public class SleuthkitCase { 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 ContentProvider contentProvider; + private final ContentStreamProvider contentProvider; /* * First parameter is used to specify the SparseBitSet to use, as object IDs @@ -339,7 +338,7 @@ public static void tryConnect(CaseDbConnectionInfo info) throws TskCoreException * * @throws Exception */ - private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbType dbType, ContentProvider contentProvider) 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; @@ -372,7 +371,7 @@ private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbTyp * * @throws Exception */ - private SleuthkitCase(String host, int port, String dbName, String userName, String password, SleuthkitJNI.CaseDbHandle caseHandle, String caseDirPath, DbType dbType, ContentProvider contentProvider) 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; @@ -421,7 +420,7 @@ private void init() throws Exception { * @return The custom content provider for this case if one exists. * Otherwise, returns null. */ - ContentProvider getContentProvider() { + ContentStreamProvider getContentProvider() { return this.contentProvider; } @@ -3002,7 +3001,7 @@ public static SleuthkitCase openCase(String dbPath) throws TskCoreException { * @throws org.sleuthkit.datamodel.TskCoreException */ @Beta - public static SleuthkitCase openCase(String dbPath, ContentProvider provider) throws TskCoreException { + 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, provider); @@ -3042,7 +3041,7 @@ public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo i * @throws TskCoreException If there is a problem opening the database. */ @Beta - public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo info, String caseDir, ContentProvider contentProvider) throws TskCoreException { + 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 @@ -3094,7 +3093,7 @@ public static SleuthkitCase newCase(String dbPath) throws TskCoreException { * @throws org.sleuthkit.datamodel.TskCoreException */ @Beta - public static SleuthkitCase newCase(String dbPath, ContentProvider contentProvider) throws TskCoreException { + public static SleuthkitCase newCase(String dbPath, ContentStreamProvider contentProvider) throws TskCoreException { try { CaseDatabaseFactory factory = new CaseDatabaseFactory(dbPath); factory.createCaseDatabase(); @@ -3143,7 +3142,7 @@ public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info, * @throws org.sleuthkit.datamodel.TskCoreException */ @Beta - public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info, String caseDirPath, ContentProvider contentProvider) throws TskCoreException { + public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info, String caseDirPath, ContentStreamProvider contentProvider) throws TskCoreException { String databaseName = createCaseDataBaseName(caseName); try { /**