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