[nrwt] handle corrupt http server pid files cleanly
[WebKit-https.git] / Source / WebCore / html / FileInputType.cpp
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 "FileInputType.h"
24
25 #include "Chrome.h"
26 #include "DragData.h"
27 #include "ElementShadow.h"
28 #include "Event.h"
29 #include "File.h"
30 #include "FileList.h"
31 #include "FileSystem.h"
32 #include "FormController.h"
33 #include "FormDataList.h"
34 #include "Frame.h"
35 #include "HTMLInputElement.h"
36 #include "HTMLNames.h"
37 #include "Icon.h"
38 #include "InputTypeNames.h"
39 #include "LocalizedStrings.h"
40 #include "RenderFileUploadControl.h"
41 #include "ScriptController.h"
42 #include "ShadowRoot.h"
43 #include "WebKitBlobBuilder.h"
44 #include <wtf/PassOwnPtr.h>
45 #include <wtf/text/StringBuilder.h>
46 #include <wtf/text/WTFString.h>
47
48 namespace WebCore {
49
50 using namespace HTMLNames;
51
52 class UploadButtonElement : public HTMLInputElement {
53 public:
54     static PassRefPtr<UploadButtonElement> create(Document*);
55     static PassRefPtr<UploadButtonElement> createForMultiple(Document*);
56
57 private:
58     UploadButtonElement(Document*);
59
60     virtual const AtomicString& shadowPseudoId() const;
61 };
62
63 PassRefPtr<UploadButtonElement> UploadButtonElement::create(Document* document)
64 {
65     RefPtr<UploadButtonElement> button = adoptRef(new UploadButtonElement(document));
66     button->setType("button");
67     button->setValue(fileButtonChooseFileLabel());
68     return button.release();
69 }
70
71 PassRefPtr<UploadButtonElement> UploadButtonElement::createForMultiple(Document* document)
72 {
73     RefPtr<UploadButtonElement> button = adoptRef(new UploadButtonElement(document));
74     button->setType("button");
75     button->setValue(fileButtonChooseMultipleFilesLabel());
76     return button.release();
77 }
78
79 UploadButtonElement::UploadButtonElement(Document* document)
80     : HTMLInputElement(inputTag, document, 0, false)
81 {
82 }
83
84 const AtomicString& UploadButtonElement::shadowPseudoId() const
85 {
86     DEFINE_STATIC_LOCAL(AtomicString, pseudoId, ("-webkit-file-upload-button", AtomicString::ConstructFromLiteral));
87     return pseudoId;
88 }
89
90 inline FileInputType::FileInputType(HTMLInputElement* element)
91     : BaseClickableWithKeyInputType(element)
92     , m_fileList(FileList::create())
93 {
94 }
95
96 PassOwnPtr<InputType> FileInputType::create(HTMLInputElement* element)
97 {
98     return adoptPtr(new FileInputType(element));
99 }
100
101 Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state)
102 {
103     Vector<FileChooserFileInfo> files;
104     for (size_t i = 0; i < state.valueSize(); i += 2) {
105         if (!state[i + 1].isEmpty())
106             files.append(FileChooserFileInfo(state[i], state[i + 1]));
107         else
108             files.append(FileChooserFileInfo(state[i]));
109     }
110     return files;
111 }
112
113 const AtomicString& FileInputType::formControlType() const
114 {
115     return InputTypeNames::file();
116 }
117
118 FormControlState FileInputType::saveFormControlState() const
119 {
120     if (m_fileList->isEmpty())
121         return FormControlState();
122     FormControlState state;
123     unsigned numFiles = m_fileList->length();
124     for (unsigned i = 0; i < numFiles; ++i) {
125         state.append(m_fileList->item(i)->path());
126         state.append(m_fileList->item(i)->name());
127     }
128     return state;
129 }
130
131 void FileInputType::restoreFormControlState(const FormControlState& state)
132 {
133     if (state.valueSize() % 2)
134         return;
135     filesChosen(filesFromFormControlState(state));
136 }
137
138 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
139 {
140     FileList* fileList = element()->files();
141     unsigned numFiles = fileList->length();
142     if (!multipart) {
143         // Send only the basenames.
144         // 4.10.16.4 and 4.10.16.6 sections in HTML5.
145
146         // Unlike the multipart case, we have no special handling for the empty
147         // fileList because Netscape doesn't support for non-multipart
148         // submission of file inputs, and Firefox doesn't add "name=" query
149         // parameter.
150         for (unsigned i = 0; i < numFiles; ++i)
151             encoding.appendData(element()->name(), fileList->item(i)->name());
152         return true;
153     }
154
155     // If no filename at all is entered, return successful but empty.
156     // Null would be more logical, but Netscape posts an empty file. Argh.
157     if (!numFiles) {
158         encoding.appendBlob(element()->name(), BlobBuilder().getBlob(ASCIILiteral("application/octet-stream")), emptyString());
159         return true;
160     }
161
162     for (unsigned i = 0; i < numFiles; ++i)
163         encoding.appendBlob(element()->name(), fileList->item(i));
164     return true;
165 }
166
167 bool FileInputType::valueMissing(const String& value) const
168 {
169     return element()->isRequired() && value.isEmpty();
170 }
171
172 String FileInputType::valueMissingText() const
173 {
174     return element()->multiple() ? validationMessageValueMissingForMultipleFileText() : validationMessageValueMissingForFileText();
175 }
176
177 void FileInputType::handleDOMActivateEvent(Event* event)
178 {
179     if (element()->disabled())
180         return;
181
182     if (!ScriptController::processingUserGesture())
183         return;
184
185     if (Chrome* chrome = this->chrome()) {
186         FileChooserSettings settings;
187         HTMLInputElement* input = element();
188 #if ENABLE(DIRECTORY_UPLOAD)
189         settings.allowsDirectoryUpload = input->fastHasAttribute(webkitdirectoryAttr);
190         settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input->fastHasAttribute(multipleAttr);
191 #else
192         settings.allowsMultipleFiles = input->fastHasAttribute(multipleAttr);
193 #endif
194         settings.acceptMIMETypes = input->acceptMIMETypes();
195         settings.acceptFileExtensions = input->acceptFileExtensions();
196         settings.selectedFiles = m_fileList->paths();
197 #if ENABLE(MEDIA_CAPTURE)
198         settings.capture = input->capture();
199 #endif
200         chrome->runOpenPanel(input->document()->frame(), newFileChooser(settings));
201     }
202     event->setDefaultHandled();
203 }
204
205 RenderObject* FileInputType::createRenderer(RenderArena* arena, RenderStyle*) const
206 {
207     return new (arena) RenderFileUploadControl(element());
208 }
209
210 bool FileInputType::canSetStringValue() const
211 {
212     return false;
213 }
214
215 bool FileInputType::canChangeFromAnotherType() const
216 {
217     // Don't allow the type to be changed to file after the first type change.
218     // In other engines this might mean a JavaScript programmer could set a text
219     // field's value to something like /etc/passwd and then change it to a file input.
220     // I don't think this would actually occur in WebKit, but this rule still may be
221     // important for compatibility.
222     return false;
223 }
224
225 FileList* FileInputType::files()
226 {
227     return m_fileList.get();
228 }
229
230 bool FileInputType::canSetValue(const String& value)
231 {
232     // For security reasons, we don't allow setting the filename, but we do allow clearing it.
233     // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't
234     // applicable to the file upload control at all, but for now we are keeping this behavior
235     // to avoid breaking existing websites that may be relying on this.
236     return value.isEmpty();
237 }
238
239 bool FileInputType::getTypeSpecificValue(String& value)
240 {
241     if (m_fileList->isEmpty()) {
242         value = String();
243         return true;
244     }
245
246     // HTML5 tells us that we're supposed to use this goofy value for
247     // file input controls. Historically, browsers revealed the real
248     // file path, but that's a privacy problem. Code on the web
249     // decided to try to parse the value by looking for backslashes
250     // (because that's what Windows file paths use). To be compatible
251     // with that code, we make up a fake path for the file.
252     value = "C:\\fakepath\\" + m_fileList->item(0)->name();
253     return true;
254 }
255
256 void FileInputType::setValue(const String&, bool, TextFieldEventBehavior)
257 {
258     m_fileList->clear();
259     m_icon.clear();
260     element()->setNeedsStyleRecalc();
261 }
262
263 PassRefPtr<FileList> FileInputType::createFileList(const Vector<FileChooserFileInfo>& files) const
264 {
265     RefPtr<FileList> fileList(FileList::create());
266     size_t size = files.size();
267
268 #if ENABLE(DIRECTORY_UPLOAD)
269     // If a directory is being selected, the UI allows a directory to be chosen
270     // and the paths provided here share a root directory somewhere up the tree;
271     // we want to store only the relative paths from that point.
272     if (size && element()->fastHasAttribute(webkitdirectoryAttr)) {
273         // Find the common root path.
274         String rootPath = directoryName(files[0].path);
275         for (size_t i = 1; i < size; i++) {
276             while (!files[i].path.startsWith(rootPath))
277                 rootPath = directoryName(rootPath);
278         }
279         rootPath = directoryName(rootPath);
280         ASSERT(rootPath.length());
281         int rootLength = rootPath.length();
282         if (rootPath[rootLength - 1] != '\\' && rootPath[rootLength - 1] != '/')
283             rootLength += 1;
284         for (size_t i = 0; i < size; i++) {
285             // Normalize backslashes to slashes before exposing the relative path to script.
286             String relativePath = files[i].path.substring(rootLength).replace('\\', '/');
287             fileList->append(File::createWithRelativePath(files[i].path, relativePath));
288         }
289         return fileList;
290     }
291 #endif
292
293     for (size_t i = 0; i < size; i++)
294         fileList->append(File::createWithName(files[i].path, files[i].displayName, File::AllContentTypes));
295     return fileList;
296 }
297
298 bool FileInputType::isFileUpload() const
299 {
300     return true;
301 }
302
303 void FileInputType::createShadowSubtree()
304 {
305     ASSERT(element()->shadow());
306     element()->userAgentShadowRoot()->appendChild(element()->multiple() ? UploadButtonElement::createForMultiple(element()->document()): UploadButtonElement::create(element()->document()), IGNORE_EXCEPTION);
307 }
308
309 void FileInputType::disabledAttributeChanged()
310 {
311     ASSERT(element()->shadow());
312     UploadButtonElement* button = static_cast<UploadButtonElement*>(element()->userAgentShadowRoot()->firstChild());
313     if (button)
314         button->setBooleanAttribute(disabledAttr, element()->disabled());
315 }
316
317 void FileInputType::multipleAttributeChanged()
318 {
319     ASSERT(element()->shadow());
320     UploadButtonElement* button = static_cast<UploadButtonElement*>(element()->userAgentShadowRoot()->firstChild());
321     if (button)
322         button->setValue(element()->multiple() ? fileButtonChooseMultipleFilesLabel() : fileButtonChooseFileLabel());
323 }
324
325 void FileInputType::requestIcon(const Vector<String>& paths)
326 {
327     if (!paths.size())
328         return;
329
330     if (Chrome* chrome = this->chrome())
331         chrome->loadIconForFiles(paths, newFileIconLoader());
332 }
333
334 void FileInputType::setFiles(PassRefPtr<FileList> files)
335 {
336     if (!files)
337         return;
338
339     RefPtr<HTMLInputElement> input = element();
340
341     bool pathsChanged = false;
342     if (files->length() != m_fileList->length())
343         pathsChanged = true;
344     else {
345         for (unsigned i = 0; i < files->length(); ++i) {
346             if (files->item(i)->path() != m_fileList->item(i)->path()) {
347                 pathsChanged = true;
348                 break;
349             }
350         }
351     }
352
353     m_fileList = files;
354
355     input->setFormControlValueMatchesRenderer(true);
356     input->notifyFormStateChanged();
357     input->setNeedsValidityCheck();
358
359     Vector<String> paths;
360     for (unsigned i = 0; i < m_fileList->length(); ++i)
361         paths.append(m_fileList->item(i)->path());
362     requestIcon(paths);
363
364     if (input->renderer())
365         input->renderer()->repaint();
366
367     if (pathsChanged) {
368         // This call may cause destruction of this instance.
369         // input instance is safe since it is ref-counted.
370         input->HTMLElement::dispatchChangeEvent();
371     }
372     input->setChangedSinceLastFormControlChangeEvent(false);
373 }
374
375 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
376 {
377     setFiles(createFileList(files));
378 }
379
380 #if ENABLE(DIRECTORY_UPLOAD)
381 void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths)
382 {
383     if (Chrome* chrome = this->chrome()) {
384         FileChooserSettings settings;
385         HTMLInputElement* input = element();
386         settings.allowsDirectoryUpload = true;
387         settings.allowsMultipleFiles = true;
388         settings.selectedFiles.append(paths[0]);
389         settings.acceptMIMETypes = input->acceptMIMETypes();
390         settings.acceptFileExtensions = input->acceptFileExtensions();
391         chrome->enumerateChosenDirectory(newFileChooser(settings));
392     }
393 }
394 #endif
395
396 void FileInputType::updateRendering(PassRefPtr<Icon> icon)
397 {
398     if (m_icon == icon)
399         return;
400
401     m_icon = icon;
402     if (element()->renderer())
403         element()->renderer()->repaint();
404 }
405
406 bool FileInputType::receiveDroppedFiles(const DragData* dragData)
407 {
408     Vector<String> paths;
409     dragData->asFilenames(paths);
410     if (paths.isEmpty())
411         return false;
412
413     HTMLInputElement* input = element();
414 #if ENABLE(DIRECTORY_UPLOAD)
415     if (input->fastHasAttribute(webkitdirectoryAttr)) {
416         receiveDropForDirectoryUpload(paths);
417         return true;
418     }
419 #endif
420
421 #if ENABLE(FILE_SYSTEM)
422     m_droppedFileSystemId = dragData->droppedFileSystemId();
423 #endif
424
425     Vector<FileChooserFileInfo> files;
426     for (unsigned i = 0; i < paths.size(); ++i)
427         files.append(FileChooserFileInfo(paths[i]));
428
429     if (input->fastHasAttribute(multipleAttr))
430         filesChosen(files);
431     else {
432         Vector<FileChooserFileInfo> firstFileOnly;
433         firstFileOnly.append(files[0]);
434         filesChosen(firstFileOnly);
435     }
436     return true;
437 }
438
439 #if ENABLE(FILE_SYSTEM)
440 String FileInputType::droppedFileSystemId()
441 {
442     return m_droppedFileSystemId;
443 }
444 #endif
445
446 Icon* FileInputType::icon() const
447 {
448     return m_icon.get();
449 }
450
451 String FileInputType::defaultToolTip() const
452 {
453     FileList* fileList = m_fileList.get();
454     unsigned listSize = fileList->length();
455     if (!listSize) {
456         if (element()->multiple())
457             return fileButtonNoFilesSelectedLabel();
458         return fileButtonNoFileSelectedLabel();
459     }
460
461     StringBuilder names;
462     for (size_t i = 0; i < listSize; ++i) {
463         names.append(fileList->item(i)->name());
464         if (i != listSize - 1)
465             names.append('\n');
466     }
467     return names.toString();
468 }
469
470 } // namespace WebCore