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