From 5f19f99abd256fd69f00f942ea445071733ba725 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 17:18:02 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A7=AA=20Add=20tests=20for=20TusServl?= =?UTF-8?q?etRequest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: tomdesair <14034630+tomdesair@users.noreply.github.com> --- .../server/util/MockServletInputStream.java | 44 ++++++ .../server/util/TusServletRequestTest.java | 149 ++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/test/java/me/desair/tus/server/util/MockServletInputStream.java create mode 100644 src/test/java/me/desair/tus/server/util/TusServletRequestTest.java diff --git a/src/test/java/me/desair/tus/server/util/MockServletInputStream.java b/src/test/java/me/desair/tus/server/util/MockServletInputStream.java new file mode 100644 index 0000000..2f3a823 --- /dev/null +++ b/src/test/java/me/desair/tus/server/util/MockServletInputStream.java @@ -0,0 +1,44 @@ +package me.desair.tus.server.util; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; + +public class MockServletInputStream extends ServletInputStream { + + private final InputStream delegate; + + public MockServletInputStream(InputStream delegate) { + this.delegate = delegate; + } + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + @Override + public boolean isFinished() { + try { + return delegate.available() == 0; + } catch (IOException e) { + return true; + } + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } +} diff --git a/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java b/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java new file mode 100644 index 0000000..78a6979 --- /dev/null +++ b/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java @@ -0,0 +1,149 @@ +package me.desair.tus.server.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.hasItems; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Set; +import jakarta.servlet.http.HttpServletRequest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import me.desair.tus.server.HttpHeader; +import me.desair.tus.server.TusExtension; +import me.desair.tus.server.checksum.ChecksumAlgorithm; + +@RunWith(MockitoJUnitRunner.class) +public class TusServletRequestTest { + + @Mock private HttpServletRequest servletRequest; + + private TusServletRequest request; + + @Before + public void setUp() { + request = new TusServletRequest(servletRequest); + } + + @Test + public void testGetContentInputStream() throws Exception { + byte[] data = "test data".getBytes(); + when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + + InputStream is = request.getContentInputStream(); + + assertThat(is, notNullValue()); + + // Read to the end to trigger counting + byte[] buffer = new byte[1024]; + int bytesRead = is.read(buffer); + + assertThat(bytesRead, is(9)); + assertThat(request.getBytesRead(), is(9L)); + } + + @Test + public void testGetContentInputStreamChunked() throws Exception { + request = new TusServletRequest(servletRequest, true); + + byte[] data = "5\r\ntest \r\n4\r\ndata\r\n0\r\n\r\n".getBytes(); + when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + when(servletRequest.getHeader(HttpHeader.TRANSFER_ENCODING)).thenReturn("chunked"); + + InputStream is = request.getContentInputStream(); + + assertThat(is, notNullValue()); + + // Read to the end to trigger counting + byte[] buffer = new byte[1024]; + int bytesRead = 0; + int read; + while ((read = is.read(buffer)) != -1) { + bytesRead += read; + } + + assertThat(bytesRead, is(9)); + assertThat(request.getBytesRead(), is(9L)); + } + + @Test + public void testGetContentInputStreamWithChecksum() throws Exception { + byte[] data = "test data".getBytes(); + when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("sha1 9I3YU4IIYIFsddVND1hNyGMyenw="); + + InputStream is = request.getContentInputStream(); + assertThat(is, notNullValue()); + + byte[] buffer = new byte[1024]; + int bytesRead = 0; + int read; + while ((read = is.read(buffer)) != -1) { + bytesRead += read; + } + + assertThat(request.hasCalculatedChecksum(), is(true)); + Set algorithms = request.getEnabledChecksums(); + assertThat(algorithms, hasItems(ChecksumAlgorithm.SHA1)); + + assertThat(request.getCalculatedChecksum(ChecksumAlgorithm.SHA1), is("9I3YU4IIYIFsddVND1hNyGMyenw=")); + } + + @Test + public void testGetContentInputStreamChunkedWithChecksum() throws Exception { + request = new TusServletRequest(servletRequest, true); + + byte[] data = "5\r\ntest \r\n4\r\ndata\r\n0\r\n\r\n".getBytes(); + when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + when(servletRequest.getHeader(HttpHeader.TRANSFER_ENCODING)).thenReturn("chunked"); + + InputStream is = request.getContentInputStream(); + assertThat(is, notNullValue()); + + byte[] buffer = new byte[1024]; + int bytesRead = 0; + int read; + while ((read = is.read(buffer)) != -1) { + bytesRead += read; + } + + assertThat(request.hasCalculatedChecksum(), is(true)); + Set algorithms = request.getEnabledChecksums(); + // Since it's chunked and checksum can come at the end, it should keep track of all algorithms + assertThat(algorithms, hasItems(ChecksumAlgorithm.values())); + + assertThat(request.getCalculatedChecksum(ChecksumAlgorithm.SHA1), is("9I3YU4IIYIFsddVND1hNyGMyenw=")); + } + + @Test + public void testIsProcessedBy() { + TusExtension extension = mock(TusExtension.class); + when(extension.getName()).thenReturn("test"); + + assertThat(request.isProcessedBy(extension), is(false)); + + request.addProcessor(extension); + + assertThat(request.isProcessedBy(extension), is(true)); + } + + @Test + public void testGetHeader() { + when(servletRequest.getHeader("X-Custom-Header")).thenReturn("custom-value"); + + assertThat(request.getHeader("X-Custom-Header"), is("custom-value")); + assertThat(request.getHeader("X-Non-Existent"), is(nullValue())); + } +} From d47279fb29d3b9effd601ef29f29b1f69ab362b6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 17:23:21 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A7=B9=20Fix=20CI=20codestyle=20and?= =?UTF-8?q?=20assertion=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: tomdesair <14034630+tomdesair@users.noreply.github.com> --- .../server/util/MockServletInputStream.java | 56 +++-- .../server/util/TusServletRequestTest.java | 204 +++++++++--------- 2 files changed, 130 insertions(+), 130 deletions(-) diff --git a/src/test/java/me/desair/tus/server/util/MockServletInputStream.java b/src/test/java/me/desair/tus/server/util/MockServletInputStream.java index 2f3a823..d335af2 100644 --- a/src/test/java/me/desair/tus/server/util/MockServletInputStream.java +++ b/src/test/java/me/desair/tus/server/util/MockServletInputStream.java @@ -1,44 +1,42 @@ package me.desair.tus.server.util; -import java.io.IOException; -import java.io.InputStream; - import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; +import java.io.IOException; +import java.io.InputStream; public class MockServletInputStream extends ServletInputStream { - private final InputStream delegate; + private final InputStream delegate; - public MockServletInputStream(InputStream delegate) { - this.delegate = delegate; - } + public MockServletInputStream(InputStream delegate) { + this.delegate = delegate; + } - @Override - public int read() throws IOException { - return delegate.read(); - } + @Override + public int read() throws IOException { + return delegate.read(); + } - @Override - public int read(byte[] b, int off, int len) throws IOException { - return delegate.read(b, off, len); - } + @Override + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } - @Override - public boolean isFinished() { - try { - return delegate.available() == 0; - } catch (IOException e) { - return true; - } + @Override + public boolean isFinished() { + try { + return delegate.available() == 0; + } catch (IOException e) { + return true; } + } - @Override - public boolean isReady() { - return true; - } + @Override + public boolean isReady() { + return true; + } - @Override - public void setReadListener(ReadListener readListener) { - } + @Override + public void setReadListener(ReadListener readListener) {} } diff --git a/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java b/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java index 78a6979..8e32577 100644 --- a/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java +++ b/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java @@ -1,149 +1,151 @@ package me.desair.tus.server.util; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.hasItems; -import static org.mockito.Mockito.when; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.Mockito.mock; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import jakarta.servlet.http.HttpServletRequest; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Set; -import jakarta.servlet.http.HttpServletRequest; - +import me.desair.tus.server.HttpHeader; +import me.desair.tus.server.TusExtension; +import me.desair.tus.server.checksum.ChecksumAlgorithm; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import me.desair.tus.server.HttpHeader; -import me.desair.tus.server.TusExtension; -import me.desair.tus.server.checksum.ChecksumAlgorithm; - @RunWith(MockitoJUnitRunner.class) public class TusServletRequestTest { - @Mock private HttpServletRequest servletRequest; + @Mock private HttpServletRequest servletRequest; - private TusServletRequest request; + private TusServletRequest request; - @Before - public void setUp() { - request = new TusServletRequest(servletRequest); - } + @Before + public void setUp() { + request = new TusServletRequest(servletRequest); + } - @Test - public void testGetContentInputStream() throws Exception { - byte[] data = "test data".getBytes(); - when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + @Test + public void testGetContentInputStream() throws Exception { + byte[] data = "test data".getBytes(); + when(servletRequest.getInputStream()) + .thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); - InputStream is = request.getContentInputStream(); + InputStream is = request.getContentInputStream(); - assertThat(is, notNullValue()); + assertThat(is, notNullValue()); - // Read to the end to trigger counting - byte[] buffer = new byte[1024]; - int bytesRead = is.read(buffer); + // Read to the end to trigger counting + byte[] buffer = new byte[1024]; + int bytesRead = is.read(buffer); - assertThat(bytesRead, is(9)); - assertThat(request.getBytesRead(), is(9L)); - } + assertThat(bytesRead, is(9)); + assertThat(request.getBytesRead(), is(9L)); + } - @Test - public void testGetContentInputStreamChunked() throws Exception { - request = new TusServletRequest(servletRequest, true); + @Test + public void testGetContentInputStreamChunked() throws Exception { + TusServletRequest chunkedRequest = new TusServletRequest(servletRequest, true); - byte[] data = "5\r\ntest \r\n4\r\ndata\r\n0\r\n\r\n".getBytes(); - when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); - when(servletRequest.getHeader(HttpHeader.TRANSFER_ENCODING)).thenReturn("chunked"); + byte[] data = "5\r\ntest \r\n4\r\ndata\r\n0\r\n\r\n".getBytes(); + when(servletRequest.getInputStream()) + .thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + when(servletRequest.getHeader(HttpHeader.TRANSFER_ENCODING)).thenReturn("chunked"); - InputStream is = request.getContentInputStream(); + InputStream is = chunkedRequest.getContentInputStream(); - assertThat(is, notNullValue()); + assertThat(is, notNullValue()); - // Read to the end to trigger counting - byte[] buffer = new byte[1024]; - int bytesRead = 0; - int read; - while ((read = is.read(buffer)) != -1) { - bytesRead += read; - } - - assertThat(bytesRead, is(9)); - assertThat(request.getBytesRead(), is(9L)); + // Read to the end to trigger counting + byte[] buffer = new byte[1024]; + int bytesRead = 0; + int read; + while ((read = is.read(buffer)) != -1) { + bytesRead += read; } - @Test - public void testGetContentInputStreamWithChecksum() throws Exception { - byte[] data = "test data".getBytes(); - when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); - when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("sha1 9I3YU4IIYIFsddVND1hNyGMyenw="); - - InputStream is = request.getContentInputStream(); - assertThat(is, notNullValue()); - - byte[] buffer = new byte[1024]; - int bytesRead = 0; - int read; - while ((read = is.read(buffer)) != -1) { - bytesRead += read; - } - - assertThat(request.hasCalculatedChecksum(), is(true)); - Set algorithms = request.getEnabledChecksums(); - assertThat(algorithms, hasItems(ChecksumAlgorithm.SHA1)); - - assertThat(request.getCalculatedChecksum(ChecksumAlgorithm.SHA1), is("9I3YU4IIYIFsddVND1hNyGMyenw=")); + assertThat(bytesRead, is(9)); + assertThat(chunkedRequest.getBytesRead(), is(9L)); + } + + @Test + public void testGetContentInputStreamWithChecksum() throws Exception { + byte[] data = "test data".getBytes(); + when(servletRequest.getInputStream()) + .thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)) + .thenReturn("sha1 9I3YU4IIYIFsddVND1hNyGMyenw="); + + InputStream is = request.getContentInputStream(); + assertThat(is, notNullValue()); + + byte[] buffer = new byte[1024]; + int read; + while ((read = is.read(buffer)) != -1) { + // Consume stream completely to calculate checksum } - @Test - public void testGetContentInputStreamChunkedWithChecksum() throws Exception { - request = new TusServletRequest(servletRequest, true); + assertThat(request.hasCalculatedChecksum(), is(true)); + Set algorithms = request.getEnabledChecksums(); + assertThat(algorithms, hasItems(ChecksumAlgorithm.SHA1)); - byte[] data = "5\r\ntest \r\n4\r\ndata\r\n0\r\n\r\n".getBytes(); - when(servletRequest.getInputStream()).thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); - when(servletRequest.getHeader(HttpHeader.TRANSFER_ENCODING)).thenReturn("chunked"); + assertThat( + request.getCalculatedChecksum(ChecksumAlgorithm.SHA1), is("9I3YU4IIYIFsddVND1hNyGMyenw=")); + } - InputStream is = request.getContentInputStream(); - assertThat(is, notNullValue()); + @Test + public void testGetContentInputStreamChunkedWithChecksum() throws Exception { + TusServletRequest chunkedRequest = new TusServletRequest(servletRequest, true); - byte[] buffer = new byte[1024]; - int bytesRead = 0; - int read; - while ((read = is.read(buffer)) != -1) { - bytesRead += read; - } + byte[] data = "5\r\ntest \r\n4\r\ndata\r\n0\r\n\r\n".getBytes(); + when(servletRequest.getInputStream()) + .thenReturn(new MockServletInputStream(new ByteArrayInputStream(data))); + when(servletRequest.getHeader(HttpHeader.TRANSFER_ENCODING)).thenReturn("chunked"); - assertThat(request.hasCalculatedChecksum(), is(true)); - Set algorithms = request.getEnabledChecksums(); - // Since it's chunked and checksum can come at the end, it should keep track of all algorithms - assertThat(algorithms, hasItems(ChecksumAlgorithm.values())); + InputStream is = chunkedRequest.getContentInputStream(); + assertThat(is, notNullValue()); - assertThat(request.getCalculatedChecksum(ChecksumAlgorithm.SHA1), is("9I3YU4IIYIFsddVND1hNyGMyenw=")); + byte[] buffer = new byte[1024]; + int read; + while ((read = is.read(buffer)) != -1) { + // Consume stream completely to calculate checksum } - @Test - public void testIsProcessedBy() { - TusExtension extension = mock(TusExtension.class); - when(extension.getName()).thenReturn("test"); + assertThat(chunkedRequest.hasCalculatedChecksum(), is(true)); + Set algorithms = chunkedRequest.getEnabledChecksums(); + // Since it's chunked and checksum can come at the end, it should keep track of all algorithms + assertThat(algorithms, hasItems(ChecksumAlgorithm.values())); - assertThat(request.isProcessedBy(extension), is(false)); + assertThat( + chunkedRequest.getCalculatedChecksum(ChecksumAlgorithm.SHA1), + is("9I3YU4IIYIFsddVND1hNyGMyenw=")); + } - request.addProcessor(extension); + @Test + public void testIsProcessedBy() { + TusExtension extension = mock(TusExtension.class); + when(extension.getName()).thenReturn("test"); - assertThat(request.isProcessedBy(extension), is(true)); - } + assertThat(request.isProcessedBy(extension), is(false)); - @Test - public void testGetHeader() { - when(servletRequest.getHeader("X-Custom-Header")).thenReturn("custom-value"); + request.addProcessor(extension); - assertThat(request.getHeader("X-Custom-Header"), is("custom-value")); - assertThat(request.getHeader("X-Non-Existent"), is(nullValue())); - } + assertThat(request.isProcessedBy(extension), is(true)); + } + + @Test + public void testGetHeader() { + when(servletRequest.getHeader("X-Custom-Header")).thenReturn("custom-value"); + + assertThat(request.getHeader("X-Custom-Header"), is("custom-value")); + assertThat(request.getHeader("X-Non-Existent"), is(nullValue())); + } } From 5ee1067f3a1d04ef36957659d1bf6cbb33666535 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 17:44:11 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A7=B9=20Fix=20CI=20codestyle=20and?= =?UTF-8?q?=20assertion=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: tomdesair <14034630+tomdesair@users.noreply.github.com> From 93c7e8dbeead0e69316c8291b19098c36e3a3271 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:02:22 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=A7=B9=20[testing=20improvement]=20Fi?= =?UTF-8?q?x=20CI=20codestyle=20formatting=20and=20assertions=20and=20reso?= =?UTF-8?q?lve=20merge=20conflicts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: tomdesair <14034630+tomdesair@users.noreply.github.com> --- AGENTS.md | 5 + .../upload/azure/AzureBlobStorageService.java | 10 +- .../server/upload/s3/S3LockingService.java | 6 -- .../server/upload/s3/S3StorageService.java | 5 +- .../server/util/TusServletRequestTest.java | 98 ++++++++++++++++++- 5 files changed, 110 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 09eac40..5d3b741 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,6 +173,11 @@ To maintain a clear separation between fast, offline unit tests and containerize - Do NOT mix data serialization models (DTOs / JSON metadata objects) with active process or lifecycle management components (such as lock handles with scheduled thread executors or storage clients). - Keep constructors focused and minimal (typically 1 or 2 constructors per class). Classes requiring data models MUST accept the dedicated data object (e.g., `LeaseData`) in their constructor rather than defining multiple telescoping or metadata-only constructor overloads. +### 22. Secure Temporary File Creation +- Do NOT use legacy `java.io.File.createTempFile(...)`. On POSIX systems, it creates files with overly permissive default umask permissions (often world-readable or group-readable, CWE-378 / SonarQube java:S5443). +- Always use `java.nio.file.Files.createTempFile(...)` (or `Utils.createTempSiblingPath(...)` / `Utils.createTempSibling(...)`), which creates temporary files with restricted owner-only permissions (`rw-------` / `0600`) by default. + + ## IETF Resumable Uploads for HTTP (RUFH) Spec Maintenance & Update Playbook ### 1. Spec Diff Review diff --git a/src/main/java/me/desair/tus/server/upload/azure/AzureBlobStorageService.java b/src/main/java/me/desair/tus/server/upload/azure/AzureBlobStorageService.java index c53751c..c5b33e7 100644 --- a/src/main/java/me/desair/tus/server/upload/azure/AzureBlobStorageService.java +++ b/src/main/java/me/desair/tus/server/upload/azure/AzureBlobStorageService.java @@ -212,7 +212,7 @@ public UploadInfo append(UploadInfo upload, InputStream inputStream) boolean streamFinished = false; IOException streamException = null; - File firstChunkFile = File.createTempFile("tus-azure-chunk-", ".tmp", tempBufferDir.toFile()); + File firstChunkFile = Files.createTempFile(tempBufferDir, "tus-azure-chunk-", ".tmp").toFile(); try { // 4. Read first chunk from incoming payload stream into local disk buffer ReadChunkResult firstChunkResult = readChunk(inputStream, firstChunkFile, optimalBlockSize); @@ -500,7 +500,7 @@ public void removeLastNumberOfBytes(UploadInfo uploadInfo, long byteCount) blockBlobClient.deleteIfExists(); } else { File tempFile = - File.createTempFile("tus-azure-block-trim-", ".tmp", tempBufferDir.toFile()); + Files.createTempFile(tempBufferDir, "tus-azure-block-trim-", ".tmp").toFile(); try { try (InputStream is = BoundedInputStream.builder() @@ -525,7 +525,7 @@ public void removeLastNumberOfBytes(UploadInfo uploadInfo, long byteCount) if (newPartSize <= 0) { partBlob.deleteIfExists(); } else { - File tempFile = File.createTempFile("tus-azure-truncate-", ".tmp", tempBufferDir.toFile()); + File tempFile = Files.createTempFile(tempBufferDir, "tus-azure-truncate-", ".tmp").toFile(); try { try (InputStream is = BoundedInputStream.builder() @@ -862,7 +862,7 @@ private ProcessChunksResult processRemainingChunks( while (!streamFinished) { File chunkFile = null; try { - chunkFile = File.createTempFile("tus-azure-chunk-", ".tmp", tempBufferDir.toFile()); + chunkFile = Files.createTempFile(tempBufferDir, "tus-azure-chunk-", ".tmp").toFile(); ReadChunkResult chunkResult = readChunk(inputStream, chunkFile, optimalBlockSize); long chunkSize = chunkResult.bytesRead; exception = chunkResult.exception; @@ -903,7 +903,7 @@ private void bufferToPartBlob( if (existingPartSize == 0) { partBlob.upload(BinaryData.fromFile(tempFile.toPath()), true); } else { - File combinedTemp = File.createTempFile("tus-azure-part-", ".tmp", tempBufferDir.toFile()); + File combinedTemp = Files.createTempFile(tempBufferDir, "tus-azure-part-", ".tmp").toFile(); try { try (InputStream partIs = partBlob.openInputStream(); InputStream tempIs = new FileInputStream(tempFile); diff --git a/src/main/java/me/desair/tus/server/upload/s3/S3LockingService.java b/src/main/java/me/desair/tus/server/upload/s3/S3LockingService.java index 61ef467..e86829a 100644 --- a/src/main/java/me/desair/tus/server/upload/s3/S3LockingService.java +++ b/src/main/java/me/desair/tus/server/upload/s3/S3LockingService.java @@ -265,12 +265,6 @@ boolean verifyLockOwnership(String lockKey, String expectedHolderId) { } } - private void deleteExpiredLockQuietly(String lockKey) { - if (isLockExpired(lockKey)) { - deleteObjectQuietly(lockKey); - } - } - boolean isLockExpired(String lockKey) { try (InputStream stream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(lockKey).build())) { diff --git a/src/main/java/me/desair/tus/server/upload/s3/S3StorageService.java b/src/main/java/me/desair/tus/server/upload/s3/S3StorageService.java index 7a92186..a46286a 100644 --- a/src/main/java/me/desair/tus/server/upload/s3/S3StorageService.java +++ b/src/main/java/me/desair/tus/server/upload/s3/S3StorageService.java @@ -21,6 +21,7 @@ import java.io.OutputStream; import java.io.SequenceInputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -653,7 +654,7 @@ private InputStream prepareStreamWithExistingIncompletePart( minioClient.getObject( GetObjectArgs.builder().bucket(bucket).object(partObjectKey).build()); File tempPrependedFile = - File.createTempFile("tus-s3-prep-", ".tmp", temporaryDirectory.toFile()); + Files.createTempFile(temporaryDirectory, "tus-s3-prep-", ".tmp").toFile(); tempPrependedFile.deleteOnExit(); try (FileOutputStream fos = new FileOutputStream(tempPrependedFile)) { @@ -691,7 +692,7 @@ private AppendResult processPayloadChunks( boolean streamFinished = false; while (!streamFinished) { File tempChunkFile = - File.createTempFile("tus-s3-chunk-", ".tmp", temporaryDirectory.toFile()); + Files.createTempFile(temporaryDirectory, "tus-s3-chunk-", ".tmp").toFile(); tempChunkFile.deleteOnExit(); long chunkBytesWritten = 0; diff --git a/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java b/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java index 8e32577..4ac1f62 100644 --- a/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java +++ b/src/test/java/me/desair/tus/server/util/TusServletRequestTest.java @@ -5,16 +5,23 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Set; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.TusExtension; import me.desair.tus.server.checksum.ChecksumAlgorithm; +import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +37,7 @@ public class TusServletRequestTest { @Before public void setUp() { - request = new TusServletRequest(servletRequest); + request = new TusServletRequest(servletRequest, true); } @Test @@ -148,4 +155,93 @@ public void testGetHeader() { assertThat(request.getHeader("X-Custom-Header"), is("custom-value")); assertThat(request.getHeader("X-Non-Existent"), is(nullValue())); } + + @Test + public void getHeaderFromSuper() { + when(servletRequest.getHeader("X-My-Header")).thenReturn("my-value"); + + assertEquals("my-value", request.getHeader("X-My-Header")); + } + + @Test + public void getHeaderFromTrailer() throws Exception { + when(servletRequest.getHeader("Transfer-Encoding")).thenReturn("chunked"); + when(servletRequest.getHeader("X-My-Trailer")).thenReturn(null); + + String chunkedContent = "5\r\n" + "hello\r\n" + "0\r\n" + "X-My-Trailer: trailer-value\r\n\r\n"; + InputStream bais = new ByteArrayInputStream(chunkedContent.getBytes(StandardCharsets.UTF_8)); + + when(servletRequest.getInputStream()) + .thenReturn( + new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) {} + + @Override + public int read() throws IOException { + return bais.read(); + } + }); + + // Read the whole input stream to parse trailers + InputStream contentInputStream = request.getContentInputStream(); + IOUtils.toByteArray(contentInputStream); + + // Verify trailer header is returned + assertEquals("trailer-value", request.getHeader("X-My-Trailer")); + } + + @Test + public void getHeaderBlankFallsBackToTrailer() throws Exception { + when(servletRequest.getHeader("Transfer-Encoding")).thenReturn("chunked"); + when(servletRequest.getHeader("X-My-Trailer")).thenReturn(""); + + String chunkedContent = "5\r\n" + "hello\r\n" + "0\r\n" + "X-My-Trailer: trailer-value\r\n\r\n"; + InputStream bais = new ByteArrayInputStream(chunkedContent.getBytes(StandardCharsets.UTF_8)); + + when(servletRequest.getInputStream()) + .thenReturn( + new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) {} + + @Override + public int read() throws IOException { + return bais.read(); + } + }); + + // Read the whole input stream to parse trailers + InputStream contentInputStream = request.getContentInputStream(); + IOUtils.toByteArray(contentInputStream); + + // Verify trailer header is returned because super returned a blank string + assertEquals("trailer-value", request.getHeader("X-My-Trailer")); + } + + @Test + public void getHeaderNotFound() { + when(servletRequest.getHeader("X-My-Header")).thenReturn(null); + assertNull(request.getHeader("X-My-Header")); + } }