This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-fileupload.git
The following commit(s) were added to refs/heads/master by this push:
new 4ef233d3 Add JakartaServletFileUploadGetItemIteratorTest and
JavaxServletFileUploadGetItemIteratorTest.
4ef233d3 is described below
commit 4ef233d35143adbd7b934ad54e947d408b12239a
Author: Gary Gregory <[email protected]>
AuthorDate: Sat Jul 25 11:52:06 2026 -0400
Add JakartaServletFileUploadGetItemIteratorTest and
JavaxServletFileUploadGetItemIteratorTest.
---
...akartaServletFileUploadGetItemIteratorTest.java | 420 +++++++++++++++++++++
...akartaServletFileUploadGetItemIteratorTest.java | 420 +++++++++++++++++++++
...akartaServletFileUploadGetItemIteratorTest.java | 420 +++++++++++++++++++++
3 files changed, 1260 insertions(+)
diff --git
a/commons-fileupload2-jakarta-servlet5/src/test/java/org/apache/commons/fileupload2/jakarta/servlet5/JakartaServletFileUploadGetItemIteratorTest.java
b/commons-fileupload2-jakarta-servlet5/src/test/java/org/apache/commons/fileupload2/jakarta/servlet5/JakartaServletFileUploadGetItemIteratorTest.java
new file mode 100644
index 00000000..2810a927
--- /dev/null
+++
b/commons-fileupload2-jakarta-servlet5/src/test/java/org/apache/commons/fileupload2/jakarta/servlet5/JakartaServletFileUploadGetItemIteratorTest.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * https://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.apache.commons.fileupload2.jakarta.servlet5;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.fileupload2.core.DiskFileItemFactory;
+import org.apache.commons.fileupload2.core.FileItemInput;
+import org.apache.commons.fileupload2.core.FileItemInputIterator;
+import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
+import org.apache.commons.fileupload2.core.FileUploadException;
+import org.junit.jupiter.api.Test;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * Tests for {@link
JakartaServletFileUpload#getItemIterator(HttpServletRequest)}.
+ * <p>
+ * Covers normal use-cases as well as edge cases such as size limits, file
count limits, empty payloads, and non-multipart requests.
+ * </p>
+ */
+class JakartaServletFileUploadGetItemIteratorTest {
+
+ /** Boundary value used throughout these tests. */
+ private static final String BOUNDARY = "---1234";
+
+ /** Content-type header value that matches {@link #BOUNDARY}. */
+ private static final String CONTENT_TYPE = "multipart/form-data;
boundary=" + BOUNDARY;
+
+ /**
+ * Builds a complete multipart body that contains {@code fileCount}
identical file parts.
+ *
+ * @param fileCount number of parts to include
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildMultiFileParts(final int fileCount) {
+ final var sb = new StringBuilder();
+ for (int i = 1; i <= fileCount; i++) {
+ sb.append("--").append(BOUNDARY).append("\r\n");
+ sb.append("Content-Disposition: form-data;
name=\"file").append(i).append("\";
filename=\"file").append(i).append(".txt\"\r\n");
+ sb.append("Content-Type: text/plain\r\n");
+ sb.append("\r\n");
+ sb.append("Content of file ").append(i).append("\r\n");
+ }
+ sb.append("--").append(BOUNDARY).append("--\r\n");
+ return sb.toString().getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Builds a complete multipart body that contains exactly one file part.
+ *
+ * @param fieldName form-field name attribute
+ * @param fileName original file name
+ * @param fileContent body content for the part
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildSingleFilePart(final String fieldName, final
String fileName, final String fileContent) {
+ // @formatter:off
+ final var body = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"" + fieldName + "\";
filename=\"" + fileName + "\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + fileContent + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ return body.getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Builds a complete multipart body that contains exactly one form-field
part (no filename).
+ *
+ * @param fieldName form-field name attribute
+ * @param fieldValue value of the form field
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildSingleFormField(final String fieldName, final
String fieldValue) {
+ // @formatter:off
+ final var body = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"" + fieldName +
"\"\r\n"
+ + "\r\n"
+ + fieldValue + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ return body.getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Creates a new {@link JakartaServletFileUpload} instance with the
default {@link DiskFileItemFactory}.
+ */
+ private static JakartaServletFileUpload<?, ?> newUpload() {
+ return new
JakartaServletFileUpload<>(DiskFileItemFactory.builder().get());
+ }
+
+ /**
+ * A multipart body with no parts (just the closing boundary): the
iterator must have no items.
+ */
+ @Test
+ void testEmptyMultipartBody() throws Exception {
+ final var body = ("--" + BOUNDARY +
"--\r\n").getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertFalse(iter.hasNext(), "Expected no items in an empty multipart
body");
+ }
+
+ /**
+ * A file part: the content read from the item's InputStream must match
the uploaded body.
+ */
+ @Test
+ void testFilePartContentIsReadable() throws Exception {
+ final var expectedContent = "Hello, World!";
+ final var body = buildSingleFilePart("file", "test.txt",
expectedContent);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ try (InputStream is = item.getInputStream()) {
+ final var content = new String(is.readAllBytes(),
StandardCharsets.US_ASCII);
+ assertEquals(expectedContent, content);
+ }
+ }
+
+ /**
+ * A part with a non-ASCII file name encoded in the content-disposition
header is returned with the original name value as parsed from the header.
+ */
+ @Test
+ void testFilePartWithSpecialCharactersInFieldName() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field-with-dashes\";
filename=\"résumé.txt\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + "content\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.UTF_8);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertEquals("field-with-dashes", item.getFieldName());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * When a single file part exceeds {@code maxFileSize}, reading its
content must throw a {@link FileUploadByteCountLimitException}.
+ */
+ @Test
+ void testFileSizeLimitExceededThrowsException() throws Exception {
+ final var largeContent = "A".repeat(1024); // 1 KiB
+ final var body = buildSingleFilePart("bigFile", "large.txt",
largeContent);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(10); // allow only 10 bytes
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertThrows(FileUploadByteCountLimitException.class, () -> {
+ try (InputStream is = item.getInputStream()) {
+ // Must read enough bytes to trigger the limit
+ is.readAllBytes();
+ }
+ });
+ }
+
+ /**
+ * {@link
FileItemInputIterator#forEachRemaining(org.apache.commons.io.function.IOConsumer)}
visits every item exactly once.
+ */
+ @Test
+ void testForEachRemainingVisitsAllItems() throws Exception {
+ final int fileCount = 3;
+ final var body = buildMultiFileParts(fileCount);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final var count = new int[1];
+ upload.getItemIterator(request).forEachRemaining(item -> count[0]++);
+ assertEquals(fileCount, count[0]);
+ }
+
+ /**
+ * A form-field part: the content read from the item's InputStream must
match the submitted value.
+ */
+ @Test
+ void testFormFieldContentIsReadable() throws Exception {
+ final var body = buildSingleFormField("answer", "42");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ try (InputStream is = item.getInputStream()) {
+ final var content = new String(is.readAllBytes(),
StandardCharsets.US_ASCII);
+ assertEquals("42", content);
+ }
+ }
+
+ /**
+ * Calling {@link
JakartaServletFileUpload#getItemIterator(HttpServletRequest)} twice on
different requests must each return an independent iterator.
+ */
+ @Test
+ void testGetItemIteratorIsReentrant() throws Exception {
+ final var body1 = buildSingleFilePart("first", "first.txt", "first");
+ final var body2 = buildMultiFileParts(2);
+ final HttpServletRequest request1 = new
JakartaMockHttpServletRequest(body1, CONTENT_TYPE);
+ final HttpServletRequest request2 = new
JakartaMockHttpServletRequest(body2, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter1 = upload.getItemIterator(request1);
+ final FileItemInputIterator iter2 = upload.getItemIterator(request2);
+ // iter1 should yield one item
+ assertTrue(iter1.hasNext());
+ assertEquals("first", iter1.next().getFieldName());
+ assertFalse(iter1.hasNext());
+ // iter2 should independently yield two items
+ final var count = new int[1];
+ iter2.forEachRemaining(item -> count[0]++);
+ assertEquals(2, count[0]);
+ }
+
+ /**
+ * The returned iterator must be non-null for a well-formed multipart
request.
+ */
+ @Test
+ void testGetItemIteratorReturnsNonNull() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ assertNotNull(upload.getItemIterator(request));
+ }
+
+ /**
+ * {@link FileItemInputIterator#getFileSizeMax()} and {@link
FileItemInputIterator#getSizeMax()} must reflect the values configured on the
upload instance
+ * when no override has been applied.
+ */
+ @Test
+ void testIteratorInheritsUploadSizeLimits() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(100_000L);
+ upload.setMaxSize(500_000L);
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertEquals(100_000L, iter.getFileSizeMax());
+ assertEquals(500_000L, iter.getSizeMax());
+ }
+
+ /**
+ * {@link FileItemInputIterator#setFileSizeMax(long)} and {@link
FileItemInputIterator#setSizeMax(long)} must override the values from the
upload instance.
+ */
+ @Test
+ void testIteratorSizeLimitOverrides() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(100_000L);
+ upload.setMaxSize(500_000L);
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ iter.setFileSizeMax(200L);
+ iter.setSizeMax(800L);
+ assertEquals(200L, iter.getFileSizeMax());
+ assertEquals(800L, iter.getSizeMax());
+ }
+
+ /**
+ * Mixed parts: the iterator correctly reports form-field vs. file parts.
+ */
+ @Test
+ void testMixedFormFieldAndFileParts() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field1\"\r\n"
+ + "\r\n"
+ + "value1\r\n"
+ + "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"file1\";
filename=\"upload.txt\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + "file content\r\n"
+ + "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field2\"\r\n"
+ + "\r\n"
+ + "value2\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ // Part 1: form field
+ assertTrue(iter.hasNext());
+ final FileItemInput part1 = iter.next();
+ assertEquals("field1", part1.getFieldName());
+ assertTrue(part1.isFormField());
+ // Part 2: file
+ assertTrue(iter.hasNext());
+ final FileItemInput part2 = iter.next();
+ assertEquals("file1", part2.getFieldName());
+ assertFalse(part2.isFormField());
+ // Part 3: form field
+ assertTrue(iter.hasNext());
+ final FileItemInput part3 = iter.next();
+ assertEquals("field2", part3.getFieldName());
+ assertTrue(part3.isFormField());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * Multiple parts: the iterator must return every part in transmission
order.
+ */
+ @Test
+ void testMultipleFileParts() throws Exception {
+ final int fileCount = 4;
+ final var body = buildMultiFileParts(fileCount);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ for (int i = 1; i <= fileCount; i++) {
+ assertTrue(iter.hasNext(), "Expected item " + i);
+ final FileItemInput item = iter.next();
+ assertEquals("file" + i, item.getFieldName());
+ assertEquals("file" + i + ".txt", item.getName());
+ }
+ assertFalse(iter.hasNext(), "Expected no more items after " +
fileCount);
+ }
+
+ /**
+ * A request whose content-type is not {@code multipart/*} must cause a
{@link FileUploadException}.
+ */
+ @Test
+ void testNonMultipartContentTypeThrowsException() {
+ final var body = "hello=world".getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, "application/x-www-form-urlencoded");
+ final var upload = newUpload();
+ assertThrows(FileUploadException.class, () ->
upload.getItemIterator(request));
+ }
+
+ /**
+ * A part with an explicit but empty {@code filename=""} attribute must be
treated as a file part (not a form field) with an empty name string.
+ */
+ @Test
+ void testPartWithEmptyFileName() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"file\";
filename=\"\"\r\n"
+ + "Content-Type: application/octet-stream\r\n"
+ + "\r\n"
+ + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertEquals("file", item.getFieldName());
+ assertEquals("", item.getName());
+ assertFalse(item.isFormField());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * A single-file part: the iterator must yield exactly one item whose
properties match the part headers.
+ */
+ @Test
+ void testSingleFilePart() throws Exception {
+ final var body = buildSingleFilePart("upload", "hello.txt", "Hello,
World!");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext(), "Expected at least one item");
+ final FileItemInput item = iter.next();
+ assertEquals("upload", item.getFieldName());
+ assertEquals("hello.txt", item.getName());
+ assertEquals("text/plain", item.getContentType());
+ assertFalse(item.isFormField(), "Part with a filename should not be a
form field");
+ assertFalse(iter.hasNext(), "Expected no more items");
+ }
+
+ /**
+ * A single form-field part (no filename): the iterator must yield one
item marked as a form field.
+ */
+ @Test
+ void testSingleFormField() throws Exception {
+ final var body = buildSingleFormField("myField", "myValue");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext(), "Expected at least one item");
+ final FileItemInput item = iter.next();
+ assertEquals("myField", item.getFieldName());
+ assertNull(item.getName(), "Form field should have no file name");
+ assertTrue(item.isFormField(), "Part without a filename should be a
form field");
+ assertFalse(iter.hasNext(), "Expected no more items");
+ }
+}
diff --git
a/commons-fileupload2-jakarta-servlet6/src/test/java/org/apache/commons/fileupload2/jakarta/servlet6/JakartaServletFileUploadGetItemIteratorTest.java
b/commons-fileupload2-jakarta-servlet6/src/test/java/org/apache/commons/fileupload2/jakarta/servlet6/JakartaServletFileUploadGetItemIteratorTest.java
new file mode 100644
index 00000000..fd584251
--- /dev/null
+++
b/commons-fileupload2-jakarta-servlet6/src/test/java/org/apache/commons/fileupload2/jakarta/servlet6/JakartaServletFileUploadGetItemIteratorTest.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * https://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.apache.commons.fileupload2.jakarta.servlet6;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.fileupload2.core.DiskFileItemFactory;
+import org.apache.commons.fileupload2.core.FileItemInput;
+import org.apache.commons.fileupload2.core.FileItemInputIterator;
+import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
+import org.apache.commons.fileupload2.core.FileUploadException;
+import org.junit.jupiter.api.Test;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * Tests for {@link
JakartaServletFileUpload#getItemIterator(HttpServletRequest)}.
+ * <p>
+ * Covers normal use-cases as well as edge cases such as size limits, file
count limits, empty payloads, and non-multipart requests.
+ * </p>
+ */
+class JakartaServletFileUploadGetItemIteratorTest {
+
+ /** Boundary value used throughout these tests. */
+ private static final String BOUNDARY = "---1234";
+
+ /** Content-type header value that matches {@link #BOUNDARY}. */
+ private static final String CONTENT_TYPE = "multipart/form-data;
boundary=" + BOUNDARY;
+
+ /**
+ * Builds a complete multipart body that contains {@code fileCount}
identical file parts.
+ *
+ * @param fileCount number of parts to include
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildMultiFileParts(final int fileCount) {
+ final var sb = new StringBuilder();
+ for (int i = 1; i <= fileCount; i++) {
+ sb.append("--").append(BOUNDARY).append("\r\n");
+ sb.append("Content-Disposition: form-data;
name=\"file").append(i).append("\";
filename=\"file").append(i).append(".txt\"\r\n");
+ sb.append("Content-Type: text/plain\r\n");
+ sb.append("\r\n");
+ sb.append("Content of file ").append(i).append("\r\n");
+ }
+ sb.append("--").append(BOUNDARY).append("--\r\n");
+ return sb.toString().getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Builds a complete multipart body that contains exactly one file part.
+ *
+ * @param fieldName form-field name attribute
+ * @param fileName original file name
+ * @param fileContent body content for the part
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildSingleFilePart(final String fieldName, final
String fileName, final String fileContent) {
+ // @formatter:off
+ final var body = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"" + fieldName + "\";
filename=\"" + fileName + "\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + fileContent + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ return body.getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Builds a complete multipart body that contains exactly one form-field
part (no filename).
+ *
+ * @param fieldName form-field name attribute
+ * @param fieldValue value of the form field
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildSingleFormField(final String fieldName, final
String fieldValue) {
+ // @formatter:off
+ final var body = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"" + fieldName +
"\"\r\n"
+ + "\r\n"
+ + fieldValue + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ return body.getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Creates a new {@link JakartaServletFileUpload} instance with the
default {@link DiskFileItemFactory}.
+ */
+ private static JakartaServletFileUpload<?, ?> newUpload() {
+ return new
JakartaServletFileUpload<>(DiskFileItemFactory.builder().get());
+ }
+
+ /**
+ * A multipart body with no parts (just the closing boundary): the
iterator must have no items.
+ */
+ @Test
+ void testEmptyMultipartBody() throws Exception {
+ final var body = ("--" + BOUNDARY +
"--\r\n").getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertFalse(iter.hasNext(), "Expected no items in an empty multipart
body");
+ }
+
+ /**
+ * A file part: the content read from the item's InputStream must match
the uploaded body.
+ */
+ @Test
+ void testFilePartContentIsReadable() throws Exception {
+ final var expectedContent = "Hello, World!";
+ final var body = buildSingleFilePart("file", "test.txt",
expectedContent);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ try (InputStream is = item.getInputStream()) {
+ final var content = new String(is.readAllBytes(),
StandardCharsets.US_ASCII);
+ assertEquals(expectedContent, content);
+ }
+ }
+
+ /**
+ * A part with a non-ASCII file name encoded in the content-disposition
header is returned with the original name value as parsed from the header.
+ */
+ @Test
+ void testFilePartWithSpecialCharactersInFieldName() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field-with-dashes\";
filename=\"résumé.txt\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + "content\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.UTF_8);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertEquals("field-with-dashes", item.getFieldName());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * When a single file part exceeds {@code maxFileSize}, reading its
content must throw a {@link FileUploadByteCountLimitException}.
+ */
+ @Test
+ void testFileSizeLimitExceededThrowsException() throws Exception {
+ final var largeContent = "A".repeat(1024); // 1 KiB
+ final var body = buildSingleFilePart("bigFile", "large.txt",
largeContent);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(10); // allow only 10 bytes
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertThrows(FileUploadByteCountLimitException.class, () -> {
+ try (InputStream is = item.getInputStream()) {
+ // Must read enough bytes to trigger the limit
+ is.readAllBytes();
+ }
+ });
+ }
+
+ /**
+ * {@link
FileItemInputIterator#forEachRemaining(org.apache.commons.io.function.IOConsumer)}
visits every item exactly once.
+ */
+ @Test
+ void testForEachRemainingVisitsAllItems() throws Exception {
+ final int fileCount = 3;
+ final var body = buildMultiFileParts(fileCount);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final var count = new int[1];
+ upload.getItemIterator(request).forEachRemaining(item -> count[0]++);
+ assertEquals(fileCount, count[0]);
+ }
+
+ /**
+ * A form-field part: the content read from the item's InputStream must
match the submitted value.
+ */
+ @Test
+ void testFormFieldContentIsReadable() throws Exception {
+ final var body = buildSingleFormField("answer", "42");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ try (InputStream is = item.getInputStream()) {
+ final var content = new String(is.readAllBytes(),
StandardCharsets.US_ASCII);
+ assertEquals("42", content);
+ }
+ }
+
+ /**
+ * Calling {@link
JakartaServletFileUpload#getItemIterator(HttpServletRequest)} twice on
different requests must each return an independent iterator.
+ */
+ @Test
+ void testGetItemIteratorIsReentrant() throws Exception {
+ final var body1 = buildSingleFilePart("first", "first.txt", "first");
+ final var body2 = buildMultiFileParts(2);
+ final HttpServletRequest request1 = new
JakartaMockHttpServletRequest(body1, CONTENT_TYPE);
+ final HttpServletRequest request2 = new
JakartaMockHttpServletRequest(body2, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter1 = upload.getItemIterator(request1);
+ final FileItemInputIterator iter2 = upload.getItemIterator(request2);
+ // iter1 should yield one item
+ assertTrue(iter1.hasNext());
+ assertEquals("first", iter1.next().getFieldName());
+ assertFalse(iter1.hasNext());
+ // iter2 should independently yield two items
+ final var count = new int[1];
+ iter2.forEachRemaining(item -> count[0]++);
+ assertEquals(2, count[0]);
+ }
+
+ /**
+ * The returned iterator must be non-null for a well-formed multipart
request.
+ */
+ @Test
+ void testGetItemIteratorReturnsNonNull() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ assertNotNull(upload.getItemIterator(request));
+ }
+
+ /**
+ * {@link FileItemInputIterator#getFileSizeMax()} and {@link
FileItemInputIterator#getSizeMax()} must reflect the values configured on the
upload instance
+ * when no override has been applied.
+ */
+ @Test
+ void testIteratorInheritsUploadSizeLimits() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(100_000L);
+ upload.setMaxSize(500_000L);
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertEquals(100_000L, iter.getFileSizeMax());
+ assertEquals(500_000L, iter.getSizeMax());
+ }
+
+ /**
+ * {@link FileItemInputIterator#setFileSizeMax(long)} and {@link
FileItemInputIterator#setSizeMax(long)} must override the values from the
upload instance.
+ */
+ @Test
+ void testIteratorSizeLimitOverrides() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(100_000L);
+ upload.setMaxSize(500_000L);
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ iter.setFileSizeMax(200L);
+ iter.setSizeMax(800L);
+ assertEquals(200L, iter.getFileSizeMax());
+ assertEquals(800L, iter.getSizeMax());
+ }
+
+ /**
+ * Mixed parts: the iterator correctly reports form-field vs. file parts.
+ */
+ @Test
+ void testMixedFormFieldAndFileParts() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field1\"\r\n"
+ + "\r\n"
+ + "value1\r\n"
+ + "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"file1\";
filename=\"upload.txt\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + "file content\r\n"
+ + "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field2\"\r\n"
+ + "\r\n"
+ + "value2\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ // Part 1: form field
+ assertTrue(iter.hasNext());
+ final FileItemInput part1 = iter.next();
+ assertEquals("field1", part1.getFieldName());
+ assertTrue(part1.isFormField());
+ // Part 2: file
+ assertTrue(iter.hasNext());
+ final FileItemInput part2 = iter.next();
+ assertEquals("file1", part2.getFieldName());
+ assertFalse(part2.isFormField());
+ // Part 3: form field
+ assertTrue(iter.hasNext());
+ final FileItemInput part3 = iter.next();
+ assertEquals("field2", part3.getFieldName());
+ assertTrue(part3.isFormField());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * Multiple parts: the iterator must return every part in transmission
order.
+ */
+ @Test
+ void testMultipleFileParts() throws Exception {
+ final int fileCount = 4;
+ final var body = buildMultiFileParts(fileCount);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ for (int i = 1; i <= fileCount; i++) {
+ assertTrue(iter.hasNext(), "Expected item " + i);
+ final FileItemInput item = iter.next();
+ assertEquals("file" + i, item.getFieldName());
+ assertEquals("file" + i + ".txt", item.getName());
+ }
+ assertFalse(iter.hasNext(), "Expected no more items after " +
fileCount);
+ }
+
+ /**
+ * A request whose content-type is not {@code multipart/*} must cause a
{@link FileUploadException}.
+ */
+ @Test
+ void testNonMultipartContentTypeThrowsException() {
+ final var body = "hello=world".getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, "application/x-www-form-urlencoded");
+ final var upload = newUpload();
+ assertThrows(FileUploadException.class, () ->
upload.getItemIterator(request));
+ }
+
+ /**
+ * A part with an explicit but empty {@code filename=""} attribute must be
treated as a file part (not a form field) with an empty name string.
+ */
+ @Test
+ void testPartWithEmptyFileName() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"file\";
filename=\"\"\r\n"
+ + "Content-Type: application/octet-stream\r\n"
+ + "\r\n"
+ + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertEquals("file", item.getFieldName());
+ assertEquals("", item.getName());
+ assertFalse(item.isFormField());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * A single-file part: the iterator must yield exactly one item whose
properties match the part headers.
+ */
+ @Test
+ void testSingleFilePart() throws Exception {
+ final var body = buildSingleFilePart("upload", "hello.txt", "Hello,
World!");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext(), "Expected at least one item");
+ final FileItemInput item = iter.next();
+ assertEquals("upload", item.getFieldName());
+ assertEquals("hello.txt", item.getName());
+ assertEquals("text/plain", item.getContentType());
+ assertFalse(item.isFormField(), "Part with a filename should not be a
form field");
+ assertFalse(iter.hasNext(), "Expected no more items");
+ }
+
+ /**
+ * A single form-field part (no filename): the iterator must yield one
item marked as a form field.
+ */
+ @Test
+ void testSingleFormField() throws Exception {
+ final var body = buildSingleFormField("myField", "myValue");
+ final HttpServletRequest request = new
JakartaMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext(), "Expected at least one item");
+ final FileItemInput item = iter.next();
+ assertEquals("myField", item.getFieldName());
+ assertNull(item.getName(), "Form field should have no file name");
+ assertTrue(item.isFormField(), "Part without a filename should be a
form field");
+ assertFalse(iter.hasNext(), "Expected no more items");
+ }
+}
diff --git
a/commons-fileupload2-javax/src/test/java/org/apache/commons/fileupload2/javax/JakartaServletFileUploadGetItemIteratorTest.java
b/commons-fileupload2-javax/src/test/java/org/apache/commons/fileupload2/javax/JakartaServletFileUploadGetItemIteratorTest.java
new file mode 100644
index 00000000..d5587935
--- /dev/null
+++
b/commons-fileupload2-javax/src/test/java/org/apache/commons/fileupload2/javax/JakartaServletFileUploadGetItemIteratorTest.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * https://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.apache.commons.fileupload2.javax;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.fileupload2.core.DiskFileItemFactory;
+import org.apache.commons.fileupload2.core.FileItemInput;
+import org.apache.commons.fileupload2.core.FileItemInputIterator;
+import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
+import org.apache.commons.fileupload2.core.FileUploadException;
+import org.junit.jupiter.api.Test;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Tests for {@link
JakartaServletFileUpload#getItemIterator(HttpServletRequest)}.
+ * <p>
+ * Covers normal use-cases as well as edge cases such as size limits, file
count limits, empty payloads, and non-multipart requests.
+ * </p>
+ */
+class JakartaServletFileUploadGetItemIteratorTest {
+
+ /** Boundary value used throughout these tests. */
+ private static final String BOUNDARY = "---1234";
+
+ /** Content-type header value that matches {@link #BOUNDARY}. */
+ private static final String CONTENT_TYPE = "multipart/form-data;
boundary=" + BOUNDARY;
+
+ /**
+ * Builds a complete multipart body that contains {@code fileCount}
identical file parts.
+ *
+ * @param fileCount number of parts to include
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildMultiFileParts(final int fileCount) {
+ final var sb = new StringBuilder();
+ for (int i = 1; i <= fileCount; i++) {
+ sb.append("--").append(BOUNDARY).append("\r\n");
+ sb.append("Content-Disposition: form-data;
name=\"file").append(i).append("\";
filename=\"file").append(i).append(".txt\"\r\n");
+ sb.append("Content-Type: text/plain\r\n");
+ sb.append("\r\n");
+ sb.append("Content of file ").append(i).append("\r\n");
+ }
+ sb.append("--").append(BOUNDARY).append("--\r\n");
+ return sb.toString().getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Builds a complete multipart body that contains exactly one file part.
+ *
+ * @param fieldName form-field name attribute
+ * @param fileName original file name
+ * @param fileContent body content for the part
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildSingleFilePart(final String fieldName, final
String fileName, final String fileContent) {
+ // @formatter:off
+ final var body = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"" + fieldName + "\";
filename=\"" + fileName + "\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + fileContent + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ return body.getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Builds a complete multipart body that contains exactly one form-field
part (no filename).
+ *
+ * @param fieldName form-field name attribute
+ * @param fieldValue value of the form field
+ * @return raw multipart bytes encoded in US-ASCII
+ */
+ private static byte[] buildSingleFormField(final String fieldName, final
String fieldValue) {
+ // @formatter:off
+ final var body = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"" + fieldName +
"\"\r\n"
+ + "\r\n"
+ + fieldValue + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ return body.getBytes(StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Creates a new {@link JakartaServletFileUpload} instance with the
default {@link DiskFileItemFactory}.
+ */
+ private static JavaxServletFileUpload<?, ?> newUpload() {
+ return new
JavaxServletFileUpload<>(DiskFileItemFactory.builder().get());
+ }
+
+ /**
+ * A multipart body with no parts (just the closing boundary): the
iterator must have no items.
+ */
+ @Test
+ void testEmptyMultipartBody() throws Exception {
+ final var body = ("--" + BOUNDARY +
"--\r\n").getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertFalse(iter.hasNext(), "Expected no items in an empty multipart
body");
+ }
+
+ /**
+ * A file part: the content read from the item's InputStream must match
the uploaded body.
+ */
+ @Test
+ void testFilePartContentIsReadable() throws Exception {
+ final var expectedContent = "Hello, World!";
+ final var body = buildSingleFilePart("file", "test.txt",
expectedContent);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ try (InputStream is = item.getInputStream()) {
+ final var content = new String(is.readAllBytes(),
StandardCharsets.US_ASCII);
+ assertEquals(expectedContent, content);
+ }
+ }
+
+ /**
+ * A part with a non-ASCII file name encoded in the content-disposition
header is returned with the original name value as parsed from the header.
+ */
+ @Test
+ void testFilePartWithSpecialCharactersInFieldName() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field-with-dashes\";
filename=\"résumé.txt\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + "content\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.UTF_8);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertEquals("field-with-dashes", item.getFieldName());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * When a single file part exceeds {@code maxFileSize}, reading its
content must throw a {@link FileUploadByteCountLimitException}.
+ */
+ @Test
+ void testFileSizeLimitExceededThrowsException() throws Exception {
+ final var largeContent = "A".repeat(1024); // 1 KiB
+ final var body = buildSingleFilePart("bigFile", "large.txt",
largeContent);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(10); // allow only 10 bytes
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertThrows(FileUploadByteCountLimitException.class, () -> {
+ try (InputStream is = item.getInputStream()) {
+ // Must read enough bytes to trigger the limit
+ is.readAllBytes();
+ }
+ });
+ }
+
+ /**
+ * {@link
FileItemInputIterator#forEachRemaining(org.apache.commons.io.function.IOConsumer)}
visits every item exactly once.
+ */
+ @Test
+ void testForEachRemainingVisitsAllItems() throws Exception {
+ final int fileCount = 3;
+ final var body = buildMultiFileParts(fileCount);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final var count = new int[1];
+ upload.getItemIterator(request).forEachRemaining(item -> count[0]++);
+ assertEquals(fileCount, count[0]);
+ }
+
+ /**
+ * A form-field part: the content read from the item's InputStream must
match the submitted value.
+ */
+ @Test
+ void testFormFieldContentIsReadable() throws Exception {
+ final var body = buildSingleFormField("answer", "42");
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ try (InputStream is = item.getInputStream()) {
+ final var content = new String(is.readAllBytes(),
StandardCharsets.US_ASCII);
+ assertEquals("42", content);
+ }
+ }
+
+ /**
+ * Calling {@link
JavaxServletFileUpload#getItemIterator(HttpServletRequest)} twice on different
requests must each return an independent iterator.
+ */
+ @Test
+ void testGetItemIteratorIsReentrant() throws Exception {
+ final var body1 = buildSingleFilePart("first", "first.txt", "first");
+ final var body2 = buildMultiFileParts(2);
+ final HttpServletRequest request1 = new
JavaxMockHttpServletRequest(body1, CONTENT_TYPE);
+ final HttpServletRequest request2 = new
JavaxMockHttpServletRequest(body2, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter1 = upload.getItemIterator(request1);
+ final FileItemInputIterator iter2 = upload.getItemIterator(request2);
+ // iter1 should yield one item
+ assertTrue(iter1.hasNext());
+ assertEquals("first", iter1.next().getFieldName());
+ assertFalse(iter1.hasNext());
+ // iter2 should independently yield two items
+ final var count = new int[1];
+ iter2.forEachRemaining(item -> count[0]++);
+ assertEquals(2, count[0]);
+ }
+
+ /**
+ * The returned iterator must be non-null for a well-formed multipart
request.
+ */
+ @Test
+ void testGetItemIteratorReturnsNonNull() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ assertNotNull(upload.getItemIterator(request));
+ }
+
+ /**
+ * {@link FileItemInputIterator#getFileSizeMax()} and {@link
FileItemInputIterator#getSizeMax()} must reflect the values configured on the
upload instance
+ * when no override has been applied.
+ */
+ @Test
+ void testIteratorInheritsUploadSizeLimits() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(100_000L);
+ upload.setMaxSize(500_000L);
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertEquals(100_000L, iter.getFileSizeMax());
+ assertEquals(500_000L, iter.getSizeMax());
+ }
+
+ /**
+ * {@link FileItemInputIterator#setFileSizeMax(long)} and {@link
FileItemInputIterator#setSizeMax(long)} must override the values from the
upload instance.
+ */
+ @Test
+ void testIteratorSizeLimitOverrides() throws Exception {
+ final var body = buildSingleFilePart("f", "a.txt", "data");
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ upload.setMaxFileSize(100_000L);
+ upload.setMaxSize(500_000L);
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ iter.setFileSizeMax(200L);
+ iter.setSizeMax(800L);
+ assertEquals(200L, iter.getFileSizeMax());
+ assertEquals(800L, iter.getSizeMax());
+ }
+
+ /**
+ * Mixed parts: the iterator correctly reports form-field vs. file parts.
+ */
+ @Test
+ void testMixedFormFieldAndFileParts() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field1\"\r\n"
+ + "\r\n"
+ + "value1\r\n"
+ + "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"file1\";
filename=\"upload.txt\"\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + "file content\r\n"
+ + "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"field2\"\r\n"
+ + "\r\n"
+ + "value2\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ // Part 1: form field
+ assertTrue(iter.hasNext());
+ final FileItemInput part1 = iter.next();
+ assertEquals("field1", part1.getFieldName());
+ assertTrue(part1.isFormField());
+ // Part 2: file
+ assertTrue(iter.hasNext());
+ final FileItemInput part2 = iter.next();
+ assertEquals("file1", part2.getFieldName());
+ assertFalse(part2.isFormField());
+ // Part 3: form field
+ assertTrue(iter.hasNext());
+ final FileItemInput part3 = iter.next();
+ assertEquals("field2", part3.getFieldName());
+ assertTrue(part3.isFormField());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * Multiple parts: the iterator must return every part in transmission
order.
+ */
+ @Test
+ void testMultipleFileParts() throws Exception {
+ final int fileCount = 4;
+ final var body = buildMultiFileParts(fileCount);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ for (int i = 1; i <= fileCount; i++) {
+ assertTrue(iter.hasNext(), "Expected item " + i);
+ final FileItemInput item = iter.next();
+ assertEquals("file" + i, item.getFieldName());
+ assertEquals("file" + i + ".txt", item.getName());
+ }
+ assertFalse(iter.hasNext(), "Expected no more items after " +
fileCount);
+ }
+
+ /**
+ * A request whose content-type is not {@code multipart/*} must cause a
{@link FileUploadException}.
+ */
+ @Test
+ void testNonMultipartContentTypeThrowsException() {
+ final var body = "hello=world".getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, "application/x-www-form-urlencoded");
+ final var upload = newUpload();
+ assertThrows(FileUploadException.class, () ->
upload.getItemIterator(request));
+ }
+
+ /**
+ * A part with an explicit but empty {@code filename=""} attribute must be
treated as a file part (not a form field) with an empty name string.
+ */
+ @Test
+ void testPartWithEmptyFileName() throws Exception {
+ // @formatter:off
+ final var bodyStr = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"file\";
filename=\"\"\r\n"
+ + "Content-Type: application/octet-stream\r\n"
+ + "\r\n"
+ + "\r\n"
+ + "--" + BOUNDARY + "--\r\n";
+ // @formatter:on
+ final var body = bodyStr.getBytes(StandardCharsets.US_ASCII);
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext());
+ final FileItemInput item = iter.next();
+ assertEquals("file", item.getFieldName());
+ assertEquals("", item.getName());
+ assertFalse(item.isFormField());
+ assertFalse(iter.hasNext());
+ }
+
+ /**
+ * A single-file part: the iterator must yield exactly one item whose
properties match the part headers.
+ */
+ @Test
+ void testSingleFilePart() throws Exception {
+ final var body = buildSingleFilePart("upload", "hello.txt", "Hello,
World!");
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext(), "Expected at least one item");
+ final FileItemInput item = iter.next();
+ assertEquals("upload", item.getFieldName());
+ assertEquals("hello.txt", item.getName());
+ assertEquals("text/plain", item.getContentType());
+ assertFalse(item.isFormField(), "Part with a filename should not be a
form field");
+ assertFalse(iter.hasNext(), "Expected no more items");
+ }
+
+ /**
+ * A single form-field part (no filename): the iterator must yield one
item marked as a form field.
+ */
+ @Test
+ void testSingleFormField() throws Exception {
+ final var body = buildSingleFormField("myField", "myValue");
+ final HttpServletRequest request = new
JavaxMockHttpServletRequest(body, CONTENT_TYPE);
+ final var upload = newUpload();
+ final FileItemInputIterator iter = upload.getItemIterator(request);
+ assertTrue(iter.hasNext(), "Expected at least one item");
+ final FileItemInput item = iter.next();
+ assertEquals("myField", item.getFieldName());
+ assertNull(item.getName(), "Form field should have no file name");
+ assertTrue(item.isFormField(), "Part without a filename should be a
form field");
+ assertFalse(iter.hasNext(), "Expected no more items");
+ }
+}