// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.client.util; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import org.eclipse.jetty.client.AbstractHttpClientServerTest; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.Assert; import org.junit.Test; public class MultiPartContentProviderTest extends AbstractHttpClientServerTest { public MultiPartContentProviderTest(SslContextFactory sslContextFactory) { super(sslContextFactory); } @Test public void testEmptyMultiPart() throws Exception { start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Collection<Part> parts = request.getParts(); Assert.assertEquals(0, parts.size()); } }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(); Assert.assertEquals(200, response.getStatus()); } @Test public void testSimpleField() throws Exception { String name = "field"; String value = "value"; start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Collection<Part> parts = request.getParts(); Assert.assertEquals(1, parts.size()); Part part = parts.iterator().next(); Assert.assertEquals(name, part.getName()); Assert.assertEquals(value, IO.toString(part.getInputStream())); } }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); multiPart.addFieldPart(name, new StringContentProvider(value), null); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(); Assert.assertEquals(200, response.getStatus()); } @Test public void testFieldWithOverridenContentType() throws Exception { String name = "field"; String value = "\u00e8"; Charset encoding = StandardCharsets.ISO_8859_1; start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Collection<Part> parts = request.getParts(); Assert.assertEquals(1, parts.size()); Part part = parts.iterator().next(); Assert.assertEquals(name, part.getName()); String contentType = part.getContentType(); Assert.assertNotNull(contentType); int equal = contentType.lastIndexOf('='); Charset charset = Charset.forName(contentType.substring(equal + 1)); Assert.assertEquals(encoding, charset); Assert.assertEquals(value, IO.toString(part.getInputStream(), charset)); } }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); HttpFields fields = new HttpFields(); fields.put(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + encoding.name()); BytesContentProvider content = new BytesContentProvider(value.getBytes(encoding)); multiPart.addFieldPart(name, content, fields); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(); Assert.assertEquals(200, response.getStatus()); } @Test public void testFieldDeferred() throws Exception { String name = "field"; byte[] data = "Hello, World".getBytes(StandardCharsets.US_ASCII); start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Collection<Part> parts = request.getParts(); Assert.assertEquals(1, parts.size()); Part part = parts.iterator().next(); Assert.assertEquals(name, part.getName()); Assert.assertEquals("text/plain", part.getContentType()); Assert.assertArrayEquals(data, IO.readBytes(part.getInputStream())); } }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); DeferredContentProvider content = new DeferredContentProvider(); multiPart.addFieldPart(name, content, null); multiPart.close(); CountDownLatch responseLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(result -> { Assert.assertTrue(String.valueOf(result.getFailure()), result.isSucceeded()); Assert.assertEquals(200, result.getResponse().getStatus()); responseLatch.countDown(); }); // Wait until the request has been sent. Thread.sleep(1000); // Provide the content. content.offer(ByteBuffer.wrap(data)); content.close(); Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } @Test public void testFileFromInputStream() throws Exception { String name = "file"; String fileName = "upload.png"; String contentType = "image/png"; byte[] data = new byte[512]; new Random().nextBytes(data); start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Collection<Part> parts = request.getParts(); Assert.assertEquals(1, parts.size()); Part part = parts.iterator().next(); Assert.assertEquals(name, part.getName()); Assert.assertEquals(contentType, part.getContentType()); Assert.assertEquals(fileName, part.getSubmittedFileName()); Assert.assertEquals(data.length, part.getSize()); Assert.assertArrayEquals(data, IO.readBytes(part.getInputStream())); } }); CountDownLatch closeLatch = new CountDownLatch(1); MultiPartContentProvider multiPart = new MultiPartContentProvider(); InputStreamContentProvider content = new InputStreamContentProvider(new ByteArrayInputStream(data) { @Override public void close() throws IOException { super.close(); closeLatch.countDown(); } }); HttpFields fields = new HttpFields(); fields.put(HttpHeader.CONTENT_TYPE, contentType); multiPart.addFilePart(name, fileName, content, fields); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(); Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); Assert.assertEquals(200, response.getStatus()); } @Test public void testFileFromPath() throws Exception { // Prepare a file to upload. String data = "multipart_test_\u20ac"; Path tmpDir = MavenTestingUtils.getTargetTestingPath(); Path tmpPath = Files.createTempFile(tmpDir, "multipart_", ".txt"); Charset encoding = StandardCharsets.UTF_8; try (BufferedWriter writer = Files.newBufferedWriter(tmpPath, encoding, StandardOpenOption.CREATE)) { writer.write(data); } String name = "file"; String contentType = "text/plain; charset=" + encoding.name(); start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Collection<Part> parts = request.getParts(); Assert.assertEquals(1, parts.size()); Part part = parts.iterator().next(); Assert.assertEquals(name, part.getName()); Assert.assertEquals(contentType, part.getContentType()); Assert.assertEquals(tmpPath.getFileName().toString(), part.getSubmittedFileName()); Assert.assertEquals(Files.size(tmpPath), part.getSize()); Assert.assertEquals(data, IO.toString(part.getInputStream(), encoding)); } }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); PathContentProvider content = new PathContentProvider(contentType, tmpPath); content.setByteBufferPool(client.getByteBufferPool()); multiPart.addFilePart(name, tmpPath.getFileName().toString(), content, null); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(); Assert.assertEquals(200, response.getStatus()); Files.delete(tmpPath); } @Test public void testFieldWithFile() throws Exception { // Prepare a file to upload. byte[] data = new byte[1024]; new Random().nextBytes(data); Path tmpDir = MavenTestingUtils.getTargetTestingPath(); Path tmpPath = Files.createTempFile(tmpDir, "multipart_", ".txt"); try (OutputStream output = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE)) { output.write(data); } String field = "field"; String value = "\u20ac"; String fileField = "file"; Charset encoding = StandardCharsets.UTF_8; String contentType = "text/plain;charset=" + encoding.name(); String headerName = "foo"; String headerValue = "bar"; start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Part> parts = new ArrayList<>(request.getParts()); Assert.assertEquals(2, parts.size()); Part fieldPart = parts.get(0); Part filePart = parts.get(1); if (!field.equals(fieldPart.getName())) { Part swap = filePart; filePart = fieldPart; fieldPart = swap; } Assert.assertEquals(field, fieldPart.getName()); Assert.assertEquals(contentType, fieldPart.getContentType()); Assert.assertEquals(value, IO.toString(fieldPart.getInputStream(), encoding)); Assert.assertEquals(headerValue, fieldPart.getHeader(headerName)); Assert.assertEquals(fileField, filePart.getName()); Assert.assertEquals("application/octet-stream", filePart.getContentType()); Assert.assertEquals(tmpPath.getFileName().toString(), filePart.getSubmittedFileName()); Assert.assertEquals(Files.size(tmpPath), filePart.getSize()); Assert.assertArrayEquals(data, IO.readBytes(filePart.getInputStream())); } }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); HttpFields fields = new HttpFields(); fields.put(headerName, headerValue); multiPart.addFieldPart(field, new StringContentProvider(value, encoding), fields); multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathContentProvider(tmpPath), null); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(); Assert.assertEquals(200, response.getStatus()); Files.delete(tmpPath); } @Test public void testFieldDeferredAndFileDeferred() throws Exception { String value = "text"; Charset encoding = StandardCharsets.US_ASCII; byte[] fileData = new byte[1024]; new Random().nextBytes(fileData); start(new AbstractMultiPartHandler() { @Override protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Part> parts = new ArrayList<>(request.getParts()); Assert.assertEquals(2, parts.size()); Part fieldPart = parts.get(0); Part filePart = parts.get(1); if (!"field".equals(fieldPart.getName())) { Part swap = filePart; filePart = fieldPart; fieldPart = swap; } Assert.assertEquals(value, IO.toString(fieldPart.getInputStream(), encoding)); Assert.assertEquals("file", filePart.getName()); Assert.assertEquals("application/octet-stream", filePart.getContentType()); Assert.assertEquals("fileName", filePart.getSubmittedFileName()); Assert.assertArrayEquals(fileData, IO.readBytes(filePart.getInputStream())); } }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); DeferredContentProvider fieldContent = new DeferredContentProvider(); multiPart.addFieldPart("field", fieldContent, null); DeferredContentProvider fileContent = new DeferredContentProvider(); multiPart.addFilePart("file", "fileName", fileContent, null); CountDownLatch responseLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) .content(multiPart) .send(result -> { Assert.assertTrue(String.valueOf(result.getFailure()), result.isSucceeded()); Assert.assertEquals(200, result.getResponse().getStatus()); responseLatch.countDown(); }); // Wait until the request has been sent. Thread.sleep(1000); // Provide the content, in reversed part order. fileContent.offer(ByteBuffer.wrap(fileData)); fileContent.close(); Thread.sleep(1000); fieldContent.offer(encoding.encode(value)); fieldContent.close(); multiPart.close(); Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } private static abstract class AbstractMultiPartHandler extends AbstractHandler { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); File tmpDir = MavenTestingUtils.getTargetTestingDir(); request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement(tmpDir.getAbsolutePath())); handle(request, response); } protected abstract void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; } }