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  * @bug 8201186
27  * @summary Tests an asynchronous BodySubscriber that completes
28  *          immediately with a Publisher<List<ByteBuffer>> whose
29  *          subscriber issues bad requests
30  * @library /lib/testlibrary http2/server
31  * @build jdk.testlibrary.SimpleSSLContext ReferenceTracker
32  * @modules java.base/sun.net.www.http
33  *          java.net.http/jdk.internal.net.http.common
34  *          java.net.http/jdk.internal.net.http.frame
35  *          java.net.http/jdk.internal.net.http.hpack
36  * @run testng/othervm InvalidSubscriptionRequest
37  */
38 
39 import com.sun.net.httpserver.HttpServer;
40 import com.sun.net.httpserver.HttpsConfigurator;
41 import com.sun.net.httpserver.HttpsServer;
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 java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.net.InetAddress;
53 import java.net.InetSocketAddress;
54 import java.net.URI;
55 import java.net.http.HttpClient;
56 import java.net.http.HttpRequest;
57 import java.net.http.HttpResponse;
58 import java.net.http.HttpResponse.BodyHandler;
59 import java.net.http.HttpResponse.BodyHandlers;
60 import java.net.http.HttpResponse.BodySubscriber;
61 import java.net.http.HttpResponse.BodySubscribers;
62 import java.nio.ByteBuffer;
63 import java.util.List;
64 import java.util.concurrent.CompletableFuture;
65 import java.util.concurrent.CompletionException;
66 import java.util.concurrent.CompletionStage;
67 import java.util.concurrent.ExecutionException;
68 import java.util.concurrent.Executor;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Flow;
71 import java.util.concurrent.Flow.Publisher;
72 import java.util.function.Supplier;
73 
74 import static java.lang.System.out;
75 import static java.nio.charset.StandardCharsets.UTF_8;
76 import static org.testng.Assert.assertEquals;
77 
78 public class InvalidSubscriptionRequest implements HttpServerAdapters {
79 
80     SSLContext sslContext;
81     HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
82     HttpTestServer httpsTestServer;   // HTTPS/1.1
83     HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
84     HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
85     String httpURI_fixed;
86     String httpURI_chunk;
87     String httpsURI_fixed;
88     String httpsURI_chunk;
89     String http2URI_fixed;
90     String http2URI_chunk;
91     String https2URI_fixed;
92     String https2URI_chunk;
93 
94     static final int ITERATION_COUNT = 3;
95     // a shared executor helps reduce the amount of threads created by the test
96     static final Executor executor = Executors.newCachedThreadPool();
97 
98     interface BHS extends Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> {
of(BHS impl, String name)99         static BHS of(BHS impl, String name) {
100             return new BHSImpl(impl, name);
101         }
102     }
103 
104     static final class BHSImpl implements BHS {
105         final BHS supplier;
106         final String name;
BHSImpl(BHS impl, String name)107         BHSImpl(BHS impl, String name) {
108             this.supplier = impl;
109             this.name = name;
110         }
111         @Override
toString()112         public String toString() {
113             return name;
114         }
115 
116         @Override
get()117         public BodyHandler<Publisher<List<ByteBuffer>>> get() {
118             return supplier.get();
119         }
120     }
121 
122     static final Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> OF_PUBLISHER_API =
123             BHS.of(BodyHandlers::ofPublisher, "BodyHandlers::ofPublisher");
124 
125     @DataProvider(name = "variants")
variants()126     public Object[][] variants() {
127         return new Object[][]{
128                 { httpURI_fixed,    false, OF_PUBLISHER_API },
129                 { httpURI_chunk,    false, OF_PUBLISHER_API },
130                 { httpsURI_fixed,   false, OF_PUBLISHER_API },
131                 { httpsURI_chunk,   false, OF_PUBLISHER_API },
132                 { http2URI_fixed,   false, OF_PUBLISHER_API },
133                 { http2URI_chunk,   false, OF_PUBLISHER_API },
134                 { https2URI_fixed,  false, OF_PUBLISHER_API },
135                 { https2URI_chunk,  false, OF_PUBLISHER_API },
136 
137                 { httpURI_fixed,    true, OF_PUBLISHER_API },
138                 { httpURI_chunk,    true, OF_PUBLISHER_API },
139                 { httpsURI_fixed,   true, OF_PUBLISHER_API },
140                 { httpsURI_chunk,   true, OF_PUBLISHER_API },
141                 { http2URI_fixed,   true, OF_PUBLISHER_API },
142                 { http2URI_chunk,   true, OF_PUBLISHER_API },
143                 { https2URI_fixed,  true, OF_PUBLISHER_API },
144                 { https2URI_chunk,  true, OF_PUBLISHER_API },
145         };
146     }
147 
148     final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
newHttpClient()149     HttpClient newHttpClient() {
150         return TRACKER.track(HttpClient.newBuilder()
151                          .proxy(HttpClient.Builder.NO_PROXY)
152                          .executor(executor)
153                          .sslContext(sslContext)
154                          .build());
155     }
156 
157     @Test(dataProvider = "variants")
testNoBody(String uri, boolean sameClient, BHS handlers)158     public void testNoBody(String uri, boolean sameClient, BHS handlers) throws Exception {
159         HttpClient client = null;
160         for (int i=0; i< ITERATION_COUNT; i++) {
161             if (!sameClient || client == null)
162                 client = newHttpClient();
163 
164             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
165                     .build();
166             BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
167             HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler);
168             // We can reuse our BodySubscribers implementations to subscribe to the
169             // Publisher<List<ByteBuffer>>
170             BodySubscriber<String> ofString = new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8));
171             // get the Publisher<List<ByteBuffer>> and
172             // subscribe to it.
173             response.body().subscribe(ofString);
174             // Get the final result and compare it with the expected body
175             try {
176                 String body = ofString.getBody().toCompletableFuture().get();
177                 assertEquals(body, "");
178                 if (uri.endsWith("/chunk")
179                         && response.version() == HttpClient.Version.HTTP_1_1) {
180                     // with /fixed and 0 length
181                     // there's no need for any call to request()
182                     throw new RuntimeException("Expected IAE not thrown");
183                 }
184             } catch (Exception x) {
185                 Throwable cause = x;
186                 if (x instanceof CompletionException || x instanceof ExecutionException) {
187                     cause = x.getCause();
188                 }
189                 if (cause instanceof IllegalArgumentException) {
190                     System.out.println("Got expected exception: " + cause);
191                 } else throw x;
192             }
193         }
194     }
195 
196     @Test(dataProvider = "variants")
testNoBodyAsync(String uri, boolean sameClient, BHS handlers)197     public void testNoBodyAsync(String uri, boolean sameClient, BHS handlers) throws Exception {
198         HttpClient client = null;
199         for (int i=0; i< ITERATION_COUNT; i++) {
200             if (!sameClient || client == null)
201                 client = newHttpClient();
202 
203             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
204                     .build();
205             BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
206             // We can reuse our BodySubscribers implementations to subscribe to the
207             // Publisher<List<ByteBuffer>>
208             BodySubscriber<String> ofString =
209                     new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8));
210             CompletableFuture<HttpResponse<Publisher<List<ByteBuffer>>>> response =
211                     client.sendAsync(req, handler);
212             CompletableFuture<String> result = response.thenCompose(
213                             (responsePublisher) -> {
214                                 // get the Publisher<List<ByteBuffer>> and
215                                 // subscribe to it.
216                                 responsePublisher.body().subscribe(ofString);
217                                 return ofString.getBody();
218                             });
219             try {
220                 // Get the final result and compare it with the expected body
221                 assertEquals(result.get(), "");
222                 if (uri.endsWith("/chunk")
223                         && response.get().version() == HttpClient.Version.HTTP_1_1) {
224                     // with /fixed and 0 length
225                     // there's no need for any call to request()
226                     throw new RuntimeException("Expected IAE not thrown");
227                 }
228             } catch (Exception x) {
229                 Throwable cause = x;
230                 if (x instanceof CompletionException || x instanceof ExecutionException) {
231                     cause = x.getCause();
232                 }
233                 if (cause instanceof IllegalArgumentException) {
234                     System.out.println("Got expected exception: " + cause);
235                 } else throw x;
236             }
237         }
238     }
239 
240     @Test(dataProvider = "variants")
testAsString(String uri, boolean sameClient, BHS handlers)241     public void testAsString(String uri, boolean sameClient, BHS handlers) throws Exception {
242         HttpClient client = null;
243         for (int i=0; i< ITERATION_COUNT; i++) {
244             if (!sameClient || client == null)
245                 client = newHttpClient();
246 
247             HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
248                     .build();
249             BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
250             HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler);
251             // We can reuse our BodySubscribers implementations to subscribe to the
252             // Publisher<List<ByteBuffer>>
253             BodySubscriber<String> ofString = new BadBodySubscriber<>(
254                     BodySubscribers.ofString(UTF_8));
255             // get the Publisher<List<ByteBuffer>> and
256             // subscribe to it.
257             response.body().subscribe(ofString);
258             // Get the final result and compare it with the expected body
259             try {
260                 String body = ofString.getBody().toCompletableFuture().get();
261                 assertEquals(body, WITH_BODY);
262                 throw new RuntimeException("Expected IAE not thrown");
263             } catch (Exception x) {
264                 Throwable cause = x;
265                 if (x instanceof CompletionException || x instanceof ExecutionException) {
266                     cause = x.getCause();
267                 }
268                 if (cause instanceof IllegalArgumentException) {
269                     System.out.println("Got expected exception: " + cause);
270                 } else throw x;
271             }
272         }
273     }
274 
275     @Test(dataProvider = "variants")
testAsStringAsync(String uri, boolean sameClient, BHS handlers)276     public void testAsStringAsync(String uri, boolean sameClient, BHS handlers) throws Exception {
277         HttpClient client = null;
278         for (int i=0; i< ITERATION_COUNT; i++) {
279             if (!sameClient || client == null)
280                 client = newHttpClient();
281 
282             HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
283                     .build();
284             BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
285             // We can reuse our BodySubscribers implementations to subscribe to the
286             // Publisher<List<ByteBuffer>>
287             BodySubscriber<String> ofString =
288                     new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8));
289             CompletableFuture<String> result = client.sendAsync(req, handler)
290                     .thenCompose((responsePublisher) -> {
291                         // get the Publisher<List<ByteBuffer>> and
292                         // subscribe to it.
293                         responsePublisher.body().subscribe(ofString);
294                         return ofString.getBody();
295                     });
296             // Get the final result and compare it with the expected body
297             try {
298                 String body = result.get();
299                 assertEquals(body, WITH_BODY);
300                 throw new RuntimeException("Expected IAE not thrown");
301             } catch (Exception x) {
302                 Throwable cause = x;
303                 if (x instanceof CompletionException || x instanceof ExecutionException) {
304                     cause = x.getCause();
305                 }
306                 if (cause instanceof IllegalArgumentException) {
307                     System.out.println("Got expected exception: " + cause);
308                 } else throw x;
309             }
310         }
311     }
312 
313     static final class BadSubscription implements Flow.Subscription {
314         Flow.Subscription subscription;
315         Executor executor;
BadSubscription(Flow.Subscription subscription)316         BadSubscription(Flow.Subscription subscription) {
317             this.subscription = subscription;
318         }
319 
320         @Override
request(long n)321         public void request(long n) {
322             if (executor == null) {
323                 subscription.request(-n);
324             } else {
325                 executor.execute(() -> subscription.request(-n));
326             }
327         }
328 
329         @Override
cancel()330         public void cancel() {
331             subscription.cancel();
332         }
333     }
334 
335     static final class BadBodySubscriber<T> implements BodySubscriber<T> {
336         final BodySubscriber<T> subscriber;
BadBodySubscriber(BodySubscriber<T> subscriber)337         BadBodySubscriber(BodySubscriber<T> subscriber) {
338             this.subscriber = subscriber;
339         }
340 
341         @Override
getBody()342         public CompletionStage<T> getBody() {
343             return subscriber.getBody();
344         }
345 
346         @Override
onSubscribe(Flow.Subscription subscription)347         public void onSubscribe(Flow.Subscription subscription) {
348             System.out.println("Subscription is: " + subscription);
349             subscriber.onSubscribe(new BadSubscription(subscription));
350         }
351 
352         @Override
onNext(List<ByteBuffer> item)353         public void onNext(List<ByteBuffer> item) {
354             subscriber.onNext(item);
355         }
356 
357         @Override
onError(Throwable throwable)358         public void onError(Throwable throwable) {
359             subscriber.onError(throwable);
360         }
361 
362         @Override
onComplete()363         public void onComplete() {
364             subscriber.onComplete();
365         }
366     }
367 
serverAuthority(HttpServer server)368     static String serverAuthority(HttpServer server) {
369         return InetAddress.getLoopbackAddress().getHostName() + ":"
370                 + server.getAddress().getPort();
371     }
372 
373     @BeforeTest
setup()374     public void setup() throws Exception {
375         sslContext = new SimpleSSLContext().get();
376         if (sslContext == null)
377             throw new AssertionError("Unexpected null sslContext");
378 
379         // HTTP/1.1
380         HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
381         HttpTestHandler h1_chunkHandler = new HTTP_VariableLengthHandler();
382         InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
383         httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
384         httpTestServer.addHandler( h1_fixedLengthHandler, "/http1/fixed");
385         httpTestServer.addHandler(h1_chunkHandler,"/http1/chunk");
386         httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed";
387         httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk";
388 
389         HttpsServer httpsServer = HttpsServer.create(sa, 0);
390         httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
391         httpsTestServer = HttpTestServer.of(httpsServer);
392         httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
393         httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
394         httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed";
395         httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk";
396 
397         // HTTP/2
398         HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
399         HttpTestHandler h2_chunkedHandler = new HTTP_VariableLengthHandler();
400 
401         http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
402         http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
403         http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
404         http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
405         http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
406 
407         https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, sslContext));
408         https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
409         https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
410         https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
411         https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
412 
413         httpTestServer.start();
414         httpsTestServer.start();
415         http2TestServer.start();
416         https2TestServer.start();
417     }
418 
419     @AfterTest
teardown()420     public void teardown() throws Exception {
421         AssertionError fail = TRACKER.check(500);
422         try {
423             httpTestServer.stop();
424             httpsTestServer.stop();
425             http2TestServer.stop();
426             https2TestServer.stop();
427         } finally {
428             if (fail != null) {
429                 throw fail;
430             }
431         }
432     }
433 
434     static final String WITH_BODY = "Lorem ipsum dolor sit amet, consectetur" +
435             " adipiscing elit, sed do eiusmod tempor incididunt ut labore et" +
436             " dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" +
437             " exercitation ullamco laboris nisi ut aliquip ex ea" +
438             " commodo consequat. Duis aute irure dolor in reprehenderit in " +
439             "voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
440             " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui" +
441             " officia deserunt mollit anim id est laborum.";
442 
443     static class HTTP_FixedLengthHandler implements HttpTestHandler {
444         @Override
handle(HttpTestExchange t)445         public void handle(HttpTestExchange t) throws IOException {
446             out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
447             try (InputStream is = t.getRequestBody()) {
448                 is.readAllBytes();
449             }
450             if (t.getRequestURI().getPath().endsWith("/withBody")) {
451                 byte[] bytes = WITH_BODY.getBytes(UTF_8);
452                 t.sendResponseHeaders(200, bytes.length);  // body
453                 try (OutputStream os = t.getResponseBody()) {
454                     os.write(bytes);
455                 }
456             } else {
457                 t.sendResponseHeaders(200, 0);  //no body
458             }
459         }
460     }
461 
462     static class HTTP_VariableLengthHandler implements HttpTestHandler {
463         @Override
handle(HttpTestExchange t)464         public void handle(HttpTestExchange t) throws IOException {
465             out.println("HTTP_VariableLengthHandler received request to " + t.getRequestURI());
466             try (InputStream is = t.getRequestBody()) {
467                 is.readAllBytes();
468             }
469             t.sendResponseHeaders(200, -1);  //chunked or variable
470             if (t.getRequestURI().getPath().endsWith("/withBody")) {
471                 byte[] bytes = WITH_BODY.getBytes(UTF_8);
472                 try (OutputStream os = t.getResponseBody()) {
473                     int chunkLen = bytes.length/10;
474                     if (chunkLen == 0) {
475                         os.write(bytes);
476                     } else {
477                         int count = 0;
478                         for (int i=0; i<10; i++) {
479                             os.write(bytes, count, chunkLen);
480                             os.flush();
481                             count += chunkLen;
482                         }
483                         os.write(bytes, count, bytes.length % chunkLen);
484                         count += bytes.length % chunkLen;
485                         assert count == bytes.length;
486                     }
487                 }
488             } else {
489                 t.getResponseBody().close();   // no body
490             }
491         }
492     }
493 }
494