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