1 /*
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 */
21
22 #include "config.h"
23 #include "core/html/forms/FileInputType.h"
24
25 #include "HTMLNames.h"
26 #include "InputTypeNames.h"
27 #include "RuntimeEnabledFeatures.h"
28 #include "bindings/v8/ExceptionStatePlaceholder.h"
29 #include "core/dom/shadow/ShadowRoot.h"
30 #include "core/events/Event.h"
31 #include "core/fileapi/File.h"
32 #include "core/fileapi/FileList.h"
33 #include "core/html/FormDataList.h"
34 #include "core/html/HTMLInputElement.h"
35 #include "core/html/forms/FormController.h"
36 #include "core/page/Chrome.h"
37 #include "core/page/DragData.h"
38 #include "core/rendering/RenderFileUploadControl.h"
39 #include "platform/FileMetadata.h"
40 #include "platform/UserGestureIndicator.h"
41 #include "platform/text/PlatformLocale.h"
42 #include "wtf/PassOwnPtr.h"
43 #include "wtf/text/StringBuilder.h"
44 #include "wtf/text/WTFString.h"
45
46 namespace WebCore {
47
48 using blink::WebLocalizedString;
49 using namespace HTMLNames;
50
FileInputType(HTMLInputElement & element)51 inline FileInputType::FileInputType(HTMLInputElement& element)
52 : BaseClickableWithKeyInputType(element)
53 , m_fileList(FileList::create())
54 {
55 }
56
create(HTMLInputElement & element)57 PassRefPtr<InputType> FileInputType::create(HTMLInputElement& element)
58 {
59 return adoptRef(new FileInputType(element));
60 }
61
filesFromFormControlState(const FormControlState & state)62 Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state)
63 {
64 Vector<FileChooserFileInfo> files;
65 for (size_t i = 0; i < state.valueSize(); i += 2) {
66 if (!state[i + 1].isEmpty())
67 files.append(FileChooserFileInfo(state[i], state[i + 1]));
68 else
69 files.append(FileChooserFileInfo(state[i]));
70 }
71 return files;
72 }
73
formControlType() const74 const AtomicString& FileInputType::formControlType() const
75 {
76 return InputTypeNames::file;
77 }
78
saveFormControlState() const79 FormControlState FileInputType::saveFormControlState() const
80 {
81 if (m_fileList->isEmpty())
82 return FormControlState();
83 FormControlState state;
84 unsigned numFiles = m_fileList->length();
85 for (unsigned i = 0; i < numFiles; ++i) {
86 state.append(m_fileList->item(i)->path());
87 state.append(m_fileList->item(i)->name());
88 }
89 return state;
90 }
91
restoreFormControlState(const FormControlState & state)92 void FileInputType::restoreFormControlState(const FormControlState& state)
93 {
94 if (state.valueSize() % 2)
95 return;
96 filesChosen(filesFromFormControlState(state));
97 }
98
appendFormData(FormDataList & encoding,bool multipart) const99 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
100 {
101 FileList* fileList = element().files();
102 unsigned numFiles = fileList->length();
103 if (!multipart) {
104 // Send only the basenames.
105 // 4.10.16.4 and 4.10.16.6 sections in HTML5.
106
107 // Unlike the multipart case, we have no special handling for the empty
108 // fileList because Netscape doesn't support for non-multipart
109 // submission of file inputs, and Firefox doesn't add "name=" query
110 // parameter.
111 for (unsigned i = 0; i < numFiles; ++i)
112 encoding.appendData(element().name(), fileList->item(i)->name());
113 return true;
114 }
115
116 // If no filename at all is entered, return successful but empty.
117 // Null would be more logical, but Netscape posts an empty file. Argh.
118 if (!numFiles) {
119 encoding.appendBlob(element().name(), File::create(""));
120 return true;
121 }
122
123 for (unsigned i = 0; i < numFiles; ++i)
124 encoding.appendBlob(element().name(), fileList->item(i));
125 return true;
126 }
127
valueMissing(const String & value) const128 bool FileInputType::valueMissing(const String& value) const
129 {
130 return element().isRequired() && value.isEmpty();
131 }
132
valueMissingText() const133 String FileInputType::valueMissingText() const
134 {
135 return locale().queryString(element().multiple() ? WebLocalizedString::ValidationValueMissingForMultipleFile : WebLocalizedString::ValidationValueMissingForFile);
136 }
137
handleDOMActivateEvent(Event * event)138 void FileInputType::handleDOMActivateEvent(Event* event)
139 {
140 if (element().isDisabledFormControl())
141 return;
142
143 if (!UserGestureIndicator::processingUserGesture())
144 return;
145
146 if (Chrome* chrome = this->chrome()) {
147 FileChooserSettings settings;
148 HTMLInputElement& input = element();
149 settings.allowsDirectoryUpload = RuntimeEnabledFeatures::directoryUploadEnabled() && input.fastHasAttribute(webkitdirectoryAttr);
150 settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input.fastHasAttribute(multipleAttr);
151 settings.acceptMIMETypes = input.acceptMIMETypes();
152 settings.acceptFileExtensions = input.acceptFileExtensions();
153 settings.selectedFiles = m_fileList->paths();
154 #if ENABLE(MEDIA_CAPTURE)
155 settings.useMediaCapture = input.capture();
156 #endif
157 chrome->runOpenPanel(input.document().frame(), newFileChooser(settings));
158 }
159 event->setDefaultHandled();
160 }
161
createRenderer(RenderStyle *) const162 RenderObject* FileInputType::createRenderer(RenderStyle*) const
163 {
164 return new RenderFileUploadControl(&element());
165 }
166
canSetStringValue() const167 bool FileInputType::canSetStringValue() const
168 {
169 return false;
170 }
171
files()172 FileList* FileInputType::files()
173 {
174 return m_fileList.get();
175 }
176
canSetValue(const String & value)177 bool FileInputType::canSetValue(const String& value)
178 {
179 // For security reasons, we don't allow setting the filename, but we do allow clearing it.
180 // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't
181 // applicable to the file upload control at all, but for now we are keeping this behavior
182 // to avoid breaking existing websites that may be relying on this.
183 return value.isEmpty();
184 }
185
getTypeSpecificValue(String & value)186 bool FileInputType::getTypeSpecificValue(String& value)
187 {
188 if (m_fileList->isEmpty()) {
189 value = String();
190 return true;
191 }
192
193 // HTML5 tells us that we're supposed to use this goofy value for
194 // file input controls. Historically, browsers revealed the real
195 // file path, but that's a privacy problem. Code on the web
196 // decided to try to parse the value by looking for backslashes
197 // (because that's what Windows file paths use). To be compatible
198 // with that code, we make up a fake path for the file.
199 value = "C:\\fakepath\\" + m_fileList->item(0)->name();
200 return true;
201 }
202
setValue(const String &,bool,TextFieldEventBehavior)203 void FileInputType::setValue(const String&, bool, TextFieldEventBehavior)
204 {
205 m_fileList->clear();
206 element().setNeedsStyleRecalc();
207 }
208
createFileList(const Vector<FileChooserFileInfo> & files) const209 PassRefPtr<FileList> FileInputType::createFileList(const Vector<FileChooserFileInfo>& files) const
210 {
211 RefPtr<FileList> fileList(FileList::create());
212 size_t size = files.size();
213
214 // If a directory is being selected, the UI allows a directory to be chosen
215 // and the paths provided here share a root directory somewhere up the tree;
216 // we want to store only the relative paths from that point.
217 if (size && element().fastHasAttribute(webkitdirectoryAttr) && RuntimeEnabledFeatures::directoryUploadEnabled()) {
218 // Find the common root path.
219 String rootPath = directoryName(files[0].path);
220 for (size_t i = 1; i < size; i++) {
221 while (!files[i].path.startsWith(rootPath))
222 rootPath = directoryName(rootPath);
223 }
224 rootPath = directoryName(rootPath);
225 ASSERT(rootPath.length());
226 int rootLength = rootPath.length();
227 if (rootPath[rootLength - 1] != '\\' && rootPath[rootLength - 1] != '/')
228 rootLength += 1;
229 for (size_t i = 0; i < size; i++) {
230 // Normalize backslashes to slashes before exposing the relative path to script.
231 String relativePath = files[i].path.substring(rootLength).replace('\\', '/');
232 fileList->append(File::createWithRelativePath(files[i].path, relativePath));
233 }
234 return fileList;
235 }
236
237 for (size_t i = 0; i < size; i++)
238 fileList->append(File::createWithName(files[i].path, files[i].displayName, File::AllContentTypes));
239 return fileList;
240 }
241
isFileUpload() const242 bool FileInputType::isFileUpload() const
243 {
244 return true;
245 }
246
createShadowSubtree()247 void FileInputType::createShadowSubtree()
248 {
249 ASSERT(element().shadow());
250 RefPtr<HTMLInputElement> button = HTMLInputElement::create(element().document(), 0, false);
251 button->setType(InputTypeNames::button);
252 button->setAttribute(valueAttr, locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel));
253 button->setPseudo(AtomicString("-webkit-file-upload-button", AtomicString::ConstructFromLiteral));
254 element().userAgentShadowRoot()->appendChild(button.release());
255 }
256
disabledAttributeChanged()257 void FileInputType::disabledAttributeChanged()
258 {
259 ASSERT(element().shadow());
260 if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
261 button->setBooleanAttribute(disabledAttr, element().isDisabledFormControl());
262 }
263
multipleAttributeChanged()264 void FileInputType::multipleAttributeChanged()
265 {
266 ASSERT(element().shadow());
267 if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
268 button->setAttribute(valueAttr, locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel));
269 }
270
setFiles(PassRefPtr<FileList> files)271 void FileInputType::setFiles(PassRefPtr<FileList> files)
272 {
273 if (!files)
274 return;
275
276 RefPtr<HTMLInputElement> input(element());
277
278 bool pathsChanged = false;
279 if (files->length() != m_fileList->length()) {
280 pathsChanged = true;
281 } else {
282 for (unsigned i = 0; i < files->length(); ++i) {
283 if (files->item(i)->path() != m_fileList->item(i)->path()) {
284 pathsChanged = true;
285 break;
286 }
287 }
288 }
289
290 m_fileList = files;
291
292 input->setFormControlValueMatchesRenderer(true);
293 input->notifyFormStateChanged();
294 input->setNeedsValidityCheck();
295
296 Vector<String> paths;
297 for (unsigned i = 0; i < m_fileList->length(); ++i)
298 paths.append(m_fileList->item(i)->path());
299
300 if (input->renderer())
301 input->renderer()->repaint();
302
303 if (pathsChanged) {
304 // This call may cause destruction of this instance.
305 // input instance is safe since it is ref-counted.
306 input->HTMLElement::dispatchChangeEvent();
307 }
308 input->setChangedSinceLastFormControlChangeEvent(false);
309 }
310
filesChosen(const Vector<FileChooserFileInfo> & files)311 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
312 {
313 setFiles(createFileList(files));
314 }
315
receiveDropForDirectoryUpload(const Vector<String> & paths)316 void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths)
317 {
318 if (Chrome* chrome = this->chrome()) {
319 FileChooserSettings settings;
320 HTMLInputElement& input = element();
321 settings.allowsDirectoryUpload = true;
322 settings.allowsMultipleFiles = true;
323 settings.selectedFiles.append(paths[0]);
324 settings.acceptMIMETypes = input.acceptMIMETypes();
325 settings.acceptFileExtensions = input.acceptFileExtensions();
326 chrome->enumerateChosenDirectory(newFileChooser(settings));
327 }
328 }
329
receiveDroppedFiles(const DragData * dragData)330 bool FileInputType::receiveDroppedFiles(const DragData* dragData)
331 {
332 Vector<String> paths;
333 dragData->asFilenames(paths);
334 if (paths.isEmpty())
335 return false;
336
337 HTMLInputElement& input = element();
338 if (input.fastHasAttribute(webkitdirectoryAttr) && RuntimeEnabledFeatures::directoryUploadEnabled()) {
339 receiveDropForDirectoryUpload(paths);
340 return true;
341 }
342
343 m_droppedFileSystemId = dragData->droppedFileSystemId();
344
345 Vector<FileChooserFileInfo> files;
346 for (unsigned i = 0; i < paths.size(); ++i)
347 files.append(FileChooserFileInfo(paths[i]));
348
349 if (input.fastHasAttribute(multipleAttr)) {
350 filesChosen(files);
351 } else {
352 Vector<FileChooserFileInfo> firstFileOnly;
353 firstFileOnly.append(files[0]);
354 filesChosen(firstFileOnly);
355 }
356 return true;
357 }
358
droppedFileSystemId()359 String FileInputType::droppedFileSystemId()
360 {
361 return m_droppedFileSystemId;
362 }
363
defaultToolTip() const364 String FileInputType::defaultToolTip() const
365 {
366 FileList* fileList = m_fileList.get();
367 unsigned listSize = fileList->length();
368 if (!listSize) {
369 return locale().queryString(WebLocalizedString::FileButtonNoFileSelectedLabel);
370 }
371
372 StringBuilder names;
373 for (size_t i = 0; i < listSize; ++i) {
374 names.append(fileList->item(i)->name());
375 if (i != listSize - 1)
376 names.append('\n');
377 }
378 return names.toString();
379 }
380
381 } // namespace WebCore
382