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