GetDirectoryListingTask.cpp (12845B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "GetDirectoryListingTask.h" 8 9 #include "HTMLSplitOnSpacesTokenizer.h" 10 #include "js/Value.h" 11 #include "mozilla/dom/FileBlobImpl.h" 12 #include "mozilla/dom/FileSystemBase.h" 13 #include "mozilla/dom/FileSystemUtils.h" 14 #include "mozilla/dom/IPCBlobUtils.h" 15 #include "mozilla/dom/PFileSystemParams.h" 16 #include "mozilla/dom/Promise.h" 17 #include "mozilla/dom/UnionTypes.h" 18 #include "mozilla/ipc/BackgroundParent.h" 19 #include "nsCExternalHandlerService.h" 20 #include "nsIFile.h" 21 #include "nsIMIMEService.h" 22 #include "nsString.h" 23 24 namespace mozilla::dom { 25 26 /** 27 * GetDirectoryListingTaskChild 28 */ 29 30 /* static */ 31 already_AddRefed<GetDirectoryListingTaskChild> 32 GetDirectoryListingTaskChild::Create(FileSystemBase* aFileSystem, 33 Directory* aDirectory, 34 nsIFile* aTargetPath, 35 const nsAString& aFilters, 36 ErrorResult& aRv) { 37 MOZ_ASSERT(aFileSystem); 38 MOZ_ASSERT(aDirectory); 39 aFileSystem->AssertIsOnOwningThread(); 40 41 nsCOMPtr<nsIGlobalObject> globalObject = aFileSystem->GetParentObject(); 42 MOZ_ASSERT(globalObject); 43 44 RefPtr<GetDirectoryListingTaskChild> task = new GetDirectoryListingTaskChild( 45 globalObject, aFileSystem, aDirectory, aTargetPath, aFilters); 46 47 // aTargetPath can be null. In this case SetError will be called. 48 49 task->mPromise = Promise::Create(globalObject, aRv); 50 if (NS_WARN_IF(aRv.Failed())) { 51 return nullptr; 52 } 53 54 return task.forget(); 55 } 56 57 GetDirectoryListingTaskChild::GetDirectoryListingTaskChild( 58 nsIGlobalObject* aGlobalObject, FileSystemBase* aFileSystem, 59 Directory* aDirectory, nsIFile* aTargetPath, const nsAString& aFilters) 60 : FileSystemTaskChildBase(aGlobalObject, aFileSystem), 61 mDirectory(aDirectory), 62 mTargetPath(aTargetPath), 63 mFilters(aFilters) { 64 MOZ_ASSERT(aFileSystem); 65 aFileSystem->AssertIsOnOwningThread(); 66 } 67 68 GetDirectoryListingTaskChild::~GetDirectoryListingTaskChild() { 69 mFileSystem->AssertIsOnOwningThread(); 70 } 71 72 already_AddRefed<Promise> GetDirectoryListingTaskChild::GetPromise() { 73 mFileSystem->AssertIsOnOwningThread(); 74 return RefPtr<Promise>(mPromise).forget(); 75 } 76 77 FileSystemParams GetDirectoryListingTaskChild::GetRequestParams( 78 const nsString& aSerializedDOMPath, ErrorResult& aRv) const { 79 mFileSystem->AssertIsOnOwningThread(); 80 81 // this is the real path. 82 nsAutoString path; 83 aRv = mTargetPath->GetPath(path); 84 if (NS_WARN_IF(aRv.Failed())) { 85 return FileSystemGetDirectoryListingParams(); 86 } 87 88 // this is the dom path. 89 nsAutoString directoryPath; 90 mDirectory->GetPath(directoryPath, aRv); 91 if (NS_WARN_IF(aRv.Failed())) { 92 return FileSystemGetDirectoryListingParams(); 93 } 94 95 return FileSystemGetDirectoryListingParams(aSerializedDOMPath, path, 96 directoryPath, mFilters); 97 } 98 99 void GetDirectoryListingTaskChild::SetSuccessRequestResult( 100 const FileSystemResponseValue& aValue, ErrorResult& aRv) { 101 mFileSystem->AssertIsOnOwningThread(); 102 MOZ_ASSERT(aValue.type() == 103 FileSystemResponseValue::TFileSystemDirectoryListingResponse); 104 105 FileSystemDirectoryListingResponse r = aValue; 106 for (uint32_t i = 0; i < r.data().Length(); ++i) { 107 const FileSystemDirectoryListingResponseData& data = r.data()[i]; 108 109 OwningFileOrDirectory* ofd = mTargetData.AppendElement(fallible); 110 if (!ofd) { 111 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 112 return; 113 } 114 115 if (data.type() == FileSystemDirectoryListingResponseData:: 116 TFileSystemDirectoryListingResponseFile) { 117 const FileSystemDirectoryListingResponseFile& d = 118 data.get_FileSystemDirectoryListingResponseFile(); 119 120 RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(d.blob()); 121 MOZ_ASSERT(blobImpl); 122 123 nsCOMPtr<nsIGlobalObject> globalObject = mFileSystem->GetParentObject(); 124 MOZ_ASSERT(globalObject); 125 126 RefPtr<File> file = File::Create(globalObject, blobImpl); 127 MOZ_ASSERT(file); 128 129 ofd->SetAsFile() = file; 130 } else { 131 MOZ_ASSERT(data.type() == 132 FileSystemDirectoryListingResponseData:: 133 TFileSystemDirectoryListingResponseDirectory); 134 const FileSystemDirectoryListingResponseDirectory& d = 135 data.get_FileSystemDirectoryListingResponseDirectory(); 136 137 nsCOMPtr<nsIFile> path; 138 aRv = NS_NewLocalFile(d.directoryRealPath(), getter_AddRefs(path)); 139 if (NS_WARN_IF(aRv.Failed())) { 140 return; 141 } 142 143 RefPtr<Directory> directory = 144 Directory::Create(mFileSystem->GetParentObject(), path, mFileSystem); 145 MOZ_ASSERT(directory); 146 147 ofd->SetAsDirectory() = directory; 148 } 149 } 150 } 151 152 void GetDirectoryListingTaskChild::HandlerCallback() { 153 mFileSystem->AssertIsOnOwningThread(); 154 155 if (mFileSystem->IsShutdown()) { 156 mPromise = nullptr; 157 return; 158 } 159 160 if (HasError()) { 161 mPromise->MaybeReject(mErrorValue); 162 mPromise = nullptr; 163 return; 164 } 165 166 mPromise->MaybeResolve(mTargetData); 167 mPromise = nullptr; 168 } 169 170 /** 171 * GetDirectoryListingTaskParent 172 */ 173 174 /* static */ 175 already_AddRefed<GetDirectoryListingTaskParent> 176 GetDirectoryListingTaskParent::Create( 177 FileSystemBase* aFileSystem, 178 const FileSystemGetDirectoryListingParams& aParam, 179 FileSystemRequestParent* aParent, ErrorResult& aRv) { 180 MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); 181 mozilla::ipc::AssertIsOnBackgroundThread(); 182 MOZ_ASSERT(aFileSystem); 183 184 RefPtr<GetDirectoryListingTaskParent> task = 185 new GetDirectoryListingTaskParent(aFileSystem, aParam, aParent); 186 187 aRv = NS_NewLocalFile(aParam.realPath(), getter_AddRefs(task->mTargetPath)); 188 if (NS_WARN_IF(aRv.Failed())) { 189 return nullptr; 190 } 191 192 return task.forget(); 193 } 194 195 GetDirectoryListingTaskParent::GetDirectoryListingTaskParent( 196 FileSystemBase* aFileSystem, 197 const FileSystemGetDirectoryListingParams& aParam, 198 FileSystemRequestParent* aParent) 199 : FileSystemTaskParentBase(aFileSystem, aParam, aParent), 200 mDOMPath(aParam.domPath()), 201 mFilters(aParam.filters()) { 202 MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); 203 mozilla::ipc::AssertIsOnBackgroundThread(); 204 MOZ_ASSERT(aFileSystem); 205 } 206 207 FileSystemResponseValue GetDirectoryListingTaskParent::GetSuccessRequestResult( 208 ErrorResult& aRv) const { 209 mozilla::ipc::AssertIsOnBackgroundThread(); 210 211 nsTArray<FileSystemDirectoryListingResponseData> inputs; 212 213 for (unsigned i = 0; i < mTargetData.Length(); i++) { 214 if (mTargetData[i].mType == FileOrDirectoryPath::eFilePath) { 215 nsCOMPtr<nsIFile> path; 216 nsresult rv = NS_NewLocalFile(mTargetData[i].mPath, getter_AddRefs(path)); 217 if (NS_WARN_IF(NS_FAILED(rv))) { 218 continue; 219 } 220 221 nsAutoString fileName; 222 path->GetLeafName(fileName); 223 224 int64_t fileSize = 0; 225 rv = path->GetFileSize(&fileSize); 226 if (NS_WARN_IF(NS_FAILED(rv))) { 227 continue; 228 } 229 230 FileSystemDirectoryListingResponseFile fileData; 231 RefPtr<BlobImpl> blobImpl = new FileBlobImpl( 232 fileName, mTargetData[i].mContentType, fileSize, path); 233 234 nsAutoString filePath; 235 filePath.Assign(mDOMPath); 236 237 // This is specific for unix root filesystem. 238 if (!mDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) { 239 filePath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); 240 } 241 242 nsAutoString name; 243 blobImpl->GetName(name); 244 filePath.Append(name); 245 blobImpl->SetDOMPath(filePath); 246 247 IPCBlob ipcBlob; 248 rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob); 249 if (NS_WARN_IF(NS_FAILED(rv))) { 250 continue; 251 } 252 253 fileData.blob() = ipcBlob; 254 inputs.AppendElement(fileData); 255 } else { 256 MOZ_ASSERT(mTargetData[i].mType == FileOrDirectoryPath::eDirectoryPath); 257 FileSystemDirectoryListingResponseDirectory directoryData; 258 directoryData.directoryRealPath() = mTargetData[i].mPath; 259 inputs.AppendElement(directoryData); 260 } 261 } 262 263 FileSystemDirectoryListingResponse response; 264 response.data() = std::move(inputs); 265 return response; 266 } 267 268 nsresult GetDirectoryListingTaskParent::IOWork() { 269 MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); 270 MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!"); 271 272 if (mFileSystem->IsShutdown()) { 273 return NS_ERROR_FAILURE; 274 } 275 276 bool exists; 277 nsresult rv = mTargetPath->Exists(&exists); 278 if (NS_WARN_IF(NS_FAILED(rv))) { 279 return rv; 280 } 281 282 if (!exists) { 283 if (!mFileSystem->ShouldCreateDirectory()) { 284 return NS_ERROR_DOM_FILE_NOT_FOUND_ERR; 285 } 286 287 rv = mTargetPath->Create(nsIFile::DIRECTORY_TYPE, 0777); 288 if (NS_WARN_IF(NS_FAILED(rv))) { 289 return rv; 290 } 291 } 292 293 // Get isDirectory. 294 bool isDir; 295 rv = mTargetPath->IsDirectory(&isDir); 296 if (NS_WARN_IF(NS_FAILED(rv))) { 297 return rv; 298 } 299 300 if (!isDir) { 301 return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR; 302 } 303 304 nsCOMPtr<nsIDirectoryEnumerator> entries; 305 rv = mTargetPath->GetDirectoryEntries(getter_AddRefs(entries)); 306 if (NS_WARN_IF(NS_FAILED(rv))) { 307 return rv; 308 } 309 310 bool filterOutSensitive = false; 311 { 312 HTMLSplitOnSpacesTokenizer tokenizer(mFilters, ';'); 313 nsAutoString token; 314 while (tokenizer.hasMoreTokens()) { 315 token = tokenizer.nextToken(); 316 if (token.EqualsLiteral("filter-out-sensitive")) { 317 filterOutSensitive = true; 318 } else { 319 MOZ_CRASH("Unrecognized filter"); 320 } 321 } 322 } 323 324 for (;;) { 325 nsCOMPtr<nsIFile> currFile; 326 if (NS_WARN_IF(NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile)))) || 327 !currFile) { 328 break; 329 } 330 bool isLink, isSpecial, isFile; 331 if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) || 332 NS_FAILED(currFile->IsSpecial(&isSpecial))) || 333 // Although we allow explicit individual selection of symlinks via the 334 // file picker, we do not process symlinks in directory traversal. Our 335 // specific policy decision is documented at 336 // https://bugzilla.mozilla.org/show_bug.cgi?id=1813299#c20 337 isLink || isSpecial) { 338 continue; 339 } 340 if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) || 341 NS_FAILED(currFile->IsDirectory(&isDir))) || 342 !(isFile || isDir)) { 343 continue; 344 } 345 346 if (filterOutSensitive) { 347 bool isHidden; 348 if (NS_WARN_IF(NS_FAILED(currFile->IsHidden(&isHidden))) || isHidden) { 349 continue; 350 } 351 nsAutoString leafName; 352 if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) { 353 continue; 354 } 355 if (leafName[0] == char16_t('.')) { 356 continue; 357 } 358 } 359 360 nsAutoString path; 361 if (NS_WARN_IF(NS_FAILED(currFile->GetPath(path)))) { 362 continue; 363 } 364 365 FileOrDirectoryPath element; 366 element.mPath = path; 367 element.mType = isDir ? FileOrDirectoryPath::eDirectoryPath 368 : FileOrDirectoryPath::eFilePath; 369 370 if (!mTargetData.AppendElement(element, fallible)) { 371 return NS_ERROR_OUT_OF_MEMORY; 372 } 373 } 374 return NS_OK; 375 } 376 377 nsresult GetDirectoryListingTaskParent::MainThreadWork() { 378 MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); 379 MOZ_ASSERT(NS_IsMainThread(), "Only call on main-thread!"); 380 381 nsresult rv; 382 nsCOMPtr<nsIMIMEService> mimeService = 383 do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); 384 if (NS_WARN_IF(NS_FAILED(rv))) { 385 return rv; 386 } 387 388 for (unsigned i = 0; i < mTargetData.Length(); i++) { 389 if (mTargetData[i].mType != FileOrDirectoryPath::eFilePath) { 390 continue; 391 } 392 393 nsCOMPtr<nsIFile> file; 394 nsresult rv = NS_NewLocalFile(mTargetData[i].mPath, getter_AddRefs(file)); 395 if (NS_WARN_IF(NS_FAILED(rv))) { 396 continue; 397 } 398 399 nsAutoCString mimeType; 400 rv = mimeService->GetTypeFromFile(file, mimeType); 401 if (NS_FAILED(rv)) { 402 mimeType.Truncate(); 403 } 404 405 AppendUTF8toUTF16(mimeType, mTargetData[i].mContentType); 406 } 407 408 return NS_OK; 409 } 410 411 bool GetDirectoryListingTaskParent::MainThreadNeeded() const { 412 MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); 413 414 // We need to go to the main-thread only if we have files in the list of 415 // target. 416 for (const FileOrDirectoryPath& data : mTargetData) { 417 if (data.mType == FileOrDirectoryPath::eFilePath) { 418 return true; 419 } 420 } 421 422 return false; 423 } 424 425 nsresult GetDirectoryListingTaskParent::GetTargetPath(nsAString& aPath) const { 426 return mTargetPath->GetPath(aPath); 427 } 428 429 } // namespace mozilla::dom