1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import static org.junit.jupiter.api.Assertions.fail;
18
19 import java.io.BufferedInputStream;
20 import java.io.ByteArrayInputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.io.Writer;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.Method;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34
35 import javax.servlet.Servlet;
36 import javax.servlet.ServletException;
37 import javax.servlet.http.HttpServlet;
38 import javax.servlet.http.HttpServletRequest;
39 import javax.servlet.http.HttpServletResponse;
40
41 import org.apache.commons.io.IOUtils;
42 import org.apache.commons.lang3.StringUtils;
43 import org.apache.http.HttpEntity;
44 import org.apache.http.HttpResponse;
45 import org.apache.http.ProtocolVersion;
46 import org.apache.http.StatusLine;
47 import org.apache.http.client.methods.HttpUriRequest;
48 import org.apache.http.entity.StringEntity;
49 import org.apache.http.entity.mime.MultipartEntityBuilder;
50 import org.apache.http.impl.client.HttpClientBuilder;
51 import org.apache.http.message.BasicHttpResponse;
52 import org.apache.http.message.BasicStatusLine;
53 import org.htmlunit.html.HtmlPage;
54 import org.htmlunit.http.HttpStatus;
55 import org.htmlunit.util.KeyDataPair;
56 import org.htmlunit.util.MimeType;
57 import org.htmlunit.util.NameValuePair;
58 import org.htmlunit.util.ServletContentWrapper;
59 import org.junit.jupiter.api.Test;
60
61
62
63
64
65
66
67
68
69
70 public class HttpWebConnectionTest extends WebServerTestCase {
71
72
73
74
75
76
77 public static void assertEquals(final byte[] expected, final byte[] actual) {
78 assertEquals(null, expected, actual, expected.length);
79 }
80
81
82
83
84
85
86
87
88 public static void assertEquals(
89 final String message, final byte[] expected, final byte[] actual, final int length) {
90 if (expected == null && actual == null) {
91 return;
92 }
93 if (expected == null || expected.length < length) {
94 fail(message);
95 }
96 if (actual == null || actual.length < length) {
97 fail(message);
98 }
99 for (int i = 0; i < length; i++) {
100 assertEquals(message, expected[i], actual[i]);
101 }
102 }
103
104
105
106
107
108
109
110 public static void assertEquals(final InputStream expected, final InputStream actual) throws IOException {
111 assertEquals(null, expected, actual);
112 }
113
114
115
116
117
118
119
120
121 public static void assertEquals(final String message, final InputStream expected,
122 final InputStream actual) throws IOException {
123
124 if (expected == null && actual == null) {
125 return;
126 }
127
128 if (expected == null || actual == null) {
129 try {
130 fail(message);
131 }
132 finally {
133 try {
134 if (expected != null) {
135 expected.close();
136 }
137 }
138 finally {
139 if (actual != null) {
140 actual.close();
141 }
142 }
143 }
144 }
145
146 try (InputStream expectedBuf = new BufferedInputStream(expected)) {
147 try (InputStream actualBuf = new BufferedInputStream(actual)) {
148
149 final byte[] expectedArray = new byte[2048];
150 final byte[] actualArray = new byte[2048];
151
152 int expectedLength = expectedBuf.read(expectedArray);
153 while (true) {
154
155 final int actualLength = actualBuf.read(actualArray);
156 assertEquals(message, expectedLength, actualLength);
157
158 if (expectedLength == -1) {
159 break;
160 }
161
162 assertEquals(message, expectedArray, actualArray, expectedLength);
163 expectedLength = expectedBuf.read(expectedArray);
164 }
165 }
166 }
167 }
168
169
170
171
172
173 @Test
174 public void jettyProofOfConcept() throws Exception {
175 startWebServer("./");
176
177 final WebClient client = getWebClient();
178 final Page page = client.getPage(URL_FIRST + "src/test/resources/event_coordinates.html");
179 final WebConnection defaultConnection = client.getWebConnection();
180 assertTrue(
181 "HttpWebConnection should be the default",
182 HttpWebConnection.class.isInstance(defaultConnection));
183 assertTrue("Response should be valid HTML", HtmlPage.class.isInstance(page));
184 }
185
186
187
188
189
190 @Test
191 public void designedForExtension() throws Exception {
192 startWebServer("./");
193
194 final WebClient webClient = getWebClient();
195 final boolean[] tabCalled = {false};
196 final WebConnection myWebConnection = new HttpWebConnection(webClient) {
197 @Override
198 protected HttpClientBuilder createHttpClientBuilder() {
199 tabCalled[0] = true;
200
201 final HttpClientBuilder builder = HttpClientBuilder.create();
202 builder.setConnectionManagerShared(true);
203 return builder;
204 }
205 };
206
207 webClient.setWebConnection(myWebConnection);
208 webClient.getPage(URL_FIRST + "LICENSE.txt");
209 assertTrue("createHttpClient has not been called", tabCalled[0]);
210 }
211
212
213
214
215
216 @Test
217 public void reinitialiseAfterClosing() throws Exception {
218 startWebServer("./");
219
220 final WebClient webClient = getWebClient();
221 try (HttpWebConnection webConnection = new HttpWebConnection(webClient)) {
222 webClient.setWebConnection(webConnection);
223 webClient.getPage(URL_FIRST + "LICENSE.txt");
224 webConnection.close();
225 webClient.getPage(URL_FIRST + "pom.xml");
226 }
227 }
228
229
230
231
232
233 @Test
234 public void buildFilePart() throws Exception {
235 final String encoding = "ISO8859-1";
236 final KeyDataPair pair = new KeyDataPair("myFile", new File("this/doesnt_exist.txt"), "something",
237 MimeType.TEXT_PLAIN, encoding);
238 final MultipartEntityBuilder builder = MultipartEntityBuilder.create().setLaxMode();
239 try (HttpWebConnection webConnection = new HttpWebConnection(getWebClient())) {
240 webConnection.buildFilePart(pair, builder);
241 }
242 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
243 builder.build().writeTo(baos);
244 final String part = baos.toString(encoding);
245
246 final String expected = "--(.*)\r\n"
247 + "Content-Disposition: form-data; name=\"myFile\"; filename=\"doesnt_exist.txt\"\r\n"
248 + "Content-Type: text/plain\r\n"
249 + "\r\n"
250 + "\r\n"
251 + "--\\1--\r\n";
252 assertTrue(part, part.matches(expected));
253 }
254
255
256
257
258 @Test
259 public void unicode() throws Exception {
260 startWebServer("./");
261 final WebClient client = getWebClient();
262 client.getPage(URL_FIRST + "src/test/resources/event_coordinates.html?param=\u00F6");
263 }
264
265
266
267
268 @Test
269 public void emptyPut() throws Exception {
270 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
271 servlets.put("/test", EmptyPutServlet.class);
272 startWebServer("./", null, servlets);
273
274 final String[] expectedAlerts = {"1"};
275 final WebClient client = getWebClient();
276 client.setAjaxController(new NicelyResynchronizingAjaxController());
277 final List<String> collectedAlerts = new ArrayList<>();
278 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
279
280 assertEquals(0, client.getCookieManager().getCookies().size());
281 client.getPage(URL_FIRST + "test");
282 assertEquals(expectedAlerts, collectedAlerts);
283 assertEquals(1, client.getCookieManager().getCookies().size());
284 }
285
286
287
288
289 public static class EmptyPutServlet extends ServletContentWrapper {
290
291 public EmptyPutServlet() {
292 super(DOCTYPE_HTML
293 + "<html>\n"
294 + "<head>\n"
295 + " <script>\n"
296 + " function test() {\n"
297 + " var xhr = window.ActiveXObject?new ActiveXObject('Microsoft.XMLHTTP'):new XMLHttpRequest();\n"
298 + " xhr.open('PUT', '" + URL_FIRST + "test" + "', true);\n"
299 + " xhr.send();\n"
300 + " alert(1);\n"
301 + " }\n"
302 + " </script>\n"
303 + "</head>\n"
304 + "<body onload='test()'></body>\n"
305 + "</html>");
306 }
307
308 @Override
309 protected void doGet(final HttpServletRequest request,
310 final HttpServletResponse response)
311 throws ServletException, IOException {
312 request.getSession().setAttribute("trigger", "session");
313 super.doGet(request, response);
314 }
315 }
316
317
318
319
320 @Test
321 public void cookiesEnabledAfterDisable() throws Exception {
322 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
323 servlets.put("/test1", Cookie1Servlet.class);
324 servlets.put("/test2", Cookie2Servlet.class);
325 startWebServer("./", null, servlets);
326
327 final WebClient client = getWebClient();
328
329 client.getCookieManager().setCookiesEnabled(false);
330 HtmlPage page = client.getPage(URL_FIRST + "test1");
331 assertTrue(page.asNormalizedText().contains("No Cookies"));
332
333 client.getCookieManager().setCookiesEnabled(true);
334 page = client.getPage(URL_FIRST + "test1");
335 assertTrue(page.asNormalizedText().contains("key1=value1"));
336 }
337
338
339
340
341 public static class Cookie1Servlet extends HttpServlet {
342
343
344
345
346 @Override
347 protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
348 response.addCookie(new javax.servlet.http.Cookie("key1", "value1"));
349 response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
350 final String location = request.getRequestURL().toString().replace("test1", "test2");
351 response.setHeader("Location", location);
352 }
353 }
354
355
356
357
358 public static class Cookie2Servlet extends HttpServlet {
359
360
361
362
363 @Override
364 protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
365 response.setContentType(MimeType.TEXT_HTML);
366 final Writer writer = response.getWriter();
367 if (request.getCookies() == null || request.getCookies().length == 0) {
368 writer.write("No Cookies");
369 }
370 else {
371 for (final javax.servlet.http.Cookie c : request.getCookies()) {
372 writer.write(c.getName() + '=' + c.getValue());
373 }
374 }
375 }
376 }
377
378
379
380
381 @Test
382 public void remotePort() throws Exception {
383 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
384 servlets.put("/test", RemotePortServlet.class);
385 startWebServer("./", null, servlets);
386
387 final WebClient client = getWebClient();
388
389 String firstPort = null;
390
391 for (int i = 0; i < 5; i++) {
392 final HtmlPage page = client.getPage(URL_FIRST + "test");
393 final String port = page.asNormalizedText();
394 if (firstPort == null) {
395 firstPort = port;
396 }
397 assertEquals(firstPort, port);
398 }
399 }
400
401
402
403
404 public static class RemotePortServlet extends HttpServlet {
405
406
407
408
409 @Override
410 protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
411 response.setContentType(MimeType.TEXT_HTML);
412 response.getWriter().write(String.valueOf(request.getRemotePort()));
413 }
414 }
415
416
417
418
419 @Test
420 public void contentLengthSmallerThanContent() throws Exception {
421 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
422 servlets.put("/contentLengthSmallerThanContent", ContentLengthSmallerThanContentServlet.class);
423 startWebServer("./", null, servlets);
424
425 final WebClient client = getWebClient();
426 final HtmlPage page = client.getPage(URL_FIRST + "contentLengthSmallerThanContent");
427 assertEquals("visible text", page.asNormalizedText());
428 }
429
430
431
432
433 public static class ContentLengthSmallerThanContentServlet extends ServletContentWrapper {
434
435
436 public ContentLengthSmallerThanContentServlet() {
437 super(DOCTYPE_HTML
438 + "<html>\n"
439 + "<body>\n"
440 + " <p>visible text</p>\n"
441 + " <p>missing text</p>\n"
442 + "</body>\n"
443 + "</html>");
444 }
445
446 @Override
447 protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
448 throws IOException, ServletException {
449 response.setContentLength(getContent().indexOf("<p>missing text</p>"));
450 super.doGet(request, response);
451 }
452 }
453
454
455
456
457 @Test
458 public void contentLengthSmallerThanContentLargeContent() throws Exception {
459 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
460 servlets.put("/contentLengthSmallerThanContent", ContentLengthSmallerThanContentLargeContentServlet.class);
461 startWebServer("./", null, servlets);
462
463 final WebClient client = getWebClient();
464 final HtmlPage page = client.getPage(URL_FIRST + "contentLengthSmallerThanContent");
465 assertTrue(page.asNormalizedText(), page.asNormalizedText().endsWith("visible text"));
466 }
467
468
469
470
471 public static class ContentLengthSmallerThanContentLargeContentServlet extends ServletContentWrapper {
472
473
474 public ContentLengthSmallerThanContentLargeContentServlet() {
475 super(DOCTYPE_HTML
476 + "<html>\n"
477 + "<body>\n"
478 + " <p>"
479 + StringUtils.repeat("HtmlUnit ", 1024 * 1024)
480 + "</p>\n"
481 + " <p>visible text</p>\n"
482 + " <p>missing text</p>\n"
483 + "</body>\n"
484 + "</html>");
485 }
486
487 @Override
488 protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
489 throws IOException, ServletException {
490 response.setContentLength(getContent().indexOf("<p>missing text</p>"));
491 super.doGet(request, response);
492 }
493 }
494
495
496
497
498 @Test
499 public void contentLengthLargerThanContent() throws Exception {
500 final String response = "HTTP/1.1 200 OK\r\n"
501 + "Content-Length: 2000\r\n"
502 + "Content-Type: text/html\r\n"
503 + "\r\n"
504 + DOCTYPE_HTML + "<html><body><p>visible text</p></body></html>";
505
506 try (PrimitiveWebServer primitiveWebServer = new PrimitiveWebServer(null, response, null)) {
507 final WebClient client = getWebClient();
508
509 final HtmlPage page = client.getPage("http://localhost:" + primitiveWebServer.getPort());
510 assertEquals("visible text", page.asNormalizedText());
511 }
512 }
513
514
515
516
517
518
519 @Test
520 public void userAgent() throws Exception {
521 final WebClient webClient = getWebClient();
522 final HttpWebConnection connection = (HttpWebConnection) webClient.getWebConnection();
523 final HttpClientBuilder builder = connection.getHttpClientBuilder();
524 final String userAgent = get(builder, "userAgent");
525 assertEquals(webClient.getBrowserVersion().getUserAgent(), userAgent);
526 }
527
528 @SuppressWarnings("unchecked")
529 private static <T> T get(final Object o, final String fieldName) throws Exception {
530 final Field field = o.getClass().getDeclaredField(fieldName);
531 field.setAccessible(true);
532 return (T) field.get(o);
533 }
534
535
536
537
538
539 @Test
540 public void makeWebResponse() throws Exception {
541 final URL url = new URL("http://htmlunit.sourceforge.net/");
542 final String content = DOCTYPE_HTML + "<html><head></head><body></body></html>";
543 final DownloadedContent downloadedContent = new DownloadedContent.InMemory(content.getBytes());
544 final long loadTime = 500L;
545
546 final ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 0);
547 final StatusLine statusLine = new BasicStatusLine(protocolVersion, HttpStatus.OK_200, null);
548 final HttpResponse httpResponse = new BasicHttpResponse(statusLine);
549
550 final HttpEntity responseEntity = new StringEntity(content);
551 httpResponse.setEntity(responseEntity);
552
553 try (HttpWebConnection connection = new HttpWebConnection(getWebClient())) {
554 final Method method = connection.getClass().getDeclaredMethod("makeWebResponse",
555 HttpResponse.class, WebRequest.class, DownloadedContent.class, long.class);
556 final WebResponse response = (WebResponse) method.invoke(connection,
557 httpResponse, new WebRequest(url), downloadedContent, Long.valueOf(loadTime));
558
559 assertEquals(HttpStatus.OK_200, response.getStatusCode());
560 assertEquals(url, response.getWebRequest().getUrl());
561 assertEquals(loadTime, response.getLoadTime());
562 assertEquals(content, response.getContentAsString());
563 try (InputStream is = response.getContentAsStream()) {
564 assertEquals(content.getBytes(), IOUtils.toByteArray(is));
565 }
566 try (InputStream is = response.getContentAsStream()) {
567 assertEquals(new ByteArrayInputStream(content.getBytes()), is);
568 }
569 }
570 }
571
572
573
574
575
576
577
578
579 @Test
580 public void contentBlocking() throws Exception {
581 final byte[] content = new byte[] {77, 44};
582 final List<NameValuePair> headers = new ArrayList<>();
583 headers.add(new NameValuePair("Content-Encoding", "gzip"));
584 headers.add(new NameValuePair(HttpHeader.CONTENT_LENGTH, String.valueOf(content.length)));
585
586 final MockWebConnection conn = getMockWebConnection();
587 conn.setResponse(URL_FIRST, content, 200, "OK", MimeType.APPLICATION_JSON, headers);
588
589 startWebServer(conn);
590
591 final WebClient client = getWebClient();
592 client.setWebConnection(new HttpWebConnection(client) {
593 @Override
594 protected WebResponse downloadResponse(final HttpUriRequest httpMethod,
595 final WebRequest webRequest, final HttpResponse httpResponse,
596 final long startTime) {
597
598 httpMethod.abort();
599
600 final DownloadedContent downloaded = new DownloadedContent.InMemory(null);
601 final long endTime = System.currentTimeMillis();
602 final WebResponse response = makeWebResponse(httpResponse, webRequest, downloaded, endTime - startTime);
603 response.markAsBlocked("test blocking");
604 return response;
605 }
606 });
607
608 final UnexpectedPage page = client.getPage(URL_FIRST);
609 assertTrue(page.getWebResponse().wasBlocked());
610 assertEquals("test blocking", page.getWebResponse().getBlockReason());
611 }
612
613
614
615
616
617
618
619
620 @Test
621 public void contentSizeBlocking() throws Exception {
622 stopWebServer();
623
624 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
625 servlets.put("/big", BigContentServlet.class);
626 startWebServer("./", null, servlets);
627
628 final WebClient client = getWebClient();
629 client.setWebConnection(new HttpWebConnection(client) {
630 @Override
631 protected WebResponse downloadResponse(final HttpUriRequest httpMethod,
632 final WebRequest webRequest, final HttpResponse httpResponse,
633 final long startTime) throws IOException {
634
635 final int contentLenght = Integer.parseInt(
636 httpResponse.getFirstHeader(HttpHeader.CONTENT_LENGTH).getValue());
637
638 if (contentLenght < 1_000) {
639 return super.downloadResponse(httpMethod, webRequest, httpResponse, startTime);
640 }
641
642 httpMethod.abort();
643
644 final DownloadedContent downloaded = new DownloadedContent.InMemory(null);
645 final long endTime = System.currentTimeMillis();
646 final WebResponse response = makeWebResponse(httpResponse, webRequest, downloaded, endTime - startTime);
647 response.markAsBlocked("blocking " + contentLenght);
648 return response;
649 }
650 });
651
652 final TextPage page = client.getPage(URL_FIRST + "big");
653 assertTrue(page.getWebResponse().wasBlocked());
654 assertEquals("blocking 10240000", page.getWebResponse().getBlockReason());
655 assertTrue("blocks sent " + BigContentServlet.SENT_, BigContentServlet.SENT_ < 5000);
656
657 BigContentServlet.CANCEL_ = true;
658 }
659
660
661
662
663 public static class BigContentServlet extends HttpServlet {
664
665
666 public static int SENT_;
667
668 public static boolean CANCEL_;
669
670
671
672
673 @Override
674 protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
675 final int blockSize = 1024;
676 final int blockCount = 10_000;
677
678 response.setHeader(HttpHeader.CONTENT_LENGTH, String.valueOf(blockSize * blockCount));
679
680 final byte[] buffer = new byte[blockSize];
681 try (OutputStream out = response.getOutputStream()) {
682 for (int i = 0; i < blockCount && !CANCEL_; i++) {
683 SENT_++;
684 out.write(buffer);
685 }
686 }
687 }
688 }
689 }