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