/* * Copyright 2002-2019 the original author or authors. * * Licensed 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.springframework.http.codec.multipart; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.CountDownLatch; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.springframework.core.ResolvableType.forClass; /** * @author Arjen Poutsma */ public class DefaultMultipartMessageReaderTests extends AbstractDataBufferAllocatingTestCase { private static final String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer iaculis metus id vestibulum nullam."; private static final String MUSPI_MEROL = new StringBuilder(LOREM_IPSUM).reverse().toString(); private static final int BUFFER_SIZE = 16; private final DefaultMultipartMessageReader reader = new DefaultMultipartMessageReader(); @Test public void canRead() { assertTrue(this.reader.canRead(forClass(Part.class), MediaType.MULTIPART_FORM_DATA)); } @Test public void partNoHeader() { MockServerHttpRequest request = createRequest( new ClassPathResource("part-no-header.multipart", getClass()), "boundary"); Flux<Part> result = this.reader.read(forClass(Part.class), request, emptyMap()); StepVerifier.create(result) .consumeNextWith(part -> { assertTrue(part.headers().isEmpty()); part.content().subscribe(DataBufferUtils::release); }) .verifyComplete(); } @Test public void partNoEndBoundary() { MockServerHttpRequest request = createRequest( new ClassPathResource("part-no-end-boundary.multipart", getClass()), "boundary"); Flux<Part> result = this.reader.read(forClass(Part.class), request, emptyMap()); StepVerifier.create(result) .consumeNextWith(part -> part.content().subscribe(DataBufferUtils::release) ) .verifyComplete(); } @Test public void firefox() { testBrowser(new ClassPathResource("firefox.multipart", getClass()), "---------------------------18399284482060392383840973206"); } @Test public void chrome() { testBrowser(new ClassPathResource("chrome.multipart", getClass()), "----WebKitFormBoundaryEveBLvRT65n21fwU"); } @Test public void safari() { testBrowser(new ClassPathResource("safari.multipart", getClass()), "----WebKitFormBoundaryG8fJ50opQOML0oGD"); } private void testBrowser(Resource resource, String boundary) { MockServerHttpRequest request = createRequest(resource, boundary); Flux<Part> result = this.reader.read(forClass(Part.class), request, emptyMap()); StepVerifier.create(result) .consumeNextWith(part -> testBrowserFormField(part, "text1", "a")) .consumeNextWith(part -> testBrowserFormField(part, "text2", "b")) .consumeNextWith(part -> testBrowserFile(part, "file1", "a.txt", LOREM_IPSUM)) .consumeNextWith(part -> testBrowserFile(part, "file2", "a.txt", LOREM_IPSUM)) .consumeNextWith(part -> testBrowserFile(part, "file2", "b.txt", MUSPI_MEROL)) .verifyComplete(); } private MockServerHttpRequest createRequest(Resource resource, String boundary) { Flux<DataBuffer> body = DataBufferUtils .readByteChannel(resource::readableChannel, this.bufferFactory, BUFFER_SIZE); MediaType contentType = new MediaType("multipart", "form-data", singletonMap("boundary", boundary)); return MockServerHttpRequest.post("/") .contentType(contentType) .body(body); } private static void testBrowserFormField(Part part, String name, String value) { assertTrue(part instanceof FormFieldPart); assertEquals(name, part.name()); FormFieldPart formField = (FormFieldPart) part; assertEquals(value, formField.value()); } private static void testBrowserFile(Part part, String name, String filename, String contents) { try { assertTrue(part instanceof FilePart); assertEquals(name, part.name()); FilePart file = (FilePart) part; assertEquals(filename, file.filename()); Path tempFile = Files.createTempFile("DefaultMultipartMessageReaderTests", null); CountDownLatch latch = new CountDownLatch(1); file.transferTo(tempFile) .subscribe(null, throwable -> fail(throwable.getMessage()), () -> { try { verifyContents(tempFile, contents); } finally { latch.countDown(); } }); latch.await(); } catch (Exception ex) { throw new AssertionError(ex); } } private static void verifyContents(Path tempFile, String contents) { try { String result = String.join("", Files.readAllLines(tempFile)); assertEquals(contents, result); } catch (IOException ex) { throw new AssertionError(ex); } } }