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); |