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 java.io.File;
18  import java.net.URL;
19  import java.nio.charset.Charset;
20  import java.nio.charset.StandardCharsets;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.htmlunit.HttpHeader;
25  import org.htmlunit.WebDriverTestCase;
26  import org.htmlunit.junit.BrowserRunner;
27  import org.htmlunit.junit.annotation.Alerts;
28  import org.htmlunit.junit.annotation.HtmlUnitNYI;
29  import org.htmlunit.util.NameValuePair;
30  import org.junit.Assert;
31  import org.junit.Test;
32  import org.junit.runner.RunWith;
33  
34  /**
35   * Additional tests for {@link XMLHttpRequest} blob handling.
36   *
37   * @author Ronald Brill
38   * @author Thorsten Wendelmuth
39   */
40  @RunWith(BrowserRunner.class)
41  public class XMLHttpRequest5Test extends WebDriverTestCase {
42  
43      /**
44       * @throws Exception if the test fails
45       */
46      @Test
47      @Alerts({"multipart/form-data; boundary=----formdata123456", "Html\r\nUnit\r\n"})
48      public void sendBlob() throws Exception {
49          sendBlobWithMimeTypeAndAssertContentType(getExpectedAlerts()[0],
50                  "Html\\r\\nUnit\\r\\n", getExpectedAlerts()[1]);
51      }
52  
53      /**
54       * @throws Exception if the test fails
55       */
56      @Test
57      @Alerts({"null", "Html\r\nUnit\r\n"})
58      public void sendBlob_emptyMimeType() throws Exception {
59          sendBlobWithMimeTypeAndAssertContentType(getExpectedAlerts()[0],
60                  "Html\\r\\nUnit\\r\\n", getExpectedAlerts()[1]);
61      }
62  
63      /**
64       * @throws Exception if the test fails
65       */
66      @Test
67      @Alerts({"doesnt/exist", "Html\r\nUnit\r\n"})
68      public void sendBlob_badMimeType() throws Exception {
69          sendBlobWithMimeTypeAndAssertContentType(getExpectedAlerts()[0],
70                  "Html\\r\\nUnit\\r\\n", getExpectedAlerts()[1]);
71      }
72  
73      private void sendBlobWithMimeTypeAndAssertContentType(final String desiredMimeType,
74              final String sentBody, final String expectedBody) throws Exception {
75          startWebServer(getMockWebConnection(), Charset.defaultCharset());
76          final String url = URL_SECOND.toString();
77  
78          final String html = DOCTYPE_HTML
79                  + "<html><head><title>foo</title>"
80                  + "<script>\n"
81                  + "  function test() {\n"
82                  + "    try {\n"
83                  + "      var xhr = new XMLHttpRequest();\n"
84                  + "      xhr.open('POST', '"
85                  + url
86                  + "', false);\n"
87                  + "      var body = ['" + sentBody + "'];\n"
88                  + "      var blob = new Blob(body, {type:'" + desiredMimeType + "'});\n"
89                  + "      xhr.send(blob);\n"
90                  + "  } catch (exception) { \n"
91                  + "    alert(exception);\n"
92                  + "  }\n"
93                  + "}\n"
94                  + "</script></head><body onload='test()'>\n"
95                  + "<form id='form'>"
96                  + "<input type='text' value='something'> "
97                  + "<input type='field' >"
98                  + "</form>"
99                  + "</body></html>";
100 
101         setExpectedAlerts();
102         loadPageWithAlerts2(html);
103 
104         Assert.assertEquals("Never received a call to URL_SECOND", URL_SECOND,
105                 getMockWebConnection().getLastWebRequest().getUrl());
106 
107         final String contentType = getMockWebConnection().getLastWebRequest()
108                 .getAdditionalHeader(HttpHeader.CONTENT_TYPE);
109 
110         final String requestBody = getMockWebConnection().getLastWebRequest().getRequestBody();
111 
112         Assert.assertEquals("Unexpected Content-Type header", desiredMimeType,
113                 contentType == null ? "null" : contentType);
114         Assert.assertEquals(expectedBody, requestBody == null ? "null" : requestBody);
115     }
116 
117     /**
118      * @throws Exception if the test fails
119      */
120     @Test
121     @Alerts({"text/plain", "HtmlUnit"})
122     public void sendBlob307() throws Exception {
123         final URL redirectUrl = new URL(URL_FIRST, "/redirect.html");
124         final URL responseUrl = new URL(URL_FIRST, "/response.html");
125 
126         final List<NameValuePair> headers = new ArrayList<>();
127         headers.add(new NameValuePair("Location", responseUrl.toExternalForm()));
128         getMockWebConnection().setResponse(redirectUrl, "", 307, "Redirected", null, headers);
129         getMockWebConnection().setResponse(responseUrl, "Result");
130 
131         startWebServer(getMockWebConnection(), Charset.defaultCharset());
132 
133         final String html = DOCTYPE_HTML
134                 + "<html><head><title>foo</title>"
135                 + "<script>\n"
136                 + "  function test() {\n"
137                 + "    try {\n"
138                 + "      var xhr = new XMLHttpRequest();\n"
139                 + "      xhr.open('POST', '/redirect.html', false);\n"
140                 + "      var body = ['HtmlUnit'];\n"
141                 + "      var blob = new Blob(body, {type:'text/plain'});\n"
142                 + "      xhr.send(blob);\n"
143                 + "  } catch (exception) { \n"
144                 + "    alert(exception);\n"
145                 + "  }\n"
146                 + "}\n"
147                 + "</script></head>\n"
148                 + "<body onload='test()'>\n"
149                 + "</body></html>";
150 
151         loadPage2(html);
152 
153         Assert.assertEquals("Never received a call to '" + responseUrl + "'", responseUrl,
154                 getMockWebConnection().getLastWebRequest().getUrl());
155 
156         Assert.assertEquals(3, getMockWebConnection().getRequestCount());
157         final String contentType = getMockWebConnection().getLastWebRequest()
158                 .getAdditionalHeader(HttpHeader.CONTENT_TYPE);
159 
160         final String requestBody = getMockWebConnection().getLastWebRequest().getRequestBody();
161 
162         Assert.assertEquals("Unexpected Content-Type header", getExpectedAlerts()[0],
163                 contentType == null ? "null" : contentType);
164         Assert.assertEquals(getExpectedAlerts()[1], requestBody == null ? "null" : requestBody);
165     }
166 
167     /**
168      * @throws Exception if the test fails
169      */
170     @Test
171     @Alerts({"0", "127", "128", "255"})
172     public void sendXUserDefined() throws Exception {
173         final URL responseUrl = new URL(URL_FIRST, "/response");
174         getMockWebConnection().setResponse(responseUrl, "\u0000\u007f\u0080\u00ff");
175 
176         startWebServer(getMockWebConnection(), StandardCharsets.US_ASCII);
177 
178         final String html = DOCTYPE_HTML
179                 + "<html><head>"
180                 + "<script>\n"
181                 + LOG_TITLE_FUNCTION
182                 + "  function test() {\n"
183                 + "    try {\n"
184                 + "      var xhr = new XMLHttpRequest();\n"
185                 + "      xhr.open('POST', '/response', false);\n"
186                 + "      xhr.overrideMimeType('text/plain; charset=x-user-defined');\n"
187                 + "      xhr.send(null);\n"
188                 + "      log(xhr.responseText.charCodeAt(0) & 0xff);\n"
189                 + "      log(xhr.responseText.charCodeAt(1) & 0xff);\n"
190                 + "      log(xhr.responseText.charCodeAt(2) & 0xff);\n"
191                 + "      log(xhr.responseText.charCodeAt(3) & 0xff);\n"
192                 + "  } catch (exception) { \n"
193                 + "    log(exception);\n"
194                 + "  }\n"
195                 + "}\n"
196                 + "</script></head>\n"
197                 + "<body onload='test()'>\n"
198                 + "</body></html>";
199 
200         loadPageVerifyTitle2(html);
201     }
202 
203     /**
204      * @throws Exception if the test fails
205      */
206     @Test
207     @Alerts({"onreadystatechange [object Event]", "readystatechange", "1",
208              "NetworkError"})
209     public void sendLocalFile() throws Exception {
210         final URL fileURL = getClass().getClassLoader().getResource("testfiles/tiny-png.img");
211         final File file = new File(fileURL.toURI());
212         assertTrue("File '" + file.getAbsolutePath() + "' does not exist", file.exists());
213 
214         startWebServer(getMockWebConnection(), StandardCharsets.US_ASCII);
215 
216         final String html = DOCTYPE_HTML
217                 + "<html><head>"
218                 + "<script>\n"
219                 + LOG_TITLE_FUNCTION
220                 + "  function test() {\n"
221                 + "    try {\n"
222                 + "      var xhr = new XMLHttpRequest();\n"
223                 + "      xhr.onreadystatechange = function(event) {\n"
224                 + "                    log('onreadystatechange ' + event);\n"
225                 + "                    log(event.type);\n"
226                 + "                    log(xhr.readyState);\n"
227                 + "                  };\n"
228                 + "      xhr.onerror = function(event) {\n"
229                 + "                    log('error ' + event);\n"
230                 + "                    log(event.type);\n"
231                 + "                    log(event.lengthComputable);\n"
232                 + "                    log(event.loaded);\n"
233                 + "                    log(event.total);\n"
234                 + "                  };\n"
235                 + "      xhr.open('GET', '" + fileURL + "', false);\n"
236                 + "      xhr.send('');\n"
237                 + "  } catch (exception) { \n"
238                 + "    log(exception.name);\n"
239                 + "  }\n"
240                 + "}\n"
241                 + "</script></head>\n"
242                 + "<body onload='test()'>\n"
243                 + "</body></html>";
244 
245         loadPageVerifyTitle2(html);
246     }
247 
248     /**
249      * @throws Exception if the test fails
250      */
251     @Test
252     @Alerts({"onreadystatechange [object Event]", "readystatechange", "1",
253              "onreadystatechange [object Event]", "readystatechange", "4",
254              "error [object ProgressEvent]", "error", "false", "0", "0"})
255     public void sendLocalFileAsync() throws Exception {
256         final URL fileURL = getClass().getClassLoader().getResource("testfiles/tiny-png.img");
257         final File file = new File(fileURL.toURI());
258         assertTrue("File '" + file.getAbsolutePath() + "' does not exist", file.exists());
259 
260         startWebServer(getMockWebConnection(), StandardCharsets.US_ASCII);
261 
262         final String html = DOCTYPE_HTML
263                 + "<html><head>"
264                 + "<script>\n"
265                 + LOG_TITLE_FUNCTION
266                 + "  function test() {\n"
267                 + "    try {\n"
268                 + "      var xhr = new XMLHttpRequest();\n"
269                 + "      xhr.onreadystatechange = function(event) {\n"
270                 + "                    log('onreadystatechange ' + event);\n"
271                 + "                    log(event.type);\n"
272                 + "                    log(xhr.readyState);\n"
273                 + "                  };\n"
274                 + "      xhr.onerror = function(event) {\n"
275                 + "                    log('error ' + event);\n"
276                 + "                    log(event.type);\n"
277                 + "                    log(event.lengthComputable);\n"
278                 + "                    log(event.loaded);\n"
279                 + "                    log(event.total);\n"
280                 + "                  };\n"
281                 + "      xhr.open('GET', '" + fileURL + "', true);\n"
282                 + "      xhr.send('');\n"
283                 + "  } catch (exception) { \n"
284                 + "    log(exception);\n"
285                 + "  }\n"
286                 + "}\n"
287                 + "</script></head>\n"
288                 + "<body onload='test()'>\n"
289                 + "</body></html>";
290 
291         loadPage2(html);
292         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
293     }
294 
295     /**
296      * @throws Exception if the test fails
297      */
298     @Test
299     @Alerts({"application/xml;charset=UTF-8", "null"})
300     public void sendXMLDocumentEmpty() throws Exception {
301         final String createXmlDoc =
302                 "    var doc = document.implementation.createDocument('', '', null);\n";
303         sendXMLDocument(DOCTYPE_HTML, createXmlDoc, getExpectedAlerts()[0], getExpectedAlerts()[1]);
304     }
305 
306     /**
307      * @throws Exception if the test fails
308      */
309     @Test
310     @Alerts({"application/xml;charset=UTF-8", "<root/>"})
311     public void sendXMLDocumentRoot() throws Exception {
312         final String createXmlDoc =
313                 "    var doc = document.implementation.createDocument('', '', null);\n"
314                 + "  var root = doc.createElement('root');\n"
315                 + "  doc.appendChild(root);\n";
316         sendXMLDocument(DOCTYPE_HTML, createXmlDoc, getExpectedAlerts()[0], getExpectedAlerts()[1]);
317     }
318 
319     /**
320      * @throws Exception if the test fails
321      */
322     @Test
323     @Alerts({"application/xml;charset=UTF-8",
324              "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body id=\"abc\"></body></html>"})
325     @HtmlUnitNYI(CHROME = {"application/xml;charset=UTF-8",
326                            "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body id=\"abc\"/></html>"},
327             EDGE = {"application/xml;charset=UTF-8",
328                     "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body id=\"abc\"/></html>"},
329             FF = {"application/xml;charset=UTF-8",
330                   "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body id=\"abc\"/></html>"},
331             FF_ESR = {"application/xml;charset=UTF-8",
332                       "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body id=\"abc\"/></html>"})
333     public void sendXMLDocumentRootNamespace() throws Exception {
334         final String createXmlDoc =
335                 "    var doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);\n"
336                 + "  var body = document.createElementNS('http://www.w3.org/1999/xhtml', 'body');\n"
337                 + "  body.setAttribute('id', 'abc');\n"
338                 + "  doc.documentElement.appendChild(body);\n";
339         sendXMLDocument(DOCTYPE_HTML, createXmlDoc, getExpectedAlerts()[0], getExpectedAlerts()[1]);
340     }
341 
342     /**
343      * @throws Exception if the test fails
344      */
345     @Test
346     @Alerts({"text/html;charset=UTF-8",
347              "<!DOCTYPE html>"
348                     + "<html><head><title>foo</title><script>\n"
349                     + "  function test() {\n"
350                     + "    try {\n"
351                     + "    var doc = document;\n"
352                     + "      var xhr = new XMLHttpRequest();\n"
353                     + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
354                     + "      xhr.send(doc);\n"
355                     + "  } catch (exception) { \n"
356                     + "    alert(exception);\n"
357                     + "  }\n"
358                     + "}\n"
359                     + "</script></head>\n"
360                     + "<body onload=\"test()\">\n"
361                     + "</body></html>"})
362     @HtmlUnitNYI(CHROME = {"text/html;charset=UTF-8",
363                            "<!DOCTYPE html>"
364                              + "<html><head><title>foo</title><script>\n"
365                              + "  function test() {\n"
366                              + "    try {\n"
367                              + "    var doc = document;\n"
368                              + "      var xhr = new XMLHttpRequest();\n"
369                              + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
370                              + "      xhr.send(doc);\n"
371                              + "  } catch (exception) { \n"
372                              + "    alert(exception);\n"
373                              + "  }\n"
374                              + "}\n"
375                              + "</script></head>"
376                              + "<body onload=\"test()\">\n"
377                              + "</body></html>"},
378                  EDGE = {"text/html;charset=UTF-8",
379                          "<!DOCTYPE html>"
380                            + "<html><head><title>foo</title><script>\n"
381                            + "  function test() {\n"
382                            + "    try {\n"
383                            + "    var doc = document;\n"
384                            + "      var xhr = new XMLHttpRequest();\n"
385                            + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
386                            + "      xhr.send(doc);\n"
387                            + "  } catch (exception) { \n"
388                            + "    alert(exception);\n"
389                            + "  }\n"
390                            + "}\n"
391                            + "</script></head>"
392                            + "<body onload=\"test()\">\n"
393                            + "</body></html>"},
394                  FF = {"text/html;charset=UTF-8",
395                        "<!DOCTYPE html>"
396                            + "<html><head><title>foo</title><script>\n"
397                            + "  function test() {\n"
398                            + "    try {\n"
399                            + "    var doc = document;\n"
400                            + "      var xhr = new XMLHttpRequest();\n"
401                            + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
402                            + "      xhr.send(doc);\n"
403                            + "  } catch (exception) { \n"
404                            + "    alert(exception);\n"
405                            + "  }\n"
406                            + "}\n"
407                            + "</script></head>"
408                            + "<body onload=\"test()\">\n"
409                            + "</body></html>"},
410                  FF_ESR = {"text/html;charset=UTF-8",
411                            "<!DOCTYPE html>"
412                            + "<html><head><title>foo</title><script>\n"
413                            + "  function test() {\n"
414                            + "    try {\n"
415                            + "    var doc = document;\n"
416                            + "      var xhr = new XMLHttpRequest();\n"
417                            + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
418                            + "      xhr.send(doc);\n"
419                            + "  } catch (exception) { \n"
420                            + "    alert(exception);\n"
421                            + "  }\n"
422                            + "}\n"
423                            + "</script></head>"
424                            + "<body onload=\"test()\">\n"
425                            + "</body></html>"})
426     public void sendDocument() throws Exception {
427         final String createXmlDoc =
428                 "    var doc = document;\n";
429         sendXMLDocument(DOCTYPE_HTML, createXmlDoc, getExpectedAlerts()[0], getExpectedAlerts()[1]);
430     }
431 
432 
433     /**
434      * @throws Exception if the test fails
435      */
436     @Test
437     @Alerts({"text/html;charset=UTF-8",
438              "<html><head><title>foo</title><script>\n"
439                     + "  function test() {\n"
440                     + "    try {\n"
441                     + "    var doc = document;\n"
442                     + "      var xhr = new XMLHttpRequest();\n"
443                     + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
444                     + "      xhr.send(doc);\n"
445                     + "  } catch (exception) { \n"
446                     + "    alert(exception);\n"
447                     + "  }\n"
448                     + "}\n"
449                     + "</script></head>\n"
450                     + "<body onload=\"test()\">\n"
451                     + "</body></html>"})
452     @HtmlUnitNYI(CHROME = {"text/html;charset=UTF-8",
453                            "<html><head><title>foo</title><script>\n"
454                              + "  function test() {\n"
455                              + "    try {\n"
456                              + "    var doc = document;\n"
457                              + "      var xhr = new XMLHttpRequest();\n"
458                              + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
459                              + "      xhr.send(doc);\n"
460                              + "  } catch (exception) { \n"
461                              + "    alert(exception);\n"
462                              + "  }\n"
463                              + "}\n"
464                              + "</script></head>"
465                              + "<body onload=\"test()\">\n"
466                              + "</body></html>"},
467                  EDGE = {"text/html;charset=UTF-8",
468                          "<html><head><title>foo</title><script>\n"
469                            + "  function test() {\n"
470                            + "    try {\n"
471                            + "    var doc = document;\n"
472                            + "      var xhr = new XMLHttpRequest();\n"
473                            + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
474                            + "      xhr.send(doc);\n"
475                            + "  } catch (exception) { \n"
476                            + "    alert(exception);\n"
477                            + "  }\n"
478                            + "}\n"
479                            + "</script></head>"
480                            + "<body onload=\"test()\">\n"
481                            + "</body></html>"},
482                  FF = {"text/html;charset=UTF-8",
483                        "<html><head><title>foo</title><script>\n"
484                            + "  function test() {\n"
485                            + "    try {\n"
486                            + "    var doc = document;\n"
487                            + "      var xhr = new XMLHttpRequest();\n"
488                            + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
489                            + "      xhr.send(doc);\n"
490                            + "  } catch (exception) { \n"
491                            + "    alert(exception);\n"
492                            + "  }\n"
493                            + "}\n"
494                            + "</script></head>"
495                            + "<body onload=\"test()\">\n"
496                            + "</body></html>"},
497                  FF_ESR = {"text/html;charset=UTF-8",
498                            "<html><head><title>foo</title><script>\n"
499                            + "  function test() {\n"
500                            + "    try {\n"
501                            + "    var doc = document;\n"
502                            + "      var xhr = new XMLHttpRequest();\n"
503                            + "      xhr.open('POST', 'http://localhost:22222/second/', false);\n"
504                            + "      xhr.send(doc);\n"
505                            + "  } catch (exception) { \n"
506                            + "    alert(exception);\n"
507                            + "  }\n"
508                            + "}\n"
509                            + "</script></head>"
510                            + "<body onload=\"test()\">\n"
511                            + "</body></html>"})
512     public void sendDocumentNoDoctype() throws Exception {
513         final String createXmlDoc =
514                 "    var doc = document;\n";
515         sendXMLDocument("", createXmlDoc, getExpectedAlerts()[0], getExpectedAlerts()[1]);
516     }
517 
518     private void sendXMLDocument(final String doctype, final String createXmlDoc,
519             final String expectedMimeType, final String expectedBody) throws Exception {
520         startWebServer(getMockWebConnection(), Charset.defaultCharset());
521         final String url = URL_SECOND.toString();
522 
523         final String html = doctype
524                 + "<html><head><title>foo</title>"
525                 + "<script>\n"
526                 + "  function test() {\n"
527                 + "    try {\n"
528 
529                 + createXmlDoc
530 
531                 + "      var xhr = new XMLHttpRequest();\n"
532                 + "      xhr.open('POST', '" + url + "', false);\n"
533                 + "      xhr.send(doc);\n"
534                 + "  } catch (exception) { \n"
535                 + "    alert(exception);\n"
536                 + "  }\n"
537                 + "}\n"
538                 + "</script></head>\n"
539                 + "<body onload='test()'>\n"
540                 + "</body></html>";
541 
542         setExpectedAlerts();
543         loadPageWithAlerts2(html);
544 
545         Assert.assertEquals("Never received a call to URL_SECOND", URL_SECOND,
546                 getMockWebConnection().getLastWebRequest().getUrl());
547 
548         final String contentType = getMockWebConnection().getLastWebRequest()
549                 .getAdditionalHeader(HttpHeader.CONTENT_TYPE);
550 
551         final String requestBody = getMockWebConnection().getLastWebRequest().getRequestBody();
552 
553         Assert.assertEquals("Unexpected Content-Type header", expectedMimeType,
554                 contentType == null ? "null" : contentType);
555         Assert.assertEquals(expectedBody, requestBody == null ? "null" : requestBody);
556     }
557 }