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