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 = StringUtils.substringBefore(urlString, "?");
1396 if (!"blank".equalsIgnoreCase(StringUtils.substringAfter(urlWithoutQuery, UrlUtils.ABOUT_SCHEME))) {
1397 throw new MalformedURLException(url + " is not supported, only about:blank is supported at the moment.");
1398 }
1399 return new StringWebResponse("", url);
1400 }
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410 private WebResponse makeWebResponseForFileUrl(final WebRequest webRequest) throws IOException {
1411 URL cleanUrl = webRequest.getUrl();
1412 if (cleanUrl.getQuery() != null) {
1413
1414 cleanUrl = UrlUtils.getUrlWithNewQuery(cleanUrl, null);
1415 }
1416 if (cleanUrl.getRef() != null) {
1417
1418 cleanUrl = UrlUtils.getUrlWithNewRef(cleanUrl, null);
1419 }
1420
1421 final WebResponse fromCache = getCache().getCachedResponse(webRequest);
1422 if (fromCache != null) {
1423 return new WebResponseFromCache(fromCache, webRequest);
1424 }
1425
1426 String fileUrl = cleanUrl.toExternalForm();
1427 fileUrl = URLDecoder.decode(fileUrl, UTF_8.name());
1428 final File file = new File(fileUrl.substring(5));
1429 if (!file.exists()) {
1430
1431 final List<NameValuePair> compiledHeaders = new ArrayList<>();
1432 compiledHeaders.add(new NameValuePair(HttpHeader.CONTENT_TYPE, MimeType.TEXT_HTML));
1433 final WebResponseData responseData =
1434 new WebResponseData(
1435 StringUtils
1436 .toByteArray("File: " + file.getAbsolutePath(), UTF_8),
1437 404, "Not Found", compiledHeaders);
1438 return new WebResponse(responseData, webRequest, 0);
1439 }
1440
1441 final String contentType = guessContentType(file);
1442
1443 final DownloadedContent content = new DownloadedContent.OnFile(file, false);
1444 final List<NameValuePair> compiledHeaders = new ArrayList<>();
1445 compiledHeaders.add(new NameValuePair(HttpHeader.CONTENT_TYPE, contentType));
1446 compiledHeaders.add(new NameValuePair(HttpHeader.LAST_MODIFIED,
1447 HttpUtils.formatDate(new Date(file.lastModified()))));
1448 final WebResponseData responseData = new WebResponseData(content, 200, "OK", compiledHeaders);
1449 final WebResponse webResponse = new WebResponse(responseData, webRequest, 0);
1450 getCache().cacheIfPossible(webRequest, webResponse, null);
1451 return webResponse;
1452 }
1453
1454 private WebResponse makeWebResponseForBlobUrl(final WebRequest webRequest) {
1455 final Window window = getCurrentWindow().getScriptableObject();
1456 final Blob fileOrBlob = window.getDocument().resolveBlobUrl(webRequest.getUrl().toString());
1457 if (fileOrBlob == null) {
1458 throw JavaScriptEngine.typeError("Cannot load data from " + webRequest.getUrl());
1459 }
1460
1461 final List<NameValuePair> headers = new ArrayList<>();
1462 final String type = fileOrBlob.getType();
1463 if (!StringUtils.isEmptyOrNull(type)) {
1464 headers.add(new NameValuePair(HttpHeader.CONTENT_TYPE, fileOrBlob.getType()));
1465 }
1466 if (fileOrBlob instanceof org.htmlunit.javascript.host.file.File) {
1467 final org.htmlunit.javascript.host.file.File file = (org.htmlunit.javascript.host.file.File) fileOrBlob;
1468 final String fileName = file.getName();
1469 if (!StringUtils.isEmptyOrNull(fileName)) {
1470
1471 headers.add(new NameValuePair(HttpHeader.CONTENT_DISPOSITION, "inline; filename=\"" + fileName + "\""));
1472 }
1473 }
1474
1475 final DownloadedContent content = new DownloadedContent.InMemory(fileOrBlob.getBytes());
1476 final WebResponseData responseData = new WebResponseData(content, 200, "OK", headers);
1477 return new WebResponse(responseData, webRequest, 0);
1478 }
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488 public String guessContentType(final File file) {
1489 final String fileName = file.getName();
1490 final String fileNameLC = fileName.toLowerCase(Locale.ROOT);
1491 if (fileNameLC.endsWith(".xhtml")) {
1492
1493 return MimeType.APPLICATION_XHTML;
1494 }
1495
1496
1497 if (fileNameLC.endsWith(".js")) {
1498 return MimeType.TEXT_JAVASCRIPT;
1499 }
1500
1501 if (fileNameLC.endsWith(".css")) {
1502 return MimeType.TEXT_CSS;
1503 }
1504
1505 String contentType = null;
1506 if (!fileNameLC.endsWith(".php")) {
1507 contentType = URLConnection.guessContentTypeFromName(fileName);
1508 }
1509 if (contentType == null) {
1510 try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
1511 contentType = URLConnection.guessContentTypeFromStream(inputStream);
1512 }
1513 catch (final IOException ignored) {
1514
1515 }
1516 }
1517 if (contentType == null) {
1518 contentType = MimeType.APPLICATION_OCTET_STREAM;
1519 }
1520 return contentType;
1521 }
1522
1523 private WebResponse makeWebResponseForJavaScriptUrl(final WebWindow webWindow, final URL url,
1524 final Charset charset) throws FailingHttpStatusCodeException, IOException {
1525
1526 HtmlPage page = null;
1527 if (webWindow instanceof FrameWindow) {
1528 final FrameWindow frameWindow = (FrameWindow) webWindow;
1529 page = (HtmlPage) frameWindow.getEnclosedPage();
1530 }
1531 else {
1532 final Page currentPage = webWindow.getEnclosedPage();
1533 if (currentPage instanceof HtmlPage) {
1534 page = (HtmlPage) currentPage;
1535 }
1536 }
1537
1538 if (page == null) {
1539 page = getPage(webWindow, WebRequest.newAboutBlankRequest());
1540 }
1541 final ScriptResult r = page.executeJavaScript(url.toExternalForm(), "JavaScript URL", 1);
1542 if (r.getJavaScriptResult() == null || ScriptResult.isUndefined(r)) {
1543
1544 return webWindow.getEnclosedPage().getWebResponse();
1545 }
1546
1547 final String contentString = r.getJavaScriptResult().toString();
1548 final StringWebResponse response = new StringWebResponse(contentString, charset, url);
1549 response.setFromJavascript(true);
1550 return response;
1551 }
1552
1553
1554
1555
1556
1557
1558
1559 public WebResponse loadWebResponse(final WebRequest webRequest) throws IOException {
1560 final String protocol = webRequest.getUrl().getProtocol();
1561 switch (protocol) {
1562 case UrlUtils.ABOUT:
1563 return makeWebResponseForAboutUrl(webRequest);
1564
1565 case "file":
1566 return makeWebResponseForFileUrl(webRequest);
1567
1568 case "data":
1569 return makeWebResponseForDataUrl(webRequest);
1570
1571 case "blob":
1572 return makeWebResponseForBlobUrl(webRequest);
1573
1574 case "http":
1575 case "https":
1576 return loadWebResponseFromWebConnection(webRequest, ALLOWED_REDIRECTIONS_SAME_URL);
1577
1578 default:
1579 throw new IOException("Unsupported protocol '" + protocol + "'");
1580 }
1581 }
1582
1583
1584
1585
1586
1587
1588
1589
1590 private WebResponse loadWebResponseFromWebConnection(final WebRequest webRequest,
1591 final int allowedRedirects) throws IOException {
1592
1593 URL url = webRequest.getUrl();
1594 final HttpMethod method = webRequest.getHttpMethod();
1595 final List<NameValuePair> parameters = webRequest.getRequestParameters();
1596
1597 WebAssert.notNull("url", url);
1598 WebAssert.notNull("method", method);
1599 WebAssert.notNull("parameters", parameters);
1600
1601 url = UrlUtils.encodeUrl(url, webRequest.getCharset());
1602 webRequest.setUrl(url);
1603
1604 if (LOG.isDebugEnabled()) {
1605 LOG.debug("Load response for " + method + " " + url.toExternalForm());
1606 }
1607
1608
1609 if (webRequest.getProxyHost() == null) {
1610 final ProxyConfig proxyConfig = getOptions().getProxyConfig();
1611 if (proxyConfig.getProxyAutoConfigUrl() != null) {
1612 if (!UrlUtils.sameFile(new URL(proxyConfig.getProxyAutoConfigUrl()), url)) {
1613 String content = proxyConfig.getProxyAutoConfigContent();
1614 if (content == null) {
1615 content = getPage(proxyConfig.getProxyAutoConfigUrl())
1616 .getWebResponse().getContentAsString();
1617 proxyConfig.setProxyAutoConfigContent(content);
1618 }
1619 final String allValue = JavaScriptEngine.evaluateProxyAutoConfig(getBrowserVersion(), content, url);
1620 if (LOG.isDebugEnabled()) {
1621 LOG.debug("Proxy Auto-Config: value '" + allValue + "' for URL " + url);
1622 }
1623 String value = allValue.split(";")[0].trim();
1624 if (value.startsWith("PROXY")) {
1625 value = value.substring(6);
1626 final int colonIndex = value.indexOf(':');
1627 webRequest.setSocksProxy(false);
1628 webRequest.setProxyHost(value.substring(0, colonIndex));
1629 webRequest.setProxyPort(Integer.parseInt(value.substring(colonIndex + 1)));
1630 }
1631 else if (value.startsWith("SOCKS")) {
1632 value = value.substring(6);
1633 final int colonIndex = value.indexOf(':');
1634 webRequest.setSocksProxy(true);
1635 webRequest.setProxyHost(value.substring(0, colonIndex));
1636 webRequest.setProxyPort(Integer.parseInt(value.substring(colonIndex + 1)));
1637 }
1638 }
1639 }
1640
1641 else if (!proxyConfig.shouldBypassProxy(webRequest.getUrl().getHost())) {
1642 webRequest.setProxyHost(proxyConfig.getProxyHost());
1643 webRequest.setProxyPort(proxyConfig.getProxyPort());
1644 webRequest.setProxyScheme(proxyConfig.getProxyScheme());
1645 webRequest.setSocksProxy(proxyConfig.isSocksProxy());
1646 }
1647 }
1648
1649
1650 addDefaultHeaders(webRequest);
1651
1652
1653 final WebResponse fromCache = getCache().getCachedResponse(webRequest);
1654 final WebResponse webResponse = getWebResponseOrUseCached(webRequest, fromCache);
1655
1656
1657 final int status = webResponse.getStatusCode();
1658 if (status == HttpStatus.USE_PROXY_305) {
1659 getIncorrectnessListener().notify("Ignoring HTTP status code [305] 'Use Proxy'", this);
1660 }
1661 else if (status >= HttpStatus.MOVED_PERMANENTLY_301
1662 && status <= HttpStatus.PERMANENT_REDIRECT_308
1663 && status != HttpStatus.NOT_MODIFIED_304
1664 && getOptions().isRedirectEnabled()) {
1665
1666 final URL newUrl;
1667 String locationString = null;
1668 try {
1669 locationString = webResponse.getResponseHeaderValue("Location");
1670 if (locationString == null) {
1671 return webResponse;
1672 }
1673 locationString = new String(locationString.getBytes(ISO_8859_1), UTF_8);
1674 newUrl = expandUrl(url, locationString);
1675 }
1676 catch (final MalformedURLException e) {
1677 getIncorrectnessListener().notify("Got a redirect status code [" + status + " "
1678 + webResponse.getStatusMessage()
1679 + "] but the location is not a valid URL [" + locationString
1680 + "]. Skipping redirection processing.", this);
1681 return webResponse;
1682 }
1683
1684 if (LOG.isDebugEnabled()) {
1685 LOG.debug("Got a redirect status code [" + status + "] new location = [" + locationString + "]");
1686 }
1687
1688 if (allowedRedirects == 0) {
1689 throw new FailingHttpStatusCodeException("Too many redirects for "
1690 + webResponse.getWebRequest().getUrl(), webResponse);
1691 }
1692
1693 if (status == HttpStatus.MOVED_PERMANENTLY_301
1694 || status == HttpStatus.FOUND_302
1695 || status == HttpStatus.SEE_OTHER_303) {
1696 final WebRequest wrs = new WebRequest(newUrl, HttpMethod.GET);
1697 wrs.setCharset(webRequest.getCharset());
1698
1699 if (HttpMethod.HEAD == webRequest.getHttpMethod()) {
1700 wrs.setHttpMethod(HttpMethod.HEAD);
1701 }
1702 for (final Map.Entry<String, String> entry : webRequest.getAdditionalHeaders().entrySet()) {
1703 wrs.setAdditionalHeader(entry.getKey(), entry.getValue());
1704 }
1705 return loadWebResponseFromWebConnection(wrs, allowedRedirects - 1);
1706 }
1707 else if (status == HttpStatus.TEMPORARY_REDIRECT_307
1708 || status == HttpStatus.PERMANENT_REDIRECT_308) {
1709
1710
1711
1712 final WebRequest wrs = new WebRequest(newUrl, webRequest.getHttpMethod());
1713 wrs.setCharset(webRequest.getCharset());
1714 if (webRequest.getRequestBody() != null) {
1715 if (HttpMethod.POST == webRequest.getHttpMethod()
1716 || HttpMethod.PUT == webRequest.getHttpMethod()
1717 || HttpMethod.PATCH == webRequest.getHttpMethod()) {
1718 wrs.setRequestBody(webRequest.getRequestBody());
1719 wrs.setEncodingType(webRequest.getEncodingType());
1720 }
1721 }
1722 else {
1723 wrs.setRequestParameters(parameters);
1724 }
1725
1726 for (final Map.Entry<String, String> entry : webRequest.getAdditionalHeaders().entrySet()) {
1727 wrs.setAdditionalHeader(entry.getKey(), entry.getValue());
1728 }
1729
1730 return loadWebResponseFromWebConnection(wrs, allowedRedirects - 1);
1731 }
1732 }
1733
1734 if (fromCache == null) {
1735 getCache().cacheIfPossible(webRequest, webResponse, null);
1736 }
1737 return webResponse;
1738 }
1739
1740
1741
1742
1743
1744
1745
1746 private WebResponse getWebResponseOrUseCached(
1747 final WebRequest webRequest, final WebResponse cached) throws IOException {
1748 if (cached == null) {
1749 return getWebConnection().getResponse(webRequest);
1750 }
1751
1752 if (!HeaderUtils.containsNoCache(cached)) {
1753 return new WebResponseFromCache(cached, webRequest);
1754 }
1755
1756
1757 if (HeaderUtils.containsETag(cached)) {
1758 webRequest.setAdditionalHeader(HttpHeader.IF_NONE_MATCH, cached.getResponseHeaderValue(HttpHeader.ETAG));
1759 }
1760 if (HeaderUtils.containsLastModified(cached)) {
1761 webRequest.setAdditionalHeader(HttpHeader.IF_MODIFIED_SINCE,
1762 cached.getResponseHeaderValue(HttpHeader.LAST_MODIFIED));
1763 }
1764
1765 final WebResponse webResponse = getWebConnection().getResponse(webRequest);
1766
1767 if (webResponse.getStatusCode() >= HttpStatus.INTERNAL_SERVER_ERROR_500) {
1768 return new WebResponseFromCache(cached, webRequest);
1769 }
1770
1771 if (webResponse.getStatusCode() == HttpStatus.NOT_MODIFIED_304) {
1772 final Map<String, NameValuePair> header2NameValuePair = new LinkedHashMap<>();
1773 for (final NameValuePair pair : cached.getResponseHeaders()) {
1774 header2NameValuePair.put(pair.getName(), pair);
1775 }
1776 for (final NameValuePair pair : webResponse.getResponseHeaders()) {
1777 if (preferHeaderFrom304Response(pair.getName())) {
1778 header2NameValuePair.put(pair.getName(), pair);
1779 }
1780 }
1781
1782
1783
1784 final WebResponse updatedCached =
1785 new WebResponseFromCache(cached, new ArrayList<>(header2NameValuePair.values()), webRequest);
1786 getCache().cacheIfPossible(webRequest, updatedCached, null);
1787 return updatedCached;
1788 }
1789
1790 getCache().cacheIfPossible(webRequest, webResponse, null);
1791 return webResponse;
1792 }
1793
1794
1795
1796
1797
1798 private static boolean preferHeaderFrom304Response(final String name) {
1799 final String lcName = name.toLowerCase(Locale.ROOT);
1800 for (final String header : DISCARDING_304_RESPONSE_HEADER_NAMES) {
1801 if (lcName.equals(header)) {
1802 return false;
1803 }
1804 }
1805 for (final String prefix : DISCARDING_304_HEADER_PREFIXES) {
1806 if (lcName.startsWith(prefix)) {
1807 return false;
1808 }
1809 }
1810 return true;
1811 }
1812
1813
1814
1815
1816
1817 private void addDefaultHeaders(final WebRequest wrs) {
1818
1819 requestHeaders_.forEach((name, value) -> {
1820 if (!wrs.isAdditionalHeader(name)) {
1821 wrs.setAdditionalHeader(name, value);
1822 }
1823 });
1824
1825
1826 if (!wrs.isAdditionalHeader(HttpHeader.ACCEPT_LANGUAGE)) {
1827 wrs.setAdditionalHeader(HttpHeader.ACCEPT_LANGUAGE, getBrowserVersion().getAcceptLanguageHeader());
1828 }
1829
1830 if (!wrs.isAdditionalHeader(HttpHeader.SEC_FETCH_DEST)) {
1831 wrs.setAdditionalHeader(HttpHeader.SEC_FETCH_DEST, "document");
1832 }
1833 if (!wrs.isAdditionalHeader(HttpHeader.SEC_FETCH_MODE)) {
1834 wrs.setAdditionalHeader(HttpHeader.SEC_FETCH_MODE, "navigate");
1835 }
1836 if (!wrs.isAdditionalHeader(HttpHeader.SEC_FETCH_SITE)) {
1837 wrs.setAdditionalHeader(HttpHeader.SEC_FETCH_SITE, "same-origin");
1838 }
1839 if (!wrs.isAdditionalHeader(HttpHeader.SEC_FETCH_USER)) {
1840 wrs.setAdditionalHeader(HttpHeader.SEC_FETCH_USER, "?1");
1841 }
1842 if (getBrowserVersion().hasFeature(HTTP_HEADER_PRIORITY)
1843 && !wrs.isAdditionalHeader(HttpHeader.PRIORITY)) {
1844 wrs.setAdditionalHeader(HttpHeader.PRIORITY, "u=0, i");
1845 }
1846
1847 if (getBrowserVersion().hasFeature(HTTP_HEADER_CH_UA)
1848 && !wrs.isAdditionalHeader(HttpHeader.SEC_CH_UA)) {
1849 wrs.setAdditionalHeader(HttpHeader.SEC_CH_UA, getBrowserVersion().getSecClientHintUserAgentHeader());
1850 }
1851 if (getBrowserVersion().hasFeature(HTTP_HEADER_CH_UA)
1852 && !wrs.isAdditionalHeader(HttpHeader.SEC_CH_UA_MOBILE)) {
1853 wrs.setAdditionalHeader(HttpHeader.SEC_CH_UA_MOBILE, "?0");
1854 }
1855 if (getBrowserVersion().hasFeature(HTTP_HEADER_CH_UA)
1856 && !wrs.isAdditionalHeader(HttpHeader.SEC_CH_UA_PLATFORM)) {
1857 wrs.setAdditionalHeader(HttpHeader.SEC_CH_UA_PLATFORM,
1858 getBrowserVersion().getSecClientHintUserAgentPlatformHeader());
1859 }
1860
1861 if (!wrs.isAdditionalHeader(HttpHeader.UPGRADE_INSECURE_REQUESTS)) {
1862 wrs.setAdditionalHeader(HttpHeader.UPGRADE_INSECURE_REQUESTS, "1");
1863 }
1864 }
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876 public List<WebWindow> getWebWindows() {
1877 return Collections.unmodifiableList(new ArrayList<>(windows_));
1878 }
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890 public boolean containsWebWindow(final WebWindow webWindow) {
1891 return windows_.contains(webWindow);
1892 }
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904 public List<TopLevelWindow> getTopLevelWindows() {
1905 return Collections.unmodifiableList(new ArrayList<>(topLevelWindows_));
1906 }
1907
1908
1909
1910
1911
1912
1913 public void setRefreshHandler(final RefreshHandler handler) {
1914 if (handler == null) {
1915 refreshHandler_ = new NiceRefreshHandler(2);
1916 }
1917 else {
1918 refreshHandler_ = handler;
1919 }
1920 }
1921
1922
1923
1924
1925
1926
1927 public RefreshHandler getRefreshHandler() {
1928 return refreshHandler_;
1929 }
1930
1931
1932
1933
1934
1935 public void setScriptPreProcessor(final ScriptPreProcessor scriptPreProcessor) {
1936 scriptPreProcessor_ = scriptPreProcessor;
1937 }
1938
1939
1940
1941
1942
1943 public ScriptPreProcessor getScriptPreProcessor() {
1944 return scriptPreProcessor_;
1945 }
1946
1947
1948
1949
1950
1951 public void setHTMLParserListener(final HTMLParserListener listener) {
1952 htmlParserListener_ = listener;
1953 }
1954
1955
1956
1957
1958
1959 public HTMLParserListener getHTMLParserListener() {
1960 return htmlParserListener_;
1961 }
1962
1963
1964
1965
1966
1967
1968
1969 public CSSErrorHandler getCssErrorHandler() {
1970 return cssErrorHandler_;
1971 }
1972
1973
1974
1975
1976
1977
1978
1979 public void setCssErrorHandler(final CSSErrorHandler cssErrorHandler) {
1980 WebAssert.notNull("cssErrorHandler", cssErrorHandler);
1981 cssErrorHandler_ = cssErrorHandler;
1982 }
1983
1984
1985
1986
1987
1988
1989
1990 public void setJavaScriptTimeout(final long timeout) {
1991 scriptEngine_.setJavaScriptTimeout(timeout);
1992 }
1993
1994
1995
1996
1997
1998
1999
2000 public long getJavaScriptTimeout() {
2001 return scriptEngine_.getJavaScriptTimeout();
2002 }
2003
2004
2005
2006
2007
2008
2009
2010 public IncorrectnessListener getIncorrectnessListener() {
2011 return incorrectnessListener_;
2012 }
2013
2014
2015
2016
2017
2018 public void setIncorrectnessListener(final IncorrectnessListener listener) {
2019 if (listener == null) {
2020 throw new IllegalArgumentException("Null is not a valid IncorrectnessListener");
2021 }
2022 incorrectnessListener_ = listener;
2023 }
2024
2025
2026
2027
2028
2029 public WebConsole getWebConsole() {
2030 if (webConsole_ == null) {
2031 webConsole_ = new WebConsole();
2032 }
2033 return webConsole_;
2034 }
2035
2036
2037
2038
2039
2040 public AjaxController getAjaxController() {
2041 return ajaxController_;
2042 }
2043
2044
2045
2046
2047
2048 public void setAjaxController(final AjaxController newValue) {
2049 if (newValue == null) {
2050 throw new IllegalArgumentException("Null is not a valid AjaxController");
2051 }
2052 ajaxController_ = newValue;
2053 }
2054
2055
2056
2057
2058
2059 public void setAttachmentHandler(final AttachmentHandler handler) {
2060 attachmentHandler_ = handler;
2061 }
2062
2063
2064
2065
2066
2067 public AttachmentHandler getAttachmentHandler() {
2068 return attachmentHandler_;
2069 }
2070
2071
2072
2073
2074
2075 public void setWebStartHandler(final WebStartHandler handler) {
2076 webStartHandler_ = handler;
2077 }
2078
2079
2080
2081
2082
2083 public WebStartHandler getWebStartHandler() {
2084 return webStartHandler_;
2085 }
2086
2087
2088
2089
2090
2091 public ClipboardHandler getClipboardHandler() {
2092 return clipboardHandler_;
2093 }
2094
2095
2096
2097
2098
2099 public void setClipboardHandler(final ClipboardHandler handler) {
2100 clipboardHandler_ = handler;
2101 }
2102
2103
2104
2105
2106
2107
2108 public PrintHandler getPrintHandler() {
2109 return printHandler_;
2110 }
2111
2112
2113
2114
2115
2116
2117
2118
2119 public void setPrintHandler(final PrintHandler handler) {
2120 printHandler_ = handler;
2121 }
2122
2123
2124
2125
2126
2127 public FrameContentHandler getFrameContentHandler() {
2128 return frameContentHandler_;
2129 }
2130
2131
2132
2133
2134
2135 public void setFrameContentHandler(final FrameContentHandler handler) {
2136 frameContentHandler_ = handler;
2137 }
2138
2139
2140
2141
2142
2143 public void setOnbeforeunloadHandler(final OnbeforeunloadHandler onbeforeunloadHandler) {
2144 onbeforeunloadHandler_ = onbeforeunloadHandler;
2145 }
2146
2147
2148
2149
2150
2151 public OnbeforeunloadHandler getOnbeforeunloadHandler() {
2152 return onbeforeunloadHandler_;
2153 }
2154
2155
2156
2157
2158
2159 public Cache getCache() {
2160 return cache_;
2161 }
2162
2163
2164
2165
2166
2167 public void setCache(final Cache cache) {
2168 if (cache == null) {
2169 throw new IllegalArgumentException("cache should not be null!");
2170 }
2171 cache_ = cache;
2172 }
2173
2174
2175
2176
2177 private static final class CurrentWindowTracker implements WebWindowListener, Serializable {
2178 private final WebClient webClient_;
2179 private final boolean ensureOneTopLevelWindow_;
2180
2181 CurrentWindowTracker(final WebClient webClient, final boolean ensureOneTopLevelWindow) {
2182 webClient_ = webClient;
2183 ensureOneTopLevelWindow_ = ensureOneTopLevelWindow;
2184 }
2185
2186
2187
2188
2189 @Override
2190 public void webWindowClosed(final WebWindowEvent event) {
2191 final WebWindow window = event.getWebWindow();
2192 if (window instanceof TopLevelWindow) {
2193 webClient_.topLevelWindows_.remove(window);
2194 if (window == webClient_.getCurrentWindow()) {
2195 if (!webClient_.topLevelWindows_.isEmpty()) {
2196
2197 webClient_.setCurrentWindow(
2198 webClient_.topLevelWindows_.get(webClient_.topLevelWindows_.size() - 1));
2199 }
2200 }
2201 }
2202 else if (window == webClient_.getCurrentWindow()) {
2203
2204 if (webClient_.topLevelWindows_.isEmpty()) {
2205 webClient_.setCurrentWindow(null);
2206 }
2207 else {
2208 webClient_.setCurrentWindow(
2209 webClient_.topLevelWindows_.get(webClient_.topLevelWindows_.size() - 1));
2210 }
2211 }
2212 }
2213
2214
2215
2216
2217 public void afterWebWindowClosedListenersProcessed(final WebWindowEvent event) {
2218 if (!ensureOneTopLevelWindow_) {
2219 return;
2220 }
2221
2222 if (webClient_.topLevelWindows_.isEmpty()) {
2223
2224 final TopLevelWindow newWindow = new TopLevelWindow("", webClient_);
2225 webClient_.setCurrentWindow(newWindow);
2226 }
2227 }
2228
2229
2230
2231
2232 @Override
2233 public void webWindowContentChanged(final WebWindowEvent event) {
2234 final WebWindow window = event.getWebWindow();
2235 boolean use = false;
2236 if (window instanceof DialogWindow) {
2237 use = true;
2238 }
2239 else if (window instanceof TopLevelWindow) {
2240 use = event.getOldPage() == null;
2241 }
2242 else if (window instanceof FrameWindow) {
2243 final FrameWindow fw = (FrameWindow) window;
2244 final String enclosingPageState = fw.getEnclosingPage().getDocumentElement().getReadyState();
2245 final URL frameUrl = fw.getEnclosedPage().getUrl();
2246 if (!DomNode.READY_STATE_COMPLETE.equals(enclosingPageState) || frameUrl == UrlUtils.URL_ABOUT_BLANK) {
2247 return;
2248 }
2249
2250
2251 final BaseFrameElement frameElement = fw.getFrameElement();
2252 if (webClient_.isJavaScriptEnabled() && frameElement.isDisplayed()) {
2253 final ComputedCssStyleDeclaration style = fw.getComputedStyle(frameElement, null);
2254 use = style.getCalculatedWidth(false, false) != 0
2255 && style.getCalculatedHeight(false, false) != 0;
2256 }
2257 }
2258 if (use) {
2259 webClient_.setCurrentWindow(window);
2260 }
2261 }
2262
2263
2264
2265
2266 @Override
2267 public void webWindowOpened(final WebWindowEvent event) {
2268 final WebWindow window = event.getWebWindow();
2269 if (window instanceof TopLevelWindow) {
2270 final TopLevelWindow tlw = (TopLevelWindow) window;
2271 webClient_.topLevelWindows_.add(tlw);
2272 }
2273
2274 }
2275 }
2276
2277
2278
2279
2280
2281
2282
2283
2284 @Override
2285 public void close() {
2286
2287 if (scriptEngine_ != null) {
2288 scriptEngine_.prepareShutdown();
2289 }
2290
2291
2292 currentWindowTracker_ = new CurrentWindowTracker(this, false);
2293
2294
2295
2296 List<WebWindow> windows = new ArrayList<>(windows_);
2297 for (final WebWindow window : windows) {
2298 if (window instanceof TopLevelWindow) {
2299 final TopLevelWindow topLevelWindow = (TopLevelWindow) window;
2300
2301 try {
2302 topLevelWindow.close(true);
2303 }
2304 catch (final Exception e) {
2305 LOG.error("Exception while closing a TopLevelWindow", e);
2306 }
2307 }
2308 else if (window instanceof DialogWindow) {
2309 final DialogWindow dialogWindow = (DialogWindow) window;
2310
2311 try {
2312 dialogWindow.close();
2313 }
2314 catch (final Exception e) {
2315 LOG.error("Exception while closing a DialogWindow", e);
2316 }
2317 }
2318 }
2319
2320
2321
2322 windows = new ArrayList<>(windows_);
2323 for (final WebWindow window : windows) {
2324 if (window instanceof TopLevelWindow) {
2325 final TopLevelWindow topLevelWindow = (TopLevelWindow) window;
2326
2327 try {
2328 topLevelWindow.close(true);
2329 }
2330 catch (final Exception e) {
2331 LOG.error("Exception while closing a TopLevelWindow", e);
2332 }
2333 }
2334 else if (window instanceof DialogWindow) {
2335 final DialogWindow dialogWindow = (DialogWindow) window;
2336
2337 try {
2338 dialogWindow.close();
2339 }
2340 catch (final Exception e) {
2341 LOG.error("Exception while closing a DialogWindow", e);
2342 }
2343 }
2344 }
2345
2346
2347 if (!topLevelWindows_.isEmpty()) {
2348 LOG.error("Sill " + topLevelWindows_.size() + " top level windows are open. Please report this error!");
2349 topLevelWindows_.clear();
2350 }
2351
2352 if (!windows_.isEmpty()) {
2353 LOG.error("Sill " + windows_.size() + " windows are open. Please report this error!");
2354 windows_.clear();
2355 }
2356 currentWindow_ = null;
2357
2358 ThreadDeath toThrow = null;
2359 if (scriptEngine_ != null) {
2360 try {
2361 scriptEngine_.shutdown();
2362 }
2363 catch (final ThreadDeath ex) {
2364
2365 toThrow = ex;
2366 }
2367 catch (final Exception e) {
2368 LOG.error("Exception while shutdown the scriptEngine", e);
2369 }
2370 }
2371 scriptEngine_ = null;
2372
2373 if (webConnection_ != null) {
2374 try {
2375 webConnection_.close();
2376 }
2377 catch (final Exception e) {
2378 LOG.error("Exception while closing the connection", e);
2379 }
2380 }
2381 webConnection_ = null;
2382
2383 synchronized (this) {
2384 if (executor_ != null) {
2385 try {
2386 executor_.shutdownNow();
2387 }
2388 catch (final Exception e) {
2389 LOG.error("Exception while shutdown the executor service", e);
2390 }
2391 }
2392 }
2393 executor_ = null;
2394
2395 cache_.clear();
2396 if (toThrow != null) {
2397 throw toThrow;
2398 }
2399 }
2400
2401
2402
2403
2404
2405
2406
2407
2408 public void reset() {
2409 close();
2410
2411
2412 webConnection_ = new HttpWebConnection(this);
2413 if (javaScriptEngineEnabled_) {
2414 scriptEngine_ = new JavaScriptEngine(this);
2415 }
2416
2417
2418 currentWindowTracker_ = new CurrentWindowTracker(this, true);
2419 currentWindow_ = new TopLevelWindow("", this);
2420 }
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464 public int waitForBackgroundJavaScript(final long timeoutMillis) {
2465 int count = 0;
2466 final long endTime = System.currentTimeMillis() + timeoutMillis;
2467 for (Iterator<WeakReference<JavaScriptJobManager>> i = jobManagers_.iterator(); i.hasNext();) {
2468 final JavaScriptJobManager jobManager;
2469 final WeakReference<JavaScriptJobManager> reference;
2470 try {
2471 reference = i.next();
2472 jobManager = reference.get();
2473 if (jobManager == null) {
2474 i.remove();
2475 continue;
2476 }
2477 }
2478 catch (final ConcurrentModificationException e) {
2479 i = jobManagers_.iterator();
2480 count = 0;
2481 continue;
2482 }
2483
2484 final long newTimeout = endTime - System.currentTimeMillis();
2485 count += jobManager.waitForJobs(newTimeout);
2486 }
2487 if (count != getAggregateJobCount()) {
2488 final long newTimeout = endTime - System.currentTimeMillis();
2489 return waitForBackgroundJavaScript(newTimeout);
2490 }
2491 return count;
2492 }
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547 public int waitForBackgroundJavaScriptStartingBefore(final long delayMillis) {
2548 return waitForBackgroundJavaScriptStartingBefore(delayMillis, -1);
2549 }
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613 public int waitForBackgroundJavaScriptStartingBefore(final long delayMillis, final long timeoutMillis) {
2614 int count = 0;
2615 long now = System.currentTimeMillis();
2616 final long endTime = now + delayMillis;
2617 long endTimeout = now + timeoutMillis;
2618 if (timeoutMillis < 0 || timeoutMillis < delayMillis) {
2619 endTimeout = -1;
2620 }
2621
2622 for (Iterator<WeakReference<JavaScriptJobManager>> i = jobManagers_.iterator(); i.hasNext();) {
2623 final JavaScriptJobManager jobManager;
2624 final WeakReference<JavaScriptJobManager> reference;
2625 try {
2626 reference = i.next();
2627 jobManager = reference.get();
2628 if (jobManager == null) {
2629 i.remove();
2630 continue;
2631 }
2632 }
2633 catch (final ConcurrentModificationException e) {
2634 i = jobManagers_.iterator();
2635 count = 0;
2636 continue;
2637 }
2638 now = System.currentTimeMillis();
2639 final long newDelay = endTime - now;
2640 final long newTimeout = (endTimeout == -1) ? -1 : endTimeout - now;
2641 count += jobManager.waitForJobsStartingBefore(newDelay, newTimeout);
2642 }
2643 if (count != getAggregateJobCount()) {
2644 now = System.currentTimeMillis();
2645 final long newDelay = endTime - now;
2646 final long newTimeout = (endTimeout == -1) ? -1 : endTimeout - now;
2647 return waitForBackgroundJavaScriptStartingBefore(newDelay, newTimeout);
2648 }
2649 return count;
2650 }
2651
2652
2653
2654
2655
2656 private int getAggregateJobCount() {
2657 int count = 0;
2658 for (Iterator<WeakReference<JavaScriptJobManager>> i = jobManagers_.iterator(); i.hasNext();) {
2659 final JavaScriptJobManager jobManager;
2660 final WeakReference<JavaScriptJobManager> reference;
2661 try {
2662 reference = i.next();
2663 jobManager = reference.get();
2664 if (jobManager == null) {
2665 i.remove();
2666 continue;
2667 }
2668 }
2669 catch (final ConcurrentModificationException e) {
2670 i = jobManagers_.iterator();
2671 count = 0;
2672 continue;
2673 }
2674 final int jobCount = jobManager.getJobCount();
2675 count += jobCount;
2676 }
2677 return count;
2678 }
2679
2680
2681
2682
2683
2684
2685
2686 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
2687 in.defaultReadObject();
2688
2689 webConnection_ = new HttpWebConnection(this);
2690 scriptEngine_ = new JavaScriptEngine(this);
2691 jobManagers_ = Collections.synchronizedList(new ArrayList<>());
2692 loadQueue_ = new ArrayList<>();
2693 css3ParserPool_ = new CSS3ParserPool();
2694 broadcastChannel_ = new HashSet<>();
2695 }
2696
2697 private static class LoadJob {
2698 private final WebWindow requestingWindow_;
2699 private final String target_;
2700 private final WebResponse response_;
2701 private final WeakReference<Page> originalPage_;
2702 private final WebRequest request_;
2703 private final String forceAttachmentWithFilename_;
2704
2705
2706
2707 LoadJob(final WebRequest request, final WebResponse response,
2708 final WebWindow requestingWindow, final String target, final String forceAttachmentWithFilename) {
2709 request_ = request;
2710 requestingWindow_ = requestingWindow;
2711 target_ = target;
2712 response_ = response;
2713 originalPage_ = new WeakReference<>(requestingWindow.getEnclosedPage());
2714 forceAttachmentWithFilename_ = forceAttachmentWithFilename;
2715 }
2716
2717 public boolean isOutdated() {
2718 if (target_ != null && !target_.isEmpty()) {
2719 return false;
2720 }
2721
2722 if (requestingWindow_.isClosed()) {
2723 return true;
2724 }
2725
2726 if (requestingWindow_.getEnclosedPage() != originalPage_.get()) {
2727 return true;
2728 }
2729
2730 return false;
2731 }
2732 }
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748 public void download(final WebWindow requestingWindow, final String target,
2749 final WebRequest request, final boolean checkHash,
2750 final String forceAttachmentWithFilename, final String description) {
2751
2752 final WebWindow targetWindow = resolveWindow(requestingWindow, target);
2753 final URL url = request.getUrl();
2754
2755 if (targetWindow != null && HttpMethod.POST != request.getHttpMethod()) {
2756 final Page page = targetWindow.getEnclosedPage();
2757 if (page != null) {
2758 if (page.isHtmlPage() && !((HtmlPage) page).isOnbeforeunloadAccepted()) {
2759 return;
2760 }
2761
2762 if (checkHash) {
2763 final URL current = page.getUrl();
2764 final boolean justHashJump =
2765 HttpMethod.GET == request.getHttpMethod()
2766 && UrlUtils.sameFile(url, current)
2767 && null != url.getRef();
2768
2769 if (justHashJump) {
2770 processOnlyHashChange(targetWindow, url);
2771 return;
2772 }
2773 }
2774 }
2775 }
2776
2777 synchronized (loadQueue_) {
2778
2779 for (final LoadJob otherLoadJob : loadQueue_) {
2780 if (otherLoadJob.response_ == null) {
2781 continue;
2782 }
2783 final WebRequest otherRequest = otherLoadJob.request_;
2784 final URL otherUrl = otherRequest.getUrl();
2785
2786 if (url.getPath().equals(otherUrl.getPath())
2787 && url.toString().equals(otherUrl.toString())
2788 && request.getRequestParameters().equals(otherRequest.getRequestParameters())
2789 && Objects.equals(request.getRequestBody(), otherRequest.getRequestBody())) {
2790 return;
2791 }
2792 }
2793 }
2794
2795 final LoadJob loadJob;
2796 try {
2797 WebResponse response;
2798 try {
2799 response = loadWebResponse(request);
2800 }
2801 catch (final NoHttpResponseException e) {
2802 LOG.error("NoHttpResponseException while downloading; generating a NoHttpResponse", e);
2803 response = new WebResponse(RESPONSE_DATA_NO_HTTP_RESPONSE, request, 0);
2804 }
2805 loadJob = new LoadJob(request, response, requestingWindow, target, forceAttachmentWithFilename);
2806 }
2807 catch (final IOException e) {
2808 throw new RuntimeException(e);
2809 }
2810
2811 synchronized (loadQueue_) {
2812 loadQueue_.add(loadJob);
2813 }
2814 }
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824 public void loadDownloadedResponses() throws FailingHttpStatusCodeException, IOException {
2825 final List<LoadJob> queue;
2826
2827
2828
2829 synchronized (loadQueue_) {
2830 if (loadQueue_.isEmpty()) {
2831 return;
2832 }
2833 queue = new ArrayList<>(loadQueue_);
2834 loadQueue_.clear();
2835 }
2836
2837 final HashSet<WebWindow> updatedWindows = new HashSet<>();
2838 for (int i = queue.size() - 1; i >= 0; --i) {
2839 final LoadJob loadJob = queue.get(i);
2840 if (loadJob.isOutdated()) {
2841 if (LOG.isInfoEnabled()) {
2842 LOG.info("No usage of download: " + loadJob);
2843 }
2844 continue;
2845 }
2846
2847 final WebWindow window = resolveWindow(loadJob.requestingWindow_, loadJob.target_);
2848 if (updatedWindows.contains(window)) {
2849 if (LOG.isInfoEnabled()) {
2850 LOG.info("No usage of download: " + loadJob);
2851 }
2852 continue;
2853 }
2854
2855 final WebWindow win = openTargetWindow(loadJob.requestingWindow_, loadJob.target_, TARGET_SELF);
2856 final Page pageBeforeLoad = win.getEnclosedPage();
2857 loadWebResponseInto(loadJob.response_, win, loadJob.forceAttachmentWithFilename_);
2858
2859
2860 if (scriptEngine_ != null) {
2861 scriptEngine_.registerWindowAndMaybeStartEventLoop(win);
2862 }
2863
2864 if (pageBeforeLoad != win.getEnclosedPage()) {
2865 updatedWindows.add(win);
2866 }
2867
2868
2869 throwFailingHttpStatusCodeExceptionIfNecessary(loadJob.response_);
2870 }
2871 }
2872
2873 private static void processOnlyHashChange(final WebWindow window, final URL urlWithOnlyHashChange) {
2874 final Page page = window.getEnclosedPage();
2875 final String oldURL = page.getUrl().toExternalForm();
2876
2877
2878 final WebRequest req = page.getWebResponse().getWebRequest();
2879 req.setUrl(urlWithOnlyHashChange);
2880
2881
2882 final Window jsWindow = window.getScriptableObject();
2883 if (null != jsWindow) {
2884 final Location location = jsWindow.getLocation();
2885 location.setHash(oldURL, urlWithOnlyHashChange.getRef());
2886 }
2887
2888
2889 window.getHistory().addPage(page);
2890 }
2891
2892
2893
2894
2895
2896 public WebClientOptions getOptions() {
2897 return options_;
2898 }
2899
2900
2901
2902
2903
2904
2905 public StorageHolder getStorageHolder() {
2906 return storageHolder_;
2907 }
2908
2909
2910
2911
2912
2913
2914
2915 public synchronized Set<Cookie> getCookies(final URL url) {
2916 final CookieManager cookieManager = getCookieManager();
2917
2918 if (!cookieManager.isCookiesEnabled()) {
2919 return Collections.emptySet();
2920 }
2921
2922 final URL normalizedUrl = HttpClientConverter.replaceForCookieIfNecessary(url);
2923
2924 final String host = normalizedUrl.getHost();
2925
2926
2927 if (host.isEmpty()) {
2928 return Collections.emptySet();
2929 }
2930
2931
2932 cookieManager.clearExpired(new Date());
2933
2934 final Set<Cookie> matchingCookies = new LinkedHashSet<>();
2935 HttpClientConverter.addMatching(cookieManager.getCookies(), normalizedUrl,
2936 getBrowserVersion(), matchingCookies);
2937 return Collections.unmodifiableSet(matchingCookies);
2938 }
2939
2940
2941
2942
2943
2944
2945
2946 public void addCookie(final String cookieString, final URL pageUrl, final Object origin) {
2947 final CookieManager cookieManager = getCookieManager();
2948 if (!cookieManager.isCookiesEnabled()) {
2949 if (LOG.isDebugEnabled()) {
2950 LOG.debug("Skipped adding cookie: '" + cookieString
2951 + "' because cookies are not enabled for the CookieManager.");
2952 }
2953 return;
2954 }
2955
2956 try {
2957 final List<Cookie> cookies = HttpClientConverter.parseCookie(cookieString, pageUrl, getBrowserVersion());
2958
2959 for (final Cookie cookie : cookies) {
2960 cookieManager.addCookie(cookie);
2961
2962 if (LOG.isDebugEnabled()) {
2963 LOG.debug("Added cookie: '" + cookieString + "'");
2964 }
2965 }
2966 }
2967 catch (final MalformedCookieException e) {
2968 if (LOG.isDebugEnabled()) {
2969 LOG.warn("Adding cookie '" + cookieString + "' failed.", e);
2970 }
2971 getIncorrectnessListener().notify("Adding cookie '" + cookieString
2972 + "' failed; reason: '" + e.getMessage() + "'.", origin);
2973 }
2974 }
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984 public boolean isJavaScriptEnabled() {
2985 return javaScriptEngineEnabled_ && getOptions().isJavaScriptEnabled();
2986 }
2987
2988
2989
2990
2991
2992
2993
2994 public boolean isJavaScriptEngineEnabled() {
2995 return javaScriptEngineEnabled_;
2996 }
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006 public HtmlPage loadHtmlCodeIntoCurrentWindow(final String htmlCode) throws IOException {
3007 final HTMLParser htmlParser = getPageCreator().getHtmlParser();
3008 final WebWindow webWindow = getCurrentWindow();
3009
3010 final StringWebResponse webResponse =
3011 new StringWebResponse(htmlCode, new URL("https://www.htmlunit.org/dummy.html"));
3012 final HtmlPage page = new HtmlPage(webResponse, webWindow);
3013 webWindow.setEnclosedPage(page);
3014
3015 htmlParser.parse(this, webResponse, page, false, false);
3016 return page;
3017 }
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027 public XHtmlPage loadXHtmlCodeIntoCurrentWindow(final String xhtmlCode) throws IOException {
3028 final HTMLParser htmlParser = getPageCreator().getHtmlParser();
3029 final WebWindow webWindow = getCurrentWindow();
3030
3031 final StringWebResponse webResponse =
3032 new StringWebResponse(xhtmlCode, new URL("https://www.htmlunit.org/dummy.html"));
3033 final XHtmlPage page = new XHtmlPage(webResponse, webWindow);
3034 webWindow.setEnclosedPage(page);
3035
3036 htmlParser.parse(this, webResponse, page, true, false);
3037 return page;
3038 }
3039
3040
3041
3042
3043
3044
3045
3046 public WebSocketAdapter buildWebSocketAdapter(final WebSocketListener webSocketListener) {
3047 return webSocketAdapterFactory_.buildWebSocketAdapter(this, webSocketListener);
3048 }
3049
3050
3051
3052
3053
3054
3055 public void setWebSocketAdapter(final WebSocketAdapterFactory factory) {
3056 webSocketAdapterFactory_ = factory;
3057 }
3058
3059
3060
3061
3062
3063
3064
3065 public PooledCSS3Parser getCSS3Parser() {
3066 return this.css3ParserPool_.get();
3067 }
3068
3069
3070
3071
3072
3073
3074 public Set<BroadcastChannel> getBroadcastChannels() {
3075 return broadcastChannel_;
3076 }
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094 static class CSS3ParserPool {
3095
3096
3097
3098
3099 private final ConcurrentLinkedDeque<PooledCSS3Parser> parsers_ = new ConcurrentLinkedDeque<>();
3100
3101
3102
3103
3104
3105
3106
3107
3108 public PooledCSS3Parser get() {
3109
3110 final PooledCSS3Parser parser = parsers_.pollLast();
3111
3112
3113 return parser != null ? parser.markInUse(this) : new PooledCSS3Parser(this);
3114 }
3115
3116
3117
3118
3119
3120
3121
3122
3123 protected void recycle(final PooledCSS3Parser parser) {
3124 parsers_.addLast(parser);
3125 }
3126 }
3127
3128
3129
3130
3131
3132
3133
3134
3135 public static class PooledCSS3Parser extends CSS3Parser implements AutoCloseable {
3136
3137
3138
3139
3140 private CSS3ParserPool pool_;
3141
3142
3143
3144
3145
3146
3147 protected PooledCSS3Parser(final CSS3ParserPool pool) {
3148 super();
3149 this.pool_ = pool;
3150 }
3151
3152
3153
3154
3155
3156
3157
3158 protected PooledCSS3Parser markInUse(final CSS3ParserPool pool) {
3159
3160 if (this.pool_ == null) {
3161 this.pool_ = pool;
3162 }
3163 else {
3164 throw new IllegalStateException("This PooledParser was not returned to the pool properly");
3165 }
3166
3167 return this;
3168 }
3169
3170
3171
3172
3173
3174
3175
3176
3177 @Override
3178 public void close() {
3179 if (this.pool_ != null) {
3180 final CSS3ParserPool oldPool = this.pool_;
3181
3182
3183 this.pool_ = null;
3184
3185
3186 oldPool.recycle(this);
3187 }
3188 else {
3189 throw new IllegalStateException("This PooledParser was returned already");
3190 }
3191 }
3192 }
3193 }