Path: blob/master/test/jdk/java/net/httpclient/EncodedCharsInURI.java
41152 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* @bug 819968326* @summary Tests that escaped characters in URI are correctly27* handled (not re-escaped and not unescaped)28* @library /test/lib http2/server29* @build jdk.test.lib.net.SimpleSSLContext HttpServerAdapters EncodedCharsInURI30* @modules java.base/sun.net.www.http31* java.net.http/jdk.internal.net.http.common32* java.net.http/jdk.internal.net.http.frame33* java.net.http/jdk.internal.net.http.hpack34* @run testng/othervm35* -Djdk.tls.acknowledgeCloseNotify=true36* -Djdk.internal.httpclient.debug=true37* -Djdk.httpclient.HttpClient.log=headers,errors EncodedCharsInURI38*/39//* -Djdk.internal.httpclient.debug=true4041import com.sun.net.httpserver.HttpServer;42import com.sun.net.httpserver.HttpsConfigurator;43import com.sun.net.httpserver.HttpsServer;44import jdk.test.lib.net.SimpleSSLContext;45import org.testng.annotations.AfterClass;46import org.testng.annotations.AfterTest;47import org.testng.annotations.BeforeTest;48import org.testng.annotations.DataProvider;49import org.testng.annotations.Test;5051import javax.net.ServerSocketFactory;52import javax.net.ssl.SSLContext;53import java.io.IOException;54import java.io.InputStream;55import java.io.OutputStream;56import java.net.InetAddress;57import java.net.InetSocketAddress;58import java.net.ServerSocket;59import java.net.Socket;60import java.net.URI;61import java.net.http.HttpClient;62import java.net.http.HttpRequest;63import java.net.http.HttpRequest.BodyPublisher;64import java.net.http.HttpRequest.BodyPublishers;65import java.net.http.HttpResponse;66import java.net.http.HttpResponse.BodyHandler;67import java.net.http.HttpResponse.BodyHandlers;68import java.util.List;69import java.util.Locale;70import java.util.StringTokenizer;71import java.util.concurrent.CompletableFuture;72import java.util.concurrent.ConcurrentHashMap;73import java.util.concurrent.ConcurrentLinkedQueue;74import java.util.concurrent.ConcurrentMap;75import java.util.concurrent.Executor;76import java.util.concurrent.Executors;77import java.util.concurrent.atomic.AtomicLong;7879import static java.lang.String.format;80import static java.lang.System.in;81import static java.lang.System.out;82import static java.nio.charset.StandardCharsets.US_ASCII;83import static java.nio.charset.StandardCharsets.UTF_8;84import static java.net.http.HttpClient.Builder.NO_PROXY;85import static org.testng.Assert.assertEquals;86import static org.testng.Assert.assertTrue;8788public class EncodedCharsInURI implements HttpServerAdapters {8990SSLContext sslContext;91HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ]92HttpTestServer httpsTestServer; // HTTPS/1.193HttpTestServer http2TestServer; // HTTP/2 ( h2c )94HttpTestServer https2TestServer; // HTTP/2 ( h2 )95DummyServer httpDummyServer; // HTTP/1.1 [ 2 servers ]96DummyServer httpsDummyServer; // HTTPS/1.197String httpURI_fixed;98String httpURI_chunk;99String httpsURI_fixed;100String httpsURI_chunk;101String http2URI_fixed;102String http2URI_chunk;103String https2URI_fixed;104String https2URI_chunk;105String httpDummy;106String httpsDummy;107108static final int ITERATION_COUNT = 1;109// a shared executor helps reduce the amount of threads created by the test110static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());111static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();112static volatile boolean tasksFailed;113static final AtomicLong serverCount = new AtomicLong();114static final AtomicLong clientCount = new AtomicLong();115static final long start = System.nanoTime();116public static String now() {117long now = System.nanoTime() - start;118long secs = now / 1000_000_000;119long mill = (now % 1000_000_000) / 1000_000;120long nan = now % 1000_000;121return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);122}123124private volatile HttpClient sharedClient;125126static class TestExecutor implements Executor {127final AtomicLong tasks = new AtomicLong();128Executor executor;129TestExecutor(Executor executor) {130this.executor = executor;131}132133@Override134public void execute(Runnable command) {135long id = tasks.incrementAndGet();136executor.execute(() -> {137try {138command.run();139} catch (Throwable t) {140tasksFailed = true;141System.out.printf(now() + "Task %s failed: %s%n", id, t);142System.err.printf(now() + "Task %s failed: %s%n", id, t);143FAILURES.putIfAbsent("Task " + id, t);144throw t;145}146});147}148}149150@AfterClass151static final void printFailedTests() {152out.println("\n=========================");153try {154out.printf("%n%sCreated %d servers and %d clients%n",155now(), serverCount.get(), clientCount.get());156if (FAILURES.isEmpty()) return;157out.println("Failed tests: ");158FAILURES.entrySet().forEach((e) -> {159out.printf("\t%s: %s%n", e.getKey(), e.getValue());160e.getValue().printStackTrace(out);161});162if (tasksFailed) {163System.out.println("WARNING: Some tasks failed");164}165} finally {166out.println("\n=========================\n");167}168}169170private String[] uris() {171return new String[] {172httpDummy,173httpsDummy,174httpURI_fixed,175httpURI_chunk,176httpsURI_fixed,177httpsURI_chunk,178http2URI_fixed,179http2URI_chunk,180https2URI_fixed,181https2URI_chunk,182};183}184185@DataProvider(name = "noThrows")186public Object[][] noThrows() {187String[] uris = uris();188Object[][] result = new Object[uris.length * 2][];189//Object[][] result = new Object[uris.length][];190int i = 0;191for (boolean sameClient : List.of(false, true)) {192//if (!sameClient) continue;193for (String uri: uris()) {194result[i++] = new Object[] {uri, sameClient};195}196}197assert i == uris.length * 2;198// assert i == uris.length ;199return result;200}201202private HttpClient makeNewClient() {203clientCount.incrementAndGet();204return HttpClient.newBuilder()205.executor(executor)206.proxy(NO_PROXY)207.sslContext(sslContext)208.build();209}210211HttpClient newHttpClient(boolean share) {212if (!share) return makeNewClient();213HttpClient shared = sharedClient;214if (shared != null) return shared;215synchronized (this) {216shared = sharedClient;217if (shared == null) {218shared = sharedClient = makeNewClient();219}220return shared;221}222}223224final String ENCODED = "/01%252F03/";225226@Test(dataProvider = "noThrows")227public void testEncodedChars(String uri, boolean sameClient)228throws Exception {229HttpClient client = null;230out.printf("%n%s testEncodedChars(%s, %b)%n", now(), uri, sameClient);231uri = uri + ENCODED;232for (int i=0; i< ITERATION_COUNT; i++) {233if (!sameClient || client == null)234client = newHttpClient(sameClient);235236BodyPublisher bodyPublisher = BodyPublishers.ofString(uri);237238HttpRequest req = HttpRequest.newBuilder(URI.create(uri))239.POST(bodyPublisher)240.build();241BodyHandler<String> handler = BodyHandlers.ofString();242CompletableFuture<HttpResponse<String>> responseCF = client.sendAsync(req, handler);243HttpResponse<String> response = responseCF.join();244String body = response.body();245if (!uri.contains(body)) {246System.err.println("Test failed: " + response);247throw new RuntimeException(uri + " doesn't contain '" + body + "'");248} else {249System.out.println("Found expected " + body + " in " + uri);250}251}252}253254@BeforeTest255public void setup() throws Exception {256sslContext = new SimpleSSLContext().get();257if (sslContext == null)258throw new AssertionError("Unexpected null sslContext");259260// HTTP/1.1261HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();262HttpTestHandler h1_chunkHandler = new HTTP_ChunkedHandler();263InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);264httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));265httpTestServer.addHandler(h1_fixedLengthHandler, "/http1/fixed");266httpTestServer.addHandler(h1_chunkHandler, "/http1/chunk");267httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed/x";268httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk/x";269270HttpsServer httpsServer = HttpsServer.create(sa, 0);271httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));272httpsTestServer = HttpTestServer.of(httpsServer);273httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");274httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");275httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed/x";276httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk/x";277278// HTTP/2279HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();280HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();281282http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));283http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");284http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");285http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";286http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";287288https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, sslContext));289https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");290https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");291https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";292https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";293294// DummyServer295httpDummyServer = DummyServer.create(sa);296httpsDummyServer = DummyServer.create(sa, sslContext);297httpDummy = "http://" + httpDummyServer.serverAuthority() + "/http1/dummy/x";298httpsDummy = "https://" + httpsDummyServer.serverAuthority() + "/https1/dummy/x";299300301serverCount.addAndGet(6);302httpTestServer.start();303httpsTestServer.start();304http2TestServer.start();305https2TestServer.start();306httpDummyServer.start();307httpsDummyServer.start();308}309310@AfterTest311public void teardown() throws Exception {312sharedClient = null;313httpTestServer.stop();314httpsTestServer.stop();315http2TestServer.stop();316https2TestServer.stop();317httpDummyServer.stopServer();318httpsDummyServer.stopServer();319}320321static class HTTP_FixedLengthHandler implements HttpTestHandler {322@Override323public void handle(HttpTestExchange t) throws IOException {324out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());325byte[] req;326try (InputStream is = t.getRequestBody()) {327req = is.readAllBytes();328}329String uri = new String(req, UTF_8);330byte[] resp = t.getRequestURI().toString().getBytes(UTF_8);331if (!uri.contains(t.getRequestURI().toString())) {332t.sendResponseHeaders(404, resp.length);333} else {334t.sendResponseHeaders(200, resp.length); //fixed content length335}336try (OutputStream os = t.getResponseBody()) {337os.write(resp);338}339}340}341342static class HTTP_ChunkedHandler implements HttpTestHandler {343@Override344public void handle(HttpTestExchange t) throws IOException {345out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());346byte[] resp;347try (InputStream is = t.getRequestBody()) {348resp = is.readAllBytes();349}350t.sendResponseHeaders(200, -1); // chunked/variable351try (OutputStream os = t.getResponseBody()) {352os.write(resp);353}354}355}356357static class DummyServer extends Thread {358final ServerSocket ss;359final boolean secure;360ConcurrentLinkedQueue<Socket> connections = new ConcurrentLinkedQueue<>();361volatile boolean stopped;362DummyServer(ServerSocket ss, boolean secure) {363super("DummyServer[" + ss.getLocalPort()+"]");364this.secure = secure;365this.ss = ss;366}367368// This is a bit shaky. It doesn't handle continuation369// lines, but our client shouldn't send any.370// Read a line from the input stream, swallowing the final371// \r\n sequence. Stops at the first \n, doesn't complain372// if it wasn't preceded by '\r'.373//374String readLine(InputStream r) throws IOException {375StringBuilder b = new StringBuilder();376int c;377while ((c = r.read()) != -1) {378if (c == '\n') break;379b.appendCodePoint(c);380}381if (b.codePointAt(b.length() -1) == '\r') {382b.delete(b.length() -1, b.length());383}384return b.toString();385}386387@Override388public void run() {389try {390while(!stopped) {391Socket clientConnection = ss.accept();392connections.add(clientConnection);393System.out.println(now() + getName() + ": Client accepted");394StringBuilder headers = new StringBuilder();395Socket targetConnection = null;396InputStream ccis = clientConnection.getInputStream();397OutputStream ccos = clientConnection.getOutputStream();398System.out.println(now() + getName() + ": Reading request line");399String requestLine = readLine(ccis);400System.out.println(now() + getName() + ": Request line: " + requestLine);401402StringTokenizer tokenizer = new StringTokenizer(requestLine);403String method = tokenizer.nextToken();404assert method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("GET");405String path = tokenizer.nextToken();406URI uri;407try {408String hostport = serverAuthority();409uri = new URI((secure ? "https" : "http") +"://" + hostport + path);410} catch (Throwable x) {411System.err.printf("Bad target address: \"%s\" in \"%s\"%n",412path, requestLine);413clientConnection.close();414continue;415}416417// Read all headers until we find the empty line that418// signals the end of all headers.419String line = requestLine;420while (!line.equals("")) {421System.out.println(now() + getName() + ": Reading header: "422+ (line = readLine(ccis)));423headers.append(line).append("\r\n");424}425426StringBuilder response = new StringBuilder();427428int index = headers.toString()429.toLowerCase(Locale.US)430.indexOf("content-length: ");431byte[] b = uri.toString().getBytes(UTF_8);432if (index >= 0) {433index = index + "content-length: ".length();434String cl = headers.toString().substring(index);435StringTokenizer tk = new StringTokenizer(cl);436int len = Integer.parseInt(tk.nextToken());437assert len < b.length * 2;438System.out.println(now() + getName()439+ ": received body: "440+ new String(ccis.readNBytes(len), UTF_8));441}442System.out.println(now()443+ getName() + ": sending back " + uri);444445response.append("HTTP/1.1 200 OK\r\nContent-Length: ")446.append(b.length)447.append("\r\n\r\n");448449// Then send the 200 OK response to the client450System.out.println(now() + getName() + ": Sending "451+ response);452ccos.write(response.toString().getBytes(UTF_8));453ccos.flush();454System.out.println(now() + getName() + ": sent response headers");455ccos.write(b);456ccos.flush();457ccos.close();458System.out.println(now() + getName() + ": sent " + b.length + " body bytes");459connections.remove(clientConnection);460clientConnection.close();461}462} catch (Throwable t) {463if (!stopped) {464System.out.println(now() + getName() + ": failed: " + t);465t.printStackTrace();466try {467stopServer();468} catch (Throwable e) {469470}471}472} finally {473System.out.println(now() + getName() + ": exiting");474}475}476477void close(Socket s) {478try {479s.close();480} catch(Throwable t) {481482}483}484public void stopServer() throws IOException {485stopped = true;486ss.close();487connections.forEach(this::close);488}489490public String serverAuthority() {491return InetAddress.getLoopbackAddress().getHostName() + ":"492+ ss.getLocalPort();493}494495static DummyServer create(InetSocketAddress sa) throws IOException {496ServerSocket ss = ServerSocketFactory.getDefault()497.createServerSocket(sa.getPort(), -1, sa.getAddress());498return new DummyServer(ss, false);499}500501static DummyServer create(InetSocketAddress sa, SSLContext sslContext) throws IOException {502ServerSocket ss = sslContext.getServerSocketFactory()503.createServerSocket(sa.getPort(), -1, sa.getAddress());504return new DummyServer(ss, true);505}506507508}509510}511512513