diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java
index e17e771b2a2d41811bcdff89a9906be94af6df34..f21873f565755fc9754694d6faa6a2b8327fd029 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java
@@ -25,6 +25,7 @@
 import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBeanResponse;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationRequest;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.FileUploadRequest;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
@@ -92,8 +93,8 @@ public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted, Long f
         return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class);
     }
 
-    public void uploadFile(String url, String fileName, InputStream fileIs) throws CTCloudException {
-        httpClient.doFileUploadPut(url, fileName, fileIs);
+    public void uploadFile(FileUploadRequest fileUploadRequest) throws CTCloudException {
+        httpClient.doFileUploadPut(fileUploadRequest);
     }
     
     public void uploadMeta(AuthenticatedRequestData authenticatedRequestData, MetadataUploadRequest metaRequest) throws CTCloudException {
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
index a5a1e0b554e50ca613b8786da630891bdf1c6b36..0a7c69cdeecb263bffda33a6b227952914bbd518 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
@@ -18,6 +18,8 @@
  */
 package com.basistech.df.cybertriage.autopsy.ctapi;
 
+import com.basistech.df.cybertriage.autopsy.ctapi.CTCloudException.ErrorCode;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.FileUploadRequest;
 import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
@@ -63,7 +65,6 @@
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.InputStreamEntity;
 import org.apache.http.entity.StringEntity;
-import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.client.HttpClients;
@@ -186,10 +187,23 @@ public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jso
         return null;
     }
 
-    public void doFileUploadPut(String fullUrlPath, String fileName, InputStream fileIs) throws CTCloudException {
-        URI postUri;
+    public void doFileUploadPut(FileUploadRequest fileUploadRequest) throws CTCloudException {
+        if (fileUploadRequest == null) {
+            throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fileUploadRequest cannot be null"));
+        }
+                
+        String fullUrlPath = fileUploadRequest.getFullUrlPath();
+        String fileName = fileUploadRequest.getFileName();
+        InputStream fileInputStream = fileUploadRequest.getFileInputStream();
+        Long contentLength = fileUploadRequest.getContentLength();
+
+        if (StringUtils.isBlank(fullUrlPath) || fileInputStream == null || contentLength == null || contentLength <= 0) {
+            throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fullUrlPath, fileInputStream, contentLength must not be empty, null or less than 0"));
+        }
+        
+        URI putUri;
         try {
-            postUri = new URI(fullUrlPath);
+            putUri = new URI(fullUrlPath);
         } catch (URISyntaxException ex) {
             LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + fullUrlPath, ex);
             throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex);
@@ -197,24 +211,11 @@ public void doFileUploadPut(String fullUrlPath, String fileName, InputStream fil
 
         try (CloseableHttpClient httpclient = createConnection(proxySelector, sslContext)) {
             LOGGER.log(Level.INFO, "initiating http post request to ctcloud server " + fullUrlPath);
-            HttpPut put = new HttpPut(postUri);
+            HttpPut put = new HttpPut(putUri);
             configureRequestTimeout(put);
 
             put.addHeader("Connection", "keep-alive");
-            put.addHeader("Content-Type", "application/octet-stream");
-
-//            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
-//            builder.addBinaryBody(
-//                    "file",
-//                    fileBytes,
-//                    ContentType.APPLICATION_OCTET_STREAM,
-//                    file.getFileName()
-//            );
-//
-//            HttpEntity multipart = builder.build();
-//            post.setEntity(multipart);
-
-            put.setEntity(new InputStreamEntity(fileIs));
+            put.setEntity(new InputStreamEntity(fileInputStream, contentLength, ContentType.APPLICATION_OCTET_STREAM));
 
             try (CloseableHttpResponse response = httpclient.execute(put)) {
                 int statusCode = response.getStatusLine().getStatusCode();
@@ -386,7 +387,7 @@ private static CloseableHttpClient createConnection(ProxySelector proxySelector,
         HttpClientBuilder builder;
 
         if (ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION
-                && StringUtils.isBlank(ProxySettings.getAuthenticationUsername()) 
+                && StringUtils.isBlank(ProxySettings.getAuthenticationUsername())
                 && ArrayUtils.isEmpty(ProxySettings.getAuthenticationPassword())
                 && WinHttpClients.isWinAuthAvailable()) {
 
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileUploadRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileUploadRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..48d8dc77b4705d9fcdc2dda816a802e1bead20a7
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileUploadRequest.java
@@ -0,0 +1,69 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * 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 com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import java.io.InputStream;
+
+/**
+ * Data for a file upload request.
+ */
+public class FileUploadRequest {
+
+    private String fullUrlPath;
+    private String fileName;
+    private InputStream fileInputStream;
+    private Long contentLength;
+
+    public String getFullUrlPath() {
+        return fullUrlPath;
+    }
+
+    public FileUploadRequest setFullUrlPath(String fullUrlPath) {
+        this.fullUrlPath = fullUrlPath;
+        return this;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public FileUploadRequest setFileName(String fileName) {
+        this.fileName = fileName;
+        return this;
+    }
+
+    public InputStream getFileInputStream() {
+        return fileInputStream;
+    }
+
+    public FileUploadRequest setFileInputStream(InputStream fileInputStream) {
+        this.fileInputStream = fileInputStream;
+        return this;
+    }
+
+    public Long getContentLength() {
+        return contentLength;
+    }
+
+    public FileUploadRequest setContentLength(Long contentLength) {
+        this.contentLength = contentLength;
+        return this;
+    }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
index cbe76ba2358d8dae72eff12fc6cdfc60bdeaf5bf..c66b88cce9b29c9452574938f6436b1804f01f68 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
@@ -23,6 +23,7 @@
 import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthenticatedRequestData;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.FileUploadRequest;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.MalwareResultBean.Status;
 import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
@@ -108,7 +109,7 @@ private static class SharedProcessing {
         // min and max upload size in bytes
         private static final long MIN_UPLOAD_SIZE = 1;
         private static final long MAX_UPLOAD_SIZE = 100_000_000; // 100MB
-        
+
         private static final int NUM_FILE_UPLOAD_RETRIES = 7;
         private static final long FILE_UPLOAD_RETRY_SLEEP_MILLIS = 60 * 1000;
 
@@ -654,7 +655,13 @@ private boolean uploadFile(IngestJobState ingestJobState, String md5, long objId
 
             // upload bytes
             ReadContentInputStream fileInputStream = new ReadContentInputStream(af);
-            ctApiDAO.uploadFile(authTokenResponse.getFileUploadUrl(), af.getName(), fileInputStream);
+
+            ctApiDAO.uploadFile(new FileUploadRequest()
+                    .setContentLength(af.getSize())
+                    .setFileInputStream(fileInputStream)
+                    .setFileName(af.getName())
+                    .setFullUrlPath(authTokenResponse.getFileUploadUrl())
+            );
 
             // upload metadata
             MetadataUploadRequest metaRequest = new MetadataUploadRequest()
@@ -843,7 +850,7 @@ private AnalysisResult createAnalysisResult(IngestJobState ingestJobState, Sleut
                     : Bundle.MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No();
 
             String justification = cloudBean.getMalwareResult().getStatusDescription();
-            
+
             return ingestJobState.getTskCase().getBlackboard().newAnalysisResult(
                     ingestJobState.getMalwareType(),
                     objId,