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