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.IOException;
18  import java.io.InputStream;
19  import java.nio.file.Files;
20  import java.nio.file.LinkOption;
21  import java.nio.file.Path;
22  import java.nio.file.Paths;
23  
24  import org.apache.commons.io.FileUtils;
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.htmlunit.Page;
29  import org.htmlunit.WebResponse;
30  
31  /**
32   * Implementation of an {@link AttachmentHandler} that mimics how browsers handle attachments, specifically
33   * <ul>
34   * <li>download file into a default folder when attachment response is detected</li>
35   * <li>infer filename from octet stream response and use that when saving file</li>
36   * <li>if a file already exists, append number to it.
37   * Keep incrementing numbers until you find a slot that is free (thats how Chrome handles duplicate filenames).</li>
38   * </ul>
39   *
40   * @author Marek Andreansky
41   * @author Ronald Brill
42   */
43  public class DownloadingAttachmentHandler implements AttachmentHandler {
44  
45      private static final Log LOG = LogFactory.getLog(DownloadingAttachmentHandler.class);
46  
47      private Path downloadFolder_;
48  
49      /**
50       * Creates a new DownloadingAttachmentHandler that stores all downloaded files in the
51       * provided directory. The directory must exist and be writable.
52       *
53       * @param downloadFolder the path to the folder for storing all downloaded files
54       * @throws IOException if the folder does not exist or the folder is not writable
55       */
56      public DownloadingAttachmentHandler(final Path downloadFolder) throws IOException {
57          downloadFolder_ = downloadFolder;
58          if (Files.notExists(downloadFolder)) {
59              throw new IOException("The provided download folder '"
60                          + downloadFolder + "' does not exist");
61          }
62          if (!Files.isWritable(downloadFolder)) {
63              throw new IOException("Can't write to the download folder '"
64                          + downloadFolder + "'");
65          }
66      }
67  
68      /**
69       * Creates a new DownloadingAttachmentHandler that stores all downloaded files in the
70       * 'temp'-dir (System.getProperty("java.io.tmpdir")).
71       *
72       * @throws IOException if the folder does not exist or the folder is not writable
73       */
74      public DownloadingAttachmentHandler() throws IOException {
75          this(Paths.get(System.getProperty("java.io.tmpdir")));
76      }
77  
78      /**
79       * {@inheritDoc}
80       */
81      @Override
82      public void handleAttachment(final Page page, final String attachmentFilename) {
83          final Path destination = determineDestionationFile(page, attachmentFilename);
84  
85          final WebResponse webResponse = page.getWebResponse();
86          try (InputStream contentAsStream = webResponse.getContentAsStream()) {
87              FileUtils.copyToFile(contentAsStream, destination.toFile());
88          }
89          catch (final Exception e) {
90              LOG.error("Failed to write attachment response content to '"
91                              + destination.toAbsolutePath() + "'");
92              return;
93          }
94  
95          webResponse.cleanUp();
96      }
97  
98      private Path determineDestionationFile(final Page page, final String attachmentFilename) {
99          String fileName = attachmentFilename;
100 
101         if (StringUtils.isAllBlank(fileName)) {
102             final String file = page.getWebResponse().getWebRequest().getUrl().getFile();
103             fileName = file.substring(file.lastIndexOf('/') + 1);
104         }
105 
106         if (StringUtils.isAllBlank(fileName)) {
107             fileName = "download";
108         }
109 
110         Path newPath = downloadFolder_.resolve(fileName);
111         int count = 1;
112         while (Files.exists(newPath, LinkOption.NOFOLLOW_LINKS)) {
113             final String newFileName;
114             final int pos = fileName.lastIndexOf('.');
115             if (pos == -1) {
116                 newFileName = fileName + "(" + count + ")";
117             }
118             else {
119                 newFileName = fileName.substring(0, pos)
120                                         + "(" + count + ")"
121                                         + fileName.substring(pos);
122             }
123 
124             newPath = downloadFolder_.resolve(newFileName);
125             count++;
126         }
127 
128         return newPath.toAbsolutePath();
129     }
130 }