View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.xml;
16  
17  import static java.nio.charset.StandardCharsets.ISO_8859_1;
18  import static java.nio.charset.StandardCharsets.UTF_8;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.servlet.Servlet;
29  import javax.servlet.http.HttpServlet;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.htmlunit.HttpHeader;
34  import org.htmlunit.WebDriverTestCase;
35  import org.htmlunit.junit.BrowserRunner;
36  import org.htmlunit.junit.annotation.Alerts;
37  import org.htmlunit.util.MimeType;
38  import org.htmlunit.util.NameValuePair;
39  import org.junit.Test;
40  import org.junit.runner.RunWith;
41  import org.openqa.selenium.WebDriver;
42  
43  /**
44   * Tests for Cross-Origin Resource Sharing for {@link XMLHttpRequest}.
45   *
46   * @author Ahmed Ashour
47   * @author Marc Guillemot
48   * @author Ronald Brill
49   * @author Frank Danek
50   */
51  @RunWith(BrowserRunner.class)
52  public class XMLHttpRequestCORSTest extends WebDriverTestCase {
53  
54      /**
55       * @throws Exception if the test fails.
56       */
57      @Test
58      @Alerts({"error [object ProgressEvent]", "error", "false", "0", "false"})
59      public void noCorsHeaderCallsErrorHandler() throws Exception {
60          final String html = DOCTYPE_HTML
61                  + "<html><head>\n"
62                  + "<script>\n"
63                  + LOG_TITLE_FUNCTION
64                  + "var xhr = new XMLHttpRequest();\n"
65                  + "function test() {\n"
66                  + "  try {\n"
67                  + "    var url = '" + URL_THIRD + "';\n"
68                  + "    xhr.open('GET', url, true);\n"
69                  + "    xhr.onerror = function(event) {\n"
70                  + "                    log('error ' + event);\n"
71                  + "                    log(event.type);\n"
72                  + "                    log(event.lengthComputable);\n"
73                  + "                    log(event.loaded);\n"
74                  + "                    log(event.total > 0);\n"
75                  + "                  };\n"
76                  + "    xhr.send();\n"
77                  + "  } catch(e) { logEx(e); }\n"
78                  + "}\n"
79                  + "</script>\n"
80                  + "</head>\n"
81                  + "<body onload='test()'></body></html>";
82          getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
83  
84          loadPage2(html);
85          verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
86      }
87  
88      /**
89       * @throws Exception if the test fails.
90       */
91      @Test
92      @Alerts({"4", "200", "<root><origin>§§URL§§</origin><cookie>null</cookie></root>"})
93      public void simple() throws Exception {
94          expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
95  
96          final String html = DOCTYPE_HTML
97                  + "<html><head>\n"
98                  + "<script>\n"
99                  + LOG_TITLE_FUNCTION
100                 + "var xhr = new XMLHttpRequest();\n"
101                 + "function test() {\n"
102                 + "  try {\n"
103                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/simple2';\n"
104                 + "    xhr.open('GET', url, false);\n"
105                 + "    xhr.send();\n"
106                 + "    log(xhr.readyState);\n"
107                 + "    log(xhr.status);\n"
108                 + "    log(xhr.responseText);\n"
109                 + "  } catch(e) { log(e) }\n"
110                 + "}\n"
111                 + "</script>\n"
112                 + "</head>\n"
113                 + "<body onload='test()'></body></html>";
114 
115         SimpleServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = "*";
116         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
117         servlets2.put("/simple2", SimpleServerServlet.class);
118         startWebServer2(".", null, servlets2);
119 
120         final List<NameValuePair> responseHeader = new ArrayList<>();
121         responseHeader.add(new NameValuePair("Set-Cookie", "cookie=sweet"));
122 
123         final URL url = new URL(URL_FIRST, "/simple1");
124         getMockWebConnection().setResponse(url, html,
125                 200, "OK", "text/html;charset=ISO-8859-1", ISO_8859_1, responseHeader);
126 
127         loadPage2(url, null);
128         verifyTitle2(getWebDriver(), getExpectedAlerts());
129     }
130 
131     /**
132      * @throws Exception if the test fails.
133      */
134     @Test
135     @Alerts({"4", "200", "null"})
136     public void simpleHead() throws Exception {
137         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
138 
139         final String html = DOCTYPE_HTML
140                 + "<html><head>\n"
141                 + "<script>\n"
142                 + LOG_TITLE_FUNCTION
143                 + "var xhr = new XMLHttpRequest();\n"
144                 + "function test() {\n"
145                 + "  try {\n"
146                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/simple2';\n"
147                 + "    xhr.open('HEAD', url, false);\n"
148                 + "    xhr.send();\n"
149                 + "    log(xhr.readyState);\n"
150                 + "    log(xhr.status);\n"
151                 + "    log(xhr.responseXML);\n"
152                 + "  } catch(e) { log(e) }\n"
153                 + "}\n"
154                 + "</script>\n"
155                 + "</head>\n"
156                 + "<body onload='test()'></body></html>";
157 
158         SimpleServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = "*";
159         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
160         servlets2.put("/simple2", SimpleServerServlet.class);
161         startWebServer2(".", null, servlets2);
162 
163         loadPage2(html, new URL(URL_FIRST, "/simple1"));
164         verifyTitle2(getWebDriver(), getExpectedAlerts());
165     }
166 
167     /**
168      * @throws Exception if the test fails.
169      */
170     @Test
171     @Alerts({"4", "200", "<root><origin>§§URL§§</origin><cookie>null</cookie></root>"})
172     public void simplePost() throws Exception {
173         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
174 
175         final String html = DOCTYPE_HTML
176                 + "<html><head>\n"
177                 + "<script>\n"
178                 + LOG_TITLE_FUNCTION
179                 + "var xhr = new XMLHttpRequest();\n"
180                 + "function test() {\n"
181                 + "  try {\n"
182                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/simple2';\n"
183                 + "    xhr.open('POST', url, false);\n"
184                 + "    xhr.send('');\n"
185                 + "    log(xhr.readyState);\n"
186                 + "    log(xhr.status);\n"
187                 + "    log(xhr.responseText);\n"
188                 + "  } catch(e) { log(e) }\n"
189                 + "}\n"
190                 + "</script>\n"
191                 + "</head>\n"
192                 + "<body onload='test()'></body></html>";
193 
194         SimpleServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = "*";
195         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
196         servlets2.put("/simple2", SimpleServerServlet.class);
197         startWebServer2(".", null, servlets2);
198 
199         loadPage2(html, new URL(URL_FIRST, "/simple1"));
200         verifyTitle2(getWebDriver(), getExpectedAlerts());
201     }
202 
203     /**
204      * @throws Exception if the test fails.
205      */
206     @Test
207     @Alerts("NetworkError/DOMException")
208     public void simplePut() throws Exception {
209         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
210 
211         final String html = DOCTYPE_HTML
212                 + "<html><head>\n"
213                 + "<script>\n"
214                 + LOG_TITLE_FUNCTION
215                 + "var xhr = new XMLHttpRequest();\n"
216                 + "function test() {\n"
217                 + "  try {\n"
218                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/simple2';\n"
219                 + "    xhr.open('PUT', url, false);\n"
220                 + "    xhr.send('');\n"
221                 + "    log(xhr.readyState);\n"
222                 + "    log(xhr.status);\n"
223                 + "    log(xhr.responseXML.firstChild.firstChild.nodeValue);\n"
224                 + "  } catch(e) { logEx(e) }\n"
225                 + "}\n"
226                 + "</script>\n"
227                 + "</head>\n"
228                 + "<body onload='test()'></body></html>";
229 
230         SimpleServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = "*";
231         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
232         servlets2.put("/simple2", SimpleServerServlet.class);
233         startWebServer2(".", null, servlets2);
234 
235         loadPage2(html, new URL(URL_FIRST, "/simple1"));
236         verifyTitle2(getWebDriver(), getExpectedAlerts());
237     }
238 
239     /**
240      * Simple CORS scenario Servlet.
241      */
242     public static class SimpleServerServlet extends HttpServlet {
243         private static String ACCESS_CONTROL_ALLOW_ORIGIN_;
244 
245         /**
246          * {@inheritDoc}
247          */
248         @Override
249         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
250             if (ACCESS_CONTROL_ALLOW_ORIGIN_ != null) {
251                 response.setHeader("Access-Control-Allow-Origin", ACCESS_CONTROL_ALLOW_ORIGIN_);
252             }
253             response.setCharacterEncoding(UTF_8.name());
254             response.setContentType(MimeType.TEXT_XML);
255 
256             String origin = request.getHeader(HttpHeader.ORIGIN);
257             if (origin == null) {
258                 origin = "No Origin!";
259             }
260             final String cookie = request.getHeader(HttpHeader.COOKIE);
261             response.getWriter().write("<root><origin>" + origin + "</origin><cookie>" + cookie + "</cookie></root>");
262         }
263 
264         /**
265          * {@inheritDoc}
266          */
267         @Override
268         protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
269             doGet(request, response);
270         }
271 
272         /**
273          * {@inheritDoc}
274          */
275         @Override
276         protected void doPut(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
277             doGet(request, response);
278         }
279     }
280 
281     /**
282      * @throws Exception if the test fails.
283      */
284     @Test
285     @Alerts({"NetworkError/DOMException", "4", "0", ""})
286     public void noAccessControlAllowOrigin() throws Exception {
287         incorrectAccessControlAllowOrigin(null);
288     }
289 
290     private void incorrectAccessControlAllowOrigin(final String header) throws Exception {
291         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
292 
293         final String html = DOCTYPE_HTML
294                 + "<html><head>\n"
295                 + "<script>\n"
296                 + LOG_TITLE_FUNCTION
297                 + "var xhr = new XMLHttpRequest();\n"
298                 + "function test() {\n"
299                 + "  try {\n"
300                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/simple2';\n"
301                 + "    xhr.open('GET', url, false);\n"
302                 + "    xhr.send();\n"
303                 + "  } catch(e) { logEx(e) }\n"
304                 + "  log(xhr.readyState);\n"
305                 + "  log(xhr.status);\n"
306                 + "  log(xhr.responseText);\n"
307                 + "}\n"
308                 + "</script>\n"
309                 + "</head>\n"
310                 + "<body onload='test()'></body></html>";
311 
312         SimpleServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = header;
313         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
314         servlets2.put("/simple2", SimpleServerServlet.class);
315         startWebServer2(".", null, servlets2);
316 
317         loadPage2(html, new URL(URL_FIRST, "/simple1"));
318         verifyTitle2(getWebDriver(), getExpectedAlerts());
319     }
320 
321     /**
322      * @throws Exception if the test fails.
323      */
324     @Test
325     @Alerts({"NetworkError/DOMException", "4", "0", ""})
326     public void nonMatchingAccessControlAllowOrigin() throws Exception {
327         incorrectAccessControlAllowOrigin("http://www.sourceforge.net");
328     }
329 
330     /**
331      * @throws Exception if the test fails.
332      */
333     @Test
334     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
335     public void preflight() throws Exception {
336         doPreflightTestAllowedMethods("POST, GET, OPTIONS", MimeType.TEXT_PLAIN, null);
337 
338         releaseResources();
339         shutDownAll();
340     }
341 
342     /**
343      * @throws Exception if the test fails.
344      */
345     @Test
346     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
347     public void preflight_cookie() throws Exception {
348         doPreflightTestAllowedMethods("POST, GET, OPTIONS", MimeType.TEXT_PLAIN, "cookie=sweet");
349 
350         releaseResources();
351         shutDownAll();
352     }
353 
354     /**
355      * @throws Exception if the test fails.
356      */
357     @Test
358     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
359     // unstable test case, this will work on real Chrome if individually run, but will fail if run with other cases
360     public void preflight_contentTypeWithCharset() throws Exception {
361         doPreflightTestAllowedMethods("POST, GET, OPTIONS", MimeType.TEXT_PLAIN + ";charset=utf-8", null);
362 
363         releaseResources();
364         shutDownAll();
365     }
366 
367     /**
368      * @throws Exception if the test fails.
369      */
370     @Test
371     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
372     // unstable test case, this will work on real Chrome if individually run, but will fail if run with other cases
373     public void preflightUrlEncoded() throws Exception {
374         doPreflightTestAllowedMethods("POST, GET, OPTIONS", "application/x-www-form-urlencoded", null);
375 
376         releaseResources();
377         shutDownAll();
378     }
379 
380     /**
381      * @throws Exception if the test fails.
382      */
383     @Test
384     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
385     // unstable test case, this will work on real Chrome if individually run, but will fail if run with other cases
386     public void preflightUrlEncoded_contentTypeWithCharset() throws Exception {
387         doPreflightTestAllowedMethods("POST, GET, OPTIONS", "application/x-www-form-urlencoded;charset=utf-8", null);
388 
389         releaseResources();
390         shutDownAll();
391     }
392 
393     /**
394      * @throws Exception if the test fails.
395      */
396     @Test
397     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
398     // unstable test case, this will work on real Chrome if individually run, but will fail if run with other cases
399     public void preflightMultipart() throws Exception {
400         doPreflightTestAllowedMethods("POST, GET, OPTIONS", "multipart/form-data", null);
401 
402         releaseResources();
403         shutDownAll();
404     }
405 
406     /**
407      * @throws Exception if the test fails.
408      */
409     @Test
410     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
411     // unstable test case, this will work on real Chrome if individually run, but will fail if run with other cases
412     public void preflightMultipart_contentTypeWithCharset() throws Exception {
413         doPreflightTestAllowedMethods("POST, GET, OPTIONS", "multipart/form-data;charset=utf-8", null);
414 
415         releaseResources();
416         shutDownAll();
417     }
418 
419     /**
420      * Seems that "Access-Control-Allow-Methods" is not considered by FF.
421      *
422      * @throws Exception if the test fails.
423      */
424     @Test
425     @Alerts({"4", "200", "§§URL§§", "§§URL§§", "GET", "x-pingother", "null"})
426     // unstable test case, this will fail on real Chrome if individually run, but will succeed if run with other cases
427     public void preflight_incorrect_methods() throws Exception {
428         doPreflightTestAllowedMethods(null, MimeType.TEXT_PLAIN, null);
429 
430         releaseResources();
431         shutDownAll();
432     }
433 
434     private void doPreflightTestAllowedMethods(final String allowedMethods,
435                     final String contentType, final String cookie)
436                             throws Exception {
437         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT)); // url without trailing "/"
438 
439         final String html = DOCTYPE_HTML
440             + "<html><head>\n"
441             + "<script>\n"
442             + LOG_TITLE_FUNCTION
443             + "var xhr = new XMLHttpRequest();\n"
444             + "function test() {\n"
445             + "  try {\n"
446             + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/preflight2';\n"
447             + "    xhr.open('GET', url, false);\n"
448             + "    xhr.setRequestHeader('X-PINGOTHER', 'pingpong');\n"
449             + "    xhr.setRequestHeader('Content-Type' , '" + contentType + "');\n"
450             + "    xhr.send();\n"
451             + "    log(xhr.readyState);\n"
452             + "    log(xhr.status);\n"
453             + "    log(xhr.responseXML.firstChild.childNodes[0].firstChild.nodeValue);\n"
454             + "    log(xhr.responseXML.firstChild.childNodes[1].firstChild.nodeValue);\n"
455             + "    log(xhr.responseXML.firstChild.childNodes[2].firstChild.nodeValue);\n"
456             + "    log(xhr.responseXML.firstChild.childNodes[3].firstChild.nodeValue);\n"
457             + "    log(xhr.responseXML.firstChild.childNodes[4].firstChild.nodeValue);\n"
458             + "  } catch(e) { log(e) }\n"
459             + "}\n"
460             + "</script>\n"
461             + "</head>\n"
462             + "<body onload='test()'></body></html>";
463 
464         PreflightServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = "http://localhost:" + PORT;
465         PreflightServerServlet.ACCESS_CONTROL_ALLOW_METHODS_ = allowedMethods;
466         PreflightServerServlet.ACCESS_CONTROL_ALLOW_HEADERS_ = "X-PINGOTHER";
467         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
468         servlets2.put("/preflight2", PreflightServerServlet.class);
469         startWebServer2(".", null, servlets2);
470 
471         final URL url = new URL(URL_FIRST, "/preflight1");
472 
473         List<NameValuePair> responseHeader = null;
474         if (cookie != null) {
475             responseHeader = new ArrayList<>();
476             responseHeader.add(new NameValuePair("Set-Cookie", cookie));
477         }
478 
479         getMockWebConnection().setResponse(url, html,
480                 200, "OK", "text/html;charset=ISO-8859-1", ISO_8859_1, responseHeader);
481         loadPage2(url, null);
482 
483         verifyTitle2(getWebDriver(), getExpectedAlerts());
484     }
485 
486     /**
487      * Preflight CORS scenario Servlet.
488      */
489     public static class PreflightServerServlet extends HttpServlet {
490         private static String ACCESS_CONTROL_ALLOW_ORIGIN_;
491         private static String ACCESS_CONTROL_ALLOW_METHODS_;
492         private static String ACCESS_CONTROL_ALLOW_HEADERS_;
493         private String options_origin_;
494         private String options_method_;
495         private String options_headers_;
496         private String options_cookie_;
497 
498         /**
499          * {@inheritDoc}
500          */
501         @Override
502         protected void doOptions(final HttpServletRequest request, final HttpServletResponse response) {
503             if (ACCESS_CONTROL_ALLOW_ORIGIN_ != null) {
504                 response.setHeader("Access-Control-Allow-Origin", ACCESS_CONTROL_ALLOW_ORIGIN_);
505             }
506             if (ACCESS_CONTROL_ALLOW_METHODS_ != null) {
507                 response.setHeader("Access-Control-Allow-Methods", ACCESS_CONTROL_ALLOW_METHODS_);
508             }
509             if (ACCESS_CONTROL_ALLOW_HEADERS_ != null) {
510                 response.setHeader("Access-Control-Allow-Headers", ACCESS_CONTROL_ALLOW_HEADERS_);
511             }
512             options_origin_ = request.getHeader(HttpHeader.ORIGIN);
513             options_method_ = request.getHeader("Access-Control-Request-Method");
514             options_headers_ = request.getHeader("Access-Control-Request-Headers");
515             options_cookie_ = request.getHeader(HttpHeader.COOKIE);
516         }
517 
518         /**
519          * {@inheritDoc}
520          */
521         @Override
522         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
523             if (ACCESS_CONTROL_ALLOW_ORIGIN_ != null) {
524                 response.setHeader("Access-Control-Allow-Origin", ACCESS_CONTROL_ALLOW_ORIGIN_);
525             }
526             response.setCharacterEncoding(UTF_8.name());
527             response.setContentType(MimeType.TEXT_XML);
528             final Writer writer = response.getWriter();
529 
530             final String origin = request.getHeader(HttpHeader.ORIGIN);
531             writer.write("<result>"
532                 + "<origin>" + origin + "</origin>"
533                 + "<options_origin>" + options_origin_ + "</options_origin>"
534                 + "<options_method>" + options_method_ + "</options_method>"
535                 + "<options_headers>" + options_headers_ + "</options_headers>"
536                 + "<options_cookie>" + options_cookie_ + "</options_cookie>"
537                 + "</result>");
538         }
539     }
540 
541     /**
542      * @throws Exception if the test fails.
543      */
544     @Test
545     @Alerts({"NetworkError/DOMException", "4", "0"})
546     // unstable test case, this will fail on real Chrome if individually run, but will succeed if run with other cases
547     public void preflight_incorrect_headers() throws Exception {
548         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
549 
550         final String html = DOCTYPE_HTML
551                 + "<html><head>\n"
552                 + "<script>\n"
553                 + LOG_TITLE_FUNCTION
554                 + "var xhr = new XMLHttpRequest();\n"
555                 + "function test() {\n"
556                 + "  try {\n"
557                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/preflight2';\n"
558                 + "    xhr.open('GET', url, false);\n"
559                 + "    xhr.setRequestHeader('X-PINGOTHER', 'pingpong');\n"
560                 + "    xhr.send();\n"
561                 + "  } catch(e) { logEx(e) }\n"
562                 + "  log(xhr.readyState);\n"
563                 + "  log(xhr.status);\n"
564                 + "}\n"
565                 + "</script>\n"
566                 + "</head>\n"
567                 + "<body onload='test()'></body></html>";
568 
569         PreflightServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = "http://localhost:" + PORT;
570         PreflightServerServlet.ACCESS_CONTROL_ALLOW_METHODS_ = "POST, GET, OPTIONS";
571         PreflightServerServlet.ACCESS_CONTROL_ALLOW_HEADERS_ = null;
572         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
573         servlets2.put("/preflight2", PreflightServerServlet.class);
574         startWebServer2(".", null, servlets2);
575 
576         loadPage2(html, new URL(URL_FIRST, "/preflight1"));
577         verifyTitle2(getWebDriver(), getExpectedAlerts());
578     }
579 
580     /**
581      * @throws Exception if the test fails.
582      */
583     @Test
584     @Alerts({"4", "200", "options_headers", "x-ping,x-pong"})
585     public void preflight_many_header_values() throws Exception {
586         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
587 
588         final String html = DOCTYPE_HTML
589                 + "<html><head>\n"
590                 + "<script>\n"
591                 + LOG_TITLE_FUNCTION
592                 + "var xhr = new XMLHttpRequest();\n"
593                 + "function test() {\n"
594                 + "  try {\n"
595                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/preflight2';\n"
596                 + "    xhr.open('GET', url, false);\n"
597                 + "    xhr.setRequestHeader('X-PING', 'ping');\n"
598                 + "    xhr.setRequestHeader('X-PONG', 'pong');\n"
599                 + "    xhr.send();\n"
600                 + "  } catch(e) { logEx(e) }\n"
601                 + "  log(xhr.readyState);\n"
602                 + "  log(xhr.status);\n"
603                 + "  log(xhr.responseXML.firstChild.childNodes[3].tagName);\n"
604                 + "  log(xhr.responseXML.firstChild.childNodes[3].firstChild.nodeValue);\n"
605                 + "}\n"
606                 + "</script>\n"
607                 + "</head>\n"
608                 + "<body onload='test()'></body></html>";
609 
610         PreflightServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = "http://localhost:" + PORT;
611         PreflightServerServlet.ACCESS_CONTROL_ALLOW_METHODS_ = "POST, GET, OPTIONS";
612         PreflightServerServlet.ACCESS_CONTROL_ALLOW_HEADERS_ = "X-PING, X-PONG";
613         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
614         servlets2.put("/preflight2", PreflightServerServlet.class);
615         startWebServer2(".", null, servlets2);
616 
617         loadPage2(html, new URL(URL_FIRST, "/preflight1"));
618         verifyTitle2(getWebDriver(), getExpectedAlerts());
619     }
620 
621     /**
622      * @throws Exception if the test fails.
623      */
624     @Test
625     @Alerts("false")
626     public void withCredentials_defaultValue() throws Exception {
627         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
628 
629         final String html = DOCTYPE_HTML
630                 + "<html><head>\n"
631                 + "<script>\n"
632                 + LOG_TITLE_FUNCTION
633                 + "var xhr = new XMLHttpRequest();\n"
634                 + "function test() {\n"
635                 + "  try {\n"
636                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/withCredentials2';\n"
637                 + "    xhr.open('GET', url, true);\n"
638                 + "    log(xhr.withCredentials);\n"
639                 + "  } catch(e) { log(e) }\n"
640                 + "}\n"
641                 + "</script>\n"
642                 + "</head>\n"
643                 + "<body onload='test()'></body></html>";
644 
645         loadPage2(html, new URL(URL_FIRST, "/withCredentials1"));
646         verifyTitle2(getWebDriver(), getExpectedAlerts());
647     }
648 
649     /**
650      * @throws Exception if the test fails.
651      */
652     @Test
653     @Alerts({"false", "true", "false", "true"})
654     public void withCredentials_setBeforeOpenSync() throws Exception {
655         final String html = DOCTYPE_HTML
656                 + "<html><head>\n"
657                 + "<script>\n"
658                 + LOG_TITLE_FUNCTION
659                 + "var xhr = new XMLHttpRequest();\n"
660                 + "function test() {\n"
661                 + "  try {\n"
662                 + "    log(xhr.withCredentials);\n"
663 
664                 + "    try {\n"
665                 + "      xhr.withCredentials = true;\n"
666                 + "      log(xhr.withCredentials);\n"
667                 + "    } catch(e) { log('ex: withCredentials=true') }\n"
668 
669                 + "    try {\n"
670                 + "      xhr.withCredentials = false;\n"
671                 + "      log(xhr.withCredentials);\n"
672                 + "    } catch(e) { log('ex: withCredentials=false') }\n"
673 
674                 + "    try {\n"
675                 + "      xhr.withCredentials = true;\n"
676                 + "    } catch(e) { log('ex: withCredentials=true') }\n"
677 
678                 + "    try {\n"
679                 + "      xhr.open('GET', '/foo.xml', false);\n"
680                 + "    } catch(e) { log('ex: open') }\n"
681                 + "    log(xhr.withCredentials);\n"
682                 + "  } catch(ex) { log(ex) }\n"
683                 + "}\n"
684                 + "</script>\n"
685                 + "</head>\n"
686                 + "<body onload='test()'></body></html>";
687 
688         loadPageVerifyTitle2(html);
689     }
690 
691     /**
692      * @throws Exception if the test fails.
693      */
694     @Test
695     @Alerts({"false", "true", "false", "true"})
696     public void withCredentials_setBeforeOpenAsync() throws Exception {
697         final String html = DOCTYPE_HTML
698                 + "<html><head>\n"
699                 + "<script>\n"
700                 + LOG_TITLE_FUNCTION
701                 + "var xhr = new XMLHttpRequest();\n"
702                 + "function test() {\n"
703                 + "  try {\n"
704                 + "    log(xhr.withCredentials);\n"
705 
706                 + "    try {\n"
707                 + "      xhr.withCredentials = true;\n"
708                 + "      log(xhr.withCredentials);\n"
709                 + "    } catch(e) { log('ex: withCredentials=true') }\n"
710 
711                 + "    try {\n"
712                 + "      xhr.withCredentials = false;\n"
713                 + "      log(xhr.withCredentials);\n"
714                 + "    } catch(e) { log('ex: withCredentials=false') }\n"
715 
716                 + "    try {\n"
717                 + "      xhr.withCredentials = true;\n"
718                 + "    } catch(e) { log('ex: withCredentials=true') }\n"
719 
720                 + "    try {\n"
721                 + "      xhr.open('GET', '/foo.xml', true);\n"
722                 + "    } catch(e) { log('ex: open') }\n"
723                 + "    log(xhr.withCredentials);\n"
724                 + "  } catch(ex) { log(ex) }\n"
725                 + "}\n"
726                 + "</script>\n"
727                 + "</head>\n"
728                 + "<body onload='test()'></body></html>";
729 
730         loadPage2(html);
731         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
732     }
733 
734     /**
735      * @throws Exception if the test fails.
736      */
737     @Test
738     @Alerts({"false", "false", "true", "false"})
739     public void withCredentials_setAfterOpenSync() throws Exception {
740         final String html = DOCTYPE_HTML
741                 + "<html><head>\n"
742                 + "<script>\n"
743                 + LOG_TITLE_FUNCTION
744                 + "var xhr = new XMLHttpRequest();\n"
745                 + "function test() {\n"
746                 + "  try {\n"
747                 + "    log(xhr.withCredentials);\n"
748                 + "    xhr.open('GET', '/foo.xml', false);\n"
749                 + "    log(xhr.withCredentials);\n"
750 
751                 + "    try {\n"
752                 + "      xhr.withCredentials = true;\n"
753                 + "      log(xhr.withCredentials);\n"
754                 + "    } catch(e) { log('ex: withCredentials=true') }\n"
755 
756                 + "    try {\n"
757                 + "      xhr.withCredentials = false;\n"
758                 + "      log(xhr.withCredentials);\n"
759                 + "    } catch(e) { log('ex: withCredentials=false') }\n"
760                 + "  } catch(ex) { log(ex) }\n"
761                 + "}\n"
762                 + "</script>\n"
763                 + "</head>\n"
764                 + "<body onload='test()'></body></html>";
765 
766         loadPageVerifyTitle2(html);
767     }
768 
769     /**
770      * @throws Exception if the test fails.
771      */
772     @Test
773     @Alerts({"false", "false", "true", "false"})
774     public void withCredentials_setAfterOpenAsync() throws Exception {
775         final String html = DOCTYPE_HTML
776                 + "<html><head>\n"
777                 + "<script>\n"
778                 + LOG_TITLE_FUNCTION
779                 + "var xhr = new XMLHttpRequest();\n"
780                 + "function test() {\n"
781                 + "  try {\n"
782                 + "    log(xhr.withCredentials);\n"
783                 + "    xhr.open('GET', '/foo.xml', false);\n"
784                 + "    log(xhr.withCredentials);\n"
785 
786                 + "    try {\n"
787                 + "      xhr.withCredentials = true;\n"
788                 + "      log(xhr.withCredentials);\n"
789                 + "    } catch(e) { log('ex: withCredentials=true') }\n"
790 
791                 + "    try {\n"
792                 + "      xhr.withCredentials = false;\n"
793                 + "      log(xhr.withCredentials);\n"
794                 + "    } catch(e) { log('ex: withCredentials=false') }\n"
795                 + "  } catch(ex) { log(ex) }\n"
796                 + "}\n"
797                 + "</script>\n"
798                 + "</head>\n"
799                 + "<body onload='test()'></body></html>";
800 
801         loadPageVerifyTitle2(html);
802     }
803 
804     /**
805      * @throws Exception if the test fails.
806      */
807     @Test
808     @Alerts({"1", "0", "4", "0", ""})
809     public void withCredentials() throws Exception {
810         testWithCredentials("*", "true");
811     }
812 
813     /**
814      * @throws Exception if the test fails.
815      */
816     @Test
817     @Alerts({"1", "0", "4", "200", "<root><origin>§§URL§§</origin><cookie>cookie=sweet</cookie></root>"})
818     public void withCredentialsServer() throws Exception {
819         testWithCredentials("http://localhost:" + PORT, "true");
820     }
821 
822     /**
823      * @throws Exception if the test fails.
824      */
825     @Test
826     @Alerts({"1", "0", "4", "0", ""})
827     public void withCredentialsServerSlashAtEnd() throws Exception {
828         testWithCredentials(URL_FIRST.toExternalForm(), "true");
829     }
830 
831     /**
832      * @throws Exception if the test fails.
833      */
834     @Test
835     @Alerts({"1", "0", "4", "0", ""})
836     public void withCredentials_no_header() throws Exception {
837         testWithCredentials("*", null);
838     }
839 
840     /**
841      * @throws Exception if the test fails.
842      */
843     @Test
844     @Alerts({"1", "0", "4", "0", ""})
845     public void withCredentials_no_header_Server() throws Exception {
846         testWithCredentials("http://localhost:" + PORT, null);
847     }
848 
849     /**
850      * @throws Exception if the test fails.
851      */
852     @Test
853     @Alerts({"1", "0", "4", "0", ""})
854     public void withCredentials_no_header_ServerSlashAtEnd() throws Exception {
855         testWithCredentials(URL_FIRST.toExternalForm(), null);
856     }
857 
858     private void testWithCredentials(final String accessControlAllowOrigin,
859             final String accessControlAllowCredentials) throws Exception {
860         expandExpectedAlertsVariables(new URL("http://localhost:" + PORT));
861 
862         final String html = DOCTYPE_HTML
863                 + "<html><head>\n"
864                 + "<script>\n"
865                 + LOG_TITLE_FUNCTION
866                 + "var xhr = new XMLHttpRequest();\n"
867                 + "function test() {\n"
868                 + "  try {\n"
869                 + "    var url = 'http://' + window.location.hostname + ':" + PORT2 + "/withCredentials2';\n"
870                 + "    xhr.open('GET', url, true);\n"
871                 + "    xhr.withCredentials = true;\n"
872                 + "    xhr.onreadystatechange = onReadyStateChange;\n"
873                 + "    xhr.send();\n"
874                 + "  } catch(e) { logEx(e) }\n"
875                 + "  log(xhr.readyState);\n"
876                 + "  try {\n"
877                 + "    log(xhr.status);\n"
878                 + "  } catch(e) { log('ex: status not available') }\n"
879 
880                 + "  function onReadyStateChange() {\n"
881                 + "    if (xhr.readyState == 4) {\n"
882                 + "      log(xhr.readyState);\n"
883                 + "      log(xhr.status);\n"
884                 + "      log(xhr.responseText);\n"
885                 + "    }\n"
886                 + "  }\n"
887                 + "}\n"
888                 + "</script>\n"
889                 + "</head>\n"
890                 + "<body onload='test()'>\n"
891                 + "</body></html>";
892 
893         WithCredentialsServerServlet.ACCESS_CONTROL_ALLOW_ORIGIN_ = accessControlAllowOrigin;
894         WithCredentialsServerServlet.ACCESS_CONTROL_ALLOW_CREDENTIALS_ = accessControlAllowCredentials;
895         final Map<String, Class<? extends Servlet>> servlets2 = new HashMap<>();
896         servlets2.put("/withCredentials2", WithCredentialsServerServlet.class);
897         startWebServer2(".", null, servlets2);
898 
899         final List<NameValuePair> responseHeader = new ArrayList<>();
900         responseHeader.add(new NameValuePair("Set-Cookie", "cookie=sweet"));
901 
902         final URL url = new URL(URL_FIRST, "/withCredentials1");
903         getMockWebConnection().setResponse(url, html,
904                 200, "OK", "text/html;charset=ISO-8859-1", ISO_8859_1, responseHeader);
905 
906         final WebDriver driver = loadPage2(url, null);
907         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
908     }
909 
910     /**
911      * @throws Exception if the test fails.
912      */
913     @Test
914     @Alerts("done 200")
915     public void testWithCredentialsIFrame() throws Exception {
916         final String html = DOCTYPE_HTML
917                 + "<html><head>\n"
918                 + "<script>\n"
919                 + LOG_WINDOW_NAME_FUNCTION
920 
921                 + "function load() {\n"
922                 + "  try {\n"
923                 + "    var myContent = '<!DOCTYPE html><html><head></head><body>"
924                             + "<script src=\"get.js\"><\\/script><p>tttttt</p></body></html>';\n"
925                 + "    window.asyncLoadIFrame = document.createElement('iframe');\n"
926                 + "    asyncLoadIFrame.id = 'asyncLoadIFrame';\n"
927                 + "    asyncLoadIFrame.src = 'about:blank';\n"
928                 + "    document.body.appendChild(asyncLoadIFrame);\n"
929 
930                 + "    asyncLoadIFrame.contentWindow.document.open('text/html', 'replace');\n"
931                 + "    asyncLoadIFrame.contentWindow.document.write(myContent);\n"
932                 + "    asyncLoadIFrame.contentWindow.document.close();\n"
933                 + "  } catch(e) { log(e) }\n"
934                 + "}\n"
935                 + "</script>\n"
936                 + "</head>\n"
937                 + "<body onload='load()'>\n"
938                 + "</body></html>";
939 
940         final String js =
941                 LOG_WINDOW_NAME_FUNCTION
942                 + "var xhr = new XMLHttpRequest();\n"
943                 + "  try {\n"
944                 + "    var url = '/data';\n"
945                 + "    xhr.open('GET', url, true);\n"
946                 + "    xhr.withCredentials = true;\n"
947                 + "    xhr.onreadystatechange = onReadyStateChange;\n"
948                 + "    xhr.send();\n"
949                 + "  } catch(e) { log(e) }\n"
950 
951                 + "  function onReadyStateChange() {\n"
952                 + "    if (xhr.readyState == 4) {\n"
953                 + "      log('done ' + xhr.status);\n"
954                 + "    }\n"
955                 + "  }\n";
956 
957         getMockWebConnection().setDefaultResponse(js, MimeType.TEXT_JAVASCRIPT);
958         final String xml = "<xml><content>blah</content></xml>";
959 
960         getMockWebConnection().setResponse(new URL(URL_FIRST, "/data"), xml, MimeType.TEXT_XML);
961 
962         loadPage2(html);
963         verifyWindowName2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
964     }
965 
966     /**
967      * CORS "With Credentials" scenario Servlet.
968      */
969     public static class WithCredentialsServerServlet extends HttpServlet {
970         private static String ACCESS_CONTROL_ALLOW_ORIGIN_;
971         private static String ACCESS_CONTROL_ALLOW_CREDENTIALS_;
972 
973         /**
974          * {@inheritDoc}
975          */
976         @Override
977         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
978             if (ACCESS_CONTROL_ALLOW_ORIGIN_ != null) {
979                 response.setHeader("Access-Control-Allow-Origin", ACCESS_CONTROL_ALLOW_ORIGIN_);
980             }
981             if (ACCESS_CONTROL_ALLOW_CREDENTIALS_ != null) {
982                 response.setHeader("Access-Control-Allow-Credentials", ACCESS_CONTROL_ALLOW_CREDENTIALS_);
983             }
984             response.setCharacterEncoding(UTF_8.name());
985             response.setContentType(MimeType.TEXT_XML);
986 
987             String origin = request.getHeader(HttpHeader.ORIGIN);
988             if (origin == null) {
989                 origin = "No Origin!";
990             }
991             final String cookie = request.getHeader(HttpHeader.COOKIE);
992             response.getWriter().write("<root><origin>" + origin + "</origin><cookie>" + cookie + "</cookie></root>");
993         }
994     }
995 
996 }