test/jdk/java/net/httpclient/http2/BadHeadersTest.java
changeset 50681 4254bed3c09d
parent 49765 ee6f7a61f3a5
child 52121 934969c63223
child 56795 03ece2518428
equal deleted inserted replaced
50678:818a23db260c 50681:4254bed3c09d
    31  * @build Http2TestServer
    31  * @build Http2TestServer
    32  * @build jdk.testlibrary.SimpleSSLContext
    32  * @build jdk.testlibrary.SimpleSSLContext
    33  * @run testng/othervm -Djdk.internal.httpclient.debug=true BadHeadersTest
    33  * @run testng/othervm -Djdk.internal.httpclient.debug=true BadHeadersTest
    34  */
    34  */
    35 
    35 
    36 import jdk.internal.net.http.common.HttpHeadersImpl;
    36 import jdk.internal.net.http.common.HttpHeadersBuilder;
    37 import jdk.internal.net.http.common.Pair;
       
    38 import jdk.internal.net.http.frame.ContinuationFrame;
    37 import jdk.internal.net.http.frame.ContinuationFrame;
    39 import jdk.internal.net.http.frame.HeaderFrame;
    38 import jdk.internal.net.http.frame.HeaderFrame;
    40 import jdk.internal.net.http.frame.HeadersFrame;
    39 import jdk.internal.net.http.frame.HeadersFrame;
    41 import jdk.internal.net.http.frame.Http2Frame;
    40 import jdk.internal.net.http.frame.Http2Frame;
    42 import jdk.testlibrary.SimpleSSLContext;
    41 import jdk.testlibrary.SimpleSSLContext;
    43 import org.testng.annotations.AfterTest;
    42 import org.testng.annotations.AfterTest;
    44 import org.testng.annotations.BeforeTest;
    43 import org.testng.annotations.BeforeTest;
    45 import org.testng.annotations.DataProvider;
    44 import org.testng.annotations.DataProvider;
    46 import org.testng.annotations.Test;
    45 import org.testng.annotations.Test;
    47 
       
    48 import javax.net.ssl.SSLContext;
    46 import javax.net.ssl.SSLContext;
    49 import javax.net.ssl.SSLSession;
    47 import javax.net.ssl.SSLSession;
    50 import java.io.IOException;
    48 import java.io.IOException;
    51 import java.io.InputStream;
    49 import java.io.InputStream;
    52 import java.io.OutputStream;
    50 import java.io.OutputStream;
    53 import java.net.Socket;
       
    54 import java.net.URI;
    51 import java.net.URI;
    55 import java.net.http.HttpClient;
    52 import java.net.http.HttpClient;
       
    53 import java.net.http.HttpHeaders;
    56 import java.net.http.HttpRequest;
    54 import java.net.http.HttpRequest;
    57 import java.net.http.HttpRequest.BodyPublishers;
    55 import java.net.http.HttpRequest.BodyPublishers;
       
    56 import java.net.http.HttpResponse;
    58 import java.net.http.HttpResponse.BodyHandlers;
    57 import java.net.http.HttpResponse.BodyHandlers;
    59 import java.nio.ByteBuffer;
    58 import java.nio.ByteBuffer;
    60 import java.util.ArrayList;
    59 import java.util.ArrayList;
    61 import java.util.LinkedHashMap;
       
    62 import java.util.List;
    60 import java.util.List;
    63 import java.util.Locale;
    61 import java.util.Map.Entry;
    64 import java.util.Map;
    62 import java.util.concurrent.ExecutionException;
    65 import java.util.concurrent.CompletionException;
       
    66 import java.util.concurrent.atomic.AtomicInteger;
       
    67 import java.util.function.BiFunction;
    63 import java.util.function.BiFunction;
    68 
       
    69 import static java.util.List.of;
    64 import static java.util.List.of;
    70 import static jdk.internal.net.http.common.Pair.pair;
    65 import static java.util.Map.entry;
    71 import static org.testng.Assert.assertThrows;
    66 import static org.testng.Assert.assertTrue;
       
    67 import static org.testng.Assert.fail;
    72 
    68 
    73 // Code copied from ContinuationFrameTest
    69 // Code copied from ContinuationFrameTest
    74 public class BadHeadersTest {
    70 public class BadHeadersTest {
    75 
    71 
    76     private static final List<List<Pair<String, String>>> BAD_HEADERS = of(
    72     private static final List<List<Entry<String, String>>> BAD_HEADERS = of(
    77             of(pair(":status", "200"),  pair(":hello", "GET")),                      // Unknown pseudo-header
    73         of(entry(":status", "200"),  entry(":hello", "GET")),                      // Unknown pseudo-header
    78             of(pair(":status", "200"),  pair("hell o", "value")),                    // Space in the name
    74         of(entry(":status", "200"),  entry("hell o", "value")),                    // Space in the name
    79             of(pair(":status", "200"),  pair("hello", "line1\r\n  line2\r\n")),      // Multiline value
    75         of(entry(":status", "200"),  entry("hello", "line1\r\n  line2\r\n")),      // Multiline value
    80             of(pair(":status", "200"),  pair("hello", "DE" + ((char) 0x7F) + "L")),  // Bad byte in value
    76         of(entry(":status", "200"),  entry("hello", "DE" + ((char) 0x7F) + "L")),  // Bad byte in value
    81             of(pair("hello", "world!"), pair(":status", "200"))                      // Pseudo header is not the first one
    77         of(entry("hello", "world!"), entry(":status", "200"))                      // Pseudo header is not the first one
    82     );
    78     );
    83 
    79 
    84     SSLContext sslContext;
    80     SSLContext sslContext;
    85     Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
    81     Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
    86     Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
    82     Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
   141 
   137 
   142     @Test(dataProvider = "variants")
   138     @Test(dataProvider = "variants")
   143     void test(String uri,
   139     void test(String uri,
   144               boolean sameClient,
   140               boolean sameClient,
   145               BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
   141               BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
   146             throws Exception
   142         throws Exception
   147     {
   143     {
   148         CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
   144         CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
   149 
   145 
   150         HttpClient client = null;
   146         HttpClient client = null;
   151         for (int i=0; i< BAD_HEADERS.size(); i++) {
   147         for (int i=0; i< BAD_HEADERS.size(); i++) {
   152             if (!sameClient || client == null)
   148             if (!sameClient || client == null)
   153                 client = HttpClient.newBuilder().sslContext(sslContext).build();
   149                 client = HttpClient.newBuilder().sslContext(sslContext).build();
   154 
   150 
   155             HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
   151             URI uriWithQuery = URI.create(uri +  "?BAD_HEADERS=" + i);
       
   152             HttpRequest request = HttpRequest.newBuilder(uriWithQuery)
   156                     .POST(BodyPublishers.ofString("Hello there!"))
   153                     .POST(BodyPublishers.ofString("Hello there!"))
   157                     .build();
   154                     .build();
       
   155             System.out.println("\nSending request:" + uriWithQuery);
   158             final HttpClient cc = client;
   156             final HttpClient cc = client;
   159             if (i % 2 == 0) {
   157             try {
   160                 assertThrows(IOException.class, () -> cc.send(request, BodyHandlers.ofString()));
   158                 HttpResponse<String> response = cc.send(request, BodyHandlers.ofString());
   161             } else {
   159                 fail("Expected exception, got :" + response + ", " + response.body());
   162                 Throwable t = null;
   160             } catch (IOException ioe) {
   163                 try {
   161                 System.out.println("Got EXPECTED: " + ioe);
   164                     cc.sendAsync(request, BodyHandlers.ofString()).join();
   162                 assertDetailMessage(ioe, i);
   165                 } catch (Throwable t0) {
   163             }
   166                     t = t0;
   164         }
       
   165     }
       
   166 
       
   167     @Test(dataProvider = "variants")
       
   168     void testAsync(String uri,
       
   169                    boolean sameClient,
       
   170                    BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
       
   171     {
       
   172         CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
       
   173 
       
   174         HttpClient client = null;
       
   175         for (int i=0; i< BAD_HEADERS.size(); i++) {
       
   176             if (!sameClient || client == null)
       
   177                 client = HttpClient.newBuilder().sslContext(sslContext).build();
       
   178 
       
   179             URI uriWithQuery = URI.create(uri +  "?BAD_HEADERS=" + i);
       
   180             HttpRequest request = HttpRequest.newBuilder(uriWithQuery)
       
   181                     .POST(BodyPublishers.ofString("Hello there!"))
       
   182                     .build();
       
   183             System.out.println("\nSending request:" + uriWithQuery);
       
   184             final HttpClient cc = client;
       
   185 
       
   186             Throwable t = null;
       
   187             try {
       
   188                 HttpResponse<String> response = cc.sendAsync(request, BodyHandlers.ofString()).get();
       
   189                 fail("Expected exception, got :" + response + ", " + response.body());
       
   190             } catch (Throwable t0) {
       
   191                 System.out.println("Got EXPECTED: " + t0);
       
   192                 if (t0 instanceof ExecutionException) {
       
   193                     t0 = t0.getCause();
   167                 }
   194                 }
   168                 if (t == null) {
   195                 t = t0;
   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             }
   196             }
       
   197             assertDetailMessage(t, i);
       
   198         }
       
   199     }
       
   200 
       
   201     // Assertions based on implementation specific detail messages. Keep in
       
   202     // sync with implementation.
       
   203     static void assertDetailMessage(Throwable throwable, int iterationIndex) {
       
   204         assertTrue(throwable instanceof IOException,
       
   205                    "Expected IOException, got, " + throwable);
       
   206         assertTrue(throwable.getMessage().contains("protocol error"),
       
   207                 "Expected \"protocol error\" in: " + throwable.getMessage());
       
   208 
       
   209         if (iterationIndex == 0) { // unknown
       
   210             assertTrue(throwable.getMessage().contains("Unknown pseudo-header"),
       
   211                     "Expected \"Unknown pseudo-header\" in: " + throwable.getMessage());
       
   212         } else if (iterationIndex == 4) { // unexpected
       
   213             assertTrue(throwable.getMessage().contains(" Unexpected pseudo-header"),
       
   214                     "Expected \" Unexpected pseudo-header\" in: " + throwable.getMessage());
       
   215         } else {
       
   216             assertTrue(throwable.getMessage().contains("Bad header"),
       
   217                     "Expected \"Bad header\" in: " + throwable.getMessage());
   180         }
   218         }
   181     }
   219     }
   182 
   220 
   183     @BeforeTest
   221     @BeforeTest
   184     public void setup() throws Exception {
   222     public void setup() throws Exception {
   185         sslContext = new SimpleSSLContext().get();
   223         sslContext = new SimpleSSLContext().get();
   186         if (sslContext == null)
   224         if (sslContext == null)
   187             throw new AssertionError("Unexpected null sslContext");
   225             throw new AssertionError("Unexpected null sslContext");
   188 
   226 
   189         http2TestServer = new Http2TestServer("localhost", false, 0) {
   227         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");
   228         http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
   204         int port = http2TestServer.getAddress().getPort();
   229         int port = http2TestServer.getAddress().getPort();
   205         http2URI = "http://localhost:" + port + "/http2/echo";
   230         http2URI = "http://localhost:" + port + "/http2/echo";
   206 
   231 
   207         https2TestServer = new Http2TestServer("localhost", true, 0){
   232         https2TestServer = new Http2TestServer("localhost", true, sslContext);
   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");
   233         https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
   222         port = https2TestServer.getAddress().getPort();
   234         port = https2TestServer.getAddress().getPort();
   223         https2URI = "https://localhost:" + port + "/https2/echo";
   235         https2URI = "https://localhost:" + port + "/https2/echo";
   224 
   236 
   225         // Override the default exchange supplier with a custom one to enable
   237         // Override the default exchange supplier with a custom one to enable
   236         http2TestServer.stop();
   248         http2TestServer.stop();
   237         https2TestServer.stop();
   249         https2TestServer.stop();
   238     }
   250     }
   239 
   251 
   240     static class Http2EchoHandler implements Http2Handler {
   252     static class Http2EchoHandler implements Http2Handler {
   241 
       
   242         private final AtomicInteger requestNo = new AtomicInteger();
       
   243 
   253 
   244         @Override
   254         @Override
   245         public void handle(Http2TestExchange t) throws IOException {
   255         public void handle(Http2TestExchange t) throws IOException {
   246             try (InputStream is = t.getRequestBody();
   256             try (InputStream is = t.getRequestBody();
   247                  OutputStream os = t.getResponseBody()) {
   257                  OutputStream os = t.getResponseBody()) {
   248                 byte[] bytes = is.readAllBytes();
   258                 byte[] bytes = is.readAllBytes();
   249                 int i = requestNo.incrementAndGet();
   259                 // Note: strictly ordered response headers will be added within
   250                 List<Pair<String, String>> p = BAD_HEADERS.get(i % BAD_HEADERS.size());
   260                 // the custom sendResponseHeaders implementation, based upon the
   251                 p.forEach(h -> t.getResponseHeaders().addHeader(h.first, h.second));
   261                 // query parameter
   252                 t.sendResponseHeaders(200, bytes.length);
   262                 t.sendResponseHeaders(200, bytes.length);
   253                 os.write(bytes);
   263                 os.write(bytes);
   254             }
   264             }
   255         }
   265         }
   256     }
   266     }
   257 
   267 
   258     // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to
   268     // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to
   259     // allow headers to be sent with a number of CONTINUATION frames.
   269     // allow headers to be sent with a number of CONTINUATION frames.
   260     static class CFTHttp2TestExchange extends Http2TestExchangeImpl {
   270     static class CFTHttp2TestExchange extends Http2TestExchangeImpl {
   261         static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier;
   271         static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier;
       
   272         volatile int badHeadersIndex = -1;
   262 
   273 
   263         static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) {
   274         static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) {
   264             headerFrameSupplier = hfs;
   275             headerFrameSupplier = hfs;
   265         }
   276         }
   266 
   277 
   267         CFTHttp2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders,
   278         CFTHttp2TestExchange(int streamid, String method, HttpHeaders reqheaders,
   268                              HttpHeadersImpl rspheaders, URI uri, InputStream is,
   279                              HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is,
   269                              SSLSession sslSession, BodyOutputStream os,
   280                              SSLSession sslSession, BodyOutputStream os,
   270                              Http2TestServerConnection conn, boolean pushAllowed) {
   281                              Http2TestServerConnection conn, boolean pushAllowed) {
   271             super(streamid, method, reqheaders, rspheaders, uri, is, sslSession,
   282             super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession,
   272                   os, conn, pushAllowed);
   283                   os, conn, pushAllowed);
   273 
   284             String query = uri.getQuery();
       
   285             badHeadersIndex = Integer.parseInt(query.substring(query.indexOf("=") + 1));
       
   286             assert badHeadersIndex >= 0 && badHeadersIndex < BAD_HEADERS.size() :
       
   287                     "Unexpected badHeadersIndex value: " + badHeadersIndex;
   274         }
   288         }
   275 
   289 
   276         @Override
   290         @Override
   277         public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
   291         public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
   278             List<ByteBuffer> encodeHeaders = conn.encodeHeaders(rspheaders);
   292             assert rspheadersBuilder.build().map().size() == 0;
       
   293             assert badHeadersIndex >= 0 && badHeadersIndex < BAD_HEADERS.size() :
       
   294                     "Unexpected badHeadersIndex value: " + badHeadersIndex;
       
   295 
       
   296             List<Entry<String,String>> headers = BAD_HEADERS.get(badHeadersIndex);
       
   297             System.out.println("Server replying with bad headers: " + headers);
       
   298             List<ByteBuffer> encodeHeaders = conn.encodeHeadersOrdered(headers);
       
   299 
   279             List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders);
   300             List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders);
   280             assert headerFrames.size() > 0;  // there must always be at least 1
   301             assert headerFrames.size() > 0;  // there must always be at least 1
   281 
   302 
   282             if (responseLength < 0) {
   303             if (responseLength < 0) {
   283                 headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM);
   304                 headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM);
   289 
   310 
   290             os.goodToGo();
   311             os.goodToGo();
   291             System.err.println("Sent response headers " + rCode);
   312             System.err.println("Sent response headers " + rCode);
   292         }
   313         }
   293     }
   314     }
   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 }
   315 }