WW-5573 Catches IO exception when a file cannot be deleted and reports it

This commit is contained in:
Lukasz Lenart
2025-09-29 08:21:03 +02:00
parent d83af8c8c7
commit 0f8c2e6558
3 changed files with 35 additions and 30 deletions
@@ -38,6 +38,7 @@ import org.apache.struts2.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
@@ -481,4 +482,18 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
}
}
/**
* Delete file if exists, reports warning if file cannot be deleted
*
* @param filePath a file to delete
*/
protected void deleteFile(Path filePath) {
if (Files.exists(filePath)) {
try {
Files.delete(filePath);
} catch (IOException e) {
LOG.warn("Failed to delete file: {}", filePath, e);
}
}
}
}
@@ -291,9 +291,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
if (LOG.isDebugEnabled()) {
LOG.debug("Cleaning up disk item: {} at {}", normalizeSpace(item.getFieldName()), itemPath);
}
if (Files.exists(itemPath) && !Files.deleteIfExists(itemPath)) {
LOG.warn("There was a problem attempting to delete uploaded file: {}", itemPath);
}
deleteFile(itemPath);
}
} catch (Exception e) {
LOG.warn("Error cleaning up DiskFileItem: {}", normalizeSpace(item.getFieldName()), e);
@@ -327,13 +325,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
protected void cleanUpTemporaryFiles() {
LOG.debug("Cleaning up {} temporary files created for in-memory uploads", temporaryFiles.size());
for (File tempFile : temporaryFiles) {
try {
if (Files.exists(tempFile.toPath()) && !Files.deleteIfExists(tempFile.toPath())) {
LOG.warn("There was a problem attempting to delete temporary file: {}", tempFile.getAbsolutePath());
}
} catch (Exception e) {
LOG.warn("Error cleaning up temporary file: {}", tempFile.getAbsolutePath(), e);
}
deleteFile(tempFile.toPath());
}
}
@@ -82,11 +82,11 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
/**
* Reads the entire contents of an input stream into a string.
*
*
* <p>This method uses a buffered approach to efficiently read the stream
* content without loading the entire stream into memory at once. It uses
* try-with-resources to ensure proper cleanup of resources.</p>
*
*
* @param inputStream the input stream to read from
* @return the stream contents as a UTF-8 string
* @throws IOException if an error occurs reading the stream
@@ -106,7 +106,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
/**
* Processes a normal form field (non-file) from the multipart request using streaming API.
*
*
* <p>This method handles text form fields by:</p>
* <ol>
* <li>Validating the field name is not null</li>
@@ -114,10 +114,10 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* <li>Checking if the field value exceeds maximum string length</li>
* <li>Adding the value to the parameters collection</li>
* </ol>
*
*
* <p>Fields with null names are skipped with a warning log message.</p>
* <p>The streaming approach is more memory-efficient for large form data.</p>
*
*
* @param fileItemInput a form field item input from the streaming API
* @throws IOException if an error occurs reading the input stream
* @see #readStream(InputStream)
@@ -129,7 +129,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
LOG.warn("Form field has null fieldName, skipping");
return;
}
String fieldValue = readStream(fileItemInput.getInputStream());
if (exceedsMaxStringLength(fieldName, fieldValue)) {
return;
@@ -191,7 +191,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
/**
* Processes a file field from the multipart request using streaming API.
*
*
* <p>This method handles file uploads by:</p>
* <ol>
* <li>Validating the file name and field name are not null/empty</li>
@@ -201,13 +201,13 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* <li>Checking if the total size exceeds maximum allowed size</li>
* <li>Creating an {@link UploadedFile} abstraction or cleaning up on size exceeded</li>
* </ol>
*
*
* <p>Files with null names or field names are skipped with appropriate logging.</p>
* <p>The streaming approach is more memory-efficient for large file uploads
* as it writes directly to disk rather than loading into memory first.</p>
*
*
* @param fileItemInput file item representing upload file from streaming API
* @param location the directory where temporary files will be created
* @param location the directory where temporary files will be created
* @throws IOException if an error occurs during file processing
* @see #createTemporaryFile(String, Path)
* @see #streamFileToDisk(FileItemInput, File)
@@ -219,7 +219,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
LOG.debug(() -> "No file has been uploaded for the field: " + normalizeSpace(fileItemInput.getFieldName()));
return;
}
// Skip file uploads that don't have a field name
if (fileItemInput.getFieldName() == null) {
LOG.warn("File upload has null fieldName, skipping");
@@ -232,13 +232,11 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
File file = createTemporaryFile(fileItemInput.getName(), location);
streamFileToDisk(fileItemInput, file);
// Reject empty files (0 bytes) as they are not considered valid uploads
if (rejectEmptyFile(file.length(), fileItemInput.getName(), fileItemInput.getFieldName())) {
// Clean up the empty temporary file
if (Files.exists(file.toPath()) && !Files.deleteIfExists(file.toPath())) {
LOG.warn("Failed to delete empty temporary file: {}", file.getAbsolutePath());
}
deleteFile(file.toPath());
return;
}
@@ -269,7 +267,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
/**
* Creates an {@link UploadedFile} abstraction over an uploaded file.
*
*
* <p>This method creates a wrapper around the uploaded file that provides
* a consistent interface for accessing file information and content.
* The created {@link UploadedFile} instance contains:</p>
@@ -279,19 +277,19 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* <li>The form field name that contained the file</li>
* <li>A reference to the temporary file on disk</li>
* </ul>
*
*
* <p>The file is automatically added to the uploaded files collection,
* grouped by field name to support multiple file uploads per field.</p>
*
*
* @param fileItemInput file item stream containing file metadata
* @param file the temporary file containing the uploaded content
* @param file the temporary file containing the uploaded content
* @see UploadedFile
* @see StrutsUploadedFile
*/
protected void createUploadedFile(FileItemInput fileItemInput, File file) {
String fileName = fileItemInput.getName();
String fieldName = fileItemInput.getFieldName();
// fieldName null check already done in processFileItemAsFileField
UploadedFile uploadedFile = StrutsUploadedFile.Builder
.create(file)