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