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.html;
16  
17  import static java.nio.charset.StandardCharsets.UTF_8;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.Writer;
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import javax.servlet.Servlet;
26  import javax.servlet.ServletException;
27  import javax.servlet.http.HttpServlet;
28  import javax.servlet.http.HttpServletRequest;
29  import javax.servlet.http.HttpServletResponse;
30  
31  import org.apache.commons.fileupload.FileItem;
32  import org.apache.commons.fileupload.FileUploadBase;
33  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
34  import org.apache.commons.fileupload.servlet.ServletFileUpload;
35  import org.htmlunit.WebDriverTestCase;
36  import org.htmlunit.junit.annotation.Alerts;
37  import org.htmlunit.util.MimeType;
38  import org.junit.jupiter.api.BeforeAll;
39  import org.junit.jupiter.api.Test;
40  import org.openqa.selenium.By;
41  import org.openqa.selenium.WebDriver;
42  
43  /**
44   * Tests for {@link HtmlFileInput}.
45   *
46   * @author Marc Guillemot
47   * @author Ahmed Ashour
48   * @author Ronald Brill
49   * @author Frank Danek
50   */
51  public class HtmlFileInput3Test extends WebDriverTestCase {
52  
53      /**
54       * @throws Exception if an error occurs
55       */
56      @Test
57      @Alerts({"CONTENT_TYPE:application/octet-stream", "charset"})
58      public void contentTypeUnknown() throws Exception {
59          contentType("unknown");
60      }
61  
62      /**
63       * @throws Exception if an error occurs
64       */
65      @Test
66      @Alerts({"CONTENT_TYPE:text/html", "charset"})
67      public void contentTypeHtm() throws Exception {
68          contentType("htm");
69      }
70  
71      /**
72       * @throws Exception if an error occurs
73       */
74      @Test
75      @Alerts({"CONTENT_TYPE:text/html", "charset"})
76      public void contentTypeHtml() throws Exception {
77          contentType("html");
78      }
79  
80      /**
81       * @throws Exception if an error occurs
82       */
83      @Test
84      @Alerts({"CONTENT_TYPE:text/css", "charset"})
85      public void contentTypeCss() throws Exception {
86          contentType("css");
87      }
88  
89      /**
90       * @throws Exception if an error occurs
91       */
92      @Test
93      @Alerts({"CONTENT_TYPE:text/xml", "charset"})
94      public void contentTypeXml() throws Exception {
95          contentType("xml");
96      }
97  
98      /**
99       * @throws Exception if an error occurs
100      */
101     @Test
102     @Alerts({"CONTENT_TYPE:image/gif", "charset"})
103     public void contentTypeGif() throws Exception {
104         contentType("gif");
105     }
106 
107     /**
108      * @throws Exception if an error occurs
109      */
110     @Test
111     @Alerts({"CONTENT_TYPE:image/jpeg", "charset"})
112     public void contentTypeJpeg() throws Exception {
113         contentType("jpeg");
114     }
115 
116     /**
117      * @throws Exception if an error occurs
118      */
119     @Test
120     @Alerts({"CONTENT_TYPE:image/jpeg", "charset"})
121     public void contentTypeJpg() throws Exception {
122         contentType("jpg");
123     }
124 
125     /**
126      * @throws Exception if an error occurs
127      */
128     @Test
129     @Alerts({"CONTENT_TYPE:image/png", "charset"})
130     public void contentTypePng() throws Exception {
131         contentType("png");
132     }
133 
134     /**
135      * @throws Exception if an error occurs
136      */
137     @Test
138     @Alerts({"CONTENT_TYPE:image/webp", "charset"})
139     public void contentTypeWebp() throws Exception {
140         contentType("webp");
141     }
142 
143     /**
144      * @throws Exception if an error occurs
145      */
146     @Test
147     @Alerts({"CONTENT_TYPE:video/mp4", "charset"})
148     public void contentTypeMp4() throws Exception {
149         contentType("mp4");
150     }
151 
152     /**
153      * @throws Exception if an error occurs
154      */
155     @Test
156     @Alerts({"CONTENT_TYPE:video/mp4", "charset"})
157     public void contentTypeM4v() throws Exception {
158         contentType("m4v");
159     }
160 
161     /**
162      * @throws Exception if an error occurs
163      */
164     @Test
165     @Alerts(DEFAULT = {"CONTENT_TYPE:audio/mp4", "charset"},
166             CHROME = {"CONTENT_TYPE:audio/x-m4a", "charset"},
167             EDGE = {"CONTENT_TYPE:audio/x-m4a", "charset"})
168     public void contentTypeM4a() throws Exception {
169         contentType("m4a");
170     }
171 
172     /**
173      * @throws Exception if an error occurs
174      */
175     @Test
176     @Alerts({"CONTENT_TYPE:audio/mpeg", "charset"})
177     public void contentTypeMp3() throws Exception {
178         contentType("mp3");
179     }
180 
181     /**
182      * @throws Exception if an error occurs
183      */
184     @Test
185     @Alerts({"CONTENT_TYPE:video/ogg", "charset"})
186     public void contentTypeOgv() throws Exception {
187         contentType("ogv");
188     }
189 
190     /**
191      * @throws Exception if an error occurs
192      */
193     @Test
194     @Alerts({"CONTENT_TYPE:video/ogg", "charset"})
195     public void contentTypeOgm() throws Exception {
196         contentType("ogm");
197     }
198 
199     /**
200      * @throws Exception if an error occurs
201      */
202     @Test
203     @Alerts(DEFAULT = {"CONTENT_TYPE:audio/ogg", "charset"},
204             FF = {"CONTENT_TYPE:application/ogg", "charset"},
205             FF_ESR = {"CONTENT_TYPE:video/ogg", "charset"})
206     public void contentTypeOgg() throws Exception {
207         contentType("ogg");
208     }
209 
210     /**
211      * @throws Exception if an error occurs
212      */
213     @Test
214     @Alerts({"CONTENT_TYPE:audio/ogg", "charset"})
215     public void contentTypeOga() throws Exception {
216         contentType("oga");
217     }
218 
219     /**
220      * @throws Exception if an error occurs
221      */
222     @Test
223     @Alerts({"CONTENT_TYPE:audio/ogg", "charset"})
224     public void contentTypeOpus() throws Exception {
225         contentType("opus");
226     }
227 
228     /**
229      * @throws Exception if an error occurs
230      */
231     @Test
232     @Alerts({"CONTENT_TYPE:video/webm", "charset"})
233     public void contentTypeWebm() throws Exception {
234         contentType("webm");
235     }
236 
237     /**
238      * @throws Exception if an error occurs
239      */
240     @Test
241     @Alerts({"CONTENT_TYPE:application/pdf", "charset"})
242     public void contentTypePdf() throws Exception {
243         contentType("pdf");
244     }
245 
246     /**
247      * @throws Exception if an error occurs
248      */
249     @Test
250     @Alerts({"CONTENT_TYPE:audio/wav", "charset"})
251     public void contentTypeWav() throws Exception {
252         contentType("wav");
253     }
254 
255     /**
256      * @throws Exception if an error occurs
257      */
258     @Test
259     @Alerts(DEFAULT = {"CONTENT_TYPE:audio/flac", "charset"},
260             FF = {"CONTENT_TYPE:audio/x-flac", "charset"},
261             FF_ESR = {"CONTENT_TYPE:audio/x-flac", "charset"})
262     public void contentTypeFlac() throws Exception {
263         contentType("flac");
264     }
265 
266     /**
267      * @throws Exception if an error occurs
268      */
269     @Test
270     @Alerts({"CONTENT_TYPE:application/xhtml+xml", "charset"})
271     public void contentTypeXhtml() throws Exception {
272         contentType("xhtml");
273     }
274 
275     /**
276      * @throws Exception if an error occurs
277      */
278     @Test
279     @Alerts({"CONTENT_TYPE:application/xhtml+xml", "charset"})
280     public void contentTypeXht() throws Exception {
281         contentType("xht");
282     }
283 
284     /**
285      * @throws Exception if an error occurs
286      */
287     @Test
288     @Alerts(DEFAULT = {"CONTENT_TYPE:application/octet-stream", "charset"},
289             CHROME = {"CONTENT_TYPE:application/xhtml+xml", "charset"},
290             EDGE = {"CONTENT_TYPE:application/xhtml+xml", "charset"})
291     public void contentTypeXhtm() throws Exception {
292         contentType("xhtm");
293     }
294 
295     /**
296      * @throws Exception if an error occurs
297      */
298     @Test
299     @Alerts({"CONTENT_TYPE:text/plain", "charset"})
300     public void contentTypeText() throws Exception {
301         contentType("text");
302     }
303 
304     /**
305      * @throws Exception if an error occurs
306      */
307     @Test
308     @Alerts({"CONTENT_TYPE:text/plain", "charset"})
309     public void contentTypeTxt() throws Exception {
310         contentType("txt");
311     }
312 
313     /**
314      * @throws Exception if an error occurs
315      */
316     @Test
317     @Alerts({"CONTENT_TYPE:text/html", "charset"})
318     public void contentTypeCaseInsensitive() throws Exception {
319         contentType("HtmL");
320     }
321 
322     /**
323      * Starts the web server.
324      */
325     @BeforeAll
326     public static void before() {
327         try {
328             final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
329             servlets.put("/upload1", Upload1Servlet.class);
330             servlets.put("/upload2", ContentTypeUpload2Servlet.class);
331             startWebServer("./", new String[0], servlets);
332         }
333         catch (final Exception e) {
334             throw new RuntimeException(e);
335         }
336     }
337 
338     private void contentType(final String extension) throws Exception {
339         final WebDriver driver = getWebDriver();
340         driver.get(URL_FIRST + "upload1");
341 
342         final File tmpFile = File.createTempFile("htmlunit-test", "." + extension);
343         try {
344             final String path = tmpFile.getAbsolutePath();
345             driver.findElement(By.name("myInput")).sendKeys(path);
346             driver.findElement(By.id("mySubmit")).submit();
347         }
348         finally {
349             assertTrue(tmpFile.delete());
350         }
351 
352         final long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
353 
354         while (System.currentTimeMillis() < maxWait) {
355             try {
356                 final String pageSource = driver.getPageSource();
357                 assertNotNull(pageSource);
358                 assertTrue(pageSource, pageSource.contains(getExpectedAlerts()[0]));
359                 assertFalse(pageSource, pageSource.contains(getExpectedAlerts()[1]));
360                 return;
361             }
362             catch (final AssertionError e) {
363                 // ignore and wait
364             }
365         }
366 
367         final String pageSource = driver.getPageSource();
368         assertTrue("\"" + pageSource + "\" does not contain \""
369                 + getExpectedAlerts()[0] + "\"", pageSource.contains(getExpectedAlerts()[0]));
370         assertFalse("\"" + pageSource + "\" contains \""
371                 + getExpectedAlerts()[0] + "\" but should not", pageSource.contains(getExpectedAlerts()[1]));
372     }
373 
374     /**
375      * Servlet for '/upload1'.
376      */
377     public static class Upload1Servlet extends HttpServlet {
378 
379         /**
380          * {@inheritDoc}
381          */
382         @Override
383         protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
384             throws ServletException, IOException {
385             response.setCharacterEncoding(UTF_8.name());
386             response.setContentType(MimeType.TEXT_HTML);
387             response.getWriter().write("<html>\n"
388                 + "<body>\n"
389                 + "<form action='upload2' method='post' enctype='multipart/form-data'>\n"
390                 + "Name: <input name='myInput' type='file'><br>\n"
391                 + "Name 2 (should stay empty): <input name='myInput2' type='file'><br>\n"
392                 + "<input type='submit' value='Upload' id='mySubmit'>\n"
393                 + "</form>\n"
394                 + "</body></html>\n");
395         }
396     }
397 
398     /**
399      * Servlet for '/upload2'.
400      */
401     public static class ContentTypeUpload2Servlet extends HttpServlet {
402 
403         /**
404          * {@inheritDoc}
405          */
406         @Override
407         protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
408             throws ServletException, IOException {
409             request.setCharacterEncoding(UTF_8.name());
410             response.setContentType(MimeType.TEXT_HTML);
411             try (Writer writer = response.getWriter()) {
412                 if (ServletFileUpload.isMultipartContent(request)) {
413                     try {
414                         final ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
415                         for (final FileItem item : upload.parseRequest(request)) {
416                             if ("myInput".equals(item.getFieldName())) {
417                                 writer.write("CONTENT_TYPE:" + item.getContentType());
418                             }
419                         }
420                     }
421                     catch (final FileUploadBase.SizeLimitExceededException e) {
422                         writer.write("SizeLimitExceeded");
423                     }
424                     catch (final Exception e) {
425                         writer.write("error");
426                     }
427                 }
428             }
429         }
430     }
431 }