1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import static java.nio.charset.StandardCharsets.ISO_8859_1;
18 import static java.nio.charset.StandardCharsets.UTF_8;
19
20 import java.io.BufferedInputStream;
21 import java.io.File;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.ObjectInputStream;
26 import java.io.Serializable;
27 import java.lang.ref.Cleaner;
28 import java.lang.ref.Cleaner.Cleanable;
29 import java.lang.ref.WeakReference;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.net.URLConnection;
33 import java.net.URLDecoder;
34 import java.nio.charset.Charset;
35 import java.nio.file.Files;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.ConcurrentModificationException;
39 import java.util.Date;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.Iterator;
43 import java.util.LinkedHashMap;
44 import java.util.LinkedHashSet;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.Map;
48 import java.util.Objects;
49 import java.util.Optional;
50 import java.util.Set;
51 import java.util.concurrent.ConcurrentLinkedDeque;
52 import java.util.concurrent.Executor;
53 import java.util.concurrent.ExecutorService;
54 import java.util.concurrent.Executors;
55 import java.util.concurrent.ThreadFactory;
56 import java.util.concurrent.ThreadPoolExecutor;
57
58 import org.apache.commons.logging.Log;
59 import org.apache.commons.logging.LogFactory;
60 import org.apache.http.NoHttpResponseException;
61 import org.apache.http.client.CredentialsProvider;
62 import org.apache.http.cookie.MalformedCookieException;
63 import org.htmlunit.attachment.Attachment;
64 import org.htmlunit.attachment.AttachmentHandler;
65 import org.htmlunit.csp.Policy;
66 import org.htmlunit.csp.url.URI;
67 import org.htmlunit.css.ComputedCssStyleDeclaration;
68 import org.htmlunit.cssparser.parser.CSSErrorHandler;
69 import org.htmlunit.cssparser.parser.javacc.CSS3Parser;
70 import org.htmlunit.html.BaseFrameElement;
71 import org.htmlunit.html.DomElement;
72 import org.htmlunit.html.DomNode;
73 import org.htmlunit.html.FrameWindow;
74 import org.htmlunit.html.FrameWindow.PageDenied;
75 import org.htmlunit.html.HtmlElement;
76 import org.htmlunit.html.HtmlInlineFrame;
77 import org.htmlunit.html.HtmlPage;
78 import org.htmlunit.html.XHtmlPage;
79 import org.htmlunit.html.parser.HTMLParser;
80 import org.htmlunit.html.parser.HTMLParserListener;
81 import org.htmlunit.http.Cookie;
82 import org.htmlunit.http.HttpStatus;
83 import org.htmlunit.http.HttpUtils;
84 import org.htmlunit.httpclient.HttpClientConverter;
85 import org.htmlunit.javascript.AbstractJavaScriptEngine;
86 import org.htmlunit.javascript.DefaultJavaScriptErrorListener;
87 import org.htmlunit.javascript.HtmlUnitScriptable;
88 import org.htmlunit.javascript.JavaScriptEngine;
89 import org.htmlunit.javascript.JavaScriptErrorListener;
90 import org.htmlunit.javascript.background.JavaScriptJobManager;
91 import org.htmlunit.javascript.host.BroadcastChannel;
92 import org.htmlunit.javascript.host.Location;
93 import org.htmlunit.javascript.host.Window;
94 import org.htmlunit.javascript.host.dom.Node;
95 import org.htmlunit.javascript.host.event.Event;
96 import org.htmlunit.javascript.host.file.Blob;
97 import org.htmlunit.javascript.host.html.HTMLIFrameElement;
98 import org.htmlunit.protocol.data.DataURLConnection;
99 import org.htmlunit.util.HeaderUtils;
100 import org.htmlunit.util.MimeType;
101 import org.htmlunit.util.NameValuePair;
102 import org.htmlunit.util.StringUtils;
103 import org.htmlunit.util.UrlUtils;
104 import org.htmlunit.websocket.JettyWebSocketAdapter.JettyWebSocketAdapterFactory;
105 import org.htmlunit.websocket.WebSocketAdapter;
106 import org.htmlunit.websocket.WebSocketAdapterFactory;
107 import org.htmlunit.websocket.WebSocketListener;
108 import org.htmlunit.webstart.WebStartHandler;
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158 @SuppressWarnings("PMD.TooManyFields")
159 public class WebClient implements Serializable, AutoCloseable {
160
161
162 private static final Log LOG = LogFactory.getLog(WebClient.class);
163
164
165 private static final int ALLOWED_REDIRECTIONS_SAME_URL = 20;
166 private static final WebResponseData RESPONSE_DATA_NO_HTTP_RESPONSE = new WebResponseData(
167 0, "No HTTP Response", Collections.emptyList());
168
169 static final Cleaner CLEANER = Cleaner.create();
170
171
172
173
174
175 private static final String[] DISCARDING_304_RESPONSE_HEADER_NAMES = {
176 "connection",
177 "proxy-connection",
178 "keep-alive",
179 "www-authenticate",
180 "proxy-authenticate",
181 "proxy-authorization",
182 "te",
183 "trailer",
184 "transfer-encoding",
185 "upgrade",
186 "content-location",
187 "content-md5",
188 "etag",
189 "content-encoding",
190 "content-range",
191 "content-type",
192 "content-length",
193 "x-frame-options",
194 "x-xss-protection",
195 };
196
197 private static final String[] DISCARDING_304_HEADER_PREFIXES = {
198 "x-content-",
199 "x-webkit-"
200 };
201
202 private transient WebConnection webConnection_;
203 private CredentialsProvider credentialsProvider_ = new DefaultCredentialsProvider();
204 private CookieManager cookieManager_ = new CookieManager();
205 private WebSocketAdapterFactory webSocketAdapterFactory_;
206 private transient AbstractJavaScriptEngine<?> scriptEngine_;
207 private transient List<LoadJob> loadQueue_;
208 private final Map<String, String> requestHeaders_ = Collections.synchronizedMap(new HashMap<>(89));
209 private IncorrectnessListener incorrectnessListener_ = new IncorrectnessListenerImpl();
210 private WebConsole webConsole_;
211 private transient ExecutorService executor_;
212
213 private AlertHandler alertHandler_;
214 private ConfirmHandler confirmHandler_;
215 private PromptHandler promptHandler_;
216 private StatusHandler statusHandler_;
217 private AttachmentHandler attachmentHandler_;
218 private ClipboardHandler clipboardHandler_;
219 private PrintHandler printHandler_;
220 private WebStartHandler webStartHandler_;
221 private FrameContentHandler frameContentHandler_;
222
223 private AjaxController ajaxController_ = new AjaxController();
224
225 private final BrowserVersion browserVersion_;
226 private PageCreator pageCreator_ = new DefaultPageCreator();
227
228
229
230
231 private CurrentWindowTracker currentWindowTracker_;
232 private final Set<WebWindowListener> webWindowListeners_ = new HashSet<>(5);
233
234 private final List<TopLevelWindow> topLevelWindows_ =
235 Collections.synchronizedList(new ArrayList<>());
236 private final List<WebWindow> windows_ = Collections.synchronizedList(new ArrayList<>());
237 private transient List<WeakReference<JavaScriptJobManager>> jobManagers_ =
238 Collections.synchronizedList(new ArrayList<>());
239 private WebWindow currentWindow_;
240
241 private transient BlobUrlStore blobUrlStore_ = new BlobUrlStore();
242
243 private HTMLParserListener htmlParserListener_;
244 private CSSErrorHandler cssErrorHandler_ = new DefaultCssErrorHandler();
245 private OnbeforeunloadHandler onbeforeunloadHandler_;
246 private Cache cache_ = new Cache();
247
248
249 private transient CSS3ParserPool css3ParserPool_ = new CSS3ParserPool();
250
251
252 public static final String TARGET_BLANK = "_blank";
253
254
255 public static final String TARGET_SELF = "_self";
256
257
258 private static final String TARGET_PARENT = "_parent";
259
260 private static final String TARGET_TOP = "_top";
261
262 private ScriptPreProcessor scriptPreProcessor_;
263
264 private RefreshHandler refreshHandler_ = new NiceRefreshHandler(2);
265 private JavaScriptErrorListener javaScriptErrorListener_ = new DefaultJavaScriptErrorListener();
266
267 private final WebClientOptions options_ = new WebClientOptions();
268 private final boolean javaScriptEngineEnabled_;
269 private final StorageHolder storageHolder_ = new StorageHolder();
270
271 private transient Set<BroadcastChannel> broadcastChannel_ = new HashSet<>();
272
273
274
275
276
277 public WebClient() {
278 this(BrowserVersion.getDefault());
279 }
280
281
282
283
284
285 public WebClient(final BrowserVersion browserVersion) {
286 this(browserVersion, null, -1);
287 }
288
289
290
291
292
293
294
295 public WebClient(final BrowserVersion browserVersion, final String proxyHost, final int proxyPort) {
296 this(browserVersion, true, proxyHost, proxyPort, null);
297 }
298
299
300
301
302
303
304
305
306 public WebClient(final BrowserVersion browserVersion,
307 final String proxyHost, final int proxyPort, final String proxyScheme) {
308 this(browserVersion, true, proxyHost, proxyPort, proxyScheme);
309 }
310
311
312
313
314
315
316
317
318 public WebClient(final BrowserVersion browserVersion, final boolean javaScriptEngineEnabled,
319 final String proxyHost, final int proxyPort) {
320 this(browserVersion, javaScriptEngineEnabled, proxyHost, proxyPort, null);
321 }
322
323
324
325
326
327
328
329
330
331 public WebClient(final BrowserVersion browserVersion, final boolean javaScriptEngineEnabled,
332 final String proxyHost, final int proxyPort, final String proxyScheme) {
333 WebAssert.notNull("browserVersion", browserVersion);
334
335 browserVersion_ = browserVersion;
336 javaScriptEngineEnabled_ = javaScriptEngineEnabled;
337
338 if (proxyHost == null) {
339 getOptions().setProxyConfig(new ProxyConfig());
340 }
341 else {
342 getOptions().setProxyConfig(new ProxyConfig(proxyHost, proxyPort, proxyScheme));
343 }
344
345 webConnection_ = new HttpWebConnection(this);
346 if (javaScriptEngineEnabled_) {
347 scriptEngine_ = new JavaScriptEngine(this);
348 }
349 loadQueue_ = new ArrayList<>();
350
351 webSocketAdapterFactory_ = new JettyWebSocketAdapterFactory();
352
353
354 currentWindowTracker_ = new CurrentWindowTracker(this, true);
355 currentWindow_ = new TopLevelWindow("", this);
356 }
357
358
359
360
361
362 private static final class ThreadNamingFactory implements ThreadFactory {
363 private static int ID_ = 1;
364 private final ThreadFactory baseFactory_;
365
366 ThreadNamingFactory(final ThreadFactory aBaseFactory) {
367 baseFactory_ = aBaseFactory;
368 }
369
370 @Override
371 public Thread newThread(final Runnable aRunnable) {
372 final Thread thread = baseFactory_.newThread(aRunnable);
373 thread.setName("WebClient Thread " + ID_++);
374 return thread;
375 }
376 }
377
378
379
380
381
382
383 public WebConnection getWebConnection() {
384 return webConnection_;
385 }
386
387
388
389
390
391
392 public void setWebConnection(final WebConnection webConnection) {
393 WebAssert.notNull("webConnection", webConnection);
394 webConnection_ = webConnection;
395 }
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420 public <P extends Page> P getPage(final WebWindow webWindow, final WebRequest webRequest)
421 throws IOException, FailingHttpStatusCodeException {
422 return getPage(webWindow, webRequest, true);
423 }
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451 @SuppressWarnings("unchecked")
452 <P extends Page> P getPage(final WebWindow webWindow, final WebRequest webRequest,
453 final boolean addToHistory)
454 throws IOException, FailingHttpStatusCodeException {
455
456 final Page page = webWindow.getEnclosedPage();
457
458 if (page != null) {
459 final URL prev = page.getUrl();
460 final URL current = webRequest.getUrl();
461 if (UrlUtils.sameFile(current, prev)
462 && current.getRef() != null
463 && !Objects.equals(current.getRef(), prev.getRef())) {
464
465 page.getWebResponse().getWebRequest().setUrl(current);
466 if (addToHistory) {
467 webWindow.getHistory().addPage(page);
468 }
469
470
471
472 if (page instanceof HtmlPage htmlPage) {
473 htmlPage.clearComputedStyles();
474 }
475
476 final Window window = webWindow.getScriptableObject();
477 if (window != null) {
478 window.getLocation().setHash(current.getRef());
479 }
480 return (P) page;
481 }
482
483 if (page.isHtmlPage()) {
484 final HtmlPage htmlPage = (HtmlPage) page;
485 if (!htmlPage.isOnbeforeunloadAccepted()) {
486 LOG.debug("The registered OnbeforeunloadHandler rejected to load a new page.");
487 return (P) page;
488 }
489 }
490 }
491
492 if (LOG.isDebugEnabled()) {
493 LOG.debug("Get page for window named '" + webWindow.getName() + "', using " + webRequest);
494 }
495
496 WebResponse webResponse;
497 final String protocol = webRequest.getUrl().getProtocol();
498 if ("javascript".equals(protocol)) {
499 webResponse = makeWebResponseForJavaScriptUrl(webWindow, webRequest.getUrl(), webRequest.getCharset());
500 if (webWindow.getEnclosedPage() != null && webWindow.getEnclosedPage().getWebResponse() == webResponse) {
501
502 return (P) webWindow.getEnclosedPage();
503 }
504 }
505 else {
506 try {
507 webResponse = loadWebResponse(webRequest);
508 }
509 catch (final NoHttpResponseException e) {
510 webResponse = new WebResponse(RESPONSE_DATA_NO_HTTP_RESPONSE, webRequest, 0);
511 }
512 }
513
514 printContentIfNecessary(webResponse);
515 loadWebResponseInto(webResponse, webWindow);
516
517
518
519
520 if (scriptEngine_ != null) {
521 scriptEngine_.registerWindowAndMaybeStartEventLoop(webWindow);
522 }
523
524
525 throwFailingHttpStatusCodeExceptionIfNecessary(webResponse);
526 return (P) webWindow.getEnclosedPage();
527 }
528
529
530
531
532
533
534
535
536
537
538
539
540
541 public <P extends Page> P getPage(final String url) throws IOException, FailingHttpStatusCodeException,
542 MalformedURLException {
543 return getPage(UrlUtils.toUrlUnsafe(url));
544 }
545
546
547
548
549
550
551
552
553
554
555
556
557 public <P extends Page> P getPage(final URL url) throws IOException, FailingHttpStatusCodeException {
558 final WebRequest request = new WebRequest(url, getBrowserVersion().getHtmlAcceptHeader(),
559 getBrowserVersion().getAcceptEncodingHeader());
560 request.setCharset(UTF_8);
561 return getPage(getCurrentWindow().getTopWindow(), request);
562 }
563
564
565
566
567
568
569
570
571
572
573
574 public <P extends Page> P getPage(final WebRequest request) throws IOException,
575 FailingHttpStatusCodeException {
576 return getPage(getCurrentWindow().getTopWindow(), request);
577 }
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596 public Page loadWebResponseInto(final WebResponse webResponse, final WebWindow webWindow)
597 throws IOException, FailingHttpStatusCodeException {
598 return loadWebResponseInto(webResponse, webWindow, null);
599 }
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622 public Page loadWebResponseInto(final WebResponse webResponse, final WebWindow webWindow,
623 String forceAttachmentWithFilename)
624 throws IOException, FailingHttpStatusCodeException {
625 WebAssert.notNull("webResponse", webResponse);
626 WebAssert.notNull("webWindow", webWindow);
627
628 if (webResponse.getStatusCode() == HttpStatus.NO_CONTENT_204) {
629 return webWindow.getEnclosedPage();
630 }
631
632 if (webStartHandler_ != null && "application/x-java-jnlp-file".equals(webResponse.getContentType())) {
633 webStartHandler_.handleJnlpResponse(webResponse);
634 return webWindow.getEnclosedPage();
635 }
636
637 if (attachmentHandler_ != null
638 && (forceAttachmentWithFilename != null || attachmentHandler_.isAttachment(webResponse))) {
639
640
641 if (StringUtils.isEmptyOrNull(forceAttachmentWithFilename)) {
642 final String disp = webResponse.getResponseHeaderValue(HttpHeader.CONTENT_DISPOSITION);
643 forceAttachmentWithFilename = Attachment.getSuggestedFilename(disp);
644 }
645
646 if (attachmentHandler_.handleAttachment(webResponse,
647 StringUtils.isEmptyOrNull(forceAttachmentWithFilename) ? null : forceAttachmentWithFilename)) {
648
649
650 return webWindow.getEnclosedPage();
651 }
652
653 final WebWindow w = openWindow(null, null, webWindow);
654 final Page page = pageCreator_.createPage(webResponse, w);
655 attachmentHandler_.handleAttachment(page,
656 StringUtils.isEmptyOrNull(forceAttachmentWithFilename)
657 ? null : forceAttachmentWithFilename);
658 return page;
659 }
660
661 final Page oldPage = webWindow.getEnclosedPage();
662 if (oldPage != null) {
663
664 oldPage.cleanUp();
665 }
666
667 Page newPage = null;
668 FrameWindow.PageDenied pageDenied = PageDenied.NONE;
669 if (windows_.contains(webWindow)) {
670 if (webWindow instanceof FrameWindow window) {
671 final String contentSecurityPolicy =
672 webResponse.getResponseHeaderValue(HttpHeader.CONTENT_SECURIRY_POLICY);
673 if (StringUtils.isNotBlank(contentSecurityPolicy)) {
674 final URL origin = UrlUtils.getUrlWithoutPathRefQuery(
675 window.getEnclosingPage().getUrl());
676 final URL source = UrlUtils.getUrlWithoutPathRefQuery(webResponse.getWebRequest().getUrl());
677 final Policy policy = Policy.parseSerializedCSP(contentSecurityPolicy,
678 Policy.PolicyErrorConsumer.ignored);
679 if (!policy.allowsFrameAncestor(
680 Optional.of(URI.parseURI(source.toExternalForm()).orElse(null)),
681 Optional.of(URI.parseURI(origin.toExternalForm()).orElse(null)))) {
682 pageDenied = PageDenied.BY_CONTENT_SECURIRY_POLICY;
683
684 if (LOG.isWarnEnabled()) {
685 LOG.warn("Load denied by Content-Security-Policy: '" + contentSecurityPolicy + "' - "
686 + webResponse.getWebRequest().getUrl() + "' does not permit framing.");
687 }
688 }
689 }
690
691 if (pageDenied == PageDenied.NONE) {
692 final String xFrameOptions = webResponse.getResponseHeaderValue(HttpHeader.X_FRAME_OPTIONS);
693 if ("DENY".equalsIgnoreCase(xFrameOptions)) {
694 pageDenied = PageDenied.BY_X_FRAME_OPTIONS;
695
696 if (LOG.isWarnEnabled()) {
697 LOG.warn("Load denied by X-Frame-Options: DENY; - '"
698 + webResponse.getWebRequest().getUrl() + "' does not permit framing.");
699 }
700 }
701 }
702 }
703
704 if (pageDenied == PageDenied.NONE) {
705 newPage = pageCreator_.createPage(webResponse, webWindow);
706 }
707 else {
708 try {
709 final WebResponse aboutBlank = loadWebResponse(WebRequest.newAboutBlankRequest());
710 newPage = pageCreator_.createPage(aboutBlank, webWindow);
711
712
713 ((FrameWindow) webWindow).setPageDenied(pageDenied);
714 }
715 catch (final IOException ignored) {
716
717 }
718 }
719
720 if (windows_.contains(webWindow)) {
721 fireWindowContentChanged(new WebWindowEvent(webWindow, WebWindowEvent.CHANGE, oldPage, newPage));
722
723
724 if (webWindow.getEnclosedPage() == newPage) {
725 newPage.initialize();
726
727
728 if (isJavaScriptEnabled()
729 && webWindow instanceof FrameWindow fw && !newPage.isHtmlPage()) {
730 final BaseFrameElement frame = fw.getFrameElement();
731 if (frame.hasEventHandlers("onload")) {
732 if (LOG.isDebugEnabled()) {
733 LOG.debug("Executing onload handler for " + frame);
734 }
735 final Event event = new Event(frame, Event.TYPE_LOAD);
736 ((Node) frame.getScriptableObject()).executeEventLocally(event);
737 }
738 }
739 }
740 }
741 }
742 return newPage;
743 }
744
745
746
747
748
749
750
751
752
753
754 public void printContentIfNecessary(final WebResponse webResponse) {
755 if (getOptions().isPrintContentOnFailingStatusCode()
756 && !webResponse.isSuccess() && LOG.isInfoEnabled()) {
757 final String contentType = webResponse.getContentType();
758 LOG.info("statusCode=[" + webResponse.getStatusCode() + "] contentType=[" + contentType + "]");
759 LOG.info(webResponse.getContentAsString());
760 }
761 }
762
763
764
765
766
767
768
769
770
771
772 public void throwFailingHttpStatusCodeExceptionIfNecessary(final WebResponse webResponse) {
773 if (getOptions().isThrowExceptionOnFailingStatusCode() && !webResponse.isSuccessOrUseProxyOrNotModified()) {
774 throw new FailingHttpStatusCodeException(webResponse);
775 }
776 }
777
778
779
780
781
782
783
784
785
786 public void addRequestHeader(final String name, final String value) {
787 if (HttpHeader.COOKIE_LC.equalsIgnoreCase(name)) {
788 throw new IllegalArgumentException("Do not add 'Cookie' header, use .getCookieManager() instead");
789 }
790 requestHeaders_.put(name, value);
791 }
792
793
794
795
796
797
798
799
800
801
802 public void removeRequestHeader(final String name) {
803 requestHeaders_.remove(name);
804 }
805
806
807
808
809
810
811
812
813 public void setCredentialsProvider(final CredentialsProvider credentialsProvider) {
814 WebAssert.notNull("credentialsProvider", credentialsProvider);
815 credentialsProvider_ = credentialsProvider;
816 }
817
818
819
820
821
822
823 public CredentialsProvider getCredentialsProvider() {
824 return credentialsProvider_;
825 }
826
827
828
829
830
831 public AbstractJavaScriptEngine<?> getJavaScriptEngine() {
832 return scriptEngine_;
833 }
834
835
836
837
838
839
840 public void setJavaScriptEngine(final AbstractJavaScriptEngine<?> engine) {
841 if (engine == null) {
842 throw new IllegalArgumentException("Can't set JavaScriptEngine to null");
843 }
844 scriptEngine_ = engine;
845 }
846
847
848
849
850
851 public CookieManager getCookieManager() {
852 return cookieManager_;
853 }
854
855
856
857
858
859 public void setCookieManager(final CookieManager cookieManager) {
860 WebAssert.notNull("cookieManager", cookieManager);
861 cookieManager_ = cookieManager;
862 }
863
864
865
866
867
868 public void setAlertHandler(final AlertHandler alertHandler) {
869 alertHandler_ = alertHandler;
870 }
871
872
873
874
875
876 public AlertHandler getAlertHandler() {
877 return alertHandler_;
878 }
879
880
881
882
883
884 public void setConfirmHandler(final ConfirmHandler handler) {
885 confirmHandler_ = handler;
886 }
887
888
889
890
891
892 public ConfirmHandler getConfirmHandler() {
893 return confirmHandler_;
894 }
895
896
897
898
899
900 public void setPromptHandler(final PromptHandler handler) {
901 promptHandler_ = handler;
902 }
903
904
905
906
907
908 public PromptHandler getPromptHandler() {
909 return promptHandler_;
910 }
911
912
913
914
915
916 public void setStatusHandler(final StatusHandler statusHandler) {
917 statusHandler_ = statusHandler;
918 }
919
920
921
922
923
924 public StatusHandler getStatusHandler() {
925 return statusHandler_;
926 }
927
928
929
930
931
932 public synchronized Executor getExecutor() {
933 if (executor_ == null) {
934 final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
935 threadPoolExecutor.setThreadFactory(new ThreadNamingFactory(threadPoolExecutor.getThreadFactory()));
936
937 executor_ = threadPoolExecutor;
938 }
939
940 return executor_;
941 }
942
943
944
945
946
947
948
949 public synchronized void setExecutor(final ExecutorService executor) {
950 if (executor_ != null) {
951 throw new IllegalStateException("Can't change the executor after first use.");
952 }
953
954 executor_ = executor;
955 }
956
957
958
959
960
961
962 public void setJavaScriptErrorListener(final JavaScriptErrorListener javaScriptErrorListener) {
963 if (javaScriptErrorListener == null) {
964 javaScriptErrorListener_ = new DefaultJavaScriptErrorListener();
965 }
966 else {
967 javaScriptErrorListener_ = javaScriptErrorListener;
968 }
969 }
970
971
972
973
974
975 public JavaScriptErrorListener getJavaScriptErrorListener() {
976 return javaScriptErrorListener_;
977 }
978
979
980
981
982
983 public BrowserVersion getBrowserVersion() {
984 return browserVersion_;
985 }
986
987
988
989
990
991
992 public WebWindow getCurrentWindow() {
993 return currentWindow_;
994 }
995
996
997
998
999
1000
1001 public void setCurrentWindow(final WebWindow window) {
1002 WebAssert.notNull("window", window);
1003 if (currentWindow_ == window) {
1004 return;
1005 }
1006
1007 if (currentWindow_ != null && !currentWindow_.isClosed()) {
1008 final Page enclosedPage = currentWindow_.getEnclosedPage();
1009 if (enclosedPage != null && enclosedPage.isHtmlPage()) {
1010 final DomElement focusedElement = ((HtmlPage) enclosedPage).getFocusedElement();
1011 if (focusedElement != null) {
1012 focusedElement.fireEvent(Event.TYPE_BLUR);
1013 }
1014 }
1015 }
1016 currentWindow_ = window;
1017
1018
1019 final boolean isIFrame = currentWindow_ instanceof FrameWindow fw
1020 && fw.getFrameElement() instanceof HtmlInlineFrame;
1021 if (!isIFrame) {
1022
1023
1024 final Page enclosedPage = currentWindow_.getEnclosedPage();
1025 if (enclosedPage != null && enclosedPage.isHtmlPage()) {
1026 final HtmlPage enclosedHtmlPage = (HtmlPage) enclosedPage;
1027 final HtmlElement activeElement = enclosedHtmlPage.getActiveElement();
1028 if (activeElement != null) {
1029 enclosedHtmlPage.setFocusedElement(activeElement, true);
1030 }
1031 }
1032 }
1033 }
1034
1035
1036
1037
1038
1039 public BlobUrlStore getBlobUrlStore() {
1040 return blobUrlStore_;
1041 }
1042
1043
1044
1045
1046
1047
1048 public void addWebWindowListener(final WebWindowListener listener) {
1049 WebAssert.notNull("listener", listener);
1050 webWindowListeners_.add(listener);
1051 }
1052
1053
1054
1055
1056
1057 public void removeWebWindowListener(final WebWindowListener listener) {
1058 WebAssert.notNull("listener", listener);
1059 webWindowListeners_.remove(listener);
1060 }
1061
1062 private void fireWindowContentChanged(final WebWindowEvent event) {
1063 if (currentWindowTracker_ != null) {
1064 currentWindowTracker_.webWindowContentChanged(event);
1065 }
1066 for (final WebWindowListener listener : new ArrayList<>(webWindowListeners_)) {
1067 listener.webWindowContentChanged(event);
1068 }
1069
1070 blobUrlStore_.removeForPage(event.getOldPage());
1071 }
1072
1073 private void fireWindowOpened(final WebWindowEvent event) {
1074 if (currentWindowTracker_ != null) {
1075 currentWindowTracker_.webWindowOpened(event);
1076 }
1077 for (final WebWindowListener listener : new ArrayList<>(webWindowListeners_)) {
1078 listener.webWindowOpened(event);
1079 }
1080 }
1081
1082 private void fireWindowClosed(final WebWindowEvent event) {
1083 if (currentWindowTracker_ != null) {
1084 currentWindowTracker_.webWindowClosed(event);
1085 }
1086
1087 for (final WebWindowListener listener : new ArrayList<>(webWindowListeners_)) {
1088 listener.webWindowClosed(event);
1089 }
1090
1091 blobUrlStore_.removeForPage(event.getOldPage());
1092
1093
1094 if (currentWindowTracker_ != null) {
1095 currentWindowTracker_.afterWebWindowClosedListenersProcessed(event);
1096 }
1097 }
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107 public WebWindow openWindow(final URL url, final String windowName) {
1108 WebAssert.notNull("windowName", windowName);
1109 return openWindow(url, windowName, getCurrentWindow());
1110 }
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121 public WebWindow openWindow(final URL url, final String windowName, final WebWindow opener) {
1122 final WebWindow window = openTargetWindow(opener, windowName, TARGET_BLANK);
1123 if (url == null) {
1124 initializeEmptyWindow(window, window.getEnclosedPage());
1125 }
1126 else {
1127 try {
1128 final WebRequest request = new WebRequest(url, getBrowserVersion().getHtmlAcceptHeader(),
1129 getBrowserVersion().getAcceptEncodingHeader());
1130 request.setCharset(UTF_8);
1131
1132 final Page openerPage = opener.getEnclosedPage();
1133 if (openerPage != null && openerPage.getUrl() != null) {
1134 request.setRefererHeader(openerPage.getUrl());
1135 }
1136 getPage(window, request);
1137 }
1138 catch (final IOException e) {
1139 LOG.error("Error loading content into window", e);
1140 }
1141 }
1142 return window;
1143 }
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 public WebWindow openTargetWindow(
1159 final WebWindow opener, final String windowName, final String defaultName) {
1160
1161 WebAssert.notNull("opener", opener);
1162 WebAssert.notNull("defaultName", defaultName);
1163
1164 String windowToOpen = windowName;
1165 if (windowToOpen == null || windowToOpen.isEmpty()) {
1166 windowToOpen = defaultName;
1167 }
1168
1169 WebWindow webWindow = resolveWindow(opener, windowToOpen);
1170
1171 if (webWindow == null) {
1172 if (TARGET_BLANK.equals(windowToOpen)) {
1173 windowToOpen = "";
1174 }
1175 webWindow = new TopLevelWindow(windowToOpen, this);
1176 }
1177
1178 if (webWindow instanceof TopLevelWindow window && webWindow != opener.getTopWindow()) {
1179 window.setOpener(opener);
1180 }
1181
1182 return webWindow;
1183 }
1184
1185 private WebWindow resolveWindow(final WebWindow opener, final String name) {
1186 if (name == null || name.isEmpty() || TARGET_SELF.equals(name)) {
1187 return opener;
1188 }
1189
1190 if (TARGET_PARENT.equals(name)) {
1191 return opener.getParentWindow();
1192 }
1193
1194 if (TARGET_TOP.equals(name)) {
1195 return opener.getTopWindow();
1196 }
1197
1198 if (TARGET_BLANK.equals(name)) {
1199 return null;
1200 }
1201
1202
1203 WebWindow window = opener;
1204 while (true) {
1205 final Page page = window.getEnclosedPage();
1206 if (page != null && page.isHtmlPage()) {
1207 try {
1208 final FrameWindow frame = ((HtmlPage) page).getFrameByName(name);
1209 final HtmlUnitScriptable scriptable = frame.getFrameElement().getScriptableObject();
1210 if (scriptable instanceof HTMLIFrameElement element) {
1211 element.onRefresh();
1212 }
1213 return frame;
1214 }
1215 catch (final ElementNotFoundException expected) {
1216
1217 }
1218 }
1219
1220 if (window == window.getParentWindow()) {
1221
1222 break;
1223 }
1224 window = window.getParentWindow();
1225 }
1226
1227 try {
1228 return getWebWindowByName(name);
1229 }
1230 catch (final WebWindowNotFoundException expected) {
1231
1232 }
1233 return null;
1234 }
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246 public DialogWindow openDialogWindow(final URL url, final WebWindow opener, final Object dialogArguments)
1247 throws IOException {
1248
1249 WebAssert.notNull("url", url);
1250 WebAssert.notNull("opener", opener);
1251
1252 final DialogWindow window = new DialogWindow(this, dialogArguments);
1253
1254 final HtmlPage openerPage = (HtmlPage) opener.getEnclosedPage();
1255 final WebRequest request = new WebRequest(url, getBrowserVersion().getHtmlAcceptHeader(),
1256 getBrowserVersion().getAcceptEncodingHeader());
1257 request.setCharset(UTF_8);
1258
1259 if (openerPage != null) {
1260 request.setRefererHeader(openerPage.getUrl());
1261 }
1262
1263 getPage(window, request);
1264
1265 return window;
1266 }
1267
1268
1269
1270
1271
1272
1273
1274 public void setPageCreator(final PageCreator pageCreator) {
1275 WebAssert.notNull("pageCreator", pageCreator);
1276 pageCreator_ = pageCreator;
1277 }
1278
1279
1280
1281
1282
1283
1284 public PageCreator getPageCreator() {
1285 return pageCreator_;
1286 }
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297 public WebWindow getWebWindowByName(final String name) throws WebWindowNotFoundException {
1298 WebAssert.notNull("name", name);
1299
1300 for (final WebWindow webWindow : windows_) {
1301 if (name.equals(webWindow.getName())) {
1302 return webWindow;
1303 }
1304 }
1305
1306 throw new WebWindowNotFoundException(name);
1307 }
1308
1309
1310
1311
1312
1313
1314
1315
1316 public void initialize(final WebWindow webWindow, final Page page) {
1317 WebAssert.notNull("webWindow", webWindow);
1318
1319 if (isJavaScriptEngineEnabled()) {
1320 scriptEngine_.initialize(webWindow, page);
1321 }
1322 }
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332 public void initializeEmptyWindow(final WebWindow webWindow, final Page page) {
1333 WebAssert.notNull("webWindow", webWindow);
1334
1335 if (isJavaScriptEngineEnabled()) {
1336 initialize(webWindow, page);
1337 ((Window) webWindow.getScriptableObject()).initialize();
1338 }
1339 }
1340
1341
1342
1343
1344
1345
1346
1347
1348 public void registerWebWindow(final WebWindow webWindow) {
1349 WebAssert.notNull("webWindow", webWindow);
1350 if (windows_.add(webWindow)) {
1351 fireWindowOpened(new WebWindowEvent(webWindow, WebWindowEvent.OPEN, webWindow.getEnclosedPage(), null));
1352 }
1353
1354 jobManagers_.add(new WeakReference<>(webWindow.getJobManager()));
1355 }
1356
1357
1358
1359
1360
1361
1362
1363
1364 public void deregisterWebWindow(final WebWindow webWindow) {
1365 WebAssert.notNull("webWindow", webWindow);
1366 if (windows_.remove(webWindow)) {
1367 fireWindowClosed(new WebWindowEvent(webWindow, WebWindowEvent.CLOSE, webWindow.getEnclosedPage(), null));
1368 }
1369 }
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379 public static Cleanable registerCleanerAction(final Object obj, final Runnable action) {
1380 return CLEANER.register(obj, action);
1381 }
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395 public static URL expandUrl(final URL baseUrl, final String relativeUrl) throws MalformedURLException {
1396 final String newUrl = UrlUtils.resolveUrl(baseUrl, relativeUrl);
1397 return UrlUtils.toUrlUnsafe(newUrl);
1398 }
1399
1400 private WebResponse makeWebResponseForDataUrl(final WebRequest webRequest) throws IOException {
1401 final URL url = webRequest.getUrl();
1402
1403 final DataURLConnection connection = new DataURLConnection(url);
1404
1405 final List<NameValuePair> responseHeaders = new ArrayList<>();
1406 responseHeaders.add(new NameValuePair(HttpHeader.CONTENT_TYPE_LC,
1407 connection.getMediaType() + ";charset=" + connection.getCharset()));
1408
1409 if (HttpMethod.HEAD.equals(webRequest.getHttpMethod())) {
1410 final WebResponseData data = new WebResponseData(200, "OK", responseHeaders);
1411 return new WebResponse(data, url, webRequest.getHttpMethod(), 0);
1412 }
1413
1414 try (InputStream is = connection.getInputStream()) {
1415 final DownloadedContent downloadedContent =
1416 HttpWebConnection.downloadContent(is,
1417 getOptions().getMaxInMemory(),
1418 getOptions().getTempFileDirectory());
1419 final WebResponseData data = new WebResponseData(downloadedContent, 200, "OK", responseHeaders);
1420 return new WebResponse(data, url, webRequest.getHttpMethod(), 0);
1421 }
1422 }
1423
1424 private static WebResponse makeWebResponseForAboutUrl(final WebRequest webRequest) throws MalformedURLException {
1425 final URL url = webRequest.getUrl();
1426 if (UrlUtils.ABOUT.equals(url.getProtocol())) {
1427 if ("blank".equalsIgnoreCase(url.getPath())) {
1428 if (url.getRef() == null && url.getQuery() == null) {
1429 return new StringWebResponse("", UrlUtils.URL_ABOUT_BLANK);
1430 }
1431 return new StringWebResponse("", url);
1432 }
1433 }
1434
1435 throw new MalformedURLException(url + " is not supported, only about:blank is supported at the moment.");
1436 }
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446 private WebResponse makeWebResponseForFileUrl(final WebRequest webRequest) throws IOException {
1447 URL cleanUrl = webRequest.getUrl();
1448 if (cleanUrl.getQuery() != null) {
1449
1450 cleanUrl = UrlUtils.getUrlWithNewQuery(cleanUrl, null);
1451 }
1452 if (cleanUrl.getRef() != null) {
1453
1454 cleanUrl = UrlUtils.getUrlWithNewRef(cleanUrl, null);
1455 }
1456
1457 final WebResponse fromCache = getCache().getCachedResponse(webRequest);
1458 if (fromCache != null) {
1459 return new WebResponseFromCache(fromCache, webRequest);
1460 }
1461
1462 String fileUrl = cleanUrl.toExternalForm();
1463 fileUrl = URLDecoder.decode(fileUrl, UTF_8);
1464 final File file = new File(fileUrl.substring(5));
1465 if (!file.exists()) {
1466
1467 final List<NameValuePair> compiledHeaders = new ArrayList<>();
1468 compiledHeaders.add(new NameValuePair(HttpHeader.CONTENT_TYPE, MimeType.TEXT_HTML));
1469 final WebResponseData responseData =
1470 new WebResponseData(
1471 StringUtils
1472 .toByteArray("File: " + file.getAbsolutePath(), UTF_8),
1473 404, "Not Found", compiledHeaders);
1474 return new WebResponse(responseData, webRequest, 0);
1475 }
1476
1477 final String contentType = guessContentType(file);
1478
1479 final DownloadedContent content = new DownloadedContent.OnFile(file, false);
1480 final List<NameValuePair> compiledHeaders = new ArrayList<>();
1481 compiledHeaders.add(new NameValuePair(HttpHeader.CONTENT_TYPE, contentType));
1482 compiledHeaders.add(new NameValuePair(HttpHeader.LAST_MODIFIED,
1483 HttpUtils.formatDate(new Date(file.lastModified()))));
1484 final WebResponseData responseData = new WebResponseData(content, 200, "OK", compiledHeaders);
1485 final WebResponse webResponse = new WebResponse(responseData, webRequest, 0);
1486 getCache().cacheIfPossible(webRequest, webResponse, null);
1487 return webResponse;
1488 }
1489
1490 private WebResponse makeWebResponseForBlobUrl(final WebRequest webRequest) throws IOException {
1491 final Blob fileOrBlob = blobUrlStore_.resolve(webRequest.getUrl().toString());
1492 if (fileOrBlob == null) {
1493 throw new FileNotFoundException("No entry for '" + webRequest.getUrl() + "' in the BlobUrlStore.");
1494 }
1495
1496 final List<NameValuePair> headers = new ArrayList<>();
1497 final String type = fileOrBlob.getType();
1498 if (!StringUtils.isEmptyOrNull(type)) {
1499 headers.add(new NameValuePair(HttpHeader.CONTENT_TYPE, fileOrBlob.getType()));
1500 }
1501 if (fileOrBlob instanceof org.htmlunit.javascript.host.file.File file) {
1502 final String fileName = file.getName();
1503 if (!StringUtils.isEmptyOrNull(fileName)) {
1504
1505 headers.add(new NameValuePair(HttpHeader.CONTENT_DISPOSITION, "inline; filename=\"" + fileName + "\""));
1506 }
1507 }
1508
1509 final DownloadedContent content = new DownloadedContent.InMemory(fileOrBlob.getBytes());
1510 final WebResponseData responseData = new WebResponseData(content, 200, "OK", headers);
1511 return new WebResponse(responseData, webRequest, 0);
1512 }
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522 public String guessContentType(final File file) {
1523 final String fileName = file.getName();
1524 final String fileNameLC = fileName.toLowerCase(Locale.ROOT);
1525 if (fileNameLC.endsWith(".xhtml")) {
1526
1527 return MimeType.APPLICATION_XHTML;
1528 }
1529
1530
1531 if (fileNameLC.endsWith(".js")) {
1532 return MimeType.TEXT_JAVASCRIPT;
1533 }
1534
1535 if (fileNameLC.endsWith(".css")) {
1536 return MimeType.TEXT_CSS;
1537 }
1538
1539 String contentType = null;
1540 if (!fileNameLC.endsWith(".php")) {
1541 contentType = URLConnection.guessContentTypeFromName(fileName);
1542 }
1543 if (contentType == null) {
1544 try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
1545 contentType = URLConnection.guessContentTypeFromStream(inputStream);
1546 }
1547 catch (final IOException ignored) {
1548
1549 }
1550 }
1551 if (contentType == null) {
1552 contentType = MimeType.APPLICATION_OCTET_STREAM;
1553 }
1554 return contentType;
1555 }
1556
1557 private WebResponse makeWebResponseForJavaScriptUrl(final WebWindow webWindow, final URL url,
1558 final Charset charset) throws FailingHttpStatusCodeException, IOException {
1559
1560 HtmlPage page = null;
1561 if (webWindow instanceof FrameWindow frameWindow) {
1562 page = (HtmlPage) frameWindow.getEnclosedPage();
1563 }
1564 else {
1565 final Page currentPage = webWindow.getEnclosedPage();
1566 if (currentPage instanceof HtmlPage htmlPage) {
1567 page = htmlPage;
1568 }
1569 }
1570
1571 if (page == null) {
1572 page = getPage(webWindow, WebRequest.newAboutBlankRequest());
1573 }
1574 final ScriptResult r = page.executeJavaScript(url.toExternalForm(), "JavaScript URL", 1);
1575 if (r.getJavaScriptResult() == null || ScriptResult.isUndefined(r)) {
1576
1577 return webWindow.getEnclosedPage().getWebResponse();
1578 }
1579
1580 final String contentString = r.getJavaScriptResult().toString();
1581 final StringWebResponse response = new StringWebResponse(contentString, charset, url);
1582 response.setFromJavascript(true);
1583 return response;
1584 }
1585
1586
1587
1588
1589
1590
1591
1592 public WebResponse loadWebResponse(final WebRequest webRequest) throws IOException {
1593 final String protocol = webRequest.getUrl().getProtocol();
1594 return switch (protocol) {
1595 case UrlUtils.ABOUT -> makeWebResponseForAboutUrl(webRequest);
1596 case "file" -> makeWebResponseForFileUrl(webRequest);
1597 case "data" -> makeWebResponseForDataUrl(webRequest);
1598 case "blob" -> makeWebResponseForBlobUrl(webRequest);
1599 case "http", "https" -> loadWebResponseFromWebConnection(webRequest, ALLOWED_REDIRECTIONS_SAME_URL);
1600 default -> throw new IOException("Unsupported protocol '" + protocol + "'");
1601 };
1602 }
1603
1604
1605
1606
1607
1608
1609
1610
1611 private WebResponse loadWebResponseFromWebConnection(final WebRequest webRequest,
1612 final int allowedRedirects) throws IOException {
1613
1614 URL url = webRequest.getUrl();
1615 final HttpMethod method = webRequest.getHttpMethod();
1616 final List<NameValuePair> parameters = webRequest.getRequestParameters();
1617
1618 WebAssert.notNull("url", url);
1619 WebAssert.notNull("method", method);
1620 WebAssert.notNull("parameters", parameters);
1621
1622 url = UrlUtils.encodeUrl(url, webRequest.getCharset());
1623 webRequest.setUrl(url);
1624
1625 if (LOG.isDebugEnabled()) {
1626 LOG.debug("Load response for " + method + " " + url.toExternalForm());
1627 }
1628
1629
1630 if (webRequest.getProxyHost() == null) {
1631 final ProxyConfig proxyConfig = getOptions().getProxyConfig();
1632 if (proxyConfig.getProxyAutoConfigUrl() != null) {
1633 if (!UrlUtils.sameFile(new URL(proxyConfig.getProxyAutoConfigUrl()), url)) {
1634 String content = proxyConfig.getProxyAutoConfigContent();
1635 if (content == null) {
1636 content = getPage(proxyConfig.getProxyAutoConfigUrl())
1637 .getWebResponse().getContentAsString();
1638 proxyConfig.setProxyAutoConfigContent(content);
1639 }
1640 final String allValue = JavaScriptEngine.evaluateProxyAutoConfig(getBrowserVersion(), content, url);
1641 if (LOG.isDebugEnabled()) {
1642 LOG.debug("Proxy Auto-Config: value '" + allValue + "' for URL " + url);
1643 }
1644 String value = allValue.split(";")[0].trim();
1645 if (value.startsWith("PROXY")) {
1646 value = value.substring(6);
1647 final int colonIndex = value.indexOf(':');
1648 webRequest.setSocksProxy(false);
1649 webRequest.setProxyHost(value.substring(0, colonIndex));
1650 webRequest.setProxyPort(Integer.parseInt(value.substring(colonIndex + 1)));
1651 }
1652 else if (value.startsWith("SOCKS")) {
1653 value = value.substring(6);
1654 final int colonIndex = value.indexOf(':');
1655 webRequest.setSocksProxy(true);
1656 webRequest.setProxyHost(value.substring(0, colonIndex));
1657 webRequest.setProxyPort(Integer.parseInt(value.substring(colonIndex + 1)));
1658 }
1659 }
1660 }
1661
1662 else if (!proxyConfig.shouldBypassProxy(webRequest.getUrl().getHost())) {
1663 webRequest.setProxyHost(proxyConfig.getProxyHost());
1664 webRequest.setProxyPort(proxyConfig.getProxyPort());
1665 webRequest.setProxyScheme(proxyConfig.getProxyScheme());
1666 webRequest.setSocksProxy(proxyConfig.isSocksProxy());
1667 }
1668 }
1669
1670
1671 addDefaultHeaders(webRequest);
1672
1673
1674 final WebResponse fromCache = getCache().getCachedResponse(webRequest);
1675 final WebResponse webResponse = getWebResponseOrUseCached(webRequest, fromCache);
1676
1677
1678 final int status = webResponse.getStatusCode();
1679 if (status == HttpStatus.USE_PROXY_305) {
1680 getIncorrectnessListener().notify("Ignoring HTTP status code [305] 'Use Proxy'", this);
1681 }
1682 else if (status >= HttpStatus.MOVED_PERMANENTLY_301
1683 && status <= HttpStatus.PERMANENT_REDIRECT_308
1684 && status != HttpStatus.NOT_MODIFIED_304
1685 && getOptions().isRedirectEnabled()
1686 && !webRequest.hasHint(WebRequest.HttpHint.DoNotFollowRedirects)) {
1687
1688 final URL newUrl;
1689 String locationString = null;
1690 try {
1691 locationString = webResponse.getResponseHeaderValue("Location");
1692 if (locationString == null) {
1693 return webResponse;
1694 }
1695 locationString = new String(locationString.getBytes(ISO_8859_1), UTF_8);
1696 newUrl = expandUrl(url, locationString);
1697 }
1698 catch (final MalformedURLException e) {
1699 getIncorrectnessListener().notify("Got a redirect status code [" + status + " "
1700 + webResponse.getStatusMessage()
1701 + "] but the location is not a valid URL [" + locationString
1702 + "]. Skipping redirection processing.", this);
1703 return webResponse;
1704 }
1705
1706 if (LOG.isDebugEnabled()) {
1707 LOG.debug("Got a redirect status code [" + status + "] new location = [" + locationString + "]");
1708 }
1709
1710 if (allowedRedirects == 0) {
1711 throw new FailingHttpStatusCodeException("Too many redirects for "
1712 + webResponse.getWebRequest().getUrl(), webResponse);
1713 }
1714
1715 final boolean crossOriginRedirect = !UrlUtils.isSameOrigin(url, newUrl);
1716
1717 if (status == HttpStatus.MOVED_PERMANENTLY_301
1718 || status == HttpStatus.FOUND_302
1719 || status == HttpStatus.SEE_OTHER_303) {
1720 final WebRequest wrs = new WebRequest(newUrl, HttpMethod.GET);
1721 wrs.setCharset(webRequest.getCharset());
1722
1723 if (HttpMethod.HEAD == webRequest.getHttpMethod()) {
1724 wrs.setHttpMethod(HttpMethod.HEAD);
1725 }
1726 for (final Map.Entry<String, String> entry : webRequest.getAdditionalHeaders().entrySet()) {
1727 wrs.setAdditionalHeader(entry.getKey(), entry.getValue());
1728 }
1729 if (crossOriginRedirect) {
1730
1731 wrs.removeAdditionalHeader("Authorization");
1732 }
1733 wrs.setFetchDestination(webRequest.getFetchDestination());
1734 wrs.setFetchModeOverride(webRequest.getFetchModeOverride());
1735 wrs.setRequestingUrl(webRequest.getRequestingUrl());
1736
1737 return loadWebResponseFromWebConnection(wrs, allowedRedirects - 1);
1738 }
1739 else if (status == HttpStatus.TEMPORARY_REDIRECT_307
1740 || status == HttpStatus.PERMANENT_REDIRECT_308) {
1741
1742
1743
1744 final WebRequest wrs = new WebRequest(newUrl, webRequest.getHttpMethod());
1745 wrs.setCharset(webRequest.getCharset());
1746 if (webRequest.getRequestBody() != null) {
1747 if (HttpMethod.POST == webRequest.getHttpMethod()
1748 || HttpMethod.PUT == webRequest.getHttpMethod()
1749 || HttpMethod.PATCH == webRequest.getHttpMethod()) {
1750 wrs.setRequestBody(webRequest.getRequestBody());
1751 wrs.setEncodingType(webRequest.getEncodingType());
1752 }
1753 }
1754 else {
1755 wrs.setRequestParameters(parameters);
1756 }
1757
1758 for (final Map.Entry<String, String> entry : webRequest.getAdditionalHeaders().entrySet()) {
1759 wrs.setAdditionalHeader(entry.getKey(), entry.getValue());
1760 }
1761 if (crossOriginRedirect) {
1762
1763 wrs.removeAdditionalHeader("Authorization");
1764 }
1765
1766 return loadWebResponseFromWebConnection(wrs, allowedRedirects - 1);
1767 }
1768 }
1769
1770 if (fromCache == null) {
1771 getCache().cacheIfPossible(webRequest, webResponse, null);
1772 }
1773 return webResponse;
1774 }
1775
1776
1777
1778
1779
1780
1781
1782 private WebResponse getWebResponseOrUseCached(
1783 final WebRequest webRequest, final WebResponse cached) throws IOException {
1784 if (cached == null) {
1785 return getWebConnection().getResponse(webRequest);
1786 }
1787
1788 if (!HeaderUtils.containsNoCache(cached)) {
1789 return new WebResponseFromCache(cached, webRequest);
1790 }
1791
1792
1793 if (HeaderUtils.containsETag(cached)) {
1794 webRequest.setAdditionalHeader(HttpHeader.IF_NONE_MATCH, cached.getResponseHeaderValue(HttpHeader.ETAG));
1795 }
1796 if (HeaderUtils.containsLastModified(cached)) {
1797 webRequest.setAdditionalHeader(HttpHeader.IF_MODIFIED_SINCE,
1798 cached.getResponseHeaderValue(HttpHeader.LAST_MODIFIED));
1799 }
1800
1801 final WebResponse webResponse = getWebConnection().getResponse(webRequest);
1802
1803 if (webResponse.getStatusCode() >= HttpStatus.INTERNAL_SERVER_ERROR_500) {
1804 return new WebResponseFromCache(cached, webRequest);
1805 }
1806
1807 if (webResponse.getStatusCode() == HttpStatus.NOT_MODIFIED_304) {
1808 final Map<String, NameValuePair> header2NameValuePair = new LinkedHashMap<>();
1809 for (final NameValuePair pair : cached.getResponseHeaders()) {
1810 header2NameValuePair.put(pair.getName(), pair);
1811 }
1812 for (final NameValuePair pair : webResponse.getResponseHeaders()) {
1813 if (preferHeaderFrom304Response(pair.getName())) {
1814 header2NameValuePair.put(pair.getName(), pair);
1815 }
1816 }
1817
1818
1819
1820 final WebResponse updatedCached =
1821 new WebResponseFromCache(cached, new ArrayList<>(header2NameValuePair.values()), webRequest);
1822 getCache().cacheIfPossible(webRequest, updatedCached, null);
1823 return updatedCached;
1824 }
1825
1826 getCache().cacheIfPossible(webRequest, webResponse, null);
1827 return webResponse;
1828 }
1829
1830
1831
1832
1833
1834 private static boolean preferHeaderFrom304Response(final String name) {
1835 final String lcName = name.toLowerCase(Locale.ROOT);
1836 for (final String header : DISCARDING_304_RESPONSE_HEADER_NAMES) {
1837 if (lcName.equals(header)) {
1838 return false;
1839 }
1840 }
1841 for (final String prefix : DISCARDING_304_HEADER_PREFIXES) {
1842 if (lcName.startsWith(prefix)) {
1843 return false;
1844 }
1845 }
1846 return true;
1847 }
1848
1849
1850
1851
1852
1853 private void addDefaultHeaders(final WebRequest wrs) {
1854
1855 requestHeaders_.forEach((name, value) -> {
1856 if (!wrs.isAdditionalHeader(name)) {
1857 wrs.setAdditionalHeader(name, value);
1858 }
1859 });
1860
1861
1862 if (!wrs.isAdditionalHeader(HttpHeader.ACCEPT_LANGUAGE)) {
1863 wrs.setAdditionalHeader(HttpHeader.ACCEPT_LANGUAGE, getBrowserVersion().getAcceptLanguageHeader());
1864 }
1865
1866
1867
1868 }
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881 public List<WebWindow> getWebWindows() {
1882 return List.copyOf(windows_);
1883 }
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895 public boolean containsWebWindow(final WebWindow webWindow) {
1896 return windows_.contains(webWindow);
1897 }
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910 public List<TopLevelWindow> getTopLevelWindows() {
1911 return List.copyOf(topLevelWindows_);
1912 }
1913
1914
1915
1916
1917
1918
1919 public void setRefreshHandler(final RefreshHandler handler) {
1920 if (handler == null) {
1921 refreshHandler_ = new NiceRefreshHandler(2);
1922 }
1923 else {
1924 refreshHandler_ = handler;
1925 }
1926 }
1927
1928
1929
1930
1931
1932
1933 public RefreshHandler getRefreshHandler() {
1934 return refreshHandler_;
1935 }
1936
1937
1938
1939
1940
1941 public void setScriptPreProcessor(final ScriptPreProcessor scriptPreProcessor) {
1942 scriptPreProcessor_ = scriptPreProcessor;
1943 }
1944
1945
1946
1947
1948
1949 public ScriptPreProcessor getScriptPreProcessor() {
1950 return scriptPreProcessor_;
1951 }
1952
1953
1954
1955
1956
1957 public void setHTMLParserListener(final HTMLParserListener listener) {
1958 htmlParserListener_ = listener;
1959 }
1960
1961
1962
1963
1964
1965 public HTMLParserListener getHTMLParserListener() {
1966 return htmlParserListener_;
1967 }
1968
1969
1970
1971
1972
1973
1974
1975 public CSSErrorHandler getCssErrorHandler() {
1976 return cssErrorHandler_;
1977 }
1978
1979
1980
1981
1982
1983
1984
1985 public void setCssErrorHandler(final CSSErrorHandler cssErrorHandler) {
1986 WebAssert.notNull("cssErrorHandler", cssErrorHandler);
1987 cssErrorHandler_ = cssErrorHandler;
1988 }
1989
1990
1991
1992
1993
1994
1995
1996 public void setJavaScriptTimeout(final long timeout) {
1997 scriptEngine_.setJavaScriptTimeout(timeout);
1998 }
1999
2000
2001
2002
2003
2004
2005
2006 public long getJavaScriptTimeout() {
2007 return scriptEngine_.getJavaScriptTimeout();
2008 }
2009
2010
2011
2012
2013
2014
2015
2016 public IncorrectnessListener getIncorrectnessListener() {
2017 return incorrectnessListener_;
2018 }
2019
2020
2021
2022
2023
2024 public void setIncorrectnessListener(final IncorrectnessListener listener) {
2025 if (listener == null) {
2026 throw new IllegalArgumentException("Null is not a valid IncorrectnessListener");
2027 }
2028 incorrectnessListener_ = listener;
2029 }
2030
2031
2032
2033
2034
2035 public WebConsole getWebConsole() {
2036 if (webConsole_ == null) {
2037 webConsole_ = new WebConsole();
2038 }
2039 return webConsole_;
2040 }
2041
2042
2043
2044
2045
2046 public AjaxController getAjaxController() {
2047 return ajaxController_;
2048 }
2049
2050
2051
2052
2053
2054 public void setAjaxController(final AjaxController newValue) {
2055 if (newValue == null) {
2056 throw new IllegalArgumentException("Null is not a valid AjaxController");
2057 }
2058 ajaxController_ = newValue;
2059 }
2060
2061
2062
2063
2064
2065 public void setAttachmentHandler(final AttachmentHandler handler) {
2066 attachmentHandler_ = handler;
2067 }
2068
2069
2070
2071
2072
2073 public AttachmentHandler getAttachmentHandler() {
2074 return attachmentHandler_;
2075 }
2076
2077
2078
2079
2080
2081 public void setWebStartHandler(final WebStartHandler handler) {
2082 webStartHandler_ = handler;
2083 }
2084
2085
2086
2087
2088
2089 public WebStartHandler getWebStartHandler() {
2090 return webStartHandler_;
2091 }
2092
2093
2094
2095
2096
2097 public ClipboardHandler getClipboardHandler() {
2098 return clipboardHandler_;
2099 }
2100
2101
2102
2103
2104
2105 public void setClipboardHandler(final ClipboardHandler handler) {
2106 clipboardHandler_ = handler;
2107 }
2108
2109
2110
2111
2112
2113
2114 public PrintHandler getPrintHandler() {
2115 return printHandler_;
2116 }
2117
2118
2119
2120
2121
2122
2123
2124
2125 public void setPrintHandler(final PrintHandler handler) {
2126 printHandler_ = handler;
2127 }
2128
2129
2130
2131
2132
2133 public FrameContentHandler getFrameContentHandler() {
2134 return frameContentHandler_;
2135 }
2136
2137
2138
2139
2140
2141 public void setFrameContentHandler(final FrameContentHandler handler) {
2142 frameContentHandler_ = handler;
2143 }
2144
2145
2146
2147
2148
2149 public void setOnbeforeunloadHandler(final OnbeforeunloadHandler onbeforeunloadHandler) {
2150 onbeforeunloadHandler_ = onbeforeunloadHandler;
2151 }
2152
2153
2154
2155
2156
2157 public OnbeforeunloadHandler getOnbeforeunloadHandler() {
2158 return onbeforeunloadHandler_;
2159 }
2160
2161
2162
2163
2164
2165 public Cache getCache() {
2166 return cache_;
2167 }
2168
2169
2170
2171
2172
2173 public void setCache(final Cache cache) {
2174 if (cache == null) {
2175 throw new IllegalArgumentException("cache should not be null!");
2176 }
2177 cache_ = cache;
2178 }
2179
2180
2181
2182
2183 private static final class CurrentWindowTracker implements WebWindowListener, Serializable {
2184 private final WebClient webClient_;
2185 private final boolean ensureOneTopLevelWindow_;
2186
2187 CurrentWindowTracker(final WebClient webClient, final boolean ensureOneTopLevelWindow) {
2188 webClient_ = webClient;
2189 ensureOneTopLevelWindow_ = ensureOneTopLevelWindow;
2190 }
2191
2192
2193
2194
2195 @Override
2196 public void webWindowClosed(final WebWindowEvent event) {
2197 final WebWindow window = event.getWebWindow();
2198 if (window instanceof TopLevelWindow) {
2199 webClient_.topLevelWindows_.remove(window);
2200 if (window == webClient_.getCurrentWindow()) {
2201 if (!webClient_.topLevelWindows_.isEmpty()) {
2202
2203 webClient_.setCurrentWindow(
2204 webClient_.topLevelWindows_.get(webClient_.topLevelWindows_.size() - 1));
2205 }
2206 }
2207 }
2208 else if (window == webClient_.getCurrentWindow()) {
2209
2210 if (webClient_.topLevelWindows_.isEmpty()) {
2211 webClient_.setCurrentWindow(null);
2212 }
2213 else {
2214 webClient_.setCurrentWindow(
2215 webClient_.topLevelWindows_.get(webClient_.topLevelWindows_.size() - 1));
2216 }
2217 }
2218 }
2219
2220
2221
2222
2223 public void afterWebWindowClosedListenersProcessed(final WebWindowEvent event) {
2224 if (!ensureOneTopLevelWindow_) {
2225 return;
2226 }
2227
2228 if (webClient_.topLevelWindows_.isEmpty()) {
2229
2230 final TopLevelWindow newWindow = new TopLevelWindow("", webClient_);
2231 webClient_.setCurrentWindow(newWindow);
2232 }
2233 }
2234
2235
2236
2237
2238 @Override
2239 public void webWindowContentChanged(final WebWindowEvent event) {
2240 final WebWindow window = event.getWebWindow();
2241 boolean use = false;
2242 if (window instanceof DialogWindow) {
2243 use = true;
2244 }
2245 else if (window instanceof TopLevelWindow) {
2246 use = event.getOldPage() == null;
2247 }
2248 else if (window instanceof FrameWindow fw) {
2249 final String enclosingPageState = fw.getEnclosingPage().getDocumentElement().getReadyState();
2250 final URL frameUrl = fw.getEnclosedPage().getUrl();
2251 if (!DomNode.READY_STATE_COMPLETE.equals(enclosingPageState) || frameUrl == UrlUtils.URL_ABOUT_BLANK) {
2252 return;
2253 }
2254
2255
2256 final BaseFrameElement frameElement = fw.getFrameElement();
2257 if (webClient_.isJavaScriptEnabled() && frameElement.isDisplayed()) {
2258 final ComputedCssStyleDeclaration style = fw.getComputedStyle(frameElement, null);
2259 use = style.getCalculatedWidth(false, false) != 0
2260 && style.getCalculatedHeight(false, false) != 0;
2261 }
2262 }
2263 if (use) {
2264 webClient_.setCurrentWindow(window);
2265 }
2266 }
2267
2268
2269
2270
2271 @Override
2272 public void webWindowOpened(final WebWindowEvent event) {
2273 final WebWindow window = event.getWebWindow();
2274 if (window instanceof TopLevelWindow tlw) {
2275 webClient_.topLevelWindows_.add(tlw);
2276 }
2277
2278 }
2279 }
2280
2281
2282
2283
2284
2285
2286 @Override
2287 public void close() {
2288
2289 if (scriptEngine_ != null) {
2290 scriptEngine_.prepareShutdown();
2291 }
2292
2293
2294 currentWindowTracker_ = new CurrentWindowTracker(this, false);
2295
2296
2297
2298 List<WebWindow> windows = new ArrayList<>(windows_);
2299 for (final WebWindow window : windows) {
2300 if (window instanceof TopLevelWindow topLevelWindow) {
2301
2302 try {
2303 topLevelWindow.close(true);
2304 }
2305 catch (final Exception e) {
2306 LOG.error("Exception while closing a TopLevelWindow", e);
2307 }
2308 }
2309 else if (window instanceof DialogWindow dialogWindow) {
2310
2311 try {
2312 dialogWindow.close();
2313 }
2314 catch (final Exception e) {
2315 LOG.error("Exception while closing a DialogWindow", e);
2316 }
2317 }
2318 }
2319
2320
2321
2322 windows = new ArrayList<>(windows_);
2323 for (final WebWindow window : windows) {
2324 if (window instanceof TopLevelWindow topLevelWindow) {
2325
2326 try {
2327 topLevelWindow.close(true);
2328 }
2329 catch (final Exception e) {
2330 LOG.error("Exception while closing a TopLevelWindow", e);
2331 }
2332 }
2333 else if (window instanceof DialogWindow dialogWindow) {
2334
2335 try {
2336 dialogWindow.close();
2337 }
2338 catch (final Exception e) {
2339 LOG.error("Exception while closing a DialogWindow", e);
2340 }
2341 }
2342 }
2343
2344
2345 if (!topLevelWindows_.isEmpty()) {
2346 LOG.error("Sill " + topLevelWindows_.size() + " top level windows are open. Please report this error!");
2347 topLevelWindows_.clear();
2348 }
2349
2350 if (!windows_.isEmpty()) {
2351 LOG.error("Sill " + windows_.size() + " windows are open. Please report this error!");
2352 windows_.clear();
2353 }
2354 currentWindow_ = null;
2355
2356 ThreadDeath toThrow = null;
2357 if (scriptEngine_ != null) {
2358 try {
2359 scriptEngine_.shutdown();
2360 }
2361 catch (final ThreadDeath ex) {
2362
2363 toThrow = ex;
2364 }
2365 catch (final Exception e) {
2366 LOG.error("Exception while shutdown the scriptEngine", e);
2367 }
2368 }
2369 scriptEngine_ = null;
2370
2371 if (webConnection_ != null) {
2372 try {
2373 webConnection_.close();
2374 }
2375 catch (final Exception e) {
2376 LOG.error("Exception while closing the connection", e);
2377 }
2378 }
2379 webConnection_ = null;
2380
2381 synchronized (this) {
2382 if (executor_ != null) {
2383 try {
2384 executor_.shutdownNow();
2385 }
2386 catch (final Exception e) {
2387 LOG.error("Exception while shutdown the executor service", e);
2388 }
2389 }
2390 }
2391 executor_ = null;
2392
2393 cache_.clear();
2394 if (toThrow != null) {
2395 throw toThrow;
2396 }
2397 }
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407 public void reset() {
2408 close();
2409
2410
2411 webConnection_ = new HttpWebConnection(this);
2412 if (javaScriptEngineEnabled_) {
2413 scriptEngine_ = new JavaScriptEngine(this);
2414 }
2415
2416
2417 currentWindowTracker_ = new CurrentWindowTracker(this, true);
2418 currentWindow_ = new TopLevelWindow("", this);
2419 }
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463 public int waitForBackgroundJavaScript(final long timeoutMillis) {
2464 int count = 0;
2465 final long endTime = System.currentTimeMillis() + timeoutMillis;
2466 for (Iterator<WeakReference<JavaScriptJobManager>> i = jobManagers_.iterator(); i.hasNext();) {
2467 final JavaScriptJobManager jobManager;
2468 final WeakReference<JavaScriptJobManager> reference;
2469 try {
2470 reference = i.next();
2471 jobManager = reference.get();
2472 if (jobManager == null) {
2473 i.remove();
2474 continue;
2475 }
2476 }
2477 catch (final ConcurrentModificationException e) {
2478 i = jobManagers_.iterator();
2479 count = 0;
2480 continue;
2481 }
2482
2483 final long newTimeout = endTime - System.currentTimeMillis();
2484 count += jobManager.waitForJobs(newTimeout);
2485 }
2486 if (count != getAggregateJobCount()) {
2487 final long newTimeout = endTime - System.currentTimeMillis();
2488 return waitForBackgroundJavaScript(newTimeout);
2489 }
2490 return count;
2491 }
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546 public int waitForBackgroundJavaScriptStartingBefore(final long delayMillis) {
2547 return waitForBackgroundJavaScriptStartingBefore(delayMillis, -1);
2548 }
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612 public int waitForBackgroundJavaScriptStartingBefore(final long delayMillis, final long timeoutMillis) {
2613 int count = 0;
2614 long now = System.currentTimeMillis();
2615 final long endTime = now + delayMillis;
2616 long endTimeout = now + timeoutMillis;
2617 if (timeoutMillis < 0 || timeoutMillis < delayMillis) {
2618 endTimeout = -1;
2619 }
2620
2621 for (Iterator<WeakReference<JavaScriptJobManager>> i = jobManagers_.iterator(); i.hasNext();) {
2622 final JavaScriptJobManager jobManager;
2623 final WeakReference<JavaScriptJobManager> reference;
2624 try {
2625 reference = i.next();
2626 jobManager = reference.get();
2627 if (jobManager == null) {
2628 i.remove();
2629 continue;
2630 }
2631 }
2632 catch (final ConcurrentModificationException e) {
2633 i = jobManagers_.iterator();
2634 count = 0;
2635 continue;
2636 }
2637 now = System.currentTimeMillis();
2638 final long newDelay = endTime - now;
2639 final long newTimeout = (endTimeout == -1) ? -1 : endTimeout - now;
2640 count += jobManager.waitForJobsStartingBefore(newDelay, newTimeout);
2641 }
2642 if (count != getAggregateJobCount()) {
2643 now = System.currentTimeMillis();
2644 final long newDelay = endTime - now;
2645 final long newTimeout = (endTimeout == -1) ? -1 : endTimeout - now;
2646 return waitForBackgroundJavaScriptStartingBefore(newDelay, newTimeout);
2647 }
2648 return count;
2649 }
2650
2651
2652
2653
2654
2655 private int getAggregateJobCount() {
2656 int count = 0;
2657 for (Iterator<WeakReference<JavaScriptJobManager>> i = jobManagers_.iterator(); i.hasNext();) {
2658 final JavaScriptJobManager jobManager;
2659 final WeakReference<JavaScriptJobManager> reference;
2660 try {
2661 reference = i.next();
2662 jobManager = reference.get();
2663 if (jobManager == null) {
2664 i.remove();
2665 continue;
2666 }
2667 }
2668 catch (final ConcurrentModificationException e) {
2669 i = jobManagers_.iterator();
2670 count = 0;
2671 continue;
2672 }
2673 final int jobCount = jobManager.getJobCount();
2674 count += jobCount;
2675 }
2676 return count;
2677 }
2678
2679
2680
2681
2682
2683
2684
2685 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
2686 in.defaultReadObject();
2687
2688 webConnection_ = new HttpWebConnection(this);
2689 scriptEngine_ = new JavaScriptEngine(this);
2690 jobManagers_ = Collections.synchronizedList(new ArrayList<>());
2691 loadQueue_ = new ArrayList<>();
2692 css3ParserPool_ = new CSS3ParserPool();
2693 broadcastChannel_ = new HashSet<>();
2694 blobUrlStore_ = new BlobUrlStore();
2695 }
2696
2697 private static class LoadJob {
2698 private final WebWindow requestingWindow_;
2699 private final String target_;
2700 private final WebResponse response_;
2701 private final WeakReference<Page> originalPage_;
2702 private final WebRequest request_;
2703 private final String forceAttachmentWithFilename_;
2704
2705
2706
2707 LoadJob(final WebRequest request, final WebResponse response,
2708 final WebWindow requestingWindow, final String target, final String forceAttachmentWithFilename) {
2709 request_ = request;
2710 requestingWindow_ = requestingWindow;
2711 target_ = target;
2712 response_ = response;
2713 originalPage_ = new WeakReference<>(requestingWindow.getEnclosedPage());
2714 forceAttachmentWithFilename_ = forceAttachmentWithFilename;
2715 }
2716
2717 public boolean isOutdated() {
2718 if (target_ != null && !target_.isEmpty()) {
2719 return false;
2720 }
2721
2722 if (requestingWindow_.isClosed()) {
2723 return true;
2724 }
2725
2726 return requestingWindow_.getEnclosedPage() != originalPage_.get();
2727 }
2728 }
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744 public void download(final WebWindow requestingWindow, final String target,
2745 final WebRequest request, final boolean checkHash,
2746 final String forceAttachmentWithFilename, final String description) {
2747
2748 final WebWindow targetWindow = resolveWindow(requestingWindow, target);
2749 final URL url = request.getUrl();
2750
2751 if (targetWindow != null && HttpMethod.POST != request.getHttpMethod()) {
2752 final Page page = targetWindow.getEnclosedPage();
2753 if (page != null) {
2754 if (page.isHtmlPage() && !((HtmlPage) page).isOnbeforeunloadAccepted()) {
2755 return;
2756 }
2757
2758 if (checkHash) {
2759 final URL current = page.getUrl();
2760 final boolean justHashJump =
2761 HttpMethod.GET == request.getHttpMethod()
2762 && UrlUtils.sameFile(url, current)
2763 && null != url.getRef();
2764
2765 if (justHashJump) {
2766 processOnlyHashChange(targetWindow, url);
2767 return;
2768 }
2769 }
2770 }
2771 }
2772
2773 synchronized (loadQueue_) {
2774
2775 for (final LoadJob otherLoadJob : loadQueue_) {
2776 if (otherLoadJob.response_ == null) {
2777 continue;
2778 }
2779 final WebRequest otherRequest = otherLoadJob.request_;
2780 final URL otherUrl = otherRequest.getUrl();
2781
2782 if (url.getPath().equals(otherUrl.getPath())
2783 && url.toString().equals(otherUrl.toString())
2784 && request.getRequestParameters().equals(otherRequest.getRequestParameters())
2785 && Objects.equals(request.getRequestBody(), otherRequest.getRequestBody())) {
2786 return;
2787 }
2788 }
2789 }
2790
2791 final LoadJob loadJob;
2792 try {
2793 WebResponse response;
2794 try {
2795 response = loadWebResponse(request);
2796 }
2797 catch (final NoHttpResponseException e) {
2798 LOG.error("NoHttpResponseException while downloading; generating a NoHttpResponse", e);
2799 response = new WebResponse(RESPONSE_DATA_NO_HTTP_RESPONSE, request, 0);
2800 }
2801 loadJob = new LoadJob(request, response, requestingWindow, target, forceAttachmentWithFilename);
2802 }
2803 catch (final IOException e) {
2804 throw new RuntimeException(e);
2805 }
2806
2807 synchronized (loadQueue_) {
2808 loadQueue_.add(loadJob);
2809 }
2810 }
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820 public void loadDownloadedResponses() throws FailingHttpStatusCodeException, IOException {
2821 final List<LoadJob> queue;
2822
2823
2824
2825 synchronized (loadQueue_) {
2826 if (loadQueue_.isEmpty()) {
2827 return;
2828 }
2829 queue = new ArrayList<>(loadQueue_);
2830 loadQueue_.clear();
2831 }
2832
2833 final HashSet<WebWindow> updatedWindows = new HashSet<>();
2834 for (int i = queue.size() - 1; i >= 0; --i) {
2835 final LoadJob loadJob = queue.get(i);
2836 if (loadJob.isOutdated()) {
2837 if (LOG.isInfoEnabled()) {
2838 LOG.info("No usage of download: " + loadJob);
2839 }
2840 continue;
2841 }
2842
2843 final WebWindow window = resolveWindow(loadJob.requestingWindow_, loadJob.target_);
2844 if (updatedWindows.contains(window)) {
2845 if (LOG.isInfoEnabled()) {
2846 LOG.info("No usage of download: " + loadJob);
2847 }
2848 continue;
2849 }
2850
2851 final WebWindow win = openTargetWindow(loadJob.requestingWindow_, loadJob.target_, TARGET_SELF);
2852 final Page pageBeforeLoad = win.getEnclosedPage();
2853 loadWebResponseInto(loadJob.response_, win, loadJob.forceAttachmentWithFilename_);
2854
2855
2856 if (scriptEngine_ != null) {
2857 scriptEngine_.registerWindowAndMaybeStartEventLoop(win);
2858 }
2859
2860 if (pageBeforeLoad != win.getEnclosedPage()) {
2861 updatedWindows.add(win);
2862 }
2863
2864
2865 throwFailingHttpStatusCodeExceptionIfNecessary(loadJob.response_);
2866 }
2867 }
2868
2869 private static void processOnlyHashChange(final WebWindow window, final URL urlWithOnlyHashChange) {
2870 final Page page = window.getEnclosedPage();
2871 final String oldURL = page.getUrl().toExternalForm();
2872
2873
2874 final WebRequest req = page.getWebResponse().getWebRequest();
2875 req.setUrl(urlWithOnlyHashChange);
2876
2877
2878 final Window jsWindow = window.getScriptableObject();
2879 if (null != jsWindow) {
2880 final Location location = jsWindow.getLocation();
2881 location.setHash(oldURL, urlWithOnlyHashChange.getRef());
2882 }
2883
2884
2885 window.getHistory().addPage(page);
2886 }
2887
2888
2889
2890
2891
2892 public WebClientOptions getOptions() {
2893 return options_;
2894 }
2895
2896
2897
2898
2899
2900
2901 public StorageHolder getStorageHolder() {
2902 return storageHolder_;
2903 }
2904
2905
2906
2907
2908
2909
2910
2911 public synchronized Set<Cookie> getCookies(final URL url) {
2912 final CookieManager cookieManager = getCookieManager();
2913
2914 if (!cookieManager.isCookiesEnabled()) {
2915 return Collections.emptySet();
2916 }
2917
2918 final URL normalizedUrl = HttpClientConverter.replaceForCookieIfNecessary(url);
2919
2920 final String host = normalizedUrl.getHost();
2921
2922
2923 if (host.isEmpty()) {
2924 return Collections.emptySet();
2925 }
2926
2927
2928 cookieManager.clearExpired(new Date());
2929
2930 final Set<Cookie> matchingCookies = new LinkedHashSet<>();
2931 HttpClientConverter.addMatching(cookieManager.getCookies(), normalizedUrl,
2932 getBrowserVersion(), matchingCookies);
2933 return Collections.unmodifiableSet(matchingCookies);
2934 }
2935
2936
2937
2938
2939
2940
2941
2942 public void addCookie(final String cookieString, final URL pageUrl, final Object origin) {
2943 final CookieManager cookieManager = getCookieManager();
2944 if (!cookieManager.isCookiesEnabled()) {
2945 if (LOG.isDebugEnabled()) {
2946 LOG.debug("Skipped adding cookie: '" + cookieString
2947 + "' because cookies are not enabled for the CookieManager.");
2948 }
2949 return;
2950 }
2951
2952 try {
2953 final List<Cookie> cookies = HttpClientConverter.parseCookie(cookieString, pageUrl, getBrowserVersion());
2954
2955
2956 for (final Cookie cookie : cookies) {
2957 cookieManager.addCookie(cookie);
2958
2959 if (LOG.isDebugEnabled()) {
2960 LOG.debug("Added cookie: '" + cookieString + "'");
2961 }
2962 }
2963 }
2964 catch (final MalformedCookieException e) {
2965 if (LOG.isDebugEnabled()) {
2966 LOG.warn("Adding cookie '" + cookieString + "' failed.", e);
2967 }
2968 getIncorrectnessListener().notify("Adding cookie '" + cookieString
2969 + "' failed; reason: '" + e.getMessage() + "'.", origin);
2970 }
2971 }
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981 public boolean isJavaScriptEnabled() {
2982 return javaScriptEngineEnabled_ && getOptions().isJavaScriptEnabled();
2983 }
2984
2985
2986
2987
2988
2989
2990
2991 public boolean isJavaScriptEngineEnabled() {
2992 return javaScriptEngineEnabled_;
2993 }
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003 public HtmlPage loadHtmlCodeIntoCurrentWindow(final String htmlCode) throws IOException {
3004 final HTMLParser htmlParser = getPageCreator().getHtmlParser();
3005 final WebWindow webWindow = getCurrentWindow();
3006
3007 final StringWebResponse webResponse =
3008 new StringWebResponse(htmlCode, new URL("https://www.htmlunit.org/dummy.html"));
3009 final HtmlPage page = new HtmlPage(webResponse, webWindow);
3010 webWindow.setEnclosedPage(page);
3011
3012 htmlParser.parse(this, webResponse, page, false, false);
3013 return page;
3014 }
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024 public XHtmlPage loadXHtmlCodeIntoCurrentWindow(final String xhtmlCode) throws IOException {
3025 final HTMLParser htmlParser = getPageCreator().getHtmlParser();
3026 final WebWindow webWindow = getCurrentWindow();
3027
3028 final StringWebResponse webResponse =
3029 new StringWebResponse(xhtmlCode, new URL("https://www.htmlunit.org/dummy.html"));
3030 final XHtmlPage page = new XHtmlPage(webResponse, webWindow);
3031 webWindow.setEnclosedPage(page);
3032
3033 htmlParser.parse(this, webResponse, page, true, false);
3034 return page;
3035 }
3036
3037
3038
3039
3040
3041
3042
3043 public WebSocketAdapter buildWebSocketAdapter(final WebSocketListener webSocketListener) {
3044 return webSocketAdapterFactory_.buildWebSocketAdapter(this, webSocketListener);
3045 }
3046
3047
3048
3049
3050
3051
3052 public void setWebSocketAdapter(final WebSocketAdapterFactory factory) {
3053 webSocketAdapterFactory_ = factory;
3054 }
3055
3056
3057
3058
3059
3060
3061
3062 public PooledCSS3Parser getCSS3Parser() {
3063 return this.css3ParserPool_.get();
3064 }
3065
3066
3067
3068
3069
3070
3071 public Set<BroadcastChannel> getBroadcastChannels() {
3072 return broadcastChannel_;
3073 }
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094 static class CSS3ParserPool {
3095
3096
3097
3098
3099 private final ConcurrentLinkedDeque<PooledCSS3Parser> parsers_ = new ConcurrentLinkedDeque<>();
3100
3101
3102
3103
3104
3105
3106
3107
3108 public PooledCSS3Parser get() {
3109
3110 final PooledCSS3Parser parser = parsers_.pollLast();
3111
3112
3113 return parser != null ? parser.markInUse(this) : new PooledCSS3Parser(this);
3114 }
3115
3116
3117
3118
3119
3120
3121
3122
3123 protected void recycle(final PooledCSS3Parser parser) {
3124 parsers_.addLast(parser);
3125 }
3126 }
3127
3128
3129
3130
3131
3132
3133
3134 public static class PooledCSS3Parser extends CSS3Parser implements AutoCloseable {
3135
3136
3137
3138
3139 private CSS3ParserPool pool_;
3140
3141
3142
3143
3144
3145
3146 protected PooledCSS3Parser(final CSS3ParserPool pool) {
3147 super();
3148 this.pool_ = pool;
3149 }
3150
3151
3152
3153
3154
3155
3156
3157 protected PooledCSS3Parser markInUse(final CSS3ParserPool pool) {
3158
3159 if (this.pool_ == null) {
3160 this.pool_ = pool;
3161 }
3162 else {
3163 throw new IllegalStateException("This PooledParser was not returned to the pool properly");
3164 }
3165
3166 return this;
3167 }
3168
3169
3170
3171
3172
3173
3174
3175
3176 @Override
3177 public void close() {
3178 if (this.pool_ != null) {
3179 final CSS3ParserPool oldPool = this.pool_;
3180
3181
3182 this.pool_ = null;
3183
3184
3185 oldPool.recycle(this);
3186 }
3187 else {
3188 throw new IllegalStateException("This PooledParser was returned already");
3189 }
3190 }
3191 }
3192 }