View Javadoc
1   /*
2    * Copyright (c) 2002-2017 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    * http://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 com.gargoylesoftware.htmlunit.html;
16  
17  import java.io.File;
18  import java.net.URI;
19  import java.net.URISyntaxException;
20  import java.nio.charset.Charset;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.lang3.StringUtils;
26  
27  import com.gargoylesoftware.htmlunit.SgmlPage;
28  import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
29  import com.gargoylesoftware.htmlunit.util.KeyDataPair;
30  import com.gargoylesoftware.htmlunit.util.NameValuePair;
31  
32  /**
33   * Wrapper for the HTML element "input".
34   *
35   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
36   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
37   * @author Daniel Gredler
38   * @author Ahmed Ashour
39   * @author Marc Guillemot
40   * @author Frank Danek
41   * @author Ronald Brill
42   */
43  public class HtmlFileInput extends HtmlInput {
44  
45      private String contentType_;
46      private byte[] data_;
47      private File[] files_ = new File[0];
48  
49      /**
50       * Creates an instance.
51       *
52       * @param qualifiedName the qualified name of the element type to instantiate
53       * @param page the page that contains this element
54       * @param attributes the initial attributes
55       */
56      HtmlFileInput(final String qualifiedName, final SgmlPage page,
57              final Map<String, DomAttr> attributes) {
58          super(qualifiedName, page, attributes);
59  
60          final DomAttr valueAttrib = attributes.get("value");
61          if (valueAttrib != null) {
62              setDefaultValue(valueAttrib.getNodeValue(), false);
63          }
64      }
65  
66      /**
67       * Returns the in-memory data assigned to this file input element, if any.
68       * @return {@code null} if {@link #setData(byte[])} hasn't be used
69       */
70      public final byte[] getData() {
71          return data_;
72      }
73  
74      /**
75       * <p>Assigns in-memory data to this file input element. During submission, instead
76       * of loading data from a file, the data is read from in-memory byte array.</p>
77       *
78       * <p>NOTE: Only use this method if you wish to upload in-memory data; if you instead
79       * wish to upload the contents of an actual file, use {@link #setValueAttribute(String)},
80       * passing in the path to the file.</p>
81       *
82       * @param data the in-memory data assigned to this file input element
83       */
84      public final void setData(final byte[] data) {
85          data_ = data;
86      }
87  
88      /**
89       * {@inheritDoc}
90       */
91      @Override
92      public NameValuePair[] getSubmitNameValuePairs() {
93          if (files_ == null || files_.length == 0) {
94              return new NameValuePair[] {new KeyDataPair(getNameAttribute(), null, null, null, (Charset) null)};
95          }
96  
97          final List<NameValuePair> list = new ArrayList<>();
98          for (File file : files_) {
99              String contentType;
100             if (contentType_ == null) {
101                 contentType = getPage().getWebClient().getBrowserVersion().getUploadMimeType(file);
102                 if (StringUtils.isEmpty(contentType)) {
103                     contentType = "application/octet-stream";
104                 }
105             }
106             else {
107                 contentType = contentType_;
108             }
109             final Charset charset = getPage().getCharset();
110             final KeyDataPair keyDataPair = new KeyDataPair(getNameAttribute(), file, null, contentType, charset);
111             keyDataPair.setData(data_);
112             list.add(keyDataPair);
113         }
114         return list.toArray(new NameValuePair[list.size()]);
115     }
116 
117     /**
118      * Sets the content type value that should be sent together with the uploaded file.
119      * If content type is not explicitly set, HtmlUnit will try to guess it from the file content.
120      * @param contentType the content type ({@code null} resets it)
121      */
122     public void setContentType(final String contentType) {
123         contentType_ = contentType;
124     }
125 
126     /**
127      * Gets the content type that should be sent together with the uploaded file.
128      * @return the content type, or {@code null} if this has not been explicitly set
129      * and should be guessed from file content
130      */
131     public String getContentType() {
132         return contentType_;
133     }
134 
135     /**
136      * {@inheritDoc}
137      */
138     @Override
139     public String asText() {
140         return "";
141     }
142 
143     /**
144      * {@inheritDoc}
145      */
146     @Override
147     public void setValueAttribute(final String newValue) {
148         setFiles(new File(newValue));
149     }
150 
151     /**
152      * Used to specify {@code multiple} files to upload.
153      *
154      * We may follow WebDriver solution, once made,
155      * see https://code.google.com/p/selenium/issues/detail?id=2239
156      * @param files the list of files to upload
157      */
158     public void setFiles(final File... files) {
159         if (files.length > 1 && getAttribute("multiple") == ATTRIBUTE_NOT_DEFINED) {
160             throw new IllegalStateException("HtmlFileInput is not 'multiple'.");
161         }
162         for (int i = 0; i < files.length; i++) {
163             files[i] = normalizeFile(files[i]);
164         }
165         files_ = files;
166         fireEvent(Event.TYPE_CHANGE);
167     }
168 
169     /**
170      * To tolerate {@code file://}
171      */
172     private static File normalizeFile(final File file) {
173         File f = null;
174         String path = file.getPath().replace('\\', '/');
175         if (path.startsWith("file:/")) {
176             if (path.startsWith("file://") && !path.startsWith("file:///")) {
177                 path = "file:///" + path.substring(7);
178             }
179             try {
180                 f = new File(new URI(path));
181             }
182             catch (final URISyntaxException e) {
183                 // nothing here
184             }
185         }
186         if (f == null) {
187             f = new File(path);
188         }
189         return f;
190     }
191 
192     /**
193      * Returns the files.
194      * @return the array of {@link File}s
195      */
196     public File[] getFiles() {
197         return files_;
198     }
199 }