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