Path: blob/master/test/jdk/java/net/httpclient/AsFileDownloadTest.java
41149 views
/*1* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @summary Basic test for ofFileDownload26* @bug 819696527* @modules java.base/sun.net.www.http28* java.net.http/jdk.internal.net.http.common29* java.net.http/jdk.internal.net.http.frame30* java.net.http/jdk.internal.net.http.hpack31* java.logging32* jdk.httpserver33* @library /test/lib http2/server34* @build Http2TestServer35* @build jdk.test.lib.net.SimpleSSLContext36* @build jdk.test.lib.Platform37* @build jdk.test.lib.util.FileUtils38* @run testng/othervm AsFileDownloadTest39* @run testng/othervm/java.security.policy=AsFileDownloadTest.policy AsFileDownloadTest40*/4142import com.sun.net.httpserver.HttpExchange;43import com.sun.net.httpserver.HttpHandler;44import com.sun.net.httpserver.HttpServer;45import com.sun.net.httpserver.HttpsConfigurator;46import com.sun.net.httpserver.HttpsServer;47import java.io.IOException;48import java.io.InputStream;49import java.io.OutputStream;50import java.io.UncheckedIOException;51import java.net.InetAddress;52import java.net.InetSocketAddress;53import java.net.URI;54import java.net.http.HttpClient;55import java.net.http.HttpHeaders;56import java.net.http.HttpRequest;57import java.net.http.HttpRequest.BodyPublishers;58import java.net.http.HttpResponse;59import java.net.http.HttpResponse.BodyHandler;60import java.nio.file.Files;61import java.nio.file.Path;62import java.nio.file.Paths;63import java.util.ArrayList;64import java.util.Arrays;65import java.util.List;66import java.util.Locale;67import java.util.Map;68import javax.net.ssl.SSLContext;69import jdk.test.lib.net.SimpleSSLContext;70import jdk.test.lib.util.FileUtils;71import org.testng.annotations.AfterTest;72import org.testng.annotations.BeforeTest;73import org.testng.annotations.DataProvider;74import org.testng.annotations.Test;75import static java.lang.System.out;76import static java.net.http.HttpResponse.BodyHandlers.ofFileDownload;77import static java.nio.charset.StandardCharsets.UTF_8;78import static java.nio.file.StandardOpenOption.*;79import static org.testng.Assert.assertEquals;80import static org.testng.Assert.assertTrue;81import static org.testng.Assert.fail;8283public class AsFileDownloadTest {8485SSLContext sslContext;86HttpServer httpTestServer; // HTTP/1.1 [ 4 servers ]87HttpsServer httpsTestServer; // HTTPS/1.188Http2TestServer http2TestServer; // HTTP/2 ( h2c )89Http2TestServer https2TestServer; // HTTP/2 ( h2 )90String httpURI;91String httpsURI;92String http2URI;93String https2URI;9495Path tempDir;9697static final String[][] contentDispositionValues = new String[][] {98// URI query Content-Type header value Expected filename99{ "001", "Attachment; filename=example001.html", "example001.html" },100{ "002", "attachment; filename=example002.html", "example002.html" },101{ "003", "ATTACHMENT; filename=example003.html", "example003.html" },102{ "004", "attAChment; filename=example004.html", "example004.html" },103{ "005", "attachmeNt; filename=example005.html", "example005.html" },104105{ "006", "attachment; Filename=example006.html", "example006.html" },106{ "007", "attachment; FILENAME=example007.html", "example007.html" },107{ "008", "attachment; fileName=example008.html", "example008.html" },108{ "009", "attachment; fIlEnAMe=example009.html", "example009.html" },109110{ "010", "attachment; filename=Example010.html", "Example010.html" },111{ "011", "attachment; filename=EXAMPLE011.html", "EXAMPLE011.html" },112{ "012", "attachment; filename=eXample012.html", "eXample012.html" },113{ "013", "attachment; filename=example013.HTML", "example013.HTML" },114{ "014", "attachment; filename =eXaMpLe014.HtMl", "eXaMpLe014.HtMl"},115116{ "015", "attachment; filename=a", "a" },117{ "016", "attachment; filename= b", "b" },118{ "017", "attachment; filename= c", "c" },119{ "018", "attachment; filename= d", "d" },120{ "019", "attachment; filename=e ; filename*=utf-8''eee.txt", "e"},121{ "020", "attachment; filename*=utf-8''fff.txt; filename=f", "f"},122{ "021", "attachment; filename=g", "g" },123{ "022", "attachment; filename= h", "h" },124125{ "023", "attachment; filename=\"space name\"", "space name" },126{ "024", "attachment; filename=me.txt; filename*=utf-8''you.txt", "me.txt" },127{ "025", "attachment; filename=\"m y.txt\"; filename*=utf-8''you.txt", "m y.txt" },128129{ "030", "attachment; filename=foo/file1.txt", "file1.txt" },130{ "031", "attachment; filename=foo/bar/file2.txt", "file2.txt" },131{ "032", "attachment; filename=baz\\file3.txt", "file3.txt" },132{ "033", "attachment; filename=baz\\bar\\file4.txt", "file4.txt" },133{ "034", "attachment; filename=x/y\\file5.txt", "file5.txt" },134{ "035", "attachment; filename=x/y\\file6.txt", "file6.txt" },135{ "036", "attachment; filename=x/y\\z/file7.txt", "file7.txt" },136{ "037", "attachment; filename=x/y\\z/\\x/file8.txt", "file8.txt" },137{ "038", "attachment; filename=/root/file9.txt", "file9.txt" },138{ "039", "attachment; filename=../file10.txt", "file10.txt" },139{ "040", "attachment; filename=..\\file11.txt", "file11.txt" },140{ "041", "attachment; filename=foo/../../file12.txt", "file12.txt" },141};142143@DataProvider(name = "positive")144public Object[][] positive() {145List<Object[]> list = new ArrayList<>();146147Arrays.asList(contentDispositionValues).stream()148.map(e -> new Object[] {httpURI + "?" + e[0], e[1], e[2]})149.forEach(list::add);150Arrays.asList(contentDispositionValues).stream()151.map(e -> new Object[] {httpsURI + "?" + e[0], e[1], e[2]})152.forEach(list::add);153Arrays.asList(contentDispositionValues).stream()154.map(e -> new Object[] {http2URI + "?" + e[0], e[1], e[2]})155.forEach(list::add);156Arrays.asList(contentDispositionValues).stream()157.map(e -> new Object[] {https2URI + "?" + e[0], e[1], e[2]})158.forEach(list::add);159160return list.stream().toArray(Object[][]::new);161}162163@Test(dataProvider = "positive")164void test(String uriString, String contentDispositionValue, String expectedFilename)165throws Exception166{167out.printf("test(%s, %s, %s): starting", uriString, contentDispositionValue, expectedFilename);168HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();169170URI uri = URI.create(uriString);171HttpRequest request = HttpRequest.newBuilder(uri)172.POST(BodyPublishers.ofString("May the luck of the Irish be with you!"))173.build();174175BodyHandler bh = ofFileDownload(tempDir.resolve(uri.getPath().substring(1)),176CREATE, TRUNCATE_EXISTING, WRITE);177HttpResponse<Path> response = client.send(request, bh);178179out.println("Got response: " + response);180out.println("Got body Path: " + response.body());181String fileContents = new String(Files.readAllBytes(response.body()), UTF_8);182out.println("Got body: " + fileContents);183184assertEquals(response.statusCode(),200);185assertEquals(response.body().getFileName().toString(), expectedFilename);186assertTrue(response.headers().firstValue("Content-Disposition").isPresent());187assertEquals(response.headers().firstValue("Content-Disposition").get(),188contentDispositionValue);189assertEquals(fileContents, "May the luck of the Irish be with you!");190191// additional checks unrelated to file download192caseInsensitivityOfHeaders(request.headers());193caseInsensitivityOfHeaders(response.headers());194}195196// --- Negative197198static final String[][] contentDispositionBADValues = new String[][] {199// URI query Content-Type header value200{ "100", "" }, // empty201{ "101", "filename=example.html" }, // no attachment202{ "102", "attachment; filename=space name" }, // unquoted with space203{ "103", "attachment; filename=" }, // empty filename param204{ "104", "attachment; filename=\"" }, // single quote205{ "105", "attachment; filename=\"\"" }, // empty quoted206{ "106", "attachment; filename=." }, // dot207{ "107", "attachment; filename=.." }, // dot dot208{ "108", "attachment; filename=\".." }, // badly quoted dot dot209{ "109", "attachment; filename=\"..\"" }, // quoted dot dot210{ "110", "attachment; filename=\"bad" }, // badly quoted211{ "111", "attachment; filename=\"bad;" }, // badly quoted with ';'212{ "112", "attachment; filename=\"bad ;" }, // badly quoted with ' ;'213{ "113", "attachment; filename*=utf-8''xx.txt "}, // no "filename" param214215{ "120", "<<NOT_PRESENT>>" }, // header not present216217};218219@DataProvider(name = "negative")220public Object[][] negative() {221List<Object[]> list = new ArrayList<>();222223Arrays.asList(contentDispositionBADValues).stream()224.map(e -> new Object[] {httpURI + "?" + e[0], e[1]})225.forEach(list::add);226Arrays.asList(contentDispositionBADValues).stream()227.map(e -> new Object[] {httpsURI + "?" + e[0], e[1]})228.forEach(list::add);229Arrays.asList(contentDispositionBADValues).stream()230.map(e -> new Object[] {http2URI + "?" + e[0], e[1]})231.forEach(list::add);232Arrays.asList(contentDispositionBADValues).stream()233.map(e -> new Object[] {https2URI + "?" + e[0], e[1]})234.forEach(list::add);235236return list.stream().toArray(Object[][]::new);237}238239@Test(dataProvider = "negative")240void negativeTest(String uriString, String contentDispositionValue)241throws Exception242{243out.printf("negativeTest(%s, %s): starting", uriString, contentDispositionValue);244HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();245246URI uri = URI.create(uriString);247HttpRequest request = HttpRequest.newBuilder(uri)248.POST(BodyPublishers.ofString("Does not matter"))249.build();250251BodyHandler bh = ofFileDownload(tempDir, CREATE, TRUNCATE_EXISTING, WRITE);252try {253HttpResponse<Path> response = client.send(request, bh);254fail("UNEXPECTED response: " + response + ", path:" + response.body());255} catch (UncheckedIOException | IOException ioe) {256System.out.println("Caught expected: " + ioe);257}258}259260// -- Infrastructure261262static String serverAuthority(HttpServer server) {263return InetAddress.getLoopbackAddress().getHostName() + ":"264+ server.getAddress().getPort();265}266267@BeforeTest268public void setup() throws Exception {269tempDir = Paths.get("asFileDownloadTest.tmp.dir");270if (Files.exists(tempDir))271throw new AssertionError("Unexpected test work dir existence: " + tempDir.toString());272273Files.createDirectory(tempDir);274// Unique dirs per test run, based on the URI path275Files.createDirectories(tempDir.resolve("http1/afdt/"));276Files.createDirectories(tempDir.resolve("https1/afdt/"));277Files.createDirectories(tempDir.resolve("http2/afdt/"));278Files.createDirectories(tempDir.resolve("https2/afdt/"));279280// HTTP/1.1 server logging in case of security exceptions ( uncomment if needed )281//Logger logger = Logger.getLogger("com.sun.net.httpserver");282//ConsoleHandler ch = new ConsoleHandler();283//logger.setLevel(Level.ALL);284//ch.setLevel(Level.ALL);285//logger.addHandler(ch);286287sslContext = new SimpleSSLContext().get();288if (sslContext == null)289throw new AssertionError("Unexpected null sslContext");290291InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);292httpTestServer = HttpServer.create(sa, 0);293httpTestServer.createContext("/http1/afdt", new Http1FileDispoHandler());294httpURI = "http://" + serverAuthority(httpTestServer) + "/http1/afdt";295296httpsTestServer = HttpsServer.create(sa, 0);297httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));298httpsTestServer.createContext("/https1/afdt", new Http1FileDispoHandler());299httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1/afdt";300301http2TestServer = new Http2TestServer("localhost", false, 0);302http2TestServer.addHandler(new Http2FileDispoHandler(), "/http2/afdt");303http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/afdt";304305https2TestServer = new Http2TestServer("localhost", true, sslContext);306https2TestServer.addHandler(new Http2FileDispoHandler(), "/https2/afdt");307https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/afdt";308309httpTestServer.start();310httpsTestServer.start();311http2TestServer.start();312https2TestServer.start();313}314315@AfterTest316public void teardown() throws Exception {317httpTestServer.stop(0);318httpsTestServer.stop(0);319http2TestServer.stop();320https2TestServer.stop();321322if (System.getSecurityManager() == null && Files.exists(tempDir)) {323// clean up before next run with security manager324FileUtils.deleteFileTreeWithRetry(tempDir);325}326}327328static String contentDispositionValueFromURI(URI uri) {329String queryIndex = uri.getQuery();330String[][] values;331if (queryIndex.startsWith("0")) // positive tests start with '0'332values = contentDispositionValues;333else if (queryIndex.startsWith("1")) // negative tests start with '1'334values = contentDispositionBADValues;335else336throw new AssertionError("SERVER: UNEXPECTED query:" + queryIndex);337338return Arrays.asList(values).stream()339.filter(e -> e[0].equals(queryIndex))340.map(e -> e[1])341.findFirst()342.orElseThrow();343}344345static class Http1FileDispoHandler implements HttpHandler {346@Override347public void handle(HttpExchange t) throws IOException {348try (InputStream is = t.getRequestBody();349OutputStream os = t.getResponseBody()) {350byte[] bytes = is.readAllBytes();351352String value = contentDispositionValueFromURI(t.getRequestURI());353if (!value.equals("<<NOT_PRESENT>>"))354t.getResponseHeaders().set("Content-Disposition", value);355356t.sendResponseHeaders(200, bytes.length);357os.write(bytes);358}359}360}361362static class Http2FileDispoHandler implements Http2Handler {363@Override364public void handle(Http2TestExchange t) throws IOException {365try (InputStream is = t.getRequestBody();366OutputStream os = t.getResponseBody()) {367byte[] bytes = is.readAllBytes();368369String value = contentDispositionValueFromURI(t.getRequestURI());370if (!value.equals("<<NOT_PRESENT>>"))371t.getResponseHeaders().addHeader("Content-Disposition", value);372373t.sendResponseHeaders(200, bytes.length);374os.write(bytes);375}376}377}378379// ---380381// Asserts case-insensitivity of headers (nothing to do with file382// download, just convenient as we have a couple of header instances. )383static void caseInsensitivityOfHeaders(HttpHeaders headers) {384try {385for (Map.Entry<String, List<String>> entry : headers.map().entrySet()) {386String headerName = entry.getKey();387List<String> headerValue = entry.getValue();388389for (String name : List.of(headerName.toUpperCase(Locale.ROOT),390headerName.toLowerCase(Locale.ROOT))) {391assertTrue(headers.firstValue(name).isPresent());392assertEquals(headers.firstValue(name).get(), headerValue.get(0));393assertEquals(headers.allValues(name).size(), headerValue.size());394assertEquals(headers.allValues(name), headerValue);395assertEquals(headers.map().get(name).size(), headerValue.size());396assertEquals(headers.map().get(name), headerValue);397}398}399} catch (Throwable t) {400System.out.println("failure in caseInsensitivityOfHeaders with:" + headers);401throw t;402}403}404}405406407