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 51 inline FileInputType::FileInputType(HTMLInputElement& element) 52 : BaseClickableWithKeyInputType(element) 53 , m_fileList(FileList::create()) 54 { 55 } 56 57 PassRefPtr<InputType> FileInputType::create(HTMLInputElement& element) 58 { 59 return adoptRef(new FileInputType(element)); 60 } 61 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 74 const AtomicString& FileInputType::formControlType() const 75 { 76 return InputTypeNames::file; 77 } 78 79 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 92 void FileInputType::restoreFormControlState(const FormControlState& state) 93 { 94 if (state.valueSize() % 2) 95 return; 96 filesChosen(filesFromFormControlState(state)); 97 } 98 99 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 128 bool FileInputType::valueMissing(const String& value) const 129 { 130 return element().isRequired() && value.isEmpty(); 131 } 132 133 String FileInputType::valueMissingText() const 134 { 135 return locale().queryString(element().multiple() ? WebLocalizedString::ValidationValueMissingForMultipleFile : WebLocalizedString::ValidationValueMissingForFile); 136 } 137 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 162 RenderObject* FileInputType::createRenderer(RenderStyle*) const 163 { 164 return new RenderFileUploadControl(&element()); 165 } 166 167 bool FileInputType::canSetStringValue() const 168 { 169 return false; 170 } 171 172 FileList* FileInputType::files() 173 { 174 return m_fileList.get(); 175 } 176 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 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 203 void FileInputType::setValue(const String&, bool, TextFieldEventBehavior) 204 { 205 m_fileList->clear(); 206 element().setNeedsStyleRecalc(); 207 } 208 209 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 242 bool FileInputType::isFileUpload() const 243 { 244 return true; 245 } 246 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 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 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 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 311 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files) 312 { 313 setFiles(createFileList(files)); 314 } 315 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 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 359 String FileInputType::droppedFileSystemId() 360 { 361 return m_droppedFileSystemId; 362 } 363 364 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