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 /test/lib server 31 * @build Http2TestServer 32 * @build jdk.test.lib.net.SimpleSSLContext 33 * @run testng/othervm -Djdk.internal.httpclient.debug=true BadHeadersTest 34 */ 35 36 import jdk.internal.net.http.common.HttpHeadersBuilder; 37 import jdk.internal.net.http.frame.ContinuationFrame; 38 import jdk.internal.net.http.frame.HeaderFrame; 39 import jdk.internal.net.http.frame.HeadersFrame; 40 import jdk.internal.net.http.frame.Http2Frame; 41 import jdk.test.lib.net.SimpleSSLContext; 42 import org.testng.annotations.AfterTest; 43 import org.testng.annotations.BeforeTest; 44 import org.testng.annotations.DataProvider; 45 import org.testng.annotations.Test; 46 import javax.net.ssl.SSLContext; 47 import javax.net.ssl.SSLSession; 48 import java.io.IOException; 49 import java.io.InputStream; 50 import java.io.OutputStream; 51 import java.net.URI; 52 import java.net.http.HttpClient; 53 import java.net.http.HttpHeaders; 54 import java.net.http.HttpRequest; 55 import java.net.http.HttpRequest.BodyPublishers; 56 import java.net.http.HttpResponse; 57 import java.net.http.HttpResponse.BodyHandlers; 58 import java.nio.ByteBuffer; 59 import java.util.ArrayList; 60 import java.util.List; 61 import java.util.Map.Entry; 62 import java.util.concurrent.ExecutionException; 63 import java.util.function.BiFunction; 64 import static java.util.List.of; 65 import static java.util.Map.entry; 66 import static org.testng.Assert.assertTrue; 67 import static org.testng.Assert.fail; 68 69 // Code copied from ContinuationFrameTest 70 public class BadHeadersTest { 71 72 private static final List<List<Entry<String, String>>> BAD_HEADERS = of( 73 of(entry(":status", "200"), entry(":hello", "GET")), // Unknown pseudo-header 74 of(entry(":status", "200"), entry("hell o", "value")), // Space in the name 75 of(entry(":status", "200"), entry("hello", "line1\r\n line2\r\n")), // Multiline value 76 of(entry(":status", "200"), entry("hello", "DE" + ((char) 0x7F) + "L")), // Bad byte in value 77 of(entry("hello", "world!"), entry(":status", "200")) // Pseudo header is not the first one 78 ); 79 80 SSLContext sslContext; 81 Http2TestServer http2TestServer; // HTTP/2 ( h2c ) 82 Http2TestServer https2TestServer; // HTTP/2 ( h2 ) 83 String http2URI; 84 String https2URI; 85 86 /** 87 * A function that returns a list of 1) a HEADERS frame ( with an empty 88 * payload ), and 2) a CONTINUATION frame with the actual headers. 89 */ 90 static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> oneContinuation = 91 (Integer streamid, List<ByteBuffer> encodedHeaders) -> { 92 List<ByteBuffer> empty = of(ByteBuffer.wrap(new byte[0])); 93 HeadersFrame hf = new HeadersFrame(streamid, 0, empty); 94 ContinuationFrame cf = new ContinuationFrame(streamid, 95 HeaderFrame.END_HEADERS, 96 encodedHeaders); 97 return of(hf, cf); 98 }; 99 100 /** 101 * A function that returns a list of a HEADERS frame followed by a number of 102 * CONTINUATION frames. Each frame contains just a single byte of payload. 103 */ 104 static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> byteAtATime = 105 (Integer streamid, List<ByteBuffer> encodedHeaders) -> { 106 assert encodedHeaders.get(0).hasRemaining(); 107 List<Http2Frame> frames = new ArrayList<>(); 108 ByteBuffer hb = ByteBuffer.wrap(new byte[] {encodedHeaders.get(0).get()}); 109 HeadersFrame hf = new HeadersFrame(streamid, 0, hb); 110 frames.add(hf); 111 for (ByteBuffer bb : encodedHeaders) { 112 while (bb.hasRemaining()) { 113 List<ByteBuffer> data = of(ByteBuffer.wrap(new byte[] {bb.get()})); 114 ContinuationFrame cf = new ContinuationFrame(streamid, 0, data); 115 frames.add(cf); 116 } 117 } 118 frames.get(frames.size() - 1).setFlag(HeaderFrame.END_HEADERS); 119 return frames; 120 }; 121 122 @DataProvider(name = "variants") variants()123 public Object[][] variants() { 124 return new Object[][] { 125 { http2URI, false, oneContinuation }, 126 { https2URI, false, oneContinuation }, 127 { http2URI, true, oneContinuation }, 128 { https2URI, true, oneContinuation }, 129 130 { http2URI, false, byteAtATime }, 131 { https2URI, false, byteAtATime }, 132 { http2URI, true, byteAtATime }, 133 { https2URI, true, byteAtATime }, 134 }; 135 } 136 137 138 @Test(dataProvider = "variants") test(String uri, boolean sameClient, BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)139 void test(String uri, 140 boolean sameClient, 141 BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier) 142 throws Exception 143 { 144 CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier); 145 146 HttpClient client = null; 147 for (int i=0; i< BAD_HEADERS.size(); i++) { 148 if (!sameClient || client == null) 149 client = HttpClient.newBuilder().sslContext(sslContext).build(); 150 151 URI uriWithQuery = URI.create(uri + "?BAD_HEADERS=" + i); 152 HttpRequest request = HttpRequest.newBuilder(uriWithQuery) 153 .POST(BodyPublishers.ofString("Hello there!")) 154 .build(); 155 System.out.println("\nSending request:" + uriWithQuery); 156 final HttpClient cc = client; 157 try { 158 HttpResponse<String> response = cc.send(request, BodyHandlers.ofString()); 159 fail("Expected exception, got :" + response + ", " + response.body()); 160 } catch (IOException ioe) { 161 System.out.println("Got EXPECTED: " + ioe); 162 assertDetailMessage(ioe, i); 163 } 164 } 165 } 166 167 @Test(dataProvider = "variants") testAsync(String uri, boolean sameClient, BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)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(); 194 } 195 t = t0; 196 } 197 assertDetailMessage(t, i); 198 } 199 } 200 201 // Assertions based on implementation specific detail messages. Keep in 202 // sync with implementation. assertDetailMessage(Throwable throwable, int iterationIndex)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()); 218 } 219 } 220 221 @BeforeTest setup()222 public void setup() throws Exception { 223 sslContext = new SimpleSSLContext().get(); 224 if (sslContext == null) 225 throw new AssertionError("Unexpected null sslContext"); 226 227 http2TestServer = new Http2TestServer("localhost", false, 0); 228 http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo"); 229 int port = http2TestServer.getAddress().getPort(); 230 http2URI = "http://localhost:" + port + "/http2/echo"; 231 232 https2TestServer = new Http2TestServer("localhost", true, sslContext); 233 https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo"); 234 port = https2TestServer.getAddress().getPort(); 235 https2URI = "https://localhost:" + port + "/https2/echo"; 236 237 // Override the default exchange supplier with a custom one to enable 238 // particular test scenarios 239 http2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new); 240 https2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new); 241 242 http2TestServer.start(); 243 https2TestServer.start(); 244 } 245 246 @AfterTest teardown()247 public void teardown() throws Exception { 248 http2TestServer.stop(); 249 https2TestServer.stop(); 250 } 251 252 static class Http2EchoHandler implements Http2Handler { 253 254 @Override handle(Http2TestExchange t)255 public void handle(Http2TestExchange t) throws IOException { 256 try (InputStream is = t.getRequestBody(); 257 OutputStream os = t.getResponseBody()) { 258 byte[] bytes = is.readAllBytes(); 259 // Note: strictly ordered response headers will be added within 260 // the custom sendResponseHeaders implementation, based upon the 261 // query parameter 262 t.sendResponseHeaders(200, bytes.length); 263 os.write(bytes); 264 } 265 } 266 } 267 268 // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to 269 // allow headers to be sent with a number of CONTINUATION frames. 270 static class CFTHttp2TestExchange extends Http2TestExchangeImpl { 271 static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier; 272 volatile int badHeadersIndex = -1; 273 setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs)274 static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) { 275 headerFrameSupplier = hfs; 276 } 277 CFTHttp2TestExchange(int streamid, String method, HttpHeaders reqheaders, HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, SSLSession sslSession, BodyOutputStream os, Http2TestServerConnection conn, boolean pushAllowed)278 CFTHttp2TestExchange(int streamid, String method, HttpHeaders reqheaders, 279 HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, 280 SSLSession sslSession, BodyOutputStream os, 281 Http2TestServerConnection conn, boolean pushAllowed) { 282 super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, 283 os, conn, pushAllowed); 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; 288 } 289 290 @Override 291 public void sendResponseHeaders(int rCode, long responseLength) throws IOException { 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 300 List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders); 301 assert headerFrames.size() > 0; // there must always be at least 1 302 303 if (responseLength < 0) { 304 headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM); 305 os.closeInternal(); 306 } 307 308 for (Http2Frame f : headerFrames) 309 conn.outputQ.put(f); 310 311 os.goodToGo(); 312 System.err.println("Sent response headers " + rCode); 313 } 314 } 315 } 316