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.assertTrue;
021
022import java.io.ByteArrayOutputStream;
023import java.io.IOException;
024import java.nio.charset.StandardCharsets;
025
026import org.junit.jupiter.api.Test;
027
028/**
029 * Tests the {@link ProgressListener}.
030 *
031 * @param <AFU> The subclass of FileUpload.
032 * @param <R>   The FileUpload request type.
033 * @param <I>   The FileItem type.
034 * @param <F>   The FileItemFactory type.
035 */
036public abstract class AbstractProgressListenerTest<AFU extends AbstractFileUpload<R, I, F>, R, I extends FileItem<I>, F extends FileItemFactory<I>>
037        extends AbstractTest<AFU, R, I, F> {
038
039    protected static class ProgressListenerImpl implements ProgressListener {
040
041        private final long expectedContentLength;
042
043        private final int expectedItems;
044
045        private Long bytesRead;
046
047        private Integer items;
048
049        ProgressListenerImpl(final long contentLength, final int itemCount) {
050            expectedContentLength = contentLength;
051            expectedItems = itemCount;
052        }
053
054        void checkFinished() {
055            assertEquals(expectedContentLength, bytesRead.longValue());
056            assertEquals(expectedItems, items.intValue());
057        }
058
059        @Override
060        public void update(final long actualBytesRead, final long actualContentLength, final int actualItems) {
061            assertTrue(actualBytesRead >= 0 && actualBytesRead <= expectedContentLength);
062            assertTrue(actualContentLength == -1 || actualContentLength == expectedContentLength);
063            assertTrue(actualItems >= 0 && actualItems <= expectedItems);
064
065            assertTrue(bytesRead == null || actualBytesRead >= bytesRead.longValue());
066            bytesRead = Long.valueOf(actualBytesRead);
067            assertTrue(items == null || actualItems >= items.intValue());
068            items = Integer.valueOf(actualItems);
069        }
070
071    }
072
073    protected void runTest(final int itemCount, final long contentLength, final R request) throws FileUploadException, IOException {
074        final var upload = newFileUpload();
075        final var listener = new ProgressListenerImpl(contentLength, itemCount);
076        upload.setProgressListener(listener);
077        final var iter = upload.getItemIterator(request);
078        for (var i = 0; i < itemCount; i++) {
079            final var idxI = i;
080            final var fileItemInput = iter.next();
081            try (final var inputStream = fileItemInput.getInputStream()) {
082                for (var j = 0; j < 16_384 + i; j++) {
083                    final var idxJ = j;
084                    //
085                    // This used to be assertEquals((byte) j, (byte) istream.read()); but this seems to trigger a bug in JRockit, so we express the same like
086                    // this:
087                    //
088                    final var b1 = (byte) j;
089                    final var b2 = (byte) inputStream.read();
090                    assertEquals(b1, b2, () -> String.format("itemCount = %,d, i = %,d, j = %,d", itemCount, idxI, idxJ));
091                }
092                assertEquals(-1, inputStream.read());
093            }
094        }
095        assertTrue(!iter.hasNext());
096        listener.checkFinished();
097    }
098
099    /**
100     * Parse a very long file upload by using a progress listener.
101     *
102     * @throws IOException Test failure.
103     */
104    @Test
105    public void testProgressListener() throws IOException {
106        final var numItems = 512;
107        final var baos = new ByteArrayOutputStream();
108        for (var i = 0; i < numItems; i++) {
109            final var header = "-----1234\r\n" + "Content-Disposition: form-data; name=\"field" + (i + 1) + "\"\r\n" + "\r\n";
110            baos.write(header.getBytes(StandardCharsets.US_ASCII));
111            for (var j = 0; j < 16384 + i; j++) {
112                baos.write((byte) j);
113            }
114            baos.write("\r\n".getBytes(StandardCharsets.US_ASCII));
115        }
116        baos.write("-----1234--\r\n".getBytes(StandardCharsets.US_ASCII));
117        final var requestBytes = baos.toByteArray();
118
119        var request = newMockHttpServletRequest(requestBytes, null, Constants.CONTENT_TYPE, null);
120        runTest(numItems, requestBytes.length, request);
121        request = newMockHttpServletRequest(requestBytes, -1L, Constants.CONTENT_TYPE, null);
122        runTest(numItems, requestBytes.length, request);
123    }
124
125}