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