001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.fileupload2.core;
018
019import static org.junit.jupiter.api.Assertions.assertEquals;
020import static org.junit.jupiter.api.Assertions.assertFalse;
021import static org.junit.jupiter.api.Assertions.assertThrows;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.ByteArrayOutputStream;
026import java.io.IOException;
027
028import org.apache.commons.io.IOUtils;
029import org.junit.jupiter.api.Test;
030
031/**
032 * Unit test for items with varying sizes.
033 *
034 * @param <AFU> The FileUpload type.
035 * @param <R>   The FileUpload request type.
036 * @param <I>   The FileItem type.
037 * @param <F>   The FileItemFactory type.
038 */
039public abstract class AbstractSizesTest<AFU extends AbstractFileUpload<R, I, F>, R, I extends FileItem<I>, F extends FileItemFactory<I>>
040        extends AbstractTest<AFU, R, I, F> {
041
042    /**
043     * Checks, whether limiting the file size works.
044     *
045     * @throws IOException Test failure.
046     */
047    @Test
048    public void testFileSizeLimit() throws IOException {
049        final var content = "This is the content of the file\n";
050        final var contentSize = content.getBytes().length;
051
052        // @formatter:off
053        final var request =
054            "-----1234\r\n" +
055            "Content-Disposition: form-data; name=\"file\"; filename=\"foo.tab\"\r\n" +
056            "Content-Type: text/whatever\r\n" +
057            "\r\n" +
058            content +
059            "\r\n" +
060            "-----1234--\r\n";
061        // @formatter:on
062
063        var upload = newFileUpload();
064        upload.setFileSizeMax(-1);
065        var req = newMockHttpServletRequest(request, null, null);
066        var fileItems = upload.parseRequest(req);
067        assertEquals(1, fileItems.size());
068        var item = fileItems.get(0);
069        assertEquals(content, new String(item.get()));
070
071        upload = newFileUpload();
072        upload.setFileSizeMax(40);
073        req = newMockHttpServletRequest(request, null, null);
074        fileItems = upload.parseRequest(req);
075        assertEquals(1, fileItems.size());
076        item = fileItems.get(0);
077        assertEquals(content, new String(item.get()));
078
079        upload = newFileUpload();
080        upload.setFileSizeMax(contentSize);
081        req = newMockHttpServletRequest(request, null, null);
082        fileItems = upload.parseRequest(req);
083        assertEquals(1, fileItems.size());
084        item = fileItems.get(0);
085        assertEquals(content, new String(item.get()));
086
087        upload = newFileUpload();
088        upload.setFileSizeMax(contentSize - 1);
089        req = newMockHttpServletRequest(request, null, null);
090        try {
091            upload.parseRequest(req);
092            fail("Expected exception.");
093        } catch (final FileUploadByteCountLimitException e) {
094            assertEquals(contentSize - 1, e.getPermitted());
095        }
096
097        upload = newFileUpload();
098        upload.setFileSizeMax(30);
099        req = newMockHttpServletRequest(request, null, null);
100        try {
101            upload.parseRequest(req);
102            fail("Expected exception.");
103        } catch (final FileUploadByteCountLimitException e) {
104            assertEquals(30, e.getPermitted());
105        }
106    }
107
108    /**
109     * Checks, whether a faked Content-Length header is detected.
110     *
111     * @throws IOException Test failure.
112     */
113    @Test
114    public void testFileSizeLimitWithFakedContentLength() throws IOException {
115        // @formatter:off
116        final var request =
117            "-----1234\r\n" +
118            "Content-Disposition: form-data; name=\"file\"; filename=\"foo.tab\"\r\n" +
119            "Content-Type: text/whatever\r\n" +
120            "Content-Length: 10\r\n" +
121            "\r\n" +
122            "This is the content of the file\n" +
123            "\r\n" +
124            "-----1234--\r\n";
125        // @formatter:on
126
127        var upload = newFileUpload();
128        upload.setFileSizeMax(-1);
129        var req = newMockHttpServletRequest(request, null, null);
130        var fileItems = upload.parseRequest(req);
131        assertEquals(1, fileItems.size());
132        var item = fileItems.get(0);
133        assertEquals("This is the content of the file\n", new String(item.get()));
134
135        upload = newFileUpload();
136        upload.setFileSizeMax(40);
137        req = newMockHttpServletRequest(request, null, null);
138        fileItems = upload.parseRequest(req);
139        assertEquals(1, fileItems.size());
140        item = fileItems.get(0);
141        assertEquals("This is the content of the file\n", new String(item.get()));
142
143        // provided Content-Length is larger than the FileSizeMax -> handled by ctor
144        upload = newFileUpload();
145        upload.setFileSizeMax(5);
146        req = newMockHttpServletRequest(request, null, null);
147        try {
148            upload.parseRequest(req);
149            fail("Expected exception.");
150        } catch (final FileUploadByteCountLimitException e) {
151            assertEquals(5, e.getPermitted());
152        }
153
154        // provided Content-Length is wrong, actual content is larger -> handled by LimitedInputStream
155        upload = newFileUpload();
156        upload.setFileSizeMax(15);
157        req = newMockHttpServletRequest(request, null, null);
158        try {
159            upload.parseRequest(req);
160            fail("Expected exception.");
161        } catch (final FileUploadByteCountLimitException e) {
162            assertEquals(15, e.getPermitted());
163        }
164    }
165
166    /**
167     * Checks whether maxSize works.
168     *
169     * @throws IOException Test failure.
170     */
171    @Test
172    public void testMaxSizeLimit() throws IOException {
173        // @formatter:off
174        final var request =
175            "-----1234\r\n" +
176            "Content-Disposition: form-data; name=\"file1\"; filename=\"foo1.tab\"\r\n" +
177            "Content-Type: text/whatever\r\n" +
178            "Content-Length: 10\r\n" +
179            "\r\n" +
180            "This is the content of the file\n" +
181            "\r\n" +
182            "-----1234\r\n" +
183            "Content-Disposition: form-data; name=\"file2\"; filename=\"foo2.tab\"\r\n" +
184            "Content-Type: text/whatever\r\n" +
185            "\r\n" +
186            "This is the content of the file\n" +
187            "\r\n" +
188            "-----1234--\r\n";
189        // @formatter:on
190
191        final var upload = newFileUpload();
192        upload.setFileSizeMax(-1);
193        upload.setSizeMax(200);
194
195        final var req = newMockHttpServletRequest(request, null, null);
196        try {
197            upload.parseRequest(req);
198            fail("Expected exception.");
199        } catch (final FileUploadSizeException e) {
200            assertEquals(200, e.getPermitted());
201        }
202    }
203
204    @Test
205    public void testMaxSizeLimitUnknownContentLength() throws IOException {
206        // @formatter:off
207        final var request =
208            "-----1234\r\n" +
209            "Content-Disposition: form-data; name=\"file1\"; filename=\"foo1.tab\"\r\n" +
210            "Content-Type: text/whatever\r\n" +
211            "Content-Length: 10\r\n" +
212            "\r\n" +
213            "This is the content of the file\n" +
214            "\r\n" +
215            "-----1234\r\n" +
216            "Content-Disposition: form-data; name=\"file2\"; filename=\"foo2.tab\"\r\n" +
217            "Content-Type: text/whatever\r\n" +
218            "\r\n" +
219            "This is the content of the file\n" +
220            "\r\n" +
221            "-----1234--\r\n";
222        // @formatter:on
223
224        final var upload = newFileUpload();
225        upload.setFileSizeMax(-1);
226        upload.setSizeMax(300);
227
228        // the first item should be within the max size limit
229        // set the read limit to 10 to simulate a "real" stream
230        // otherwise the buffer would be immediately filled
231
232        final var req = newMockHttpServletRequest(request, -1L, 10);
233
234        final var it = upload.getItemIterator(req);
235        assertTrue(it.hasNext());
236
237        final var item = it.next();
238        assertFalse(item.isFormField());
239        assertEquals("file1", item.getFieldName());
240        assertEquals("foo1.tab", item.getName());
241
242        {
243            try (final var baos = new ByteArrayOutputStream();
244                    final var stream = item.getInputStream()) {
245                IOUtils.copy(stream, baos);
246            }
247
248        }
249
250        // the second item is over the size max, thus we expect an error
251        // the header is still within size max -> this shall still succeed
252        assertTrue(it.hasNext());
253
254        assertThrows(FileUploadException.class, () -> {
255            final var item2 = it.next();
256            try (final var baos = new ByteArrayOutputStream();
257                    final var stream = item2.getInputStream()) {
258                IOUtils.copy(stream, baos);
259            }
260        });
261    }
262}