test/jdk/java/net/httpclient/http2/BadHeadersTest.java
changeset 49765 ee6f7a61f3a5
child 50681 4254bed3c09d
child 56451 9585061fdb04
equal deleted inserted replaced
49707:f7fd051519ac 49765:ee6f7a61f3a5
       
     1 /*
       
     2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 /*
       
    25  * @test
       
    26  * @modules java.base/sun.net.www.http
       
    27  *          java.net.http/jdk.internal.net.http.common
       
    28  *          java.net.http/jdk.internal.net.http.frame
       
    29  *          java.net.http/jdk.internal.net.http.hpack
       
    30  * @library /lib/testlibrary server
       
    31  * @build Http2TestServer
       
    32  * @build jdk.testlibrary.SimpleSSLContext
       
    33  * @run testng/othervm -Djdk.internal.httpclient.debug=true BadHeadersTest
       
    34  */
       
    35 
       
    36 import jdk.internal.net.http.common.HttpHeadersImpl;
       
    37 import jdk.internal.net.http.common.Pair;
       
    38 import jdk.internal.net.http.frame.ContinuationFrame;
       
    39 import jdk.internal.net.http.frame.HeaderFrame;
       
    40 import jdk.internal.net.http.frame.HeadersFrame;
       
    41 import jdk.internal.net.http.frame.Http2Frame;
       
    42 import jdk.testlibrary.SimpleSSLContext;
       
    43 import org.testng.annotations.AfterTest;
       
    44 import org.testng.annotations.BeforeTest;
       
    45 import org.testng.annotations.DataProvider;
       
    46 import org.testng.annotations.Test;
       
    47 
       
    48 import javax.net.ssl.SSLContext;
       
    49 import javax.net.ssl.SSLSession;
       
    50 import java.io.IOException;
       
    51 import java.io.InputStream;
       
    52 import java.io.OutputStream;
       
    53 import java.net.Socket;
       
    54 import java.net.URI;
       
    55 import java.net.http.HttpClient;
       
    56 import java.net.http.HttpRequest;
       
    57 import java.net.http.HttpRequest.BodyPublishers;
       
    58 import java.net.http.HttpResponse.BodyHandlers;
       
    59 import java.nio.ByteBuffer;
       
    60 import java.util.ArrayList;
       
    61 import java.util.LinkedHashMap;
       
    62 import java.util.List;
       
    63 import java.util.Locale;
       
    64 import java.util.Map;
       
    65 import java.util.concurrent.CompletionException;
       
    66 import java.util.concurrent.atomic.AtomicInteger;
       
    67 import java.util.function.BiFunction;
       
    68 
       
    69 import static java.util.List.of;
       
    70 import static jdk.internal.net.http.common.Pair.pair;
       
    71 import static org.testng.Assert.assertThrows;
       
    72 
       
    73 // Code copied from ContinuationFrameTest
       
    74 public class BadHeadersTest {
       
    75 
       
    76     private static final List<List<Pair<String, String>>> BAD_HEADERS = of(
       
    77             of(pair(":status", "200"),  pair(":hello", "GET")),                      // Unknown pseudo-header
       
    78             of(pair(":status", "200"),  pair("hell o", "value")),                    // Space in the name
       
    79             of(pair(":status", "200"),  pair("hello", "line1\r\n  line2\r\n")),      // Multiline value
       
    80             of(pair(":status", "200"),  pair("hello", "DE" + ((char) 0x7F) + "L")),  // Bad byte in value
       
    81             of(pair("hello", "world!"), pair(":status", "200"))                      // Pseudo header is not the first one
       
    82     );
       
    83 
       
    84     SSLContext sslContext;
       
    85     Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
       
    86     Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
       
    87     String http2URI;
       
    88     String https2URI;
       
    89 
       
    90     /**
       
    91      * A function that returns a list of 1) a HEADERS frame ( with an empty
       
    92      * payload ), and 2) a CONTINUATION frame with the actual headers.
       
    93      */
       
    94     static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> oneContinuation =
       
    95             (Integer streamid, List<ByteBuffer> encodedHeaders) -> {
       
    96                 List<ByteBuffer> empty =  of(ByteBuffer.wrap(new byte[0]));
       
    97                 HeadersFrame hf = new HeadersFrame(streamid, 0, empty);
       
    98                 ContinuationFrame cf = new ContinuationFrame(streamid,
       
    99                                                              HeaderFrame.END_HEADERS,
       
   100                                                              encodedHeaders);
       
   101                 return of(hf, cf);
       
   102             };
       
   103 
       
   104     /**
       
   105      * A function that returns a list of a HEADERS frame followed by a number of
       
   106      * CONTINUATION frames. Each frame contains just a single byte of payload.
       
   107      */
       
   108     static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> byteAtATime =
       
   109             (Integer streamid, List<ByteBuffer> encodedHeaders) -> {
       
   110                 assert encodedHeaders.get(0).hasRemaining();
       
   111                 List<Http2Frame> frames = new ArrayList<>();
       
   112                 ByteBuffer hb = ByteBuffer.wrap(new byte[] {encodedHeaders.get(0).get()});
       
   113                 HeadersFrame hf = new HeadersFrame(streamid, 0, hb);
       
   114                 frames.add(hf);
       
   115                 for (ByteBuffer bb : encodedHeaders) {
       
   116                     while (bb.hasRemaining()) {
       
   117                         List<ByteBuffer> data = of(ByteBuffer.wrap(new byte[] {bb.get()}));
       
   118                         ContinuationFrame cf = new ContinuationFrame(streamid, 0, data);
       
   119                         frames.add(cf);
       
   120                     }
       
   121                 }
       
   122                 frames.get(frames.size() - 1).setFlag(HeaderFrame.END_HEADERS);
       
   123                 return frames;
       
   124             };
       
   125 
       
   126     @DataProvider(name = "variants")
       
   127     public Object[][] variants() {
       
   128         return new Object[][] {
       
   129                 { http2URI,  false, oneContinuation },
       
   130                 { https2URI, false, oneContinuation },
       
   131                 { http2URI,  true,  oneContinuation },
       
   132                 { https2URI, true,  oneContinuation },
       
   133 
       
   134                 { http2URI,  false, byteAtATime },
       
   135                 { https2URI, false, byteAtATime },
       
   136                 { http2URI,  true,  byteAtATime },
       
   137                 { https2URI, true,  byteAtATime },
       
   138         };
       
   139     }
       
   140 
       
   141 
       
   142     @Test(dataProvider = "variants")
       
   143     void test(String uri,
       
   144               boolean sameClient,
       
   145               BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
       
   146             throws Exception
       
   147     {
       
   148         CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
       
   149 
       
   150         HttpClient client = null;
       
   151         for (int i=0; i< BAD_HEADERS.size(); i++) {
       
   152             if (!sameClient || client == null)
       
   153                 client = HttpClient.newBuilder().sslContext(sslContext).build();
       
   154 
       
   155             HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
       
   156                     .POST(BodyPublishers.ofString("Hello there!"))
       
   157                     .build();
       
   158             final HttpClient cc = client;
       
   159             if (i % 2 == 0) {
       
   160                 assertThrows(IOException.class, () -> cc.send(request, BodyHandlers.ofString()));
       
   161             } else {
       
   162                 Throwable t = null;
       
   163                 try {
       
   164                     cc.sendAsync(request, BodyHandlers.ofString()).join();
       
   165                 } catch (Throwable t0) {
       
   166                     t = t0;
       
   167                 }
       
   168                 if (t == null) {
       
   169                     throw new AssertionError("An exception was expected");
       
   170                 }
       
   171                 if (t instanceof CompletionException) {
       
   172                     Throwable c = t.getCause();
       
   173                     if (!(c instanceof IOException)) {
       
   174                         throw new AssertionError("Unexpected exception", c);
       
   175                     }
       
   176                 } else if (!(t instanceof IOException)) {
       
   177                     throw new AssertionError("Unexpected exception", t);
       
   178                 }
       
   179             }
       
   180         }
       
   181     }
       
   182 
       
   183     @BeforeTest
       
   184     public void setup() throws Exception {
       
   185         sslContext = new SimpleSSLContext().get();
       
   186         if (sslContext == null)
       
   187             throw new AssertionError("Unexpected null sslContext");
       
   188 
       
   189         http2TestServer = new Http2TestServer("localhost", false, 0) {
       
   190             @Override
       
   191             protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer,
       
   192                                                                  Socket socket,
       
   193                                                                  Http2TestExchangeSupplier exchangeSupplier)
       
   194                     throws IOException {
       
   195                 return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier) {
       
   196                     @Override
       
   197                     protected HttpHeadersImpl createNewResponseHeaders() {
       
   198                         return new OrderedHttpHeaders();
       
   199                     }
       
   200                 };
       
   201             }
       
   202         };
       
   203         http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
       
   204         int port = http2TestServer.getAddress().getPort();
       
   205         http2URI = "http://localhost:" + port + "/http2/echo";
       
   206 
       
   207         https2TestServer = new Http2TestServer("localhost", true, 0){
       
   208             @Override
       
   209             protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer,
       
   210                                                                  Socket socket,
       
   211                                                                  Http2TestExchangeSupplier exchangeSupplier)
       
   212                     throws IOException {
       
   213                 return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier) {
       
   214                     @Override
       
   215                     protected HttpHeadersImpl createNewResponseHeaders() {
       
   216                         return new OrderedHttpHeaders();
       
   217                     }
       
   218                 };
       
   219             }
       
   220         };
       
   221         https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
       
   222         port = https2TestServer.getAddress().getPort();
       
   223         https2URI = "https://localhost:" + port + "/https2/echo";
       
   224 
       
   225         // Override the default exchange supplier with a custom one to enable
       
   226         // particular test scenarios
       
   227         http2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new);
       
   228         https2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new);
       
   229 
       
   230         http2TestServer.start();
       
   231         https2TestServer.start();
       
   232     }
       
   233 
       
   234     @AfterTest
       
   235     public void teardown() throws Exception {
       
   236         http2TestServer.stop();
       
   237         https2TestServer.stop();
       
   238     }
       
   239 
       
   240     static class Http2EchoHandler implements Http2Handler {
       
   241 
       
   242         private final AtomicInteger requestNo = new AtomicInteger();
       
   243 
       
   244         @Override
       
   245         public void handle(Http2TestExchange t) throws IOException {
       
   246             try (InputStream is = t.getRequestBody();
       
   247                  OutputStream os = t.getResponseBody()) {
       
   248                 byte[] bytes = is.readAllBytes();
       
   249                 int i = requestNo.incrementAndGet();
       
   250                 List<Pair<String, String>> p = BAD_HEADERS.get(i % BAD_HEADERS.size());
       
   251                 p.forEach(h -> t.getResponseHeaders().addHeader(h.first, h.second));
       
   252                 t.sendResponseHeaders(200, bytes.length);
       
   253                 os.write(bytes);
       
   254             }
       
   255         }
       
   256     }
       
   257 
       
   258     // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to
       
   259     // allow headers to be sent with a number of CONTINUATION frames.
       
   260     static class CFTHttp2TestExchange extends Http2TestExchangeImpl {
       
   261         static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier;
       
   262 
       
   263         static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) {
       
   264             headerFrameSupplier = hfs;
       
   265         }
       
   266 
       
   267         CFTHttp2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders,
       
   268                              HttpHeadersImpl rspheaders, URI uri, InputStream is,
       
   269                              SSLSession sslSession, BodyOutputStream os,
       
   270                              Http2TestServerConnection conn, boolean pushAllowed) {
       
   271             super(streamid, method, reqheaders, rspheaders, uri, is, sslSession,
       
   272                   os, conn, pushAllowed);
       
   273 
       
   274         }
       
   275 
       
   276         @Override
       
   277         public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
       
   278             List<ByteBuffer> encodeHeaders = conn.encodeHeaders(rspheaders);
       
   279             List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders);
       
   280             assert headerFrames.size() > 0;  // there must always be at least 1
       
   281 
       
   282             if (responseLength < 0) {
       
   283                 headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM);
       
   284                 os.closeInternal();
       
   285             }
       
   286 
       
   287             for (Http2Frame f : headerFrames)
       
   288                 conn.outputQ.put(f);
       
   289 
       
   290             os.goodToGo();
       
   291             System.err.println("Sent response headers " + rCode);
       
   292         }
       
   293     }
       
   294 
       
   295     /*
       
   296      * Use carefully. This class might not be suitable outside this test's
       
   297      * context. Pay attention working with multi Map view returned from map().
       
   298      * The reason is that header names must be lower-cased prior to any
       
   299      * operation that depends on whether or not the map contains a specific
       
   300      * element.
       
   301      */
       
   302     private static class OrderedHttpHeaders extends HttpHeadersImpl {
       
   303 
       
   304         private final Map<String, List<String>> map = new LinkedHashMap<>();
       
   305 
       
   306         @Override
       
   307         public void addHeader(String name, String value) {
       
   308             super.addHeader(name.toLowerCase(Locale.ROOT), value);
       
   309         }
       
   310 
       
   311         @Override
       
   312         public void setHeader(String name, String value) {
       
   313             super.setHeader(name.toLowerCase(Locale.ROOT), value);
       
   314         }
       
   315 
       
   316         @Override
       
   317         protected Map<String, List<String>> headersMap() {
       
   318             return map;
       
   319         }
       
   320 
       
   321         @Override
       
   322         protected HttpHeadersImpl newDeepCopy() {
       
   323             return new OrderedHttpHeaders();
       
   324         }
       
   325     }
       
   326 }