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.BufferedReader;
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.Writer;
24  import java.util.Arrays;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  import javax.servlet.Servlet;
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServlet;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import org.apache.commons.io.FileUtils;
36  import org.apache.commons.lang3.StringUtils;
37  import org.htmlunit.HttpHeader;
38  import org.htmlunit.WebDriverTestCase;
39  import org.htmlunit.junit.annotation.Alerts;
40  import org.htmlunit.junit.annotation.HtmlUnitNYI;
41  import org.htmlunit.util.MimeType;
42  import org.junit.jupiter.api.Test;
43  import org.openqa.selenium.By;
44  import org.openqa.selenium.WebDriver;
45  
46  /**
47   * Tests for {@link FormData}.
48   *
49   * @author Ahmed Ashour
50   * @author Ronald Brill
51   * @author Frank Danek
52   * @author Thorsten Wendelmuth
53   * @author Lai Quang Duong
54   */
55  public class FormDataTest extends WebDriverTestCase {
56  
57      /**
58       * @throws Exception if the test fails
59       */
60      @Test
61      @Alerts({"function", "function", "function", "function", "function", "function",
62               "function", "function", "function", "function"})
63      public void functions() throws Exception {
64          final String html = DOCTYPE_HTML
65              + "<html><head><script>\n"
66              + LOG_TITLE_FUNCTION
67              + "  function test() {\n"
68              + "    if (window.FormData) {\n"
69              + "      var formData = new FormData();\n"
70              + "      log(typeof formData.append);\n"
71              + "      log(typeof formData.delete);\n"
72              + "      log(typeof formData.entries);\n"
73              + "      log(typeof formData.forEach);\n"
74              + "      log(typeof formData.get);\n"
75              + "      log(typeof formData.getAll);\n"
76              + "      log(typeof formData.has);\n"
77              + "      log(typeof formData.keys);\n"
78              + "      log(typeof formData.set);\n"
79              + "      log(typeof formData.values);\n"
80              + "    }\n"
81              + "  }\n"
82              + "</script></head><body onload='test()'>\n"
83              + "</body></html>";
84          loadPageVerifyTitle2(html);
85      }
86  
87      /**
88       * @throws Exception if an error occurs
89       */
90      @Test
91      public void empty() throws Exception {
92          final String html = DOCTYPE_HTML
93              + "<html><head><title>foo</title><script>\n"
94              + "function test() {\n"
95              + "  try {\n"
96              + "    var formData = new FormData();\n"
97              + "  } catch(e) {\n"
98              + "    alert('create: ' + e.message);\n"
99              + "    return;\n"
100             + "  }\n"
101             + "  try {\n"
102             + "    var xhr = new XMLHttpRequest();\n"
103             + "    xhr.open('POST', '/test2', false);\n"
104             + "    xhr.send(formData);\n"
105             + "    alert(xhr.responseText);\n"
106             + "  } catch(e) {\n"
107             + "    alert('send: ' + e.message);\n"
108             + "  }\n"
109             + "}\n"
110             + "</script></head><body onload='test()'></body></html>";
111 
112         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
113         servlets.put("/test2", PostServlet.class);
114 
115         final WebDriver driver = loadPage2(html, servlets);
116         final List<String> alerts = getCollectedAlerts(driver, 1);
117         if (!alerts.isEmpty()) {
118             String[] lines = alerts.get(0).split("\\n");
119             if (lines.length == 2 && lines[0].isEmpty()) {
120                 // response of IE contains an additional empty line -> remove
121                 lines = Arrays.copyOfRange(lines, 1, 2);
122             }
123             assertEquals("Response: " + alerts.get(0) + "; line count", 1, lines.length);
124             assertTrue(lines[0].startsWith("--") && lines[0].endsWith("--"));
125         }
126     }
127 
128     /**
129      * @throws Exception if an error occurs
130      */
131     @Test
132     public void append() throws Exception {
133         final String html = DOCTYPE_HTML
134             + "<html><head><title>foo</title><script>\n"
135             + "function test() {\n"
136             + "  try {\n"
137             + "    var formData = new FormData();\n"
138             + "    formData.append('myKey', 'myValue');\n"
139             + "    formData.append('myKey', 'myValue2');\n"
140             + "    formData.append('myKeyNull', null);\n"
141             + "    formData.append('myKeyUndef', undefined);\n"
142             + "    formData.append('myKeyEmpty', '');\n"
143             + "  } catch(e) {\n"
144             + "    alert('create: ' + e.message);\n"
145             + "    return;\n"
146             + "  }\n"
147             + "  try {\n"
148             + "    var xhr = new XMLHttpRequest();\n"
149             + "    xhr.open('POST', '/test2', false);\n"
150             + "    xhr.send(formData);\n"
151             + "    alert(xhr.responseText);\n"
152             + "  } catch(e) {\n"
153             + "    alert('send: ' + e.message);\n"
154             + "  }\n"
155             + "}\n"
156             + "</script></head><body onload='test()'></body></html>";
157 
158         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
159         servlets.put("/test2", PostServlet.class);
160 
161         final WebDriver driver = loadPage2(html, servlets);
162         final List<String> alerts = getCollectedAlerts(driver, 1);
163         if (!alerts.isEmpty()) {
164             final String[] lines = alerts.get(0).split("\\n");
165             assertEquals("Response: " + alerts.get(0) + "; line count", 21, lines.length);
166             assertEquals("Content-Disposition: form-data; name=\"myKey\"", lines[1]);
167             assertEquals("", lines[2]);
168             assertEquals("myValue", lines[3]);
169             assertEquals(lines[0], lines[4]);
170             assertEquals("Content-Disposition: form-data; name=\"myKey\"", lines[5]);
171             assertEquals("", lines[6]);
172             assertEquals("myValue2", lines[7]);
173             assertEquals(lines[0], lines[8]);
174             assertEquals("Content-Disposition: form-data; name=\"myKeyNull\"", lines[9]);
175             assertEquals("", lines[10]);
176             assertEquals("null", lines[11]);
177             assertEquals(lines[0], lines[12]);
178             assertEquals("Content-Disposition: form-data; name=\"myKeyUndef\"", lines[13]);
179             assertEquals("", lines[14]);
180             assertEquals("undefined", lines[15]);
181             assertEquals(lines[0], lines[16]);
182             assertEquals("Content-Disposition: form-data; name=\"myKeyEmpty\"", lines[17]);
183             assertEquals("", lines[18]);
184             assertEquals("", lines[19]);
185             assertEquals(lines[0] + "--", lines[20]);
186         }
187     }
188 
189     /**
190      * @throws Exception if the test fails
191      */
192     @Test
193     public void appendFile() throws Exception {
194         final String html = DOCTYPE_HTML
195             + "<html>\n"
196             + "<head><title>foo</title>\n"
197             + "<script>\n"
198             + "function test() {\n"
199             + "  try {\n"
200             + "    var files = document.testForm.myFile.files;\n"
201             + "    var formData = new FormData();\n"
202             + "    formData.append('myKey', files[0]);\n"
203             + "  } catch(e) {\n"
204             + "    alert('create: ' + e.message);\n"
205             + "    return;\n"
206             + "  }\n"
207             + "  try {\n"
208             + "    var xhr = new XMLHttpRequest();\n"
209             + "    xhr.open('POST', '/test2', false);\n"
210             + "    xhr.send(formData);\n"
211             + "    alert(xhr.responseText);\n"
212             + "  } catch(e) {\n"
213             + "    alert('send: ' + e.message);\n"
214             + "  }\n"
215             + "}\n"
216             + "</script>\n"
217             + "</head>\n"
218             + "<body>\n"
219             + "  <form name='testForm'>\n"
220             + "    <input type='file' id='myFile' name='myFile'>\n"
221             + "  </form>\n"
222             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
223             + "</body>\n"
224             + "</html>";
225 
226         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
227         servlets.put("/test2", PostServlet.class);
228 
229         final WebDriver driver = loadPage2(html, servlets);
230 
231         final File tstFile = File.createTempFile("HtmlUnitUploadTest", ".txt");
232         try {
233             FileUtils.writeStringToFile(tstFile, "Hello HtmlUnit", ISO_8859_1);
234 
235             final String path = tstFile.getCanonicalPath();
236             driver.findElement(By.name("myFile")).sendKeys(path);
237 
238             driver.findElement(By.id("testBtn")).click();
239 
240             final List<String> alerts = getCollectedAlerts(driver, 1);
241             if (!alerts.isEmpty()) {
242                 final String[] lines = alerts.get(0).split("\\n");
243                 assertEquals(6, lines.length);
244                 assertEquals("Content-Disposition: form-data; name=\"myKey\"; filename=\""
245                                 + tstFile.getName() + "\"", lines[1]);
246                 assertEquals("Content-Type: text/plain", lines[2]);
247                 assertEquals("", lines[3]);
248                 assertEquals("Hello HtmlUnit", lines[4]);
249                 assertEquals(lines[0] + "--", lines[5]);
250             }
251         }
252         finally {
253             FileUtils.deleteQuietly(tstFile);
254         }
255     }
256 
257     /**
258      * @throws Exception if the test fails
259      */
260     @Test
261     public void appendFileWithFileName() throws Exception {
262         final String alerts = appendFile(".txt", "myFileName");
263         final String[] lines = alerts.split("\\n");
264         assertEquals(6, lines.length);
265         assertEquals("Content-Disposition: form-data; name=\"myKey\"; filename=\"myFileName\"", lines[1]);
266         assertEquals("Content-Type: text/plain", lines[2]);
267         assertEquals("", lines[3]);
268         assertEquals("Hello HtmlUnit", lines[4]);
269         assertEquals(lines[0] + "--", lines[5]);
270     }
271 
272     /**
273      * @throws Exception if the test fails
274      */
275     @Test
276     public void appendFileWithEmptyFileName() throws Exception {
277         final String alerts = appendFile(".txt", "");
278 
279         final String[] lines = alerts.split("\\n");
280         assertEquals(6, lines.length);
281         assertEquals("Content-Disposition: form-data; name=\"myKey\"; filename=\"\"", lines[1]);
282         assertEquals("Content-Type: text/plain", lines[2]);
283         assertEquals("", lines[3]);
284         assertEquals("Hello HtmlUnit", lines[4]);
285         assertEquals(lines[0] + "--", lines[5]);
286     }
287 
288     /**
289      * @throws Exception if the test fails
290      */
291     @Test
292     @Alerts({"Content-Disposition: form-data; name=\"myKey\"; filename=\"test\"",
293              "Content-Type: application/octet-stream", "", "Hello HtmlUnit"})
294     public void appendFileWithUnknownExtension() throws Exception {
295         final String alerts = appendFile(".htmlunit", "test");
296 
297         final String[] expectedAlerts = getExpectedAlerts();
298         final String[] lines = alerts.split("\\n");
299         assertEquals(6, lines.length);
300         assertEquals(expectedAlerts[0], lines[1]);
301         assertEquals(expectedAlerts[1], lines[2]);
302         assertEquals(expectedAlerts[2], lines[3]);
303         assertEquals(expectedAlerts[3], lines[4]);
304         assertEquals(lines[0] + "--", lines[5]);
305     }
306 
307     /**
308      * @throws Exception if the test fails
309      */
310     @Test
311     @Alerts({"Content-Disposition: form-data; name=\"myKey\"; filename=\"test\"",
312              "Content-Type: application/octet-stream", "", "Hello HtmlUnit"})
313     public void appendFileWithoutExtension() throws Exception {
314         final String alerts = appendFile("", "test");
315 
316         final String[] expectedAlerts = getExpectedAlerts();
317         final String[] lines = alerts.split("\\n");
318         assertEquals(6, lines.length);
319         assertEquals(expectedAlerts[0], lines[1]);
320         assertEquals(expectedAlerts[1], lines[2]);
321         assertEquals(expectedAlerts[2], lines[3]);
322         assertEquals(expectedAlerts[3], lines[4]);
323         assertEquals(lines[0] + "--", lines[5]);
324     }
325 
326     private String appendFile(final String extension, final String name) throws Exception {
327         final String html = DOCTYPE_HTML
328             + "<html>\n"
329             + "<head><title>foo</title>\n"
330             + "<script>\n"
331             + "function test() {\n"
332             + "  try {\n"
333             + "    var files = document.testForm.myFile.files;\n"
334             + "    var formData = new FormData();\n"
335             + "    formData.append('myKey', files[0], '" + name + "');\n"
336             + "  } catch(e) {\n"
337             + "    alert('create: ' + e.message);\n"
338             + "    return;\n"
339             + "  }\n"
340             + "  try {\n"
341             + "    var xhr = new XMLHttpRequest();\n"
342             + "    xhr.open('POST', '/test2', false);\n"
343             + "    xhr.send(formData);\n"
344             + "    alert(xhr.responseText);\n"
345             + "  } catch(e) {\n"
346             + "    alert('send: ' + e.message);\n"
347             + "  }\n"
348             + "}\n"
349             + "</script>\n"
350             + "</head>\n"
351             + "<body>\n"
352             + "  <form name='testForm'>\n"
353             + "    <input type='file' id='myFile' name='myFile'>\n"
354             + "  </form>\n"
355             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
356             + "</body>\n"
357             + "</html>";
358 
359         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
360         servlets.put("/test2", PostServlet.class);
361 
362         final WebDriver driver = loadPage2(html, servlets);
363 
364         final File tstFile = File.createTempFile("HtmlUnitUploadTest", extension);
365         try {
366             FileUtils.writeStringToFile(tstFile, "Hello HtmlUnit", ISO_8859_1);
367 
368             final String path = tstFile.getCanonicalPath();
369             driver.findElement(By.name("myFile")).sendKeys(path);
370 
371             driver.findElement(By.id("testBtn")).click();
372 
373             final List<String> alerts = getCollectedAlerts(driver, 1);
374             return alerts.get(0);
375         }
376         finally {
377             FileUtils.deleteQuietly(tstFile);
378         }
379     }
380 
381     /**
382      * @throws Exception if the test fails
383      */
384     @Test
385     public void appendInMemoryFile() throws Exception {
386         final String html = DOCTYPE_HTML
387             + "<html>\n"
388             + "<head><title>foo</title>\n"
389             + "<script>\n"
390             + "function test() {\n"
391             + "  try {\n"
392             + "    var formData = new FormData();\n"
393             + "    let file = new File(['Html', 'Unit', 'is great'], 'htMluniT.txt');\n"
394             + "    formData.append('myKey', file);\n"
395             + "  } catch(e) {\n"
396             + "    alert('create: ' + e.message);\n"
397             + "    return;\n"
398             + "  }\n"
399             + "  try {\n"
400             + "    var xhr = new XMLHttpRequest();\n"
401             + "    xhr.open('POST', '/test2', false);\n"
402             + "    xhr.send(formData);\n"
403             + "    alert(xhr.responseText);\n"
404             + "  } catch(e) {\n"
405             + "    alert('send: ' + e.message);\n"
406             + "  }\n"
407             + "}\n"
408             + "</script>\n"
409             + "</head>\n"
410             + "<body>\n"
411             + "  <form name='testForm'>\n"
412             + "  </form>\n"
413             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
414             + "</body>\n"
415             + "</html>";
416 
417         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
418         servlets.put("/test2", PostServlet.class);
419 
420         final WebDriver driver = loadPage2(html, servlets);
421 
422         driver.findElement(By.id("testBtn")).click();
423 
424         final List<String> alerts = getCollectedAlerts(driver, 1);
425         if (!alerts.isEmpty()) {
426             final String[] lines = alerts.get(0).split("\\n");
427             assertEquals(6, lines.length);
428             assertEquals("Content-Disposition: form-data; name=\"myKey\"; filename=\"htMluniT.txt\"", lines[1]);
429             assertEquals("Content-Type: application/octet-stream", lines[2]);
430             assertEquals("", lines[3]);
431             assertEquals("HtmlUnitis great", lines[4]);
432             assertEquals(lines[0] + "--", lines[5]);
433         }
434     }
435 
436     /**
437      * @throws Exception if the test fails
438      */
439     @Test
440     public void appendBlob() throws Exception {
441         final String html = DOCTYPE_HTML
442             + "<html>\n"
443             + "<head><title>foo</title>\n"
444             + "<script>\n"
445             + "function test() {\n"
446             + "  try {\n"
447             + "    var formData = new FormData();\n"
448             + "    let blob = new Blob(['Hello HtmlUnit'], {type : 'text/html'});\n"
449             + "    formData.append('myKey', blob);\n"
450             + "  } catch(e) {\n"
451             + "    alert('create: ' + e.message);\n"
452             + "    return;\n"
453             + "  }\n"
454             + "  try {\n"
455             + "    var xhr = new XMLHttpRequest();\n"
456             + "    xhr.open('POST', '/test2', false);\n"
457             + "    xhr.send(formData);\n"
458             + "    alert(xhr.responseText);\n"
459             + "  } catch(e) {\n"
460             + "    alert('send: ' + e.message);\n"
461             + "  }\n"
462             + "}\n"
463             + "</script>\n"
464             + "</head>\n"
465             + "<body>\n"
466             + "  <form name='testForm'>\n"
467             + "  </form>\n"
468             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
469             + "</body>\n"
470             + "</html>";
471 
472         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
473         servlets.put("/test2", PostServlet.class);
474 
475         final WebDriver driver = loadPage2(html, servlets);
476 
477         driver.findElement(By.id("testBtn")).click();
478 
479         final List<String> alerts = getCollectedAlerts(driver, 1);
480         if (!alerts.isEmpty()) {
481             final String[] lines = alerts.get(0).split("\\n");
482             assertEquals(6, lines.length);
483             assertEquals("Content-Disposition: form-data; name=\"myKey\"; filename=\"blob\"", lines[1]);
484             assertEquals("Content-Type: text/html", lines[2]);
485             assertEquals("", lines[3]);
486             assertEquals("Hello HtmlUnit", lines[4]);
487             assertEquals(lines[0] + "--", lines[5]);
488         }
489     }
490 
491     /**
492      * @throws Exception if an error occurs
493      */
494     @Test
495     @Alerts({"myKey", "myKey1"})
496     public void delete() throws Exception {
497         final String html = DOCTYPE_HTML
498             + "<html><head><title>foo</title><script>\n"
499             + "function test() {\n"
500             + "  try {\n"
501             + "    var formData = new FormData();\n"
502             + "    if (!formData.delete) { alert('no delete'); return; }\n"
503 
504             + "    formData.append('myKey', 'myValue');\n"
505             + "    formData.append('myKey1', '');\n"
506             + "    formData.append('mykey 2', '');\n"
507             + "    formData.append('mykey3', 'myVal3');\n"
508             + "    formData.append('mykey3', 'myVal4');\n"
509             + "  } catch(e) {\n"
510             + "    alert('create: ' + e.message);\n"
511             + "    return;\n"
512             + "  }\n"
513 
514             + "  try {\n"
515             + "    formData.delete('mykey');\n"
516             + "    formData.delete('mykey 2');\n"
517             + "    formData.delete('mykey3');\n"
518             + "    formData.delete('');\n"
519             + "  } catch(e) {\n"
520             + "    alert('delete: ' + e.message);\n"
521             + "    return;\n"
522             + "  }\n"
523             + "  try {\n"
524             + "    var xhr = new XMLHttpRequest();\n"
525             + "    xhr.open('POST', '/test2', false);\n"
526             + "    xhr.send(formData);\n"
527             + "    alert(xhr.responseText);\n"
528             + "  } catch(e) {\n"
529             + "    alert('send: ' + e.message);\n"
530             + "  }\n"
531             + "}\n"
532             + "</script></head><body onload='test()'></body></html>";
533 
534         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
535         servlets.put("/test2", PostServlet.class);
536         final WebDriver driver = loadPage2(html, servlets);
537         final String alerts = getCollectedAlerts(driver, 1).get(0);
538         for (final String expected : getExpectedAlerts()) {
539             assertTrue(expected + " not found", alerts.contains(expected));
540         }
541     }
542 
543     /**
544      * @throws Exception if an error occurs
545      */
546     @Test
547     @Alerts({"myValue", "null", "null", "null", "null"})
548     public void get() throws Exception {
549         final String html = DOCTYPE_HTML
550             + "<html><head>\n"
551             + "<script>\n"
552             + LOG_TITLE_FUNCTION
553             + "function test() {\n"
554             + "  try {\n"
555             + "    var formData = new FormData();\n"
556             + "    if (!formData.get) { log('no get'); return; }\n"
557 
558             + "    formData.append('myKey', 'myValue');\n"
559             + "    formData.append('myKey', 'myValue2');\n"
560             + "    formData.append('mykey3', 'myValue3');\n"
561             + "    formData.append('mykey4', '');\n"
562             + "  } catch(e) {\n"
563             + "    log('create: ' + e.message);\n"
564             + "    return;\n"
565             + "  }\n"
566 
567             + "  try {\n"
568             + "    log(formData.get('myKey'));\n"
569             + "    log(formData.get('mykey'));\n"
570             + "    log(formData.get('myKey3'));\n"
571             + "    log(formData.get('myKey4'));\n"
572             + "    log(formData.get(''));\n"
573             + "  } catch(e) {\n"
574             + "    log('get: ' + e.message);\n"
575             + "    return;\n"
576             + "  }\n"
577             + "}\n"
578             + "</script></head><body onload='test()'></body></html>";
579 
580         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
581         servlets.put("/test2", PostServlet.class);
582 
583         loadPage2(html, URL_FIRST, servlets);
584         verifyTitle2(getWebDriver(), getExpectedAlerts());
585     }
586 
587     /**
588      * @throws Exception if an error occurs
589      */
590     @Test
591     @Alerts({"myValue,myValue2", "", "", "", ""})
592     public void getAll() throws Exception {
593         final String html = DOCTYPE_HTML
594             + "<html><head>\n"
595             + "<script>\n"
596             + LOG_TITLE_FUNCTION
597             + "function test() {\n"
598             + "  try {\n"
599             + "    var formData = new FormData();\n"
600             + "    if (!formData.get) { log('no getAll'); return; }\n"
601 
602             + "    formData.append('myKey', 'myValue');\n"
603             + "    formData.append('myKey', 'myValue2');\n"
604             + "    formData.append('mykey3', 'myValue3');\n"
605             + "    formData.append('mykey4', '');\n"
606             + "  } catch(e) {\n"
607             + "    log('create: ' + e.message);\n"
608             + "    return;\n"
609             + "  }\n"
610 
611             + "  try {\n"
612             + "    log(formData.getAll('myKey'));\n"
613             + "    log(formData.getAll('mykey'));\n"
614             + "    log(formData.getAll('myKey3'));\n"
615             + "    log(formData.getAll('myKey4'));\n"
616             + "    log(formData.getAll(''));\n"
617             + "  } catch(e) {\n"
618             + "    log('getAll: ' + e.message);\n"
619             + "    return;\n"
620             + "  }\n"
621             + "}\n"
622             + "</script></head><body onload='test()'></body></html>";
623 
624         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
625         servlets.put("/test2", PostServlet.class);
626 
627         loadPage2(html, URL_FIRST, servlets);
628         verifyTitle2(getWebDriver(), getExpectedAlerts());
629     }
630 
631     /**
632      * @throws Exception if an error occurs
633      */
634     @Test
635     @Alerts({"true", "false", "false"})
636     public void has() throws Exception {
637         final String html = DOCTYPE_HTML
638             + "<html><head>\n"
639             + "<script>\n"
640             + LOG_TITLE_FUNCTION
641             + "function test() {\n"
642             + "  try {\n"
643             + "    var formData = new FormData();\n"
644             + "    if (!formData.has) { log('no has'); return; }\n"
645 
646             + "    formData.append('myKey', 'myValue');\n"
647             + "    formData.append('myKey1', '');\n"
648             + "    formData.append('mykey 2', '');\n"
649             + "  } catch(e) {\n"
650             + "    log('create: ' + e.message);\n"
651             + "    return;\n"
652             + "  }\n"
653 
654             + "  try {\n"
655             + "    log(formData.has('myKey'));\n"
656             + "    log(formData.has('mykey'));\n"
657             + "    log(formData.has(''));\n"
658             + "  } catch(e) {\n"
659             + "    log('has: ' + e.message);\n"
660             + "  }\n"
661             + "}\n"
662             + "</script></head><body onload='test()'></body></html>";
663 
664         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
665         servlets.put("/test2", PostServlet.class);
666 
667         loadPageVerifyTitle2(html);
668     }
669 
670     /**
671      * @throws Exception if an error occurs
672      */
673     @Test
674     @Alerts(DEFAULT = "no set",
675             FF = "")
676     public void set() throws Exception {
677         final String html = DOCTYPE_HTML
678             + "<html><head><title>foo</title><script>\n"
679             + "function test() {\n"
680             + "  try {\n"
681             + "    var formData = new FormData();\n"
682             + "    if (!formData.set) { alert('no set'); return; }\n"
683             + "    formData.append('myKey1', 'myValue1');\n"
684             + "    formData.append('myKey1', 'myValue2');\n"
685             + "    formData.append('myKey3', 'myValue3');\n"
686             + "    formData.append('myKey4', 'myValue4');\n"
687             + "    formData.set('myKey1', 'new1');\n"
688             + "    formData.set('myKey4', 'new4');\n"
689             + "    formData.set('myKeyX', 'newX');\n"
690             + "  } catch(e) {\n"
691             + "    alert('set: ' + e.message);\n"
692             + "    return;\n"
693             + "  }\n"
694             + "  try {\n"
695             + "    var xhr = new XMLHttpRequest();\n"
696             + "    xhr.open('POST', '/test2', false);\n"
697             + "    xhr.send(formData);\n"
698             + "    alert(xhr.responseText);\n"
699             + "  } catch(e) {\n"
700             + "    alert('send: ' + e.message);\n"
701             + "  }\n"
702             + "}\n"
703             + "</script></head><body onload='test()'></body></html>";
704 
705         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
706         servlets.put("/test2", PostServlet.class);
707 
708         final WebDriver driver = loadPage2(html, servlets);
709         final List<String> alerts = getCollectedAlerts(driver, 1);
710 
711         if (alerts.get(0).equals("no set")) {
712             assertEquals(getExpectedAlerts(), alerts);
713             return;
714         }
715 
716         final String[] lines = alerts.get(0).split("\\n");
717         assertEquals("Response: " + alerts.get(0) + "; line count", 17, lines.length);
718         assertEquals("Content-Disposition: form-data; name=\"myKey1\"", lines[1]);
719         assertEquals("", lines[2]);
720         assertEquals("new1", lines[3]);
721         assertEquals(lines[0], lines[4]);
722         assertEquals("Content-Disposition: form-data; name=\"myKey3\"", lines[5]);
723         assertEquals("", lines[6]);
724         assertEquals("myValue3", lines[7]);
725         assertEquals(lines[0], lines[8]);
726         assertEquals("Content-Disposition: form-data; name=\"myKey4\"", lines[9]);
727         assertEquals("", lines[10]);
728         assertEquals("new4", lines[11]);
729         assertEquals(lines[0], lines[12]);
730         assertEquals("Content-Disposition: form-data; name=\"myKeyX\"", lines[13]);
731         assertEquals("", lines[14]);
732         assertEquals("newX", lines[15]);
733         assertEquals(lines[0] + "--", lines[16]);
734     }
735 
736     /**
737      * @throws Exception if the test fails
738      */
739     @Test
740     public void setInMemoryFile() throws Exception {
741         final String html = DOCTYPE_HTML
742             + "<html>\n"
743             + "<head><title>foo</title>\n"
744             + "<script>\n"
745             + "function test() {\n"
746             + "  try {\n"
747             + "    var formData = new FormData();\n"
748             + "    let file = new File(['Html', 'Unit', 'is great'], 'htMluniT.txt');\n"
749             + "    formData.set('myKey', file);\n"
750             + "  } catch(e) {\n"
751             + "    alert('create: ' + e.message);\n"
752             + "    return;\n"
753             + "  }\n"
754             + "  try {\n"
755             + "    var xhr = new XMLHttpRequest();\n"
756             + "    xhr.open('POST', '/test2', false);\n"
757             + "    xhr.send(formData);\n"
758             + "    alert(xhr.responseText);\n"
759             + "  } catch(e) {\n"
760             + "    alert('send: ' + e.message);\n"
761             + "  }\n"
762             + "}\n"
763             + "</script>\n"
764             + "</head>\n"
765             + "<body>\n"
766             + "  <form name='testForm'>\n"
767             + "  </form>\n"
768             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
769             + "</body>\n"
770             + "</html>";
771 
772         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
773         servlets.put("/test2", PostServlet.class);
774 
775         final WebDriver driver = loadPage2(html, servlets);
776 
777         driver.findElement(By.id("testBtn")).click();
778 
779         final List<String> alerts = getCollectedAlerts(driver, 1);
780         if (!alerts.isEmpty()) {
781             final String[] lines = alerts.get(0).split("\\n");
782             assertEquals(6, lines.length);
783             assertEquals("Content-Disposition: form-data; name=\"myKey\"; filename=\"htMluniT.txt\"", lines[1]);
784             assertEquals("Content-Type: application/octet-stream", lines[2]);
785             assertEquals("", lines[3]);
786             assertEquals("HtmlUnitis great", lines[4]);
787             assertEquals(lines[0] + "--", lines[5]);
788         }
789     }
790 
791     /**
792      * @throws Exception if the test fails
793      */
794     @Test
795     public void setBlob() throws Exception {
796         final String html = DOCTYPE_HTML
797             + "<html>\n"
798             + "<head><title>foo</title>\n"
799             + "<script>\n"
800             + "function test() {\n"
801             + "  try {\n"
802             + "    var formData = new FormData();\n"
803             + "    let blob = new Blob(['Hello HtmlUnit'], {type : 'text/html'});\n"
804             + "    formData.set('myKey', blob);\n"
805             + "  } catch(e) {\n"
806             + "    alert('create: ' + e.message);\n"
807             + "    return;\n"
808             + "  }\n"
809             + "  try {\n"
810             + "    var xhr = new XMLHttpRequest();\n"
811             + "    xhr.open('POST', '/test2', false);\n"
812             + "    xhr.send(formData);\n"
813             + "    alert(xhr.responseText);\n"
814             + "  } catch(e) {\n"
815             + "    alert('send: ' + e.message);\n"
816             + "  }\n"
817             + "}\n"
818             + "</script>\n"
819             + "</head>\n"
820             + "<body>\n"
821             + "  <form name='testForm'>\n"
822             + "  </form>\n"
823             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
824             + "</body>\n"
825             + "</html>";
826 
827         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
828         servlets.put("/test2", PostServlet.class);
829 
830         final WebDriver driver = loadPage2(html, servlets);
831 
832         driver.findElement(By.id("testBtn")).click();
833 
834         final List<String> alerts = getCollectedAlerts(driver, 1);
835         if (!alerts.isEmpty()) {
836             final String[] lines = alerts.get(0).split("\\n");
837             assertEquals(6, lines.length);
838             assertEquals("Content-Disposition: form-data; name=\"myKey\"; filename=\"blob\"", lines[1]);
839             assertEquals("Content-Type: text/html", lines[2]);
840             assertEquals("", lines[3]);
841             assertEquals("Hello HtmlUnit", lines[4]);
842             assertEquals(lines[0] + "--", lines[5]);
843         }
844     }
845 
846     /**
847      * @throws Exception if the test fails
848      */
849     @Test
850     public void fromForm() throws Exception {
851         final String html = DOCTYPE_HTML
852             + "<html>\n"
853             + "<head><title>foo</title>\n"
854             + "<script>\n"
855             + "function test() {\n"
856             + "  try {\n"
857             + "    var formData = new FormData(document.testForm);\n"
858             + "  } catch(e) {\n"
859             + "    alert('create: ' + e.message);\n"
860             + "  }\n"
861             + "  try {\n"
862             + "    var xhr = new XMLHttpRequest();\n"
863             + "    xhr.open('POST', '/test2', false);\n"
864             + "    xhr.send(formData);\n"
865             + "    alert(xhr.responseText);\n"
866             + "  } catch(e) {\n"
867             + "    alert('send: ' + e.message);\n"
868             + "  }\n"
869             + "}\n"
870             + "</script>\n"
871             + "</head>\n"
872             + "<body>\n"
873             + "  <form name='testForm'>\n"
874             + "    <input type='text' id='myText' name='myText' value='textxy'>\n"
875             + "    <input type='file' id='myFile' name='myFile'>\n"
876             + "    <input type='hidden' id='myHidden' name='myHidden' value='hiddenxy'>\n"
877             + "    <input type='button' id='myButton' name='myButton' value='buttonxy'>\n"
878             + "    <input type='submit' id='mySubmit' name='mySubmit' value='submitxy'>\n"
879             + "  </form>\n"
880             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
881             + "</body>\n"
882             + "</html>";
883 
884         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
885         servlets.put("/test2", PostServlet.class);
886 
887         final WebDriver driver = loadPage2(html, servlets);
888 
889         final File tstFile = File.createTempFile("HtmlUnitUploadTest", ".txt");
890         try {
891             FileUtils.writeStringToFile(tstFile, "Hello HtmlUnit", ISO_8859_1);
892 
893             final String path = tstFile.getCanonicalPath();
894             driver.findElement(By.name("myFile")).sendKeys(path);
895 
896             driver.findElement(By.id("testBtn")).click();
897 
898             final List<String> alerts = getCollectedAlerts(driver, 1);
899             if (!alerts.isEmpty()) {
900                 final String[] lines = alerts.get(0).split("\\n");
901                 assertEquals("Response: " + alerts.get(0) + "; line count", 14, lines.length);
902                 assertEquals("Content-Disposition: form-data; name=\"myText\"", lines[1]);
903                 assertEquals("", lines[2]);
904                 assertEquals("textxy", lines[3]);
905                 assertEquals(lines[0], lines[4]);
906                 assertTrue(lines[5].startsWith("Content-Disposition: form-data; name=\"myFile\"; filename="));
907                 assertEquals("Content-Type: text/plain", lines[6]);
908                 assertEquals("", lines[7]);
909                 assertEquals("Hello HtmlUnit", lines[8]);
910                 assertEquals(lines[0], lines[9]);
911                 assertEquals("Content-Disposition: form-data; name=\"myHidden\"", lines[10]);
912                 assertEquals("", lines[11]);
913                 assertEquals("hiddenxy", lines[12]);
914                 assertEquals(lines[0] + "--", lines[13]);
915             }
916         }
917         finally {
918             FileUtils.deleteQuietly(tstFile);
919         }
920     }
921 
922     /**
923      * @throws Exception if the test fails
924      */
925     @Test
926     public void fromFormAndAppend() throws Exception {
927         final String html = DOCTYPE_HTML
928             + "<html>\n"
929             + "<head><title>foo</title>\n"
930             + "<script>\n"
931             + "function test() {\n"
932             + "  try {\n"
933             + "    var formData = new FormData(document.testForm);\n"
934             + "  } catch(e) {\n"
935             + "    alert('create: ' + e.message);\n"
936             + "  }\n"
937             + "  try {\n"
938             + "    formData.append('myText', 'new value');\n"
939             + "    var xhr = new XMLHttpRequest();\n"
940             + "    xhr.open('POST', '/test2', false);\n"
941             + "    xhr.send(formData);\n"
942             + "    alert(xhr.responseText);\n"
943             + "  } catch(e) {\n"
944             + "    alert('send: ' + e.message);\n"
945             + "  }\n"
946             + "}\n"
947             + "</script>\n"
948             + "</head>\n"
949             + "<body>\n"
950             + "  <form name='testForm'>\n"
951             + "    <input type='text' id='myText' name='myText' value='textxy'>\n"
952             + "  </form>\n"
953             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
954             + "</body>\n"
955             + "</html>";
956 
957         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
958         servlets.put("/test2", PostServlet.class);
959 
960         final WebDriver driver = loadPage2(html, servlets);
961 
962         driver.findElement(By.id("testBtn")).click();
963 
964         final List<String> alerts = getCollectedAlerts(driver, 1);
965         if (!alerts.isEmpty()) {
966             final String[] lines = alerts.get(0).split("\\n");
967             assertEquals("Response: " + alerts.get(0) + "; line count", 9, lines.length);
968             assertEquals("Content-Disposition: form-data; name=\"myText\"", lines[1]);
969             assertEquals("", lines[2]);
970             assertEquals("textxy", lines[3]);
971             assertEquals(lines[0], lines[4]);
972             assertEquals("Content-Disposition: form-data; name=\"myText\"", lines[5]);
973             assertEquals("", lines[6]);
974             assertEquals("new value", lines[7]);
975             assertEquals(lines[0] + "--", lines[8]);
976         }
977     }
978 
979     /**
980      * @throws Exception if the test fails
981      */
982     @Test
983     public void fromFormChangeBeforeSend() throws Exception {
984         final String html = DOCTYPE_HTML
985             + "<html>\n"
986             + "<head><title>foo</title>\n"
987             + "<script>\n"
988             + "function test() {\n"
989             + "  try {\n"
990             + "    var formData = new FormData(document.testForm);\n"
991             + "  } catch(e) {\n"
992             + "    alert('create: ' + e.message);\n"
993             + "  }\n"
994             + "  try {\n"
995             + "    document.getElementById('myText').value = 'new value';\n"
996             + "    var xhr = new XMLHttpRequest();\n"
997             + "    xhr.open('POST', '/test2', false);\n"
998             + "    xhr.send(formData);\n"
999             + "    alert(xhr.responseText);\n"
1000             + "  } catch(e) {\n"
1001             + "    alert('send: ' + e.message);\n"
1002             + "  }\n"
1003             + "}\n"
1004             + "</script>\n"
1005             + "</head>\n"
1006             + "<body>\n"
1007             + "  <form name='testForm'>\n"
1008             + "    <input type='text' id='myText' name='myText' value='textxy'>\n"
1009             + "  </form>\n"
1010             + "  <button id='testBtn' onclick='test()'>Tester</button>\n"
1011             + "</body>\n"
1012             + "</html>";
1013 
1014         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
1015         servlets.put("/test2", PostServlet.class);
1016 
1017         final WebDriver driver = loadPage2(html, servlets);
1018 
1019         driver.findElement(By.id("testBtn")).click();
1020 
1021         final List<String> alerts = getCollectedAlerts(driver, 1);
1022         if (!alerts.isEmpty()) {
1023             final String[] lines = alerts.get(0).split("\\n");
1024             assertEquals("Response: " + alerts.get(0) + "; line count", 5, lines.length);
1025             assertEquals("Content-Disposition: form-data; name=\"myText\"", lines[1]);
1026             assertEquals("", lines[2]);
1027             assertEquals("textxy", lines[3]);
1028             assertEquals(lines[0] + "--", lines[4]);
1029         }
1030     }
1031 
1032     /**
1033      * @throws Exception if the test fails
1034      */
1035     @Test
1036     @Alerts("multipart/form-data")
1037     public void defaultEnctype() throws Exception {
1038         enctype(null);
1039     }
1040 
1041     /**
1042      * @throws Exception if the test fails
1043      */
1044     @Test
1045     @Alerts("multipart/form-data")
1046     public void emptyEnctype() throws Exception {
1047         enctype("");
1048     }
1049 
1050     /**
1051      * @throws Exception if the test fails
1052      */
1053     @Test
1054     @Alerts("multipart/form-data")
1055     public void blankEnctype() throws Exception {
1056         enctype(" ");
1057     }
1058 
1059     /**
1060      * @throws Exception if the test fails
1061      */
1062     @Test
1063     @Alerts("multipart/form-data")
1064     public void unknownEnctype() throws Exception {
1065         enctype("unknown");
1066     }
1067 
1068     /**
1069      * @throws Exception if the test fails
1070      */
1071     @Test
1072     @Alerts("multipart/form-data")
1073     public void urlencodedEnctype() throws Exception {
1074         enctype("application/x-www-form-urlencoded");
1075     }
1076 
1077     /**
1078      * @throws Exception if the test fails
1079      */
1080     @Test
1081     @Alerts("multipart/form-data")
1082     public void multipartEnctype() throws Exception {
1083         enctype("multipart/form-data");
1084     }
1085 
1086     /**
1087      * @throws Exception if the test fails
1088      */
1089     @Test
1090     @Alerts("multipart/form-data")
1091     public void plainEnctype() throws Exception {
1092         enctype("text/plain");
1093     }
1094 
1095     /**
1096      * @throws Exception if the test fails
1097      */
1098     @Test
1099     @Alerts("multipart/form-data")
1100     public void xmlEnctype() throws Exception {
1101         enctype("text/xml");
1102     }
1103 
1104     /**
1105      * @throws Exception if the test fails
1106      */
1107     @Test
1108     @Alerts("multipart/form-data")
1109     public void jsonEnctype() throws Exception {
1110         enctype("application/json");
1111     }
1112 
1113     private void enctype(final String enctype) throws Exception {
1114         final String html = DOCTYPE_HTML
1115             + "<html>\n"
1116             + "<head>\n"
1117             + "<script>\n"
1118             + LOG_TITLE_FUNCTION
1119             + "function doTest() {\n"
1120             + "  try {\n"
1121             + "    var formData = new FormData(document.testForm);\n"
1122             + "    var xhr = new XMLHttpRequest();\n"
1123             + "    xhr.open('POST', '/test2', false);\n"
1124             + "    xhr.send(formData);\n"
1125             + "  } catch(e) {\n"
1126             + "    log('send: ' + e.message);\n"
1127             + "  }\n"
1128             + "}\n"
1129             + "</script>\n"
1130             + "</head>\n"
1131             + "<body>\n"
1132             + "  <form name='testForm' enctype='" + enctype + "'>\n"
1133             + "    <input type='text' id='myText' name='myText' value='textxy'>\n"
1134             + "  </form>\n"
1135             + "  <button id='testBtn' onclick='doTest()'>Tester</button>\n"
1136             + "</body>\n"
1137             + "</html>";
1138 
1139         getMockWebConnection().setDefaultResponse("<html><title>Response</title></html>");
1140 
1141         final WebDriver driver = loadPage2(html);
1142         verifyTitle2(driver, new String[] {});
1143 
1144         driver.findElement(By.id("testBtn")).click();
1145         String headerValue = getMockWebConnection().getLastWebRequest().getAdditionalHeaders()
1146             .get(HttpHeader.CONTENT_TYPE);
1147         // Can't test equality for multipart/form-data as it will have the form:
1148         // multipart/form-data; boundary=---------------------------42937861433140731107235900
1149         headerValue = StringUtils.substringBefore(headerValue, ";");
1150         assertEquals(getExpectedAlerts()[0], headerValue);
1151     }
1152 
1153 
1154     /**
1155      * @throws Exception if an error occurs
1156      */
1157     @Test
1158     @Alerts({"function keys() { [native code] }", "[object FormData Iterator]",
1159              "key1", "key2", "key1", "undefined", "true"})
1160     public void keys() throws Exception {
1161         final String html = DOCTYPE_HTML
1162             + "<html>\n"
1163             + "<head>\n"
1164             + "  <script>\n"
1165             + LOG_TITLE_FUNCTION
1166             + "    function test() {\n"
1167             + "      var formData = new FormData();\n"
1168 
1169             + "      if (!formData.forEach) {\n"
1170             + "        log('no keys');\n"
1171             + "        return;"
1172             + "      }\n"
1173 
1174             + "      formData.append('key1', 'val1');\n"
1175             + "      formData.append('key2', undefined);\n"
1176             + "      formData.append('key1', 'val3');\n"
1177             + "      formData.append(undefined, 'val3');\n"
1178 
1179 
1180             + "      log(formData.keys);\n"
1181             + "      var iter = formData.keys();\n"
1182             + "      log(iter);\n"
1183 
1184             + "      var entry = iter.next().value;\n"
1185             + "      log(entry);\n"
1186             + "      entry = iter.next().value;\n"
1187             + "      log(entry);\n"
1188             + "      entry = iter.next().value;\n"
1189             + "      log(entry);\n"
1190             + "      entry = iter.next().value;\n"
1191             + "      log(entry);\n"
1192 
1193             + "      log(iter.next().done);\n"
1194             + "    }\n"
1195             + "  </script>\n"
1196             + "</head>\n"
1197             + "<body onload='test()'>\n"
1198             + "</body>\n"
1199             + "</html>";
1200 
1201         loadPageVerifyTitle2(html);
1202     }
1203 
1204     /**
1205      * @throws Exception if an error occurs
1206      */
1207     @Test
1208     @Alerts({"function values() { [native code] }", "[object FormData Iterator]",
1209              "val1", "undefined", "val3", "val4", "true"})
1210     public void values() throws Exception {
1211         final String html = DOCTYPE_HTML
1212             + "<html>\n"
1213             + "<head>\n"
1214             + "  <script>\n"
1215             + LOG_TITLE_FUNCTION
1216             + "    function test() {\n"
1217             + "      var formData = new FormData();\n"
1218 
1219             + "      if (!formData.forEach) {\n"
1220             + "        log('no values');\n"
1221             + "        return;"
1222             + "      }\n"
1223 
1224             + "      formData.append('key1', 'val1');\n"
1225             + "      formData.append('key2', undefined);\n"
1226             + "      formData.append('key1', 'val3');\n"
1227             + "      formData.append(undefined, 'val4');\n"
1228 
1229             + "      log(formData.values);\n"
1230             + "      var iter = formData.values();\n"
1231             + "      log(iter);\n"
1232 
1233             + "      var entry = iter.next().value;\n"
1234             + "      log(entry);\n"
1235             + "      entry = iter.next().value;\n"
1236             + "      log(entry);\n"
1237             + "      entry = iter.next().value;\n"
1238             + "      log(entry);\n"
1239             + "      entry = iter.next().value;\n"
1240             + "      log(entry);\n"
1241 
1242             + "      log(iter.next().done);\n"
1243             + "    }\n"
1244             + "  </script>\n"
1245             + "</head>\n"
1246             + "<body onload='test()'>\n"
1247             + "</body>\n"
1248             + "</html>";
1249 
1250         loadPageVerifyTitle2(html);
1251     }
1252 
1253     /**
1254      * @throws Exception if an error occurs
1255      */
1256     @Test
1257     @Alerts({"val1", "undefined", "val3", "val4"})
1258     public void valuesForOf() throws Exception {
1259         final String html = DOCTYPE_HTML
1260             + "<html>\n"
1261             + "<head>\n"
1262             + "  <script>\n"
1263             + LOG_TITLE_FUNCTION
1264             + "    function test() {\n"
1265             + "      var formData = new FormData();\n"
1266 
1267             + "      if (!formData.forEach) {\n"
1268             + "        log('no values');\n"
1269             + "        return;"
1270             + "      }\n"
1271 
1272             + "      formData.append('key1', 'val1');\n"
1273             + "      formData.append('key2', undefined);\n"
1274             + "      formData.append('key1', 'val3');\n"
1275             + "      formData.append(undefined, 'val4');\n"
1276 
1277             + "      for (var i of formData.values()) {\n"
1278             + "        log(i);\n"
1279             + "      }\n"
1280             + "    }\n"
1281             + "  </script>\n"
1282             + "</head>\n"
1283             + "<body onload='test()'>\n"
1284             + "</body>\n"
1285             + "</html>";
1286 
1287         loadPageVerifyTitle2(html);
1288     }
1289 
1290     /**
1291      * @throws Exception if an error occurs
1292      */
1293     @Test
1294     @Alerts({"key1-val1", "key2-val2", "key3-val3",
1295              "key1-val1", "key3-val3",
1296              "key2-val2", "key3-val3"})
1297     public void forEach() throws Exception {
1298         final String html = DOCTYPE_HTML
1299                 + "<html>\n"
1300                 + "<head>\n"
1301                 + "  <script>\n"
1302                 + LOG_TITLE_FUNCTION
1303                 + "    function test() {\n"
1304                 + "      var formData = new FormData();\n"
1305 
1306                 + "      if (!formData.forEach) {\n"
1307                 + "        log('no forEach');\n"
1308                 + "        return;"
1309                 + "      }\n"
1310 
1311                 + "      formData.append('key1', 'val1');\n"
1312                 + "      formData.append('key2', 'val2');\n"
1313                 + "      formData.append('key3', 'val3');\n"
1314 
1315                 + "      formData.forEach((value, key) => {\n"
1316                 + "        log(key + '-' + value);\n"
1317                 + "      });\n"
1318 
1319                 + "      formData.forEach((value, key) => {\n"
1320                 + "        log(key + '-' + value);\n"
1321                 + "        if (value == 'val1' || value == 'val2') {\n"
1322                 + "          formData.delete(key);\n"
1323                 + "        }\n"
1324                 + "      });\n"
1325 
1326                 + "      formData.forEach((value, key) => {\n"
1327                 + "        log(key + '-' + value);\n"
1328                 + "      });\n"
1329                 + "    }\n"
1330                 + "  </script>\n"
1331                 + "</head>\n"
1332                 + "<body onload='test()'>\n"
1333                 + "</body>\n"
1334                 + "</html>";
1335 
1336         loadPageVerifyTitle2(html);
1337     }
1338 
1339     /**
1340      * Going through entries() via suggested for ... of method
1341      * @throws Exception if an error occurs
1342      */
1343     @Test
1344     @Alerts({"myKey", "myValue", "myKey2", "", "myKey", "myvalue2"})
1345     public void entries_forOf() throws Exception {
1346         final String html = DOCTYPE_HTML
1347             + "<html><head>\n"
1348             + "<script>\n"
1349             + LOG_TITLE_FUNCTION
1350             + "function test() {\n"
1351             + "  var formData = new FormData();\n"
1352             + "  if (!formData.get) {\n"
1353             + "    log('no entries');\n"
1354             + "    return;"
1355             + "  }\n"
1356 
1357             + "  formData.append('myKey', 'myValue');\n"
1358             + "  formData.append('myKey2', '');\n"
1359             + "  formData.append('myKey', 'myvalue2');\n"
1360 
1361             + "  for (var pair of formData.entries()) {\n"
1362             + "    log(pair[0]);\n"
1363             + "    log(pair[1]);\n"
1364             + "  }\n"
1365             + "}\n"
1366             + "</script>\n"
1367             + "</head>\n"
1368             + "<body onload='test()'></body>\n"
1369             + "</html>";
1370 
1371         loadPageVerifyTitle2(html);
1372     }
1373 
1374     /**
1375      * Checks if the iterator works correctly.
1376      * @throws Exception if an error occurs
1377      */
1378     @Test
1379     @Alerts({"true", "[object FormData Iterator]", "done", "value",
1380              "myKey", "myValue", "myKey2", "", "myKey", "myvalue2"})
1381     @HtmlUnitNYI(CHROME = {"true", "[object FormData Iterator]", "value", "done",
1382                            "myKey", "myValue", "myKey2", "", "myKey", "myvalue2"},
1383             EDGE = {"true", "[object FormData Iterator]", "value", "done",
1384                     "myKey", "myValue", "myKey2", "", "myKey", "myvalue2"},
1385             FF = {"true", "[object FormData Iterator]", "value", "done",
1386                   "myKey", "myValue", "myKey2", "", "myKey", "myvalue2"},
1387             FF_ESR = {"true", "[object FormData Iterator]", "value", "done",
1388                       "myKey", "myValue", "myKey2", "", "myKey", "myvalue2"})
1389     public void entriesIterator() throws Exception {
1390         final String html = DOCTYPE_HTML
1391             + "<html><head><script>\n"
1392             + LOG_TITLE_FUNCTION
1393             + "function test() {\n"
1394             + "  var formData = new FormData();\n"
1395             + "  if (!formData.get) {\n"
1396             + "    log('no entries');\n"
1397             + "    return;"
1398             + "  }\n"
1399 
1400             + "  formData.append('myKey', 'myValue');\n"
1401             + "  formData.append('myKey2', '');\n"
1402             + "  formData.append('myKey', 'myvalue2');\n"
1403 
1404             + "  if (typeof Symbol != 'undefined') {\n"
1405             + "    log(formData[Symbol.iterator] === formData.entries);\n"
1406             + "  }\n"
1407 
1408             + "  var iterator = formData.entries();\n"
1409             + "  log(iterator);\n"
1410 
1411             + "  var nextItem = iterator.next();\n"
1412             + "  for (var x in nextItem) {\n"
1413             + "    log(x);\n"
1414             + "  }\n"
1415 
1416             + "  while (nextItem.done == false) {\n"
1417             + "    log(nextItem.value[0]);\n"
1418             + "    log(nextItem.value[1]);\n"
1419             + "    nextItem = iterator.next();\n"
1420             + "  }\n"
1421 
1422             + "}\n"
1423             + "</script>\n"
1424             + "</head>\n"
1425             + "<body onload='test()'></body>\n"
1426             + "</html>";
1427 
1428         loadPageVerifyTitle2(html);
1429     }
1430 
1431     /**
1432      * @throws Exception if an error occurs
1433      */
1434     @Test
1435     @Alerts({"true", "false", "true", "false"})
1436     public void fromFormDisabled() throws Exception {
1437         final String html = DOCTYPE_HTML
1438             + "<html><head>\n"
1439             + "<script>\n"
1440             + LOG_TITLE_FUNCTION
1441             + "function test() {\n"
1442             + "  var formData = new FormData(document.getElementById('frm'));\n"
1443 
1444             + "  try {\n"
1445             + "    log(formData.has('n1'));\n"
1446             + "    log(formData.has('n2'));\n"
1447             + "    log(formData.has('n3'));\n"
1448             + "    log(formData.has('n4'));\n"
1449             + "  } catch(e) {\n"
1450             + "    log('has: ' + e.message);\n"
1451             + "  }\n"
1452             + "}\n"
1453             + "</script></head>\n"
1454             + "<body onload='test()'>\n"
1455             + "  <form id='frm'>\n"
1456             + "    <textarea id='i1' name='n1'>text</textarea>"
1457             + "    <textarea id='i2' name='n2' disabled></textarea>"
1458             + "    <fieldset><input id='i3' name='n3'></fieldset>"
1459             + "    <fieldset disabled><input id='i4'  name='n4'></fieldset>"
1460             + "  </form>\n"
1461             + "</body></html>";
1462 
1463         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
1464         servlets.put("/test2", PostServlet.class);
1465 
1466         loadPageVerifyTitle2(html);
1467     }
1468 
1469     /**
1470      * Servlet for post().
1471      */
1472     public static class PostServlet extends HttpServlet {
1473 
1474         /**
1475          * {@inheritDoc}
1476          */
1477         @Override
1478         protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
1479             throws ServletException, IOException {
1480             request.setCharacterEncoding(UTF_8.name());
1481             response.setContentType(MimeType.TEXT_HTML);
1482             final Writer writer = response.getWriter();
1483             final BufferedReader reader = request.getReader();
1484             String line;
1485             while ((line = reader.readLine()) != null) {
1486                 writer.write(line);
1487                 writer.write('\n');
1488             }
1489         }
1490     }
1491 }