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.attachment;
16  
17  import java.io.ByteArrayInputStream;
18  import java.io.InputStream;
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.htmlunit.HttpWebConnectionTest;
23  import org.htmlunit.MockWebConnection;
24  import org.htmlunit.Page;
25  import org.htmlunit.SimpleWebTestCase;
26  import org.htmlunit.TextPage;
27  import org.htmlunit.WebClient;
28  import org.htmlunit.WebResponse;
29  import org.htmlunit.html.HtmlAnchor;
30  import org.htmlunit.html.HtmlElement;
31  import org.htmlunit.html.HtmlPage;
32  import org.htmlunit.junit.BrowserRunner;
33  import org.htmlunit.util.MimeType;
34  import org.htmlunit.util.NameValuePair;
35  import org.junit.Test;
36  import org.junit.runner.RunWith;
37  
38  /**
39   * Tests for {@link Attachment}.
40   *
41   * @author Bruce Chapman
42   * @author Sudhan Moghe
43   * @author Daniel Gredler
44   * @author Ronald Brill
45   * @author Lai Quang Duong
46   */
47  @RunWith(BrowserRunner.class)
48  public class AttachmentTest extends SimpleWebTestCase {
49  
50      /**
51       * Tests attachment callbacks and the contents of attachments.
52       * @throws Exception if an error occurs
53       */
54      @Test
55      public void basic() throws Exception {
56          final String content1 = DOCTYPE_HTML
57              + "<html><body>\n"
58              + "<form method='POST' name='form' action='" + URL_SECOND + "'>\n"
59              + "<input type='submit' value='ok'>\n"
60              + "</form>\n"
61              + "<a href='#' onclick='document.form.submit()'>click me</a>\n"
62              + "</body></html>";
63          final String content2 = "download file contents";
64  
65          final WebClient client = getWebClient();
66          final List<Attachment> attachments = new ArrayList<>();
67          client.setAttachmentHandler(new CollectingAttachmentHandler(attachments));
68  
69          final List<NameValuePair> headers = new ArrayList<>();
70          headers.add(new NameValuePair("Content-Disposition", "attachment"));
71  
72          final MockWebConnection conn = new MockWebConnection();
73          conn.setResponse(URL_FIRST, content1);
74          conn.setResponse(URL_SECOND, content2, 200, "OK", MimeType.TEXT_HTML, headers);
75          client.setWebConnection(conn);
76          assertTrue(attachments.isEmpty());
77  
78          final HtmlPage result = client.getPage(URL_FIRST);
79          final HtmlAnchor anchor = result.getAnchors().get(0);
80          final Page clickResult = anchor.click();
81          assertEquals(result, clickResult);
82          assertEquals(1, attachments.size());
83          assertTrue(HtmlPage.class.isInstance(attachments.get(0).getPage()));
84          // the attachment is opened inside a new window
85          assertEquals(2, client.getWebWindows().size());
86  
87          final Attachment attachment = attachments.get(0);
88          final Page attachedPage = attachment.getPage();
89          final WebResponse attachmentResponse = attachedPage.getWebResponse();
90          final InputStream attachmentStream = attachmentResponse.getContentAsStream();
91          HttpWebConnectionTest.assertEquals(new ByteArrayInputStream(content2.getBytes()), attachmentStream);
92          assertEquals(MimeType.TEXT_HTML, attachmentResponse.getContentType());
93          assertEquals(200, attachmentResponse.getStatusCode());
94          assertEquals(URL_SECOND, attachmentResponse.getWebRequest().getUrl());
95      }
96  
97      /**
98       * Tests attachment callbacks and the contents of attachments.
99       * @throws Exception if an error occurs
100      */
101     @Test
102     public void contentDispositionCaseInsensitive() throws Exception {
103         final String content1 = DOCTYPE_HTML
104             + "<html><body>\n"
105             + "<form method='POST' name='form' action='" + URL_SECOND + "'>\n"
106             + "<input type='submit' value='ok'>\n"
107             + "</form>\n"
108             + "<a href='#' onclick='document.form.submit()'>click me</a>\n"
109             + "</body></html>";
110         final String content2 = "download file contents";
111 
112         final WebClient client = getWebClient();
113         final List<Attachment> attachments = new ArrayList<>();
114         client.setAttachmentHandler(new CollectingAttachmentHandler(attachments));
115 
116         final List<NameValuePair> headers = new ArrayList<>();
117         headers.add(new NameValuePair("Content-Disposition", "AttachMent"));
118 
119         final MockWebConnection conn = new MockWebConnection();
120         conn.setResponse(URL_FIRST, content1);
121         conn.setResponse(URL_SECOND, content2, 200, "OK", MimeType.TEXT_HTML, headers);
122         client.setWebConnection(conn);
123         assertTrue(attachments.isEmpty());
124 
125         final HtmlPage result = client.getPage(URL_FIRST);
126         final HtmlAnchor anchor = result.getAnchors().get(0);
127         final Page clickResult = anchor.click();
128         assertEquals(result, clickResult);
129         assertEquals(1, attachments.size());
130     }
131 
132     /**
133      * Tests {@link Attachment#getSuggestedFilename()}.
134      * @throws Exception if an error occurs
135      */
136     @Test
137     public void filename() throws Exception {
138         final String content = DOCTYPE_HTML + "<html>But is it really?</html>";
139 
140         final WebClient client = getWebClient();
141         final MockWebConnection conn = new MockWebConnection();
142         client.setWebConnection(conn);
143         final List<Attachment> attachments = new ArrayList<>();
144         client.setAttachmentHandler(new CollectingAttachmentHandler(attachments));
145 
146         final List<NameValuePair> headers1 = new ArrayList<>();
147         headers1.add(new NameValuePair("Content-Disposition", "attachment;filename=\"hello.html\""));
148         conn.setResponse(URL_FIRST, content, 200, "OK", MimeType.TEXT_HTML, headers1);
149         client.getPage(URL_FIRST);
150         final Attachment result = attachments.get(0);
151         assertEquals(result.getSuggestedFilename(), "hello.html");
152         attachments.clear();
153 
154         final List<NameValuePair> headers2 = new ArrayList<>();
155         headers2.add(new NameValuePair("Content-Disposition", "attachment; filename=hello2.html; something=else"));
156         conn.setResponse(URL_SECOND, content, 200, "OK", MimeType.TEXT_PLAIN, headers2);
157         client.getPage(URL_SECOND);
158         final Attachment result2 = attachments.get(0);
159         assertEquals(result2.getSuggestedFilename(), "hello2.html");
160         assertEquals(content, ((TextPage) result2.getPage()).getContent());
161         attachments.clear();
162 
163         final List<NameValuePair> headers3 = new ArrayList<>();
164         headers3.add(new NameValuePair("Content-Disposition", "attachment; filename="));
165         conn.setResponse(URL_SECOND, content, 200, "OK", MimeType.TEXT_PLAIN, headers3);
166         client.getPage(URL_SECOND);
167         final Attachment result3 = attachments.get(0);
168         assertNull(result3.getSuggestedFilename());
169         assertEquals(content, ((TextPage) result3.getPage()).getContent());
170         attachments.clear();
171 
172         final List<NameValuePair> headers4 = new ArrayList<>();
173         headers4.add(new NameValuePair("Content-Disposition", "attachment"));
174         final byte[] contentb = new byte[] {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE};
175         conn.setResponse(URL_THIRD, contentb, 200, "OK", "application/x-rubbish", headers4);
176         client.getPage(URL_THIRD);
177         final Attachment result4 = attachments.get(0);
178         final InputStream result4Stream = result4.getPage().getWebResponse().getContentAsStream();
179         assertNull(result4.getSuggestedFilename());
180         assertEquals(result4.getPage().getWebResponse().getContentType(), "application/x-rubbish");
181         HttpWebConnectionTest.assertEquals(new ByteArrayInputStream(contentb), result4Stream);
182         attachments.clear();
183     }
184 
185     /**
186      * @throws Exception if an error occurs
187      */
188     @Test
189     public void filenameFromAnchor() throws Exception {
190         final WebClient client = getWebClient();
191         final MockWebConnection conn = new MockWebConnection();
192         client.setWebConnection(conn);
193         final List<Attachment> attachments = new ArrayList<>();
194         client.setAttachmentHandler(new CollectingAttachmentHandler(attachments));
195 
196         final String content = DOCTYPE_HTML
197                 + "<html>\n"
198                 + "<head>\n"
199                 + "<script>\n"
200                 + "var blob = new Blob(['foo'], {type: 'text/plain'}),\n"
201                 + "  url = URL.createObjectURL(blob),\n"
202                 + "  elem = document.createElement('a');\n"
203                 + "elem.href = url, elem.download = 'foo.txt', elem.click();\n"
204                 + "</script>\n"
205                 + "</head>\n"
206                 + "</html>\n";
207 
208         conn.setResponse(URL_FIRST, content);
209         client.getPage(URL_FIRST);
210 
211         final Attachment result = attachments.get(0);
212         assertEquals(result.getSuggestedFilename(), "foo.txt");
213         assertEquals("foo", ((TextPage) result.getPage()).getContent());
214         attachments.clear();
215     }
216 
217     /**
218      * @throws Exception if an error occurs
219      */
220     @Test
221     public void filenameFromFile() throws Exception {
222         final WebClient client = getWebClient();
223         final MockWebConnection conn = new MockWebConnection();
224         client.setWebConnection(conn);
225         final List<Attachment> attachments = new ArrayList<>();
226         client.setAttachmentHandler(new CollectingAttachmentHandler(attachments));
227 
228         final String content = DOCTYPE_HTML
229                 + "<html>\n"
230                 + "<head>\n"
231                 + "<script>\n"
232                 + "var blob = new File(['bar'], 'bar.txt', {type: 'text/plain'}),\n"
233                 + "  url = URL.createObjectURL(blob),\n"
234                 + "  elem = document.createElement('a');\n"
235                 + "elem.href = url, elem.download = '', elem.click();\n"
236                 + "</script>\n"
237                 + "</head>\n"
238                 + "</html>\n";
239 
240         conn.setResponse(URL_FIRST, content);
241         client.getPage(URL_FIRST);
242 
243         final Attachment result = attachments.get(0);
244         assertEquals(result.getSuggestedFilename(), "bar.txt");
245         assertEquals("bar", ((TextPage) result.getPage()).getContent());
246         attachments.clear();
247     }
248 
249 
250     /**
251      * Prioritize name from Anchor.download over File.name.
252      * @throws Exception if an error occurs
253      */
254     @Test
255     public void filenameFromFilePrioritizeAnchorDownloadOverFileName() throws Exception {
256         final WebClient client = getWebClient();
257         final MockWebConnection conn = new MockWebConnection();
258         client.setWebConnection(conn);
259         final List<Attachment> attachments = new ArrayList<>();
260         client.setAttachmentHandler(new CollectingAttachmentHandler(attachments));
261 
262         final String content = DOCTYPE_HTML
263                 + "<html>\n"
264                 + "<head>\n"
265                 + "<script>\n"
266                 + "var blob = new File(['bar'], 'bar.txt', {type: 'text/plain'}),\n"
267                 + "  url = URL.createObjectURL(blob),\n"
268                 + "  elem = document.createElement('a');\n"
269                 + "elem.href = url, elem.download = 'foobar.txt', elem.click();\n"
270                 + "</script>\n"
271                 + "</head>\n"
272                 + "</html>\n";
273 
274         conn.setResponse(URL_FIRST, content);
275         client.getPage(URL_FIRST);
276 
277         final Attachment result = attachments.get(0);
278         assertEquals(result.getSuggestedFilename(), "foobar.txt");
279         assertEquals("bar", ((TextPage) result.getPage()).getContent());
280         attachments.clear();
281     }
282 
283     /**
284      * This was causing a ClassCastException in Location.setHref as of 2013-10-08 because the TextPage
285      * used for the attachment was wrongly associated to the HTMLDocument of the first page.
286      * @throws Exception if an error occurs
287      */
288     @Test
289     public void jsChangeLocationAfterReceptionOfAttachment() throws Exception {
290         final String html = DOCTYPE_HTML
291             + "<html><body>\n"
292             + "<form action='action'>\n"
293             + "<input type='submit' onclick='window.location=\"foo\"; return false'>\n"
294             + "</form>\n"
295             + "<a href='" + URL_SECOND + "'>download</a>\n"
296             + "</body></html>";
297 
298         final WebClient client = getWebClient();
299         final List<Attachment> attachments = new ArrayList<>();
300         client.setAttachmentHandler(new CollectingAttachmentHandler(attachments));
301 
302         final List<NameValuePair> headers = new ArrayList<>();
303         headers.add(new NameValuePair("Content-Disposition", "attachment"));
304 
305         final MockWebConnection conn = getMockWebConnection();
306         conn.setDefaultResponse("");
307         conn.setResponse(URL_SECOND, "some text", 200, "OK", MimeType.TEXT_PLAIN, headers);
308 
309         final HtmlPage page = loadPage(html);
310         // download text attachment
311         page.getAnchors().get(0).click();
312         assertEquals(1, attachments.size());
313 
314         final HtmlElement htmlElement = (HtmlElement) page.getFirstByXPath("//input");
315         htmlElement.click(); // exception was occurring here
316     }
317 
318     /**
319      * Tests attachment callbacks and the contents of attachments.
320      * @throws Exception if an error occurs
321      */
322     @Test
323     public void handleResponseFromHandler() throws Exception {
324         final String content1 = DOCTYPE_HTML
325             + "<html><body>\n"
326             + "<form method='POST' name='form' action='" + URL_SECOND + "'>\n"
327             + "<input type='submit' value='ok'>\n"
328             + "</form>\n"
329             + "<a href='#' onclick='document.form.submit()'>click me</a>\n"
330             + "</body></html>";
331         final String content2 = "download file contents";
332 
333         final WebClient client = getWebClient();
334         final List<WebResponse> attachments = new ArrayList<>();
335         final List<String> attachmentFilenames = new ArrayList<>();
336 
337         client.setAttachmentHandler(new AttachmentHandler() {
338             @Override
339             public boolean handleAttachment(final WebResponse response, final String attachmentFilename) {
340                 attachments.add(response);
341                 attachmentFilenames.add(attachmentFilename);
342                 return true;
343             }
344 
345             @Override
346             public void handleAttachment(final Page page, final String attachmentFilename) {
347                 throw new IllegalAccessError("handleAttachment(Page, String) called");
348             }
349         });
350 
351         final List<NameValuePair> headers = new ArrayList<>();
352         headers.add(new NameValuePair("Content-Disposition", "attachment"));
353 
354         final MockWebConnection conn = new MockWebConnection();
355         conn.setResponse(URL_FIRST, content1);
356         conn.setResponse(URL_SECOND, content2, 200, "OK", MimeType.TEXT_HTML, headers);
357         client.setWebConnection(conn);
358         assertTrue(attachments.isEmpty());
359         assertTrue(attachmentFilenames.isEmpty());
360 
361         final HtmlPage result = client.getPage(URL_FIRST);
362         final HtmlAnchor anchor = result.getAnchors().get(0);
363         final Page clickResult = anchor.click();
364         assertEquals(result, clickResult);
365         assertEquals(1, attachments.size());
366         assertEquals(1, attachmentFilenames.size());
367         assertEquals(1, client.getWebWindows().size());
368 
369         final WebResponse attachmentResponse = attachments.get(0);
370         final InputStream attachmentStream = attachmentResponse.getContentAsStream();
371         HttpWebConnectionTest.assertEquals(new ByteArrayInputStream(content2.getBytes()), attachmentStream);
372         assertEquals(MimeType.TEXT_HTML, attachmentResponse.getContentType());
373         assertEquals(200, attachmentResponse.getStatusCode());
374         assertEquals(URL_SECOND, attachmentResponse.getWebRequest().getUrl());
375 
376         assertNull(attachmentFilenames.get(0));
377     }
378 
379     /**
380      * Tests attachment callbacks and the contents of attachments.
381      * @throws Exception if an error occurs
382      */
383     @Test
384     public void handleResponseFromHandlerWithFileName() throws Exception {
385         final String content1 = DOCTYPE_HTML
386             + "<html><body>\n"
387             + "<form method='POST' name='form' action='" + URL_SECOND + "'>\n"
388             + "<input type='submit' value='ok'>\n"
389             + "</form>\n"
390             + "<a href='#' onclick='document.form.submit()'>click me</a>\n"
391             + "</body></html>";
392         final String content2 = "download file contents";
393 
394         final WebClient client = getWebClient();
395         final List<WebResponse> attachments = new ArrayList<>();
396         final List<String> attachmentFilenames = new ArrayList<>();
397 
398         client.setAttachmentHandler(new AttachmentHandler() {
399             @Override
400             public boolean handleAttachment(final WebResponse response, final String attachmentFilename) {
401                 attachments.add(response);
402                 attachmentFilenames.add(attachmentFilename);
403                 return true;
404             }
405 
406             @Override
407             public void handleAttachment(final Page page, final String attachmentFilename) {
408                 throw new IllegalAccessError("handleAttachment(Page, String) called");
409             }
410         });
411 
412         final List<NameValuePair> headers = new ArrayList<>();
413         headers.add(new NameValuePair("Content-Disposition", "attachment; filename=htmlunit.zip"));
414 
415         final MockWebConnection conn = new MockWebConnection();
416         conn.setResponse(URL_FIRST, content1);
417         conn.setResponse(URL_SECOND, content2, 200, "OK", MimeType.TEXT_HTML, headers);
418         client.setWebConnection(conn);
419         assertTrue(attachments.isEmpty());
420         assertTrue(attachmentFilenames.isEmpty());
421 
422         final HtmlPage result = client.getPage(URL_FIRST);
423         final HtmlAnchor anchor = result.getAnchors().get(0);
424         final Page clickResult = anchor.click();
425         assertEquals(result, clickResult);
426         assertEquals(1, attachments.size());
427         assertEquals(1, attachmentFilenames.size());
428         assertEquals(1, client.getWebWindows().size());
429 
430         final WebResponse attachmentResponse = attachments.get(0);
431         final InputStream attachmentStream = attachmentResponse.getContentAsStream();
432         HttpWebConnectionTest.assertEquals(new ByteArrayInputStream(content2.getBytes()), attachmentStream);
433         assertEquals(MimeType.TEXT_HTML, attachmentResponse.getContentType());
434         assertEquals(200, attachmentResponse.getStatusCode());
435         assertEquals(URL_SECOND, attachmentResponse.getWebRequest().getUrl());
436 
437         assertEquals("htmlunit.zip", attachmentFilenames.get(0));
438     }
439 
440     /**
441      * Tests attachment callbacks and the contents of attachments.
442      * @throws Exception if an error occurs
443      */
444     @Test
445     public void handleResponseOnlyApplicationOctetstream() throws Exception {
446         final String content1 = DOCTYPE_HTML
447             + "<html><body>\n"
448             + "<form method='POST' name='form' action='" + URL_SECOND + "'>\n"
449             + "<input type='submit' value='ok'>\n"
450             + "</form>\n"
451             + "<a href='#' onclick='document.form.submit()'>click me</a>\n"
452             + "</body></html>";
453         final String content2 = "download file contents";
454 
455         final WebClient client = getWebClient();
456         final List<WebResponse> attachments = new ArrayList<>();
457         final List<String> attachmentFilenames = new ArrayList<>();
458 
459         client.setAttachmentHandler(new AttachmentHandler() {
460             @Override
461             public boolean handleAttachment(final WebResponse response, final String attachmentFilename) {
462                 attachments.add(response);
463                 attachmentFilenames.add(attachmentFilename);
464                 return true;
465             }
466 
467             @Override
468             public void handleAttachment(final Page page, final String attachmentFilename) {
469                 throw new IllegalAccessError("handleAttachment(Page, String) called");
470             }
471         });
472 
473         final List<NameValuePair> headers = new ArrayList<>();
474         headers.add(new NameValuePair("Content-Type", MimeType.APPLICATION_OCTET_STREAM));
475 
476         final MockWebConnection conn = new MockWebConnection();
477         conn.setResponse(URL_FIRST, content1);
478         conn.setResponse(URL_SECOND, content2, 200, "OK", MimeType.TEXT_HTML, headers);
479         client.setWebConnection(conn);
480         assertTrue(attachments.isEmpty());
481         assertTrue(attachmentFilenames.isEmpty());
482 
483         final HtmlPage result = client.getPage(URL_FIRST);
484         final HtmlAnchor anchor = result.getAnchors().get(0);
485         final Page clickResult = anchor.click();
486         assertEquals(result, clickResult);
487         assertEquals(1, attachments.size());
488         assertEquals(1, attachmentFilenames.size());
489         assertEquals(1, client.getWebWindows().size());
490 
491         final WebResponse attachmentResponse = attachments.get(0);
492         final InputStream attachmentStream = attachmentResponse.getContentAsStream();
493         HttpWebConnectionTest.assertEquals(new ByteArrayInputStream(content2.getBytes()), attachmentStream);
494         assertEquals(MimeType.APPLICATION_OCTET_STREAM, attachmentResponse.getContentType());
495         assertEquals(200, attachmentResponse.getStatusCode());
496         assertEquals(URL_SECOND, attachmentResponse.getWebRequest().getUrl());
497 
498         assertNull(attachmentFilenames.get(0));
499     }
500 }