xpcom/io/nsLocalFileWin.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "mozilla/ArrayUtils.h"
michael@0 7 #include "mozilla/DebugOnly.h"
michael@0 8 #include "mozilla/WindowsVersion.h"
michael@0 9
michael@0 10 #include "nsCOMPtr.h"
michael@0 11 #include "nsAutoPtr.h"
michael@0 12 #include "nsMemory.h"
michael@0 13
michael@0 14 #include "nsLocalFile.h"
michael@0 15 #include "nsIDirectoryEnumerator.h"
michael@0 16 #include "nsNativeCharsetUtils.h"
michael@0 17
michael@0 18 #include "nsISimpleEnumerator.h"
michael@0 19 #include "nsIComponentManager.h"
michael@0 20 #include "prio.h"
michael@0 21 #include "private/pprio.h" // To get PR_ImportFile
michael@0 22 #include "prprf.h"
michael@0 23 #include "prmem.h"
michael@0 24 #include "nsHashKeys.h"
michael@0 25
michael@0 26 #include "nsXPIDLString.h"
michael@0 27 #include "nsReadableUtils.h"
michael@0 28
michael@0 29 #include <direct.h>
michael@0 30 #include <windows.h>
michael@0 31 #include <shlwapi.h>
michael@0 32 #include <aclapi.h>
michael@0 33
michael@0 34 #include "shellapi.h"
michael@0 35 #include "shlguid.h"
michael@0 36
michael@0 37 #include <io.h>
michael@0 38 #include <stdio.h>
michael@0 39 #include <stdlib.h>
michael@0 40 #include <mbstring.h>
michael@0 41
michael@0 42 #include "nsXPIDLString.h"
michael@0 43 #include "prproces.h"
michael@0 44 #include "prlink.h"
michael@0 45
michael@0 46 #include "mozilla/Mutex.h"
michael@0 47 #include "SpecialSystemDirectory.h"
michael@0 48
michael@0 49 #include "nsTraceRefcnt.h"
michael@0 50 #include "nsXPCOMCIDInternal.h"
michael@0 51 #include "nsThreadUtils.h"
michael@0 52 #include "nsXULAppAPI.h"
michael@0 53
michael@0 54 using namespace mozilla;
michael@0 55
michael@0 56 #define CHECK_mWorkingPath() \
michael@0 57 PR_BEGIN_MACRO \
michael@0 58 if (mWorkingPath.IsEmpty()) \
michael@0 59 return NS_ERROR_NOT_INITIALIZED; \
michael@0 60 PR_END_MACRO
michael@0 61
michael@0 62 // CopyFileEx only supports unbuffered I/O in Windows Vista and above
michael@0 63 #ifndef COPY_FILE_NO_BUFFERING
michael@0 64 #define COPY_FILE_NO_BUFFERING 0x00001000
michael@0 65 #endif
michael@0 66
michael@0 67 #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
michael@0 68 #define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
michael@0 69 #endif
michael@0 70
michael@0 71 #ifndef DRIVE_REMOTE
michael@0 72 #define DRIVE_REMOTE 4
michael@0 73 #endif
michael@0 74
michael@0 75 /**
michael@0 76 * A runnable to dispatch back to the main thread when
michael@0 77 * AsyncLocalFileWinOperation completes.
michael@0 78 */
michael@0 79 class AsyncLocalFileWinDone : public nsRunnable
michael@0 80 {
michael@0 81 public:
michael@0 82 AsyncLocalFileWinDone() :
michael@0 83 mWorkerThread(do_GetCurrentThread())
michael@0 84 {
michael@0 85 // Objects of this type must only be created on worker threads
michael@0 86 MOZ_ASSERT(!NS_IsMainThread());
michael@0 87 }
michael@0 88
michael@0 89 NS_IMETHOD Run() {
michael@0 90 // This event shuts down the worker thread and so must be main thread.
michael@0 91 MOZ_ASSERT(NS_IsMainThread());
michael@0 92
michael@0 93 // If we don't destroy the thread when we're done with it, it will hang
michael@0 94 // around forever... and that is bad!
michael@0 95 mWorkerThread->Shutdown();
michael@0 96 return NS_OK;
michael@0 97 }
michael@0 98
michael@0 99 private:
michael@0 100 nsCOMPtr<nsIThread> mWorkerThread;
michael@0 101 };
michael@0 102
michael@0 103 /**
michael@0 104 * A runnable to dispatch from the main thread when an async operation should
michael@0 105 * be performed.
michael@0 106 */
michael@0 107 class AsyncLocalFileWinOperation : public nsRunnable
michael@0 108 {
michael@0 109 public:
michael@0 110 enum FileOp { RevealOp, LaunchOp };
michael@0 111
michael@0 112 AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation,
michael@0 113 const nsAString &aResolvedPath) :
michael@0 114 mOperation(aOperation),
michael@0 115 mResolvedPath(aResolvedPath)
michael@0 116 {
michael@0 117 }
michael@0 118
michael@0 119 NS_IMETHOD Run() {
michael@0 120 MOZ_ASSERT(!NS_IsMainThread(),
michael@0 121 "AsyncLocalFileWinOperation should not be run on the main thread!");
michael@0 122
michael@0 123 CoInitialize(nullptr);
michael@0 124 switch(mOperation) {
michael@0 125 case RevealOp: {
michael@0 126 Reveal();
michael@0 127 }
michael@0 128 break;
michael@0 129 case LaunchOp: {
michael@0 130 Launch();
michael@0 131 }
michael@0 132 break;
michael@0 133 }
michael@0 134 CoUninitialize();
michael@0 135
michael@0 136 // Send the result back to the main thread so that it can shutdown
michael@0 137 nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone();
michael@0 138 NS_DispatchToMainThread(resultrunnable);
michael@0 139 return NS_OK;
michael@0 140 }
michael@0 141
michael@0 142 private:
michael@0 143 // Reveals the path in explorer.
michael@0 144 nsresult Reveal()
michael@0 145 {
michael@0 146 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
michael@0 147 if (INVALID_FILE_ATTRIBUTES == attributes) {
michael@0 148 return NS_ERROR_FILE_INVALID_PATH;
michael@0 149 }
michael@0 150
michael@0 151 HRESULT hr;
michael@0 152 if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
michael@0 153 // We have a directory so we should open the directory itself.
michael@0 154 ITEMIDLIST *dir = ILCreateFromPathW(mResolvedPath.get());
michael@0 155 if (!dir) {
michael@0 156 return NS_ERROR_FAILURE;
michael@0 157 }
michael@0 158
michael@0 159 const ITEMIDLIST* selection[] = { dir };
michael@0 160 UINT count = ArrayLength(selection);
michael@0 161
michael@0 162 //Perform the open of the directory.
michael@0 163 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
michael@0 164 CoTaskMemFree(dir);
michael@0 165 } else {
michael@0 166 int32_t len = mResolvedPath.Length();
michael@0 167 // We don't currently handle UNC long paths of the form \\?\ anywhere so
michael@0 168 // this should be fine.
michael@0 169 if (len > MAX_PATH) {
michael@0 170 return NS_ERROR_FILE_INVALID_PATH;
michael@0 171 }
michael@0 172 WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 };
michael@0 173 wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH);
michael@0 174 PathRemoveFileSpecW(parentDirectoryPath);
michael@0 175
michael@0 176 // We have a file so we should open the parent directory.
michael@0 177 ITEMIDLIST *dir = ILCreateFromPathW(parentDirectoryPath);
michael@0 178 if (!dir) {
michael@0 179 return NS_ERROR_FAILURE;
michael@0 180 }
michael@0 181
michael@0 182 // Set the item in the directory to select to the file we want to reveal.
michael@0 183 ITEMIDLIST *item = ILCreateFromPathW(mResolvedPath.get());
michael@0 184 if (!item) {
michael@0 185 CoTaskMemFree(dir);
michael@0 186 return NS_ERROR_FAILURE;
michael@0 187 }
michael@0 188
michael@0 189 const ITEMIDLIST* selection[] = { item };
michael@0 190 UINT count = ArrayLength(selection);
michael@0 191
michael@0 192 //Perform the selection of the file.
michael@0 193 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
michael@0 194
michael@0 195 CoTaskMemFree(dir);
michael@0 196 CoTaskMemFree(item);
michael@0 197 }
michael@0 198
michael@0 199 return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
michael@0 200 }
michael@0 201
michael@0 202 // Launches the default shell operation for the file path
michael@0 203 nsresult Launch()
michael@0 204 {
michael@0 205 // use the app registry name to launch a shell execute....
michael@0 206 SHELLEXECUTEINFOW seinfo;
michael@0 207 memset(&seinfo, 0, sizeof(seinfo));
michael@0 208 seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
michael@0 209 if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) {
michael@0 210 seinfo.fMask = SEE_MASK_FLAG_LOG_USAGE;
michael@0 211 }
michael@0 212 seinfo.hwnd = nullptr;
michael@0 213 seinfo.lpVerb = nullptr;
michael@0 214 seinfo.lpFile = mResolvedPath.get();
michael@0 215 seinfo.lpParameters = nullptr;
michael@0 216 seinfo.lpDirectory = nullptr;
michael@0 217 seinfo.nShow = SW_SHOWNORMAL;
michael@0 218
michael@0 219 // Use the directory of the file we're launching as the working
michael@0 220 // directory. That way if we have a self extracting EXE it won't
michael@0 221 // suggest to extract to the install directory.
michael@0 222 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
michael@0 223 wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
michael@0 224 if (PathRemoveFileSpecW(workingDirectory)) {
michael@0 225 seinfo.lpDirectory = workingDirectory;
michael@0 226 } else {
michael@0 227 NS_WARNING("Could not set working directory for launched file.");
michael@0 228 }
michael@0 229
michael@0 230 if (ShellExecuteExW(&seinfo)) {
michael@0 231 return NS_OK;
michael@0 232 }
michael@0 233 DWORD r = GetLastError();
michael@0 234 // if the file has no association, we launch windows'
michael@0 235 // "what do you want to do" dialog
michael@0 236 if (r == SE_ERR_NOASSOC) {
michael@0 237 nsAutoString shellArg;
michael@0 238 shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") +
michael@0 239 mResolvedPath);
michael@0 240 seinfo.lpFile = L"RUNDLL32.EXE";
michael@0 241 seinfo.lpParameters = shellArg.get();
michael@0 242 if (ShellExecuteExW(&seinfo))
michael@0 243 return NS_OK;
michael@0 244 r = GetLastError();
michael@0 245 }
michael@0 246 if (r < 32) {
michael@0 247 switch (r) {
michael@0 248 case 0:
michael@0 249 case SE_ERR_OOM:
michael@0 250 return NS_ERROR_OUT_OF_MEMORY;
michael@0 251 case ERROR_FILE_NOT_FOUND:
michael@0 252 return NS_ERROR_FILE_NOT_FOUND;
michael@0 253 case ERROR_PATH_NOT_FOUND:
michael@0 254 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 255 case ERROR_BAD_FORMAT:
michael@0 256 return NS_ERROR_FILE_CORRUPTED;
michael@0 257 case SE_ERR_ACCESSDENIED:
michael@0 258 return NS_ERROR_FILE_ACCESS_DENIED;
michael@0 259 case SE_ERR_ASSOCINCOMPLETE:
michael@0 260 case SE_ERR_NOASSOC:
michael@0 261 return NS_ERROR_UNEXPECTED;
michael@0 262 case SE_ERR_DDEBUSY:
michael@0 263 case SE_ERR_DDEFAIL:
michael@0 264 case SE_ERR_DDETIMEOUT:
michael@0 265 return NS_ERROR_NOT_AVAILABLE;
michael@0 266 case SE_ERR_DLLNOTFOUND:
michael@0 267 return NS_ERROR_FAILURE;
michael@0 268 case SE_ERR_SHARE:
michael@0 269 return NS_ERROR_FILE_IS_LOCKED;
michael@0 270 default:
michael@0 271 return NS_ERROR_FILE_EXECUTION_FAILED;
michael@0 272 }
michael@0 273 }
michael@0 274 return NS_OK;
michael@0 275 }
michael@0 276
michael@0 277 // Stores the operation that will be performed on the thread
michael@0 278 AsyncLocalFileWinOperation::FileOp mOperation;
michael@0 279
michael@0 280 // Stores the path to perform the operation on
michael@0 281 nsString mResolvedPath;
michael@0 282 };
michael@0 283
michael@0 284 class nsDriveEnumerator : public nsISimpleEnumerator
michael@0 285 {
michael@0 286 public:
michael@0 287 nsDriveEnumerator();
michael@0 288 virtual ~nsDriveEnumerator();
michael@0 289 NS_DECL_ISUPPORTS
michael@0 290 NS_DECL_NSISIMPLEENUMERATOR
michael@0 291 nsresult Init();
michael@0 292 private:
michael@0 293 /* mDrives stores the null-separated drive names.
michael@0 294 * Init sets them.
michael@0 295 * HasMoreElements checks mStartOfCurrentDrive.
michael@0 296 * GetNext advances mStartOfCurrentDrive.
michael@0 297 */
michael@0 298 nsString mDrives;
michael@0 299 nsAString::const_iterator mStartOfCurrentDrive;
michael@0 300 nsAString::const_iterator mEndOfDrivesString;
michael@0 301 };
michael@0 302
michael@0 303 //----------------------------------------------------------------------------
michael@0 304 // short cut resolver
michael@0 305 //----------------------------------------------------------------------------
michael@0 306 class ShortcutResolver
michael@0 307 {
michael@0 308 public:
michael@0 309 ShortcutResolver();
michael@0 310 // nonvirtual since we're not subclassed
michael@0 311 ~ShortcutResolver();
michael@0 312
michael@0 313 nsresult Init();
michael@0 314 nsresult Resolve(const WCHAR* in, WCHAR* out);
michael@0 315 nsresult SetShortcut(bool updateExisting,
michael@0 316 const WCHAR* shortcutPath,
michael@0 317 const WCHAR* targetPath,
michael@0 318 const WCHAR* workingDir,
michael@0 319 const WCHAR* args,
michael@0 320 const WCHAR* description,
michael@0 321 const WCHAR* iconFile,
michael@0 322 int32_t iconIndex);
michael@0 323
michael@0 324 private:
michael@0 325 Mutex mLock;
michael@0 326 nsRefPtr<IPersistFile> mPersistFile;
michael@0 327 nsRefPtr<IShellLinkW> mShellLink;
michael@0 328 };
michael@0 329
michael@0 330 ShortcutResolver::ShortcutResolver() :
michael@0 331 mLock("ShortcutResolver.mLock")
michael@0 332 {
michael@0 333 CoInitialize(nullptr);
michael@0 334 }
michael@0 335
michael@0 336 ShortcutResolver::~ShortcutResolver()
michael@0 337 {
michael@0 338 CoUninitialize();
michael@0 339 }
michael@0 340
michael@0 341 nsresult
michael@0 342 ShortcutResolver::Init()
michael@0 343 {
michael@0 344 // Get a pointer to the IPersistFile interface.
michael@0 345 if (FAILED(CoCreateInstance(CLSID_ShellLink,
michael@0 346 nullptr,
michael@0 347 CLSCTX_INPROC_SERVER,
michael@0 348 IID_IShellLinkW,
michael@0 349 getter_AddRefs(mShellLink))) ||
michael@0 350 FAILED(mShellLink->QueryInterface(IID_IPersistFile,
michael@0 351 getter_AddRefs(mPersistFile)))) {
michael@0 352 mShellLink = nullptr;
michael@0 353 return NS_ERROR_FAILURE;
michael@0 354 }
michael@0 355 return NS_OK;
michael@0 356 }
michael@0 357
michael@0 358 // |out| must be an allocated buffer of size MAX_PATH
michael@0 359 nsresult
michael@0 360 ShortcutResolver::Resolve(const WCHAR* in, WCHAR* out)
michael@0 361 {
michael@0 362 if (!mShellLink)
michael@0 363 return NS_ERROR_FAILURE;
michael@0 364
michael@0 365 MutexAutoLock lock(mLock);
michael@0 366
michael@0 367 if (FAILED(mPersistFile->Load(in, STGM_READ)) ||
michael@0 368 FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) ||
michael@0 369 FAILED(mShellLink->GetPath(out, MAX_PATH, nullptr, SLGP_UNCPRIORITY)))
michael@0 370 return NS_ERROR_FAILURE;
michael@0 371 return NS_OK;
michael@0 372 }
michael@0 373
michael@0 374 nsresult
michael@0 375 ShortcutResolver::SetShortcut(bool updateExisting,
michael@0 376 const WCHAR* shortcutPath,
michael@0 377 const WCHAR* targetPath,
michael@0 378 const WCHAR* workingDir,
michael@0 379 const WCHAR* args,
michael@0 380 const WCHAR* description,
michael@0 381 const WCHAR* iconPath,
michael@0 382 int32_t iconIndex)
michael@0 383 {
michael@0 384 if (!mShellLink) {
michael@0 385 return NS_ERROR_FAILURE;
michael@0 386 }
michael@0 387
michael@0 388 if (!shortcutPath) {
michael@0 389 return NS_ERROR_FAILURE;
michael@0 390 }
michael@0 391
michael@0 392 MutexAutoLock lock(mLock);
michael@0 393
michael@0 394 if (updateExisting) {
michael@0 395 if (FAILED(mPersistFile->Load(shortcutPath, STGM_READWRITE))) {
michael@0 396 return NS_ERROR_FAILURE;
michael@0 397 }
michael@0 398 } else {
michael@0 399 if (!targetPath) {
michael@0 400 return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
michael@0 401 }
michael@0 402
michael@0 403 // Since we reuse our IPersistFile, we have to clear out any values that
michael@0 404 // may be left over from previous calls to SetShortcut.
michael@0 405 if (FAILED(mShellLink->SetWorkingDirectory(L""))
michael@0 406 || FAILED(mShellLink->SetArguments(L""))
michael@0 407 || FAILED(mShellLink->SetDescription(L""))
michael@0 408 || FAILED(mShellLink->SetIconLocation(L"", 0))) {
michael@0 409 return NS_ERROR_FAILURE;
michael@0 410 }
michael@0 411 }
michael@0 412
michael@0 413 if (targetPath && FAILED(mShellLink->SetPath(targetPath))) {
michael@0 414 return NS_ERROR_FAILURE;
michael@0 415 }
michael@0 416
michael@0 417 if (workingDir && FAILED(mShellLink->SetWorkingDirectory(workingDir))) {
michael@0 418 return NS_ERROR_FAILURE;
michael@0 419 }
michael@0 420
michael@0 421 if (args && FAILED(mShellLink->SetArguments(args))) {
michael@0 422 return NS_ERROR_FAILURE;
michael@0 423 }
michael@0 424
michael@0 425 if (description && FAILED(mShellLink->SetDescription(description))) {
michael@0 426 return NS_ERROR_FAILURE;
michael@0 427 }
michael@0 428
michael@0 429 if (iconPath && FAILED(mShellLink->SetIconLocation(iconPath, iconIndex))) {
michael@0 430 return NS_ERROR_FAILURE;
michael@0 431 }
michael@0 432
michael@0 433 if (FAILED(mPersistFile->Save(shortcutPath,
michael@0 434 TRUE))) {
michael@0 435 // Second argument indicates whether the file path specified in the
michael@0 436 // first argument should become the "current working file" for this
michael@0 437 // IPersistFile
michael@0 438 return NS_ERROR_FAILURE;
michael@0 439 }
michael@0 440
michael@0 441 return NS_OK;
michael@0 442 }
michael@0 443
michael@0 444 static ShortcutResolver * gResolver = nullptr;
michael@0 445
michael@0 446 static nsresult NS_CreateShortcutResolver()
michael@0 447 {
michael@0 448 gResolver = new ShortcutResolver();
michael@0 449 if (!gResolver)
michael@0 450 return NS_ERROR_OUT_OF_MEMORY;
michael@0 451
michael@0 452 return gResolver->Init();
michael@0 453 }
michael@0 454
michael@0 455 static void NS_DestroyShortcutResolver()
michael@0 456 {
michael@0 457 delete gResolver;
michael@0 458 gResolver = nullptr;
michael@0 459 }
michael@0 460
michael@0 461
michael@0 462 //-----------------------------------------------------------------------------
michael@0 463 // static helper functions
michael@0 464 //-----------------------------------------------------------------------------
michael@0 465
michael@0 466 // certainly not all the error that can be
michael@0 467 // encountered, but many of them common ones
michael@0 468 static nsresult ConvertWinError(DWORD winErr)
michael@0 469 {
michael@0 470 nsresult rv;
michael@0 471
michael@0 472 switch (winErr)
michael@0 473 {
michael@0 474 case ERROR_FILE_NOT_FOUND:
michael@0 475 case ERROR_PATH_NOT_FOUND:
michael@0 476 case ERROR_INVALID_DRIVE:
michael@0 477 rv = NS_ERROR_FILE_NOT_FOUND;
michael@0 478 break;
michael@0 479 case ERROR_ACCESS_DENIED:
michael@0 480 case ERROR_NOT_SAME_DEVICE:
michael@0 481 rv = NS_ERROR_FILE_ACCESS_DENIED;
michael@0 482 break;
michael@0 483 case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags
michael@0 484 case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx
michael@0 485 rv = NS_ERROR_FILE_IS_LOCKED;
michael@0 486 break;
michael@0 487 case ERROR_NOT_ENOUGH_MEMORY:
michael@0 488 case ERROR_INVALID_BLOCK:
michael@0 489 case ERROR_INVALID_HANDLE:
michael@0 490 case ERROR_ARENA_TRASHED:
michael@0 491 rv = NS_ERROR_OUT_OF_MEMORY;
michael@0 492 break;
michael@0 493 case ERROR_CURRENT_DIRECTORY:
michael@0 494 rv = NS_ERROR_FILE_DIR_NOT_EMPTY;
michael@0 495 break;
michael@0 496 case ERROR_WRITE_PROTECT:
michael@0 497 rv = NS_ERROR_FILE_READ_ONLY;
michael@0 498 break;
michael@0 499 case ERROR_HANDLE_DISK_FULL:
michael@0 500 rv = NS_ERROR_FILE_TOO_BIG;
michael@0 501 break;
michael@0 502 case ERROR_FILE_EXISTS:
michael@0 503 case ERROR_ALREADY_EXISTS:
michael@0 504 case ERROR_CANNOT_MAKE:
michael@0 505 rv = NS_ERROR_FILE_ALREADY_EXISTS;
michael@0 506 break;
michael@0 507 case ERROR_FILENAME_EXCED_RANGE:
michael@0 508 rv = NS_ERROR_FILE_NAME_TOO_LONG;
michael@0 509 break;
michael@0 510 case ERROR_DIRECTORY:
michael@0 511 rv = NS_ERROR_FILE_NOT_DIRECTORY;
michael@0 512 break;
michael@0 513 case 0:
michael@0 514 rv = NS_OK;
michael@0 515 break;
michael@0 516 default:
michael@0 517 rv = NS_ERROR_FAILURE;
michael@0 518 break;
michael@0 519 }
michael@0 520 return rv;
michael@0 521 }
michael@0 522
michael@0 523 // as suggested in the MSDN documentation on SetFilePointer
michael@0 524 static __int64
michael@0 525 MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod)
michael@0 526 {
michael@0 527 LARGE_INTEGER li;
michael@0 528
michael@0 529 li.QuadPart = aDistance;
michael@0 530 li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod);
michael@0 531 if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
michael@0 532 {
michael@0 533 li.QuadPart = -1;
michael@0 534 }
michael@0 535
michael@0 536 return li.QuadPart;
michael@0 537 }
michael@0 538
michael@0 539 static bool
michael@0 540 IsShortcutPath(const nsAString &path)
michael@0 541 {
michael@0 542 // Under Windows, the shortcuts are just files with a ".lnk" extension.
michael@0 543 // Note also that we don't resolve links in the middle of paths.
michael@0 544 // i.e. "c:\foo.lnk\bar.txt" is invalid.
michael@0 545 NS_ABORT_IF_FALSE(!path.IsEmpty(), "don't pass an empty string");
michael@0 546 int32_t len = path.Length();
michael@0 547 return len >= 4 && (StringTail(path, 4).LowerCaseEqualsASCII(".lnk"));
michael@0 548 }
michael@0 549
michael@0 550 //-----------------------------------------------------------------------------
michael@0 551 // We need the following three definitions to make |OpenFile| convert a file
michael@0 552 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
michael@0 553 // specified. It is defined in a private header of NSPR (primpl.h) we can't
michael@0 554 // include. As a temporary workaround until we decide how to extend
michael@0 555 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
michael@0 556 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
michael@0 557 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
michael@0 558 // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
michael@0 559 // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
michael@0 560 // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
michael@0 561 // need to be changed to match the definitions for WinNT.
michael@0 562 //-----------------------------------------------------------------------------
michael@0 563 typedef enum {
michael@0 564 _PR_TRI_TRUE = 1,
michael@0 565 _PR_TRI_FALSE = 0,
michael@0 566 _PR_TRI_UNKNOWN = -1
michael@0 567 } _PRTriStateBool;
michael@0 568
michael@0 569 struct _MDFileDesc {
michael@0 570 PROsfd osfd;
michael@0 571 };
michael@0 572
michael@0 573 struct PRFilePrivate {
michael@0 574 int32_t state;
michael@0 575 bool nonblocking;
michael@0 576 _PRTriStateBool inheritable;
michael@0 577 PRFileDesc *next;
michael@0 578 int lockCount; /* 0: not locked
michael@0 579 * -1: a native lockfile call is in progress
michael@0 580 * > 0: # times the file is locked */
michael@0 581 bool appendMode;
michael@0 582 _MDFileDesc md;
michael@0 583 };
michael@0 584
michael@0 585 //-----------------------------------------------------------------------------
michael@0 586 // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
michael@0 587 // OpenDir, CloseDir, ReadDir) should go away once the corresponding
michael@0 588 // UTF-16 APIs are implemented on all the supported platforms (or at least
michael@0 589 // Windows 9x/ME) in NSPR. Currently, they're only implemented on
michael@0 590 // Windows NT4 or later. (bug 330665)
michael@0 591 //-----------------------------------------------------------------------------
michael@0 592
michael@0 593 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
michael@0 594 // PR_Open and _PR_MD_OPEN
michael@0 595 nsresult
michael@0 596 OpenFile(const nsAFlatString &name, int osflags, int mode,
michael@0 597 PRFileDesc **fd)
michael@0 598 {
michael@0 599 int32_t access = 0;
michael@0 600
michael@0 601 int32_t disposition = 0;
michael@0 602 int32_t attributes = 0;
michael@0 603
michael@0 604 if (osflags & PR_SYNC)
michael@0 605 attributes = FILE_FLAG_WRITE_THROUGH;
michael@0 606 if (osflags & PR_RDONLY || osflags & PR_RDWR)
michael@0 607 access |= GENERIC_READ;
michael@0 608 if (osflags & PR_WRONLY || osflags & PR_RDWR)
michael@0 609 access |= GENERIC_WRITE;
michael@0 610
michael@0 611 if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL )
michael@0 612 disposition = CREATE_NEW;
michael@0 613 else if (osflags & PR_CREATE_FILE) {
michael@0 614 if (osflags & PR_TRUNCATE)
michael@0 615 disposition = CREATE_ALWAYS;
michael@0 616 else
michael@0 617 disposition = OPEN_ALWAYS;
michael@0 618 } else {
michael@0 619 if (osflags & PR_TRUNCATE)
michael@0 620 disposition = TRUNCATE_EXISTING;
michael@0 621 else
michael@0 622 disposition = OPEN_EXISTING;
michael@0 623 }
michael@0 624
michael@0 625 if (osflags & nsIFile::DELETE_ON_CLOSE) {
michael@0 626 attributes |= FILE_FLAG_DELETE_ON_CLOSE;
michael@0 627 }
michael@0 628
michael@0 629 if (osflags & nsIFile::OS_READAHEAD) {
michael@0 630 attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
michael@0 631 }
michael@0 632
michael@0 633 // If no write permissions are requested, and if we are possibly creating
michael@0 634 // the file, then set the new file as read only.
michael@0 635 // The flag has no effect if we happen to open the file.
michael@0 636 if (!(mode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) &&
michael@0 637 disposition != OPEN_EXISTING) {
michael@0 638 attributes |= FILE_ATTRIBUTE_READONLY;
michael@0 639 }
michael@0 640
michael@0 641 HANDLE file = ::CreateFileW(name.get(), access,
michael@0 642 FILE_SHARE_READ|FILE_SHARE_WRITE,
michael@0 643 nullptr, disposition, attributes, nullptr);
michael@0 644
michael@0 645 if (file == INVALID_HANDLE_VALUE) {
michael@0 646 *fd = nullptr;
michael@0 647 return ConvertWinError(GetLastError());
michael@0 648 }
michael@0 649
michael@0 650 *fd = PR_ImportFile((PROsfd) file);
michael@0 651 if (*fd) {
michael@0 652 // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
michael@0 653 // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
michael@0 654 (*fd)->secret->appendMode = (PR_APPEND & osflags) ? true : false;
michael@0 655 return NS_OK;
michael@0 656 }
michael@0 657
michael@0 658 nsresult rv = NS_ErrorAccordingToNSPR();
michael@0 659
michael@0 660 CloseHandle(file);
michael@0 661
michael@0 662 return rv;
michael@0 663 }
michael@0 664
michael@0 665 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
michael@0 666 // PR_FileTimeToPRTime and _PR_FileTimeToPRTime
michael@0 667 static
michael@0 668 void FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm)
michael@0 669 {
michael@0 670 #ifdef __GNUC__
michael@0 671 const PRTime _pr_filetime_offset = 116444736000000000LL;
michael@0 672 #else
michael@0 673 const PRTime _pr_filetime_offset = 116444736000000000i64;
michael@0 674 #endif
michael@0 675
michael@0 676 PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
michael@0 677 ::CopyMemory(prtm, filetime, sizeof(PRTime));
michael@0 678 #ifdef __GNUC__
michael@0 679 *prtm = (*prtm - _pr_filetime_offset) / 10LL;
michael@0 680 #else
michael@0 681 *prtm = (*prtm - _pr_filetime_offset) / 10i64;
michael@0 682 #endif
michael@0 683 }
michael@0 684
michael@0 685 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
michael@0 686 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
michael@0 687 static nsresult
michael@0 688 GetFileInfo(const nsAFlatString &name, PRFileInfo64 *info)
michael@0 689 {
michael@0 690 WIN32_FILE_ATTRIBUTE_DATA fileData;
michael@0 691
michael@0 692 if (name.IsEmpty() || name.FindCharInSet(MOZ_UTF16("?*")) != kNotFound)
michael@0 693 return NS_ERROR_INVALID_ARG;
michael@0 694
michael@0 695 if (!::GetFileAttributesExW(name.get(), GetFileExInfoStandard, &fileData))
michael@0 696 return ConvertWinError(GetLastError());
michael@0 697
michael@0 698 if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
michael@0 699 info->type = PR_FILE_DIRECTORY;
michael@0 700 } else {
michael@0 701 info->type = PR_FILE_FILE;
michael@0 702 }
michael@0 703
michael@0 704 info->size = fileData.nFileSizeHigh;
michael@0 705 info->size = (info->size << 32) + fileData.nFileSizeLow;
michael@0 706
michael@0 707 FileTimeToPRTime(&fileData.ftLastWriteTime, &info->modifyTime);
michael@0 708
michael@0 709 if (0 == fileData.ftCreationTime.dwLowDateTime &&
michael@0 710 0 == fileData.ftCreationTime.dwHighDateTime) {
michael@0 711 info->creationTime = info->modifyTime;
michael@0 712 } else {
michael@0 713 FileTimeToPRTime(&fileData.ftCreationTime, &info->creationTime);
michael@0 714 }
michael@0 715
michael@0 716 return NS_OK;
michael@0 717 }
michael@0 718
michael@0 719 struct nsDir
michael@0 720 {
michael@0 721 HANDLE handle;
michael@0 722 WIN32_FIND_DATAW data;
michael@0 723 bool firstEntry;
michael@0 724 };
michael@0 725
michael@0 726 static nsresult
michael@0 727 OpenDir(const nsAFlatString &name, nsDir * *dir)
michael@0 728 {
michael@0 729 if (NS_WARN_IF(!dir))
michael@0 730 return NS_ERROR_INVALID_ARG;
michael@0 731
michael@0 732 *dir = nullptr;
michael@0 733 if (name.Length() + 3 >= MAX_PATH)
michael@0 734 return NS_ERROR_FILE_NAME_TOO_LONG;
michael@0 735
michael@0 736 nsDir *d = PR_NEW(nsDir);
michael@0 737 if (!d)
michael@0 738 return NS_ERROR_OUT_OF_MEMORY;
michael@0 739
michael@0 740 nsAutoString filename(name);
michael@0 741
michael@0 742 //If 'name' ends in a slash or backslash, do not append
michael@0 743 //another backslash.
michael@0 744 if (filename.Last() == L'/' || filename.Last() == L'\\')
michael@0 745 filename.Append('*');
michael@0 746 else
michael@0 747 filename.AppendLiteral("\\*");
michael@0 748
michael@0 749 filename.ReplaceChar(L'/', L'\\');
michael@0 750
michael@0 751 // FindFirstFileW Will have a last error of ERROR_DIRECTORY if
michael@0 752 // <file_path>\* is passed in. If <unknown_path>\* is passed in then
michael@0 753 // ERROR_PATH_NOT_FOUND will be the last error.
michael@0 754 d->handle = ::FindFirstFileW(filename.get(), &(d->data) );
michael@0 755
michael@0 756 if (d->handle == INVALID_HANDLE_VALUE) {
michael@0 757 PR_Free(d);
michael@0 758 return ConvertWinError(GetLastError());
michael@0 759 }
michael@0 760 d->firstEntry = true;
michael@0 761
michael@0 762 *dir = d;
michael@0 763 return NS_OK;
michael@0 764 }
michael@0 765
michael@0 766 static nsresult
michael@0 767 ReadDir(nsDir *dir, PRDirFlags flags, nsString& name)
michael@0 768 {
michael@0 769 name.Truncate();
michael@0 770 if (NS_WARN_IF(!dir))
michael@0 771 return NS_ERROR_INVALID_ARG;
michael@0 772
michael@0 773 while (1) {
michael@0 774 BOOL rv;
michael@0 775 if (dir->firstEntry)
michael@0 776 {
michael@0 777 dir->firstEntry = false;
michael@0 778 rv = 1;
michael@0 779 } else
michael@0 780 rv = ::FindNextFileW(dir->handle, &(dir->data));
michael@0 781
michael@0 782 if (rv == 0)
michael@0 783 break;
michael@0 784
michael@0 785 const wchar_t *fileName;
michael@0 786 nsString tmp;
michael@0 787 fileName = (dir)->data.cFileName;
michael@0 788
michael@0 789 if ((flags & PR_SKIP_DOT) &&
michael@0 790 (fileName[0] == L'.') && (fileName[1] == L'\0'))
michael@0 791 continue;
michael@0 792 if ((flags & PR_SKIP_DOT_DOT) &&
michael@0 793 (fileName[0] == L'.') && (fileName[1] == L'.') &&
michael@0 794 (fileName[2] == L'\0'))
michael@0 795 continue;
michael@0 796
michael@0 797 DWORD attrib = dir->data.dwFileAttributes;
michael@0 798 if ((flags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN))
michael@0 799 continue;
michael@0 800
michael@0 801 if (fileName == tmp.get())
michael@0 802 name = tmp;
michael@0 803 else
michael@0 804 name = fileName;
michael@0 805 return NS_OK;
michael@0 806 }
michael@0 807
michael@0 808 DWORD err = GetLastError();
michael@0 809 return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err);
michael@0 810 }
michael@0 811
michael@0 812 static nsresult
michael@0 813 CloseDir(nsDir *&d)
michael@0 814 {
michael@0 815 if (NS_WARN_IF(!d))
michael@0 816 return NS_ERROR_INVALID_ARG;
michael@0 817
michael@0 818 BOOL isOk = FindClose(d->handle);
michael@0 819 // PR_DELETE also nulls out the passed in pointer.
michael@0 820 PR_DELETE(d);
michael@0 821 return isOk ? NS_OK : ConvertWinError(GetLastError());
michael@0 822 }
michael@0 823
michael@0 824 //-----------------------------------------------------------------------------
michael@0 825 // nsDirEnumerator
michael@0 826 //-----------------------------------------------------------------------------
michael@0 827
michael@0 828 class nsDirEnumerator MOZ_FINAL : public nsISimpleEnumerator,
michael@0 829 public nsIDirectoryEnumerator
michael@0 830 {
michael@0 831 public:
michael@0 832
michael@0 833 NS_DECL_ISUPPORTS
michael@0 834
michael@0 835 nsDirEnumerator() : mDir(nullptr)
michael@0 836 {
michael@0 837 }
michael@0 838
michael@0 839 nsresult Init(nsIFile* parent)
michael@0 840 {
michael@0 841 nsAutoString filepath;
michael@0 842 parent->GetTarget(filepath);
michael@0 843
michael@0 844 if (filepath.IsEmpty())
michael@0 845 {
michael@0 846 parent->GetPath(filepath);
michael@0 847 }
michael@0 848
michael@0 849 if (filepath.IsEmpty())
michael@0 850 {
michael@0 851 return NS_ERROR_UNEXPECTED;
michael@0 852 }
michael@0 853
michael@0 854 // IsDirectory is not needed here because OpenDir will return
michael@0 855 // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
michael@0 856 nsresult rv = OpenDir(filepath, &mDir);
michael@0 857 if (NS_FAILED(rv))
michael@0 858 return rv;
michael@0 859
michael@0 860 mParent = parent;
michael@0 861 return NS_OK;
michael@0 862 }
michael@0 863
michael@0 864 NS_IMETHOD HasMoreElements(bool *result)
michael@0 865 {
michael@0 866 nsresult rv;
michael@0 867 if (mNext == nullptr && mDir)
michael@0 868 {
michael@0 869 nsString name;
michael@0 870 rv = ReadDir(mDir, PR_SKIP_BOTH, name);
michael@0 871 if (NS_FAILED(rv))
michael@0 872 return rv;
michael@0 873 if (name.IsEmpty())
michael@0 874 {
michael@0 875 // end of dir entries
michael@0 876 if (NS_FAILED(CloseDir(mDir)))
michael@0 877 return NS_ERROR_FAILURE;
michael@0 878
michael@0 879 *result = false;
michael@0 880 return NS_OK;
michael@0 881 }
michael@0 882
michael@0 883 nsCOMPtr<nsIFile> file;
michael@0 884 rv = mParent->Clone(getter_AddRefs(file));
michael@0 885 if (NS_FAILED(rv))
michael@0 886 return rv;
michael@0 887
michael@0 888 rv = file->Append(name);
michael@0 889 if (NS_FAILED(rv))
michael@0 890 return rv;
michael@0 891
michael@0 892 mNext = do_QueryInterface(file);
michael@0 893 }
michael@0 894 *result = mNext != nullptr;
michael@0 895 if (!*result)
michael@0 896 Close();
michael@0 897 return NS_OK;
michael@0 898 }
michael@0 899
michael@0 900 NS_IMETHOD GetNext(nsISupports **result)
michael@0 901 {
michael@0 902 nsresult rv;
michael@0 903 bool hasMore;
michael@0 904 rv = HasMoreElements(&hasMore);
michael@0 905 if (NS_FAILED(rv)) return rv;
michael@0 906
michael@0 907 *result = mNext; // might return nullptr
michael@0 908 NS_IF_ADDREF(*result);
michael@0 909
michael@0 910 mNext = nullptr;
michael@0 911 return NS_OK;
michael@0 912 }
michael@0 913
michael@0 914 NS_IMETHOD GetNextFile(nsIFile **result)
michael@0 915 {
michael@0 916 *result = nullptr;
michael@0 917 bool hasMore = false;
michael@0 918 nsresult rv = HasMoreElements(&hasMore);
michael@0 919 if (NS_FAILED(rv) || !hasMore)
michael@0 920 return rv;
michael@0 921 *result = mNext;
michael@0 922 NS_IF_ADDREF(*result);
michael@0 923 mNext = nullptr;
michael@0 924 return NS_OK;
michael@0 925 }
michael@0 926
michael@0 927 NS_IMETHOD Close()
michael@0 928 {
michael@0 929 if (mDir)
michael@0 930 {
michael@0 931 nsresult rv = CloseDir(mDir);
michael@0 932 NS_ASSERTION(NS_SUCCEEDED(rv), "close failed");
michael@0 933 if (NS_FAILED(rv))
michael@0 934 return NS_ERROR_FAILURE;
michael@0 935 }
michael@0 936 return NS_OK;
michael@0 937 }
michael@0 938
michael@0 939 // dtor can be non-virtual since there are no subclasses, but must be
michael@0 940 // public to use the class on the stack.
michael@0 941 ~nsDirEnumerator()
michael@0 942 {
michael@0 943 Close();
michael@0 944 }
michael@0 945
michael@0 946 protected:
michael@0 947 nsDir* mDir;
michael@0 948 nsCOMPtr<nsIFile> mParent;
michael@0 949 nsCOMPtr<nsIFile> mNext;
michael@0 950 };
michael@0 951
michael@0 952 NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator)
michael@0 953
michael@0 954
michael@0 955 //-----------------------------------------------------------------------------
michael@0 956 // nsLocalFile <public>
michael@0 957 //-----------------------------------------------------------------------------
michael@0 958
michael@0 959 nsLocalFile::nsLocalFile()
michael@0 960 : mDirty(true)
michael@0 961 , mResolveDirty(true)
michael@0 962 , mFollowSymlinks(false)
michael@0 963 {
michael@0 964 }
michael@0 965
michael@0 966 nsresult
michael@0 967 nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
michael@0 968 {
michael@0 969 if (NS_WARN_IF(!aInstancePtr))
michael@0 970 return NS_ERROR_INVALID_ARG;
michael@0 971 if (NS_WARN_IF(outer))
michael@0 972 return NS_ERROR_NO_AGGREGATION;
michael@0 973
michael@0 974 nsLocalFile* inst = new nsLocalFile();
michael@0 975 if (inst == nullptr)
michael@0 976 return NS_ERROR_OUT_OF_MEMORY;
michael@0 977
michael@0 978 nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
michael@0 979 if (NS_FAILED(rv))
michael@0 980 {
michael@0 981 delete inst;
michael@0 982 return rv;
michael@0 983 }
michael@0 984 return NS_OK;
michael@0 985 }
michael@0 986
michael@0 987
michael@0 988 //-----------------------------------------------------------------------------
michael@0 989 // nsLocalFile::nsISupports
michael@0 990 //-----------------------------------------------------------------------------
michael@0 991
michael@0 992 NS_IMPL_ISUPPORTS(nsLocalFile,
michael@0 993 nsILocalFile,
michael@0 994 nsIFile,
michael@0 995 nsILocalFileWin,
michael@0 996 nsIHashable)
michael@0 997
michael@0 998
michael@0 999 //-----------------------------------------------------------------------------
michael@0 1000 // nsLocalFile <private>
michael@0 1001 //-----------------------------------------------------------------------------
michael@0 1002
michael@0 1003 nsLocalFile::nsLocalFile(const nsLocalFile& other)
michael@0 1004 : mDirty(true)
michael@0 1005 , mResolveDirty(true)
michael@0 1006 , mFollowSymlinks(other.mFollowSymlinks)
michael@0 1007 , mWorkingPath(other.mWorkingPath)
michael@0 1008 {
michael@0 1009 }
michael@0 1010
michael@0 1011 // Resolve the shortcut file from mWorkingPath and write the path
michael@0 1012 // it points to into mResolvedPath.
michael@0 1013 nsresult
michael@0 1014 nsLocalFile::ResolveShortcut()
michael@0 1015 {
michael@0 1016 // we can't do anything without the resolver
michael@0 1017 if (!gResolver)
michael@0 1018 return NS_ERROR_FAILURE;
michael@0 1019
michael@0 1020 mResolvedPath.SetLength(MAX_PATH);
michael@0 1021 if (mResolvedPath.Length() != MAX_PATH)
michael@0 1022 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1023
michael@0 1024 wchar_t *resolvedPath = wwc(mResolvedPath.BeginWriting());
michael@0 1025
michael@0 1026 // resolve this shortcut
michael@0 1027 nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath);
michael@0 1028
michael@0 1029 size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath);
michael@0 1030 mResolvedPath.SetLength(len);
michael@0 1031
michael@0 1032 return rv;
michael@0 1033 }
michael@0 1034
michael@0 1035 // Resolve any shortcuts and stat the resolved path. After a successful return
michael@0 1036 // the path is guaranteed valid and the members of mFileInfo64 can be used.
michael@0 1037 nsresult
michael@0 1038 nsLocalFile::ResolveAndStat()
michael@0 1039 {
michael@0 1040 // if we aren't dirty then we are already done
michael@0 1041 if (!mDirty)
michael@0 1042 return NS_OK;
michael@0 1043
michael@0 1044 // we can't resolve/stat anything that isn't a valid NSPR addressable path
michael@0 1045 if (mWorkingPath.IsEmpty())
michael@0 1046 return NS_ERROR_FILE_INVALID_PATH;
michael@0 1047
michael@0 1048 // this is usually correct
michael@0 1049 mResolvedPath.Assign(mWorkingPath);
michael@0 1050
michael@0 1051 // slutty hack designed to work around bug 134796 until it is fixed
michael@0 1052 nsAutoString nsprPath(mWorkingPath.get());
michael@0 1053 if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':')
michael@0 1054 nsprPath.Append('\\');
michael@0 1055
michael@0 1056 // first we will see if the working path exists. If it doesn't then
michael@0 1057 // there is nothing more that can be done
michael@0 1058 nsresult rv = GetFileInfo(nsprPath, &mFileInfo64);
michael@0 1059 if (NS_FAILED(rv))
michael@0 1060 return rv;
michael@0 1061
michael@0 1062 // if this isn't a shortcut file or we aren't following symlinks then we're done
michael@0 1063 if (!mFollowSymlinks
michael@0 1064 || mFileInfo64.type != PR_FILE_FILE
michael@0 1065 || !IsShortcutPath(mWorkingPath))
michael@0 1066 {
michael@0 1067 mDirty = false;
michael@0 1068 mResolveDirty = false;
michael@0 1069 return NS_OK;
michael@0 1070 }
michael@0 1071
michael@0 1072 // we need to resolve this shortcut to what it points to, this will
michael@0 1073 // set mResolvedPath. Even if it fails we need to have the resolved
michael@0 1074 // path equal to working path for those functions that always use
michael@0 1075 // the resolved path.
michael@0 1076 rv = ResolveShortcut();
michael@0 1077 if (NS_FAILED(rv))
michael@0 1078 {
michael@0 1079 mResolvedPath.Assign(mWorkingPath);
michael@0 1080 return rv;
michael@0 1081 }
michael@0 1082 mResolveDirty = false;
michael@0 1083
michael@0 1084 // get the details of the resolved path
michael@0 1085 rv = GetFileInfo(mResolvedPath, &mFileInfo64);
michael@0 1086 if (NS_FAILED(rv))
michael@0 1087 return rv;
michael@0 1088
michael@0 1089 mDirty = false;
michael@0 1090 return NS_OK;
michael@0 1091 }
michael@0 1092
michael@0 1093 /**
michael@0 1094 * Fills the mResolvedPath member variable with the file or symlink target
michael@0 1095 * if follow symlinks is on. This is a copy of the Resolve parts from
michael@0 1096 * ResolveAndStat. ResolveAndStat is much slower though because of the stat.
michael@0 1097 *
michael@0 1098 * @return NS_OK on success.
michael@0 1099 */
michael@0 1100 nsresult
michael@0 1101 nsLocalFile::Resolve()
michael@0 1102 {
michael@0 1103 // if we aren't dirty then we are already done
michael@0 1104 if (!mResolveDirty) {
michael@0 1105 return NS_OK;
michael@0 1106 }
michael@0 1107
michael@0 1108 // we can't resolve/stat anything that isn't a valid NSPR addressable path
michael@0 1109 if (mWorkingPath.IsEmpty()) {
michael@0 1110 return NS_ERROR_FILE_INVALID_PATH;
michael@0 1111 }
michael@0 1112
michael@0 1113 // this is usually correct
michael@0 1114 mResolvedPath.Assign(mWorkingPath);
michael@0 1115
michael@0 1116 // if this isn't a shortcut file or we aren't following symlinks then
michael@0 1117 // we're done.
michael@0 1118 if (!mFollowSymlinks ||
michael@0 1119 !IsShortcutPath(mWorkingPath)) {
michael@0 1120 mResolveDirty = false;
michael@0 1121 return NS_OK;
michael@0 1122 }
michael@0 1123
michael@0 1124 // we need to resolve this shortcut to what it points to, this will
michael@0 1125 // set mResolvedPath. Even if it fails we need to have the resolved
michael@0 1126 // path equal to working path for those functions that always use
michael@0 1127 // the resolved path.
michael@0 1128 nsresult rv = ResolveShortcut();
michael@0 1129 if (NS_FAILED(rv)) {
michael@0 1130 mResolvedPath.Assign(mWorkingPath);
michael@0 1131 return rv;
michael@0 1132 }
michael@0 1133
michael@0 1134 mResolveDirty = false;
michael@0 1135 return NS_OK;
michael@0 1136 }
michael@0 1137
michael@0 1138 //-----------------------------------------------------------------------------
michael@0 1139 // nsLocalFile::nsIFile,nsILocalFile
michael@0 1140 //-----------------------------------------------------------------------------
michael@0 1141
michael@0 1142 NS_IMETHODIMP
michael@0 1143 nsLocalFile::Clone(nsIFile **file)
michael@0 1144 {
michael@0 1145 // Just copy-construct ourselves
michael@0 1146 *file = new nsLocalFile(*this);
michael@0 1147 if (!*file)
michael@0 1148 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1149
michael@0 1150 NS_ADDREF(*file);
michael@0 1151
michael@0 1152 return NS_OK;
michael@0 1153 }
michael@0 1154
michael@0 1155 NS_IMETHODIMP
michael@0 1156 nsLocalFile::InitWithFile(nsIFile *aFile)
michael@0 1157 {
michael@0 1158 if (NS_WARN_IF(!aFile))
michael@0 1159 return NS_ERROR_INVALID_ARG;
michael@0 1160
michael@0 1161 nsAutoString path;
michael@0 1162 aFile->GetPath(path);
michael@0 1163 if (path.IsEmpty())
michael@0 1164 return NS_ERROR_INVALID_ARG;
michael@0 1165 return InitWithPath(path);
michael@0 1166 }
michael@0 1167
michael@0 1168 NS_IMETHODIMP
michael@0 1169 nsLocalFile::InitWithPath(const nsAString &filePath)
michael@0 1170 {
michael@0 1171 MakeDirty();
michael@0 1172
michael@0 1173 nsAString::const_iterator begin, end;
michael@0 1174 filePath.BeginReading(begin);
michael@0 1175 filePath.EndReading(end);
michael@0 1176
michael@0 1177 // input string must not be empty
michael@0 1178 if (begin == end)
michael@0 1179 return NS_ERROR_FAILURE;
michael@0 1180
michael@0 1181 char16_t firstChar = *begin;
michael@0 1182 char16_t secondChar = *(++begin);
michael@0 1183
michael@0 1184 // just do a sanity check. if it has any forward slashes, it is not a Native path
michael@0 1185 // on windows. Also, it must have a colon at after the first char.
michael@0 1186 if (FindCharInReadable(L'/', begin, end))
michael@0 1187 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1188
michael@0 1189 if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\'))
michael@0 1190 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1191
michael@0 1192 if (secondChar == L':') {
michael@0 1193 // Make sure we have a valid drive, later code assumes the drive letter
michael@0 1194 // is a single char a-z or A-Z.
michael@0 1195 if (PathGetDriveNumberW(filePath.Data()) == -1) {
michael@0 1196 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1197 }
michael@0 1198 }
michael@0 1199
michael@0 1200 mWorkingPath = filePath;
michael@0 1201 // kill any trailing '\'
michael@0 1202 if (mWorkingPath.Last() == L'\\')
michael@0 1203 mWorkingPath.Truncate(mWorkingPath.Length() - 1);
michael@0 1204
michael@0 1205 return NS_OK;
michael@0 1206
michael@0 1207 }
michael@0 1208
michael@0 1209 NS_IMETHODIMP
michael@0 1210 nsLocalFile::OpenNSPRFileDesc(int32_t flags, int32_t mode, PRFileDesc **_retval)
michael@0 1211 {
michael@0 1212 nsresult rv = Resolve();
michael@0 1213 if (NS_FAILED(rv))
michael@0 1214 return rv;
michael@0 1215
michael@0 1216 return OpenFile(mResolvedPath, flags, mode, _retval);
michael@0 1217 }
michael@0 1218
michael@0 1219
michael@0 1220 NS_IMETHODIMP
michael@0 1221 nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval)
michael@0 1222 {
michael@0 1223 nsresult rv = ResolveAndStat();
michael@0 1224 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
michael@0 1225 return rv;
michael@0 1226
michael@0 1227 *_retval = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(mode).get());
michael@0 1228 if (*_retval)
michael@0 1229 return NS_OK;
michael@0 1230
michael@0 1231 return NS_ERROR_FAILURE;
michael@0 1232 }
michael@0 1233
michael@0 1234
michael@0 1235
michael@0 1236 NS_IMETHODIMP
michael@0 1237 nsLocalFile::Create(uint32_t type, uint32_t attributes)
michael@0 1238 {
michael@0 1239 if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE)
michael@0 1240 return NS_ERROR_FILE_UNKNOWN_TYPE;
michael@0 1241
michael@0 1242 nsresult rv = ResolveAndStat();
michael@0 1243 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
michael@0 1244 return rv;
michael@0 1245
michael@0 1246 // create directories to target
michael@0 1247 //
michael@0 1248 // A given local file can be either one of these forms:
michael@0 1249 //
michael@0 1250 // - normal: X:\some\path\on\this\drive
michael@0 1251 // ^--- start here
michael@0 1252 //
michael@0 1253 // - UNC path: \\machine\volume\some\path\on\this\drive
michael@0 1254 // ^--- start here
michael@0 1255 //
michael@0 1256 // Skip the first 'X:\' for the first form, and skip the first full
michael@0 1257 // '\\machine\volume\' segment for the second form.
michael@0 1258
michael@0 1259 wchar_t* path = wwc(mResolvedPath.BeginWriting());
michael@0 1260
michael@0 1261 if (path[0] == L'\\' && path[1] == L'\\')
michael@0 1262 {
michael@0 1263 // dealing with a UNC path here; skip past '\\machine\'
michael@0 1264 path = wcschr(path + 2, L'\\');
michael@0 1265 if (!path)
michael@0 1266 return NS_ERROR_FILE_INVALID_PATH;
michael@0 1267 ++path;
michael@0 1268 }
michael@0 1269
michael@0 1270 // search for first slash after the drive (or volume) name
michael@0 1271 wchar_t* slash = wcschr(path, L'\\');
michael@0 1272
michael@0 1273 nsresult directoryCreateError = NS_OK;
michael@0 1274 if (slash)
michael@0 1275 {
michael@0 1276 // skip the first '\\'
michael@0 1277 ++slash;
michael@0 1278 slash = wcschr(slash, L'\\');
michael@0 1279
michael@0 1280 while (slash)
michael@0 1281 {
michael@0 1282 *slash = L'\0';
michael@0 1283
michael@0 1284 if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
michael@0 1285 rv = ConvertWinError(GetLastError());
michael@0 1286 if (NS_ERROR_FILE_NOT_FOUND == rv &&
michael@0 1287 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
michael@0 1288 // If a previous CreateDirectory failed due to access, return that.
michael@0 1289 return NS_ERROR_FILE_ACCESS_DENIED;
michael@0 1290 }
michael@0 1291 // perhaps the base path already exists, or perhaps we don't have
michael@0 1292 // permissions to create the directory. NOTE: access denied could
michael@0 1293 // occur on a parent directory even though it exists.
michael@0 1294 else if (NS_ERROR_FILE_ALREADY_EXISTS != rv &&
michael@0 1295 NS_ERROR_FILE_ACCESS_DENIED != rv) {
michael@0 1296 return rv;
michael@0 1297 }
michael@0 1298
michael@0 1299 directoryCreateError = rv;
michael@0 1300 }
michael@0 1301 *slash = L'\\';
michael@0 1302 ++slash;
michael@0 1303 slash = wcschr(slash, L'\\');
michael@0 1304 }
michael@0 1305 }
michael@0 1306
michael@0 1307 if (type == NORMAL_FILE_TYPE)
michael@0 1308 {
michael@0 1309 PRFileDesc* file;
michael@0 1310 rv = OpenFile(mResolvedPath,
michael@0 1311 PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, attributes,
michael@0 1312 &file);
michael@0 1313 if (file)
michael@0 1314 PR_Close(file);
michael@0 1315
michael@0 1316 if (rv == NS_ERROR_FILE_ACCESS_DENIED)
michael@0 1317 {
michael@0 1318 // need to return already-exists for directories (bug 452217)
michael@0 1319 bool isdir;
michael@0 1320 if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir)
michael@0 1321 rv = NS_ERROR_FILE_ALREADY_EXISTS;
michael@0 1322 } else if (NS_ERROR_FILE_NOT_FOUND == rv &&
michael@0 1323 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
michael@0 1324 // If a previous CreateDirectory failed due to access, return that.
michael@0 1325 return NS_ERROR_FILE_ACCESS_DENIED;
michael@0 1326 }
michael@0 1327 return rv;
michael@0 1328 }
michael@0 1329
michael@0 1330 if (type == DIRECTORY_TYPE)
michael@0 1331 {
michael@0 1332 if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
michael@0 1333 rv = ConvertWinError(GetLastError());
michael@0 1334 if (NS_ERROR_FILE_NOT_FOUND == rv &&
michael@0 1335 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
michael@0 1336 // If a previous CreateDirectory failed due to access, return that.
michael@0 1337 return NS_ERROR_FILE_ACCESS_DENIED;
michael@0 1338 } else {
michael@0 1339 return rv;
michael@0 1340 }
michael@0 1341 }
michael@0 1342 else
michael@0 1343 return NS_OK;
michael@0 1344 }
michael@0 1345
michael@0 1346 return NS_ERROR_FILE_UNKNOWN_TYPE;
michael@0 1347 }
michael@0 1348
michael@0 1349
michael@0 1350 NS_IMETHODIMP
michael@0 1351 nsLocalFile::Append(const nsAString &node)
michael@0 1352 {
michael@0 1353 // append this path, multiple components are not permitted
michael@0 1354 return AppendInternal(PromiseFlatString(node), false);
michael@0 1355 }
michael@0 1356
michael@0 1357 NS_IMETHODIMP
michael@0 1358 nsLocalFile::AppendRelativePath(const nsAString &node)
michael@0 1359 {
michael@0 1360 // append this path, multiple components are permitted
michael@0 1361 return AppendInternal(PromiseFlatString(node), true);
michael@0 1362 }
michael@0 1363
michael@0 1364
michael@0 1365 nsresult
michael@0 1366 nsLocalFile::AppendInternal(const nsAFlatString &node, bool multipleComponents)
michael@0 1367 {
michael@0 1368 if (node.IsEmpty())
michael@0 1369 return NS_OK;
michael@0 1370
michael@0 1371 // check the relative path for validity
michael@0 1372 if (node.First() == L'\\' // can't start with an '\'
michael@0 1373 || node.FindChar(L'/') != kNotFound // can't contain /
michael@0 1374 || node.EqualsASCII("..")) // can't be ..
michael@0 1375 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1376
michael@0 1377 if (multipleComponents)
michael@0 1378 {
michael@0 1379 // can't contain .. as a path component. Ensure that the valid components
michael@0 1380 // "foo..foo", "..foo", and "foo.." are not falsely detected,
michael@0 1381 // but the invalid paths "..\", "foo\..", "foo\..\foo",
michael@0 1382 // "..\foo", etc are.
michael@0 1383 NS_NAMED_LITERAL_STRING(doubleDot, "\\..");
michael@0 1384 nsAString::const_iterator start, end, offset;
michael@0 1385 node.BeginReading(start);
michael@0 1386 node.EndReading(end);
michael@0 1387 offset = end;
michael@0 1388 while (FindInReadable(doubleDot, start, offset))
michael@0 1389 {
michael@0 1390 if (offset == end || *offset == L'\\')
michael@0 1391 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1392 start = offset;
michael@0 1393 offset = end;
michael@0 1394 }
michael@0 1395
michael@0 1396 // catches the remaining cases of prefixes
michael@0 1397 if (StringBeginsWith(node, NS_LITERAL_STRING("..\\")))
michael@0 1398 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1399 }
michael@0 1400 // single components can't contain '\'
michael@0 1401 else if (node.FindChar(L'\\') != kNotFound)
michael@0 1402 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1403
michael@0 1404 MakeDirty();
michael@0 1405
michael@0 1406 mWorkingPath.Append(NS_LITERAL_STRING("\\") + node);
michael@0 1407
michael@0 1408 return NS_OK;
michael@0 1409 }
michael@0 1410
michael@0 1411 #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \
michael@0 1412 (u) - (L'a' - L'A') : (u))
michael@0 1413
michael@0 1414 NS_IMETHODIMP
michael@0 1415 nsLocalFile::Normalize()
michael@0 1416 {
michael@0 1417 // XXX See bug 187957 comment 18 for possible problems with this implementation.
michael@0 1418
michael@0 1419 if (mWorkingPath.IsEmpty())
michael@0 1420 return NS_OK;
michael@0 1421
michael@0 1422 nsAutoString path(mWorkingPath);
michael@0 1423
michael@0 1424 // find the index of the root backslash for the path. Everything before
michael@0 1425 // this is considered fully normalized and cannot be ascended beyond
michael@0 1426 // using ".." For a local drive this is the first slash (e.g. "c:\").
michael@0 1427 // For a UNC path it is the slash following the share name
michael@0 1428 // (e.g. "\\server\share\").
michael@0 1429 int32_t rootIdx = 2; // default to local drive
michael@0 1430 if (path.First() == L'\\') // if a share then calculate the rootIdx
michael@0 1431 {
michael@0 1432 rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server
michael@0 1433 if (rootIdx == kNotFound)
michael@0 1434 return NS_OK; // already normalized
michael@0 1435 rootIdx = path.FindChar(L'\\', rootIdx+1);
michael@0 1436 if (rootIdx == kNotFound)
michael@0 1437 return NS_OK; // already normalized
michael@0 1438 }
michael@0 1439 else if (path.CharAt(rootIdx) != L'\\')
michael@0 1440 {
michael@0 1441 // The path has been specified relative to the current working directory
michael@0 1442 // for that drive. To normalize it, the current working directory for
michael@0 1443 // that drive needs to be inserted before the supplied relative path
michael@0 1444 // which will provide an absolute path (and the rootIdx will still be 2).
michael@0 1445 WCHAR cwd[MAX_PATH];
michael@0 1446 WCHAR * pcwd = cwd;
michael@0 1447 int drive = TOUPPER(path.First()) - 'A' + 1;
michael@0 1448 /* We need to worry about IPH, for details read bug 419326.
michael@0 1449 * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
michael@0 1450 * uses a bitmask, bit 0 is 'a:'
michael@0 1451 * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
michael@0 1452 * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
michael@0 1453 * take an int, 1 is 'a:'.
michael@0 1454 *
michael@0 1455 * Because of this, we need to do some math. Subtract 1 to convert from
michael@0 1456 * _chdrive/_getdcwd format to _getdrives drive numbering.
michael@0 1457 * Shift left x bits to convert from integer indexing to bitfield indexing.
michael@0 1458 * And of course, we need to find out if the drive is in the bitmask.
michael@0 1459 *
michael@0 1460 * If we're really unlucky, we can still lose, but only if the user
michael@0 1461 * manages to eject the drive between our call to _getdrives() and
michael@0 1462 * our *calls* to _wgetdcwd.
michael@0 1463 */
michael@0 1464 if (!((1 << (drive - 1)) & _getdrives()))
michael@0 1465 return NS_ERROR_FILE_INVALID_PATH;
michael@0 1466 if (!_wgetdcwd(drive, pcwd, MAX_PATH))
michael@0 1467 pcwd = _wgetdcwd(drive, 0, 0);
michael@0 1468 if (!pcwd)
michael@0 1469 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1470 nsAutoString currentDir(pcwd);
michael@0 1471 if (pcwd != cwd)
michael@0 1472 free(pcwd);
michael@0 1473
michael@0 1474 if (currentDir.Last() == '\\')
michael@0 1475 path.Replace(0, 2, currentDir);
michael@0 1476 else
michael@0 1477 path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\"));
michael@0 1478 }
michael@0 1479 NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid");
michael@0 1480 NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid");
michael@0 1481
michael@0 1482 // if there is nothing following the root path then it is already normalized
michael@0 1483 if (rootIdx + 1 == (int32_t)path.Length())
michael@0 1484 return NS_OK;
michael@0 1485
michael@0 1486 // assign the root
michael@0 1487 const char16_t * pathBuffer = path.get(); // simplify access to the buffer
michael@0 1488 mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer
michael@0 1489 mWorkingPath.Assign(pathBuffer, rootIdx);
michael@0 1490
michael@0 1491 // Normalize the path components. The actions taken are:
michael@0 1492 //
michael@0 1493 // "\\" condense to single backslash
michael@0 1494 // "." remove from path
michael@0 1495 // ".." up a directory
michael@0 1496 // "..." remove from path (any number of dots > 2)
michael@0 1497 //
michael@0 1498 // The last form is something that Windows 95 and 98 supported and
michael@0 1499 // is a shortcut for changing up multiple directories. Windows XP
michael@0 1500 // and ilk ignore it in a path, as is done here.
michael@0 1501 int32_t len, begin, end = rootIdx;
michael@0 1502 while (end < (int32_t)path.Length())
michael@0 1503 {
michael@0 1504 // find the current segment (text between the backslashes) to
michael@0 1505 // be examined, this will set the following variables:
michael@0 1506 // begin == index of first char in segment
michael@0 1507 // end == index 1 char after last char in segment
michael@0 1508 // len == length of segment
michael@0 1509 begin = end + 1;
michael@0 1510 end = path.FindChar('\\', begin);
michael@0 1511 if (end == kNotFound)
michael@0 1512 end = path.Length();
michael@0 1513 len = end - begin;
michael@0 1514
michael@0 1515 // ignore double backslashes
michael@0 1516 if (len == 0)
michael@0 1517 continue;
michael@0 1518
michael@0 1519 // len != 0, and interesting paths always begin with a dot
michael@0 1520 if (pathBuffer[begin] == '.')
michael@0 1521 {
michael@0 1522 // ignore single dots
michael@0 1523 if (len == 1)
michael@0 1524 continue;
michael@0 1525
michael@0 1526 // handle multiple dots
michael@0 1527 if (len >= 2 && pathBuffer[begin+1] == L'.')
michael@0 1528 {
michael@0 1529 // back up a path component on double dot
michael@0 1530 if (len == 2)
michael@0 1531 {
michael@0 1532 int32_t prev = mWorkingPath.RFindChar('\\');
michael@0 1533 if (prev >= rootIdx)
michael@0 1534 mWorkingPath.Truncate(prev);
michael@0 1535 continue;
michael@0 1536 }
michael@0 1537
michael@0 1538 // length is > 2 and the first two characters are dots.
michael@0 1539 // if the rest of the string is dots, then ignore it.
michael@0 1540 int idx = len - 1;
michael@0 1541 for (; idx >= 2; --idx)
michael@0 1542 {
michael@0 1543 if (pathBuffer[begin+idx] != L'.')
michael@0 1544 break;
michael@0 1545 }
michael@0 1546
michael@0 1547 // this is true if the loop above didn't break
michael@0 1548 // and all characters in this segment are dots.
michael@0 1549 if (idx < 2)
michael@0 1550 continue;
michael@0 1551 }
michael@0 1552 }
michael@0 1553
michael@0 1554 // add the current component to the path, including the preceding backslash
michael@0 1555 mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
michael@0 1556 }
michael@0 1557
michael@0 1558 // kill trailing dots and spaces.
michael@0 1559 int32_t filePathLen = mWorkingPath.Length() - 1;
michael@0 1560 while(filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
michael@0 1561 mWorkingPath[filePathLen] == L'.'))
michael@0 1562 {
michael@0 1563 mWorkingPath.Truncate(filePathLen--);
michael@0 1564 }
michael@0 1565
michael@0 1566 MakeDirty();
michael@0 1567 return NS_OK;
michael@0 1568 }
michael@0 1569
michael@0 1570 NS_IMETHODIMP
michael@0 1571 nsLocalFile::GetLeafName(nsAString &aLeafName)
michael@0 1572 {
michael@0 1573 aLeafName.Truncate();
michael@0 1574
michael@0 1575 if (mWorkingPath.IsEmpty())
michael@0 1576 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1577
michael@0 1578 int32_t offset = mWorkingPath.RFindChar(L'\\');
michael@0 1579
michael@0 1580 // if the working path is just a node without any lashes.
michael@0 1581 if (offset == kNotFound)
michael@0 1582 aLeafName = mWorkingPath;
michael@0 1583 else
michael@0 1584 aLeafName = Substring(mWorkingPath, offset + 1);
michael@0 1585
michael@0 1586 return NS_OK;
michael@0 1587 }
michael@0 1588
michael@0 1589 NS_IMETHODIMP
michael@0 1590 nsLocalFile::SetLeafName(const nsAString &aLeafName)
michael@0 1591 {
michael@0 1592 MakeDirty();
michael@0 1593
michael@0 1594 if (mWorkingPath.IsEmpty())
michael@0 1595 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 1596
michael@0 1597 // cannot use nsCString::RFindChar() due to 0x5c problem
michael@0 1598 int32_t offset = mWorkingPath.RFindChar(L'\\');
michael@0 1599 if (offset)
michael@0 1600 {
michael@0 1601 mWorkingPath.Truncate(offset+1);
michael@0 1602 }
michael@0 1603 mWorkingPath.Append(aLeafName);
michael@0 1604
michael@0 1605 return NS_OK;
michael@0 1606 }
michael@0 1607
michael@0 1608
michael@0 1609 NS_IMETHODIMP
michael@0 1610 nsLocalFile::GetPath(nsAString &_retval)
michael@0 1611 {
michael@0 1612 _retval = mWorkingPath;
michael@0 1613 return NS_OK;
michael@0 1614 }
michael@0 1615
michael@0 1616 NS_IMETHODIMP
michael@0 1617 nsLocalFile::GetCanonicalPath(nsAString &aResult)
michael@0 1618 {
michael@0 1619 EnsureShortPath();
michael@0 1620 aResult.Assign(mShortWorkingPath);
michael@0 1621 return NS_OK;
michael@0 1622 }
michael@0 1623
michael@0 1624 typedef struct {
michael@0 1625 WORD wLanguage;
michael@0 1626 WORD wCodePage;
michael@0 1627 } LANGANDCODEPAGE;
michael@0 1628
michael@0 1629 NS_IMETHODIMP
michael@0 1630 nsLocalFile::GetVersionInfoField(const char* aField, nsAString& _retval)
michael@0 1631 {
michael@0 1632 nsresult rv = ResolveAndStat();
michael@0 1633 if (NS_FAILED(rv))
michael@0 1634 return rv;
michael@0 1635
michael@0 1636 rv = NS_ERROR_FAILURE;
michael@0 1637
michael@0 1638 const WCHAR *path = mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get();
michael@0 1639
michael@0 1640 DWORD dummy;
michael@0 1641 DWORD size = ::GetFileVersionInfoSizeW(path, &dummy);
michael@0 1642 if (!size)
michael@0 1643 return rv;
michael@0 1644
michael@0 1645 void* ver = calloc(size, 1);
michael@0 1646 if (!ver)
michael@0 1647 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1648
michael@0 1649 if (::GetFileVersionInfoW(path, 0, size, ver))
michael@0 1650 {
michael@0 1651 LANGANDCODEPAGE* translate = nullptr;
michael@0 1652 UINT pageCount;
michael@0 1653 BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation",
michael@0 1654 (void**)&translate, &pageCount);
michael@0 1655 if (queryResult && translate)
michael@0 1656 {
michael@0 1657 for (int32_t i = 0; i < 2; ++i)
michael@0 1658 {
michael@0 1659 wchar_t subBlock[MAX_PATH];
michael@0 1660 _snwprintf(subBlock, MAX_PATH,
michael@0 1661 L"\\StringFileInfo\\%04x%04x\\%s",
michael@0 1662 (i == 0 ? translate[0].wLanguage
michael@0 1663 : ::GetUserDefaultLangID()),
michael@0 1664 translate[0].wCodePage,
michael@0 1665 NS_ConvertASCIItoUTF16(
michael@0 1666 nsDependentCString(aField)).get());
michael@0 1667 subBlock[MAX_PATH - 1] = 0;
michael@0 1668 LPVOID value = nullptr;
michael@0 1669 UINT size;
michael@0 1670 queryResult = ::VerQueryValueW(ver, subBlock, &value, &size);
michael@0 1671 if (queryResult && value)
michael@0 1672 {
michael@0 1673 _retval.Assign(static_cast<char16_t*>(value));
michael@0 1674 if (!_retval.IsEmpty())
michael@0 1675 {
michael@0 1676 rv = NS_OK;
michael@0 1677 break;
michael@0 1678 }
michael@0 1679 }
michael@0 1680 }
michael@0 1681 }
michael@0 1682 }
michael@0 1683 free(ver);
michael@0 1684
michael@0 1685 return rv;
michael@0 1686 }
michael@0 1687
michael@0 1688 NS_IMETHODIMP
michael@0 1689 nsLocalFile::SetShortcut(nsIFile* targetFile,
michael@0 1690 nsIFile* workingDir,
michael@0 1691 const char16_t* args,
michael@0 1692 const char16_t* description,
michael@0 1693 nsIFile* iconFile,
michael@0 1694 int32_t iconIndex)
michael@0 1695 {
michael@0 1696 bool exists;
michael@0 1697 nsresult rv = this->Exists(&exists);
michael@0 1698 if (NS_FAILED(rv)) {
michael@0 1699 return rv;
michael@0 1700 }
michael@0 1701
michael@0 1702 const WCHAR* targetFilePath = nullptr;
michael@0 1703 const WCHAR* workingDirPath = nullptr;
michael@0 1704 const WCHAR* iconFilePath = nullptr;
michael@0 1705
michael@0 1706 nsAutoString targetFilePathAuto;
michael@0 1707 if (targetFile) {
michael@0 1708 rv = targetFile->GetPath(targetFilePathAuto);
michael@0 1709 if (NS_FAILED(rv)) {
michael@0 1710 return rv;
michael@0 1711 }
michael@0 1712 targetFilePath = targetFilePathAuto.get();
michael@0 1713 }
michael@0 1714
michael@0 1715 nsAutoString workingDirPathAuto;
michael@0 1716 if (workingDir) {
michael@0 1717 rv = workingDir->GetPath(workingDirPathAuto);
michael@0 1718 if (NS_FAILED(rv)) {
michael@0 1719 return rv;
michael@0 1720 }
michael@0 1721 workingDirPath = workingDirPathAuto.get();
michael@0 1722 }
michael@0 1723
michael@0 1724 nsAutoString iconPathAuto;
michael@0 1725 if (iconFile) {
michael@0 1726 rv = iconFile->GetPath(iconPathAuto);
michael@0 1727 if (NS_FAILED(rv)) {
michael@0 1728 return rv;
michael@0 1729 }
michael@0 1730 iconFilePath = iconPathAuto.get();
michael@0 1731 }
michael@0 1732
michael@0 1733 rv = gResolver->SetShortcut(exists,
michael@0 1734 mWorkingPath.get(),
michael@0 1735 targetFilePath,
michael@0 1736 workingDirPath,
michael@0 1737 char16ptr_t(args),
michael@0 1738 char16ptr_t(description),
michael@0 1739 iconFilePath,
michael@0 1740 iconFilePath? iconIndex : 0);
michael@0 1741 if (targetFilePath && NS_SUCCEEDED(rv)) {
michael@0 1742 MakeDirty();
michael@0 1743 }
michael@0 1744
michael@0 1745 return rv;
michael@0 1746 }
michael@0 1747
michael@0 1748 /**
michael@0 1749 * Determines if the drive type for the specified file is rmeote or local.
michael@0 1750 *
michael@0 1751 * @param path The path of the file to check
michael@0 1752 * @param remote Out parameter, on function success holds true if the specified
michael@0 1753 * file path is remote, or false if the file path is local.
michael@0 1754 * @return true on success. The return value implies absolutely nothing about
michael@0 1755 * wether the file is local or remote.
michael@0 1756 */
michael@0 1757 static bool
michael@0 1758 IsRemoteFilePath(LPCWSTR path, bool &remote)
michael@0 1759 {
michael@0 1760 // Obtain the parent directory path and make sure it ends with
michael@0 1761 // a trailing backslash.
michael@0 1762 WCHAR dirPath[MAX_PATH + 1] = { 0 };
michael@0 1763 wcsncpy(dirPath, path, MAX_PATH);
michael@0 1764 if (!PathRemoveFileSpecW(dirPath)) {
michael@0 1765 return false;
michael@0 1766 }
michael@0 1767 size_t len = wcslen(dirPath);
michael@0 1768 // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we
michael@0 1769 // recheck the required length here since we need to terminate it with
michael@0 1770 // a backslash.
michael@0 1771 if (len >= MAX_PATH) {
michael@0 1772 return false;
michael@0 1773 }
michael@0 1774
michael@0 1775 dirPath[len] = L'\\';
michael@0 1776 dirPath[len + 1] = L'\0';
michael@0 1777 UINT driveType = GetDriveTypeW(dirPath);
michael@0 1778 remote = driveType == DRIVE_REMOTE;
michael@0 1779 return true;
michael@0 1780 }
michael@0 1781
michael@0 1782 nsresult
michael@0 1783 nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent,
michael@0 1784 const nsAString &newName, uint32_t options)
michael@0 1785 {
michael@0 1786 nsresult rv = NS_OK;
michael@0 1787 nsAutoString filePath;
michael@0 1788
michael@0 1789 bool move = options & (Move | Rename);
michael@0 1790
michael@0 1791 // get the path that we are going to copy to.
michael@0 1792 // Since windows does not know how to auto
michael@0 1793 // resolve shortcuts, we must work with the
michael@0 1794 // target.
michael@0 1795 nsAutoString destPath;
michael@0 1796 destParent->GetTarget(destPath);
michael@0 1797
michael@0 1798 destPath.Append('\\');
michael@0 1799
michael@0 1800 if (newName.IsEmpty())
michael@0 1801 {
michael@0 1802 nsAutoString aFileName;
michael@0 1803 sourceFile->GetLeafName(aFileName);
michael@0 1804 destPath.Append(aFileName);
michael@0 1805 }
michael@0 1806 else
michael@0 1807 {
michael@0 1808 destPath.Append(newName);
michael@0 1809 }
michael@0 1810
michael@0 1811
michael@0 1812 if (options & FollowSymlinks)
michael@0 1813 {
michael@0 1814 rv = sourceFile->GetTarget(filePath);
michael@0 1815 if (filePath.IsEmpty())
michael@0 1816 rv = sourceFile->GetPath(filePath);
michael@0 1817 }
michael@0 1818 else
michael@0 1819 {
michael@0 1820 rv = sourceFile->GetPath(filePath);
michael@0 1821 }
michael@0 1822
michael@0 1823 if (NS_FAILED(rv))
michael@0 1824 return rv;
michael@0 1825
michael@0 1826 // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying
michael@0 1827 // to a SMBV2 remote drive. Without this parameter subsequent append mode
michael@0 1828 // file writes can cause the resultant file to become corrupt. We only need to do
michael@0 1829 // this if the major version of Windows is > 5(Only Windows Vista and above
michael@0 1830 // can support SMBV2). With a 7200RPM hard drive:
michael@0 1831 // Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms.
michael@0 1832 // Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms.
michael@0 1833 // So we only use COPY_FILE_NO_BUFFERING when we have a remote drive.
michael@0 1834 int copyOK;
michael@0 1835 DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
michael@0 1836 if (IsVistaOrLater()) {
michael@0 1837 bool path1Remote, path2Remote;
michael@0 1838 if (!IsRemoteFilePath(filePath.get(), path1Remote) ||
michael@0 1839 !IsRemoteFilePath(destPath.get(), path2Remote) ||
michael@0 1840 path1Remote || path2Remote) {
michael@0 1841 dwCopyFlags |= COPY_FILE_NO_BUFFERING;
michael@0 1842 }
michael@0 1843 }
michael@0 1844
michael@0 1845 if (!move)
michael@0 1846 {
michael@0 1847 copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr,
michael@0 1848 nullptr, nullptr, dwCopyFlags);
michael@0 1849 }
michael@0 1850 else
michael@0 1851 {
michael@0 1852 copyOK = ::MoveFileExW(filePath.get(), destPath.get(), MOVEFILE_REPLACE_EXISTING);
michael@0 1853
michael@0 1854 // Check if copying the source file to a different volume,
michael@0 1855 // as this could be an SMBV2 mapped drive.
michael@0 1856 if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE)
michael@0 1857 {
michael@0 1858 if (options & Rename) {
michael@0 1859 return NS_ERROR_FILE_ACCESS_DENIED;
michael@0 1860 }
michael@0 1861 copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr,
michael@0 1862 nullptr, nullptr, dwCopyFlags);
michael@0 1863
michael@0 1864 if (copyOK)
michael@0 1865 DeleteFileW(filePath.get());
michael@0 1866 }
michael@0 1867 }
michael@0 1868
michael@0 1869 if (!copyOK) // CopyFileEx and MoveFileEx return zero at failure.
michael@0 1870 rv = ConvertWinError(GetLastError());
michael@0 1871 else if (move && !(options & SkipNtfsAclReset))
michael@0 1872 {
michael@0 1873 // Set security permissions to inherit from parent.
michael@0 1874 // Note: propagates to all children: slow for big file trees
michael@0 1875 PACL pOldDACL = nullptr;
michael@0 1876 PSECURITY_DESCRIPTOR pSD = nullptr;
michael@0 1877 ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
michael@0 1878 DACL_SECURITY_INFORMATION,
michael@0 1879 nullptr, nullptr, &pOldDACL, nullptr, &pSD);
michael@0 1880 if (pOldDACL)
michael@0 1881 ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
michael@0 1882 DACL_SECURITY_INFORMATION |
michael@0 1883 UNPROTECTED_DACL_SECURITY_INFORMATION,
michael@0 1884 nullptr, nullptr, pOldDACL, nullptr);
michael@0 1885 if (pSD)
michael@0 1886 LocalFree((HLOCAL)pSD);
michael@0 1887 }
michael@0 1888
michael@0 1889 return rv;
michael@0 1890 }
michael@0 1891
michael@0 1892 nsresult
michael@0 1893 nsLocalFile::CopyMove(nsIFile *aParentDir, const nsAString &newName, uint32_t options)
michael@0 1894 {
michael@0 1895 bool move = options & (Move | Rename);
michael@0 1896 bool followSymlinks = options & FollowSymlinks;
michael@0 1897
michael@0 1898 nsCOMPtr<nsIFile> newParentDir = aParentDir;
michael@0 1899 // check to see if this exists, otherwise return an error.
michael@0 1900 // we will check this by resolving. If the user wants us
michael@0 1901 // to follow links, then we are talking about the target,
michael@0 1902 // hence we can use the |FollowSymlinks| option.
michael@0 1903 nsresult rv = ResolveAndStat();
michael@0 1904 if (NS_FAILED(rv))
michael@0 1905 return rv;
michael@0 1906
michael@0 1907 if (!newParentDir)
michael@0 1908 {
michael@0 1909 // no parent was specified. We must rename.
michael@0 1910 if (newName.IsEmpty())
michael@0 1911 return NS_ERROR_INVALID_ARG;
michael@0 1912
michael@0 1913 rv = GetParent(getter_AddRefs(newParentDir));
michael@0 1914 if (NS_FAILED(rv))
michael@0 1915 return rv;
michael@0 1916 }
michael@0 1917
michael@0 1918 if (!newParentDir)
michael@0 1919 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
michael@0 1920
michael@0 1921 // make sure it exists and is a directory. Create it if not there.
michael@0 1922 bool exists;
michael@0 1923 newParentDir->Exists(&exists);
michael@0 1924 if (!exists)
michael@0 1925 {
michael@0 1926 rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
michael@0 1927 if (NS_FAILED(rv))
michael@0 1928 return rv;
michael@0 1929 }
michael@0 1930 else
michael@0 1931 {
michael@0 1932 bool isDir;
michael@0 1933 newParentDir->IsDirectory(&isDir);
michael@0 1934 if (!isDir)
michael@0 1935 {
michael@0 1936 if (followSymlinks)
michael@0 1937 {
michael@0 1938 bool isLink;
michael@0 1939 newParentDir->IsSymlink(&isLink);
michael@0 1940 if (isLink)
michael@0 1941 {
michael@0 1942 nsAutoString target;
michael@0 1943 newParentDir->GetTarget(target);
michael@0 1944
michael@0 1945 nsCOMPtr<nsIFile> realDest = new nsLocalFile();
michael@0 1946 if (realDest == nullptr)
michael@0 1947 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1948
michael@0 1949 rv = realDest->InitWithPath(target);
michael@0 1950
michael@0 1951 if (NS_FAILED(rv))
michael@0 1952 return rv;
michael@0 1953
michael@0 1954 return CopyMove(realDest, newName, options);
michael@0 1955 }
michael@0 1956 }
michael@0 1957 else
michael@0 1958 {
michael@0 1959 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
michael@0 1960 }
michael@0 1961 }
michael@0 1962 }
michael@0 1963
michael@0 1964 // Try different ways to move/copy files/directories
michael@0 1965 bool done = false;
michael@0 1966 bool isDir;
michael@0 1967 IsDirectory(&isDir);
michael@0 1968 bool isSymlink;
michael@0 1969 IsSymlink(&isSymlink);
michael@0 1970
michael@0 1971 // Try to move the file or directory, or try to copy a single file (or non-followed symlink)
michael@0 1972 if (move || !isDir || (isSymlink && !followSymlinks))
michael@0 1973 {
michael@0 1974 // Copy/Move single file, or move a directory
michael@0 1975 if (!aParentDir) {
michael@0 1976 options |= SkipNtfsAclReset;
michael@0 1977 }
michael@0 1978 rv = CopySingleFile(this, newParentDir, newName, options);
michael@0 1979 done = NS_SUCCEEDED(rv);
michael@0 1980 // If we are moving a directory and that fails, fallback on directory
michael@0 1981 // enumeration. See bug 231300 for details.
michael@0 1982 if (!done && !(move && isDir))
michael@0 1983 return rv;
michael@0 1984 }
michael@0 1985
michael@0 1986 // Not able to copy or move directly, so enumerate it
michael@0 1987 if (!done)
michael@0 1988 {
michael@0 1989 // create a new target destination in the new parentDir;
michael@0 1990 nsCOMPtr<nsIFile> target;
michael@0 1991 rv = newParentDir->Clone(getter_AddRefs(target));
michael@0 1992
michael@0 1993 if (NS_FAILED(rv))
michael@0 1994 return rv;
michael@0 1995
michael@0 1996 nsAutoString allocatedNewName;
michael@0 1997 if (newName.IsEmpty())
michael@0 1998 {
michael@0 1999 bool isLink;
michael@0 2000 IsSymlink(&isLink);
michael@0 2001 if (isLink)
michael@0 2002 {
michael@0 2003 nsAutoString temp;
michael@0 2004 GetTarget(temp);
michael@0 2005 int32_t offset = temp.RFindChar(L'\\');
michael@0 2006 if (offset == kNotFound)
michael@0 2007 allocatedNewName = temp;
michael@0 2008 else
michael@0 2009 allocatedNewName = Substring(temp, offset + 1);
michael@0 2010 }
michael@0 2011 else
michael@0 2012 {
michael@0 2013 GetLeafName(allocatedNewName);// this should be the leaf name of the
michael@0 2014 }
michael@0 2015 }
michael@0 2016 else
michael@0 2017 {
michael@0 2018 allocatedNewName = newName;
michael@0 2019 }
michael@0 2020
michael@0 2021 rv = target->Append(allocatedNewName);
michael@0 2022 if (NS_FAILED(rv))
michael@0 2023 return rv;
michael@0 2024
michael@0 2025 allocatedNewName.Truncate();
michael@0 2026
michael@0 2027 // check if the destination directory already exists
michael@0 2028 target->Exists(&exists);
michael@0 2029 if (!exists)
michael@0 2030 {
michael@0 2031 // if the destination directory cannot be created, return an error
michael@0 2032 rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
michael@0 2033 if (NS_FAILED(rv))
michael@0 2034 return rv;
michael@0 2035 }
michael@0 2036 else
michael@0 2037 {
michael@0 2038 // check if the destination directory is writable and empty
michael@0 2039 bool isWritable;
michael@0 2040
michael@0 2041 target->IsWritable(&isWritable);
michael@0 2042 if (!isWritable)
michael@0 2043 return NS_ERROR_FILE_ACCESS_DENIED;
michael@0 2044
michael@0 2045 nsCOMPtr<nsISimpleEnumerator> targetIterator;
michael@0 2046 rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
michael@0 2047 if (NS_FAILED(rv))
michael@0 2048 return rv;
michael@0 2049
michael@0 2050 bool more;
michael@0 2051 targetIterator->HasMoreElements(&more);
michael@0 2052 // return error if target directory is not empty
michael@0 2053 if (more)
michael@0 2054 return NS_ERROR_FILE_DIR_NOT_EMPTY;
michael@0 2055 }
michael@0 2056
michael@0 2057 nsDirEnumerator dirEnum;
michael@0 2058
michael@0 2059 rv = dirEnum.Init(this);
michael@0 2060 if (NS_FAILED(rv)) {
michael@0 2061 NS_WARNING("dirEnum initialization failed");
michael@0 2062 return rv;
michael@0 2063 }
michael@0 2064
michael@0 2065 bool more = false;
michael@0 2066 while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more)
michael@0 2067 {
michael@0 2068 nsCOMPtr<nsISupports> item;
michael@0 2069 nsCOMPtr<nsIFile> file;
michael@0 2070 dirEnum.GetNext(getter_AddRefs(item));
michael@0 2071 file = do_QueryInterface(item);
michael@0 2072 if (file)
michael@0 2073 {
michael@0 2074 bool isDir, isLink;
michael@0 2075
michael@0 2076 file->IsDirectory(&isDir);
michael@0 2077 file->IsSymlink(&isLink);
michael@0 2078
michael@0 2079 if (move)
michael@0 2080 {
michael@0 2081 if (followSymlinks)
michael@0 2082 return NS_ERROR_FAILURE;
michael@0 2083
michael@0 2084 rv = file->MoveTo(target, EmptyString());
michael@0 2085 if (NS_FAILED(rv))
michael@0 2086 return rv;
michael@0 2087 }
michael@0 2088 else
michael@0 2089 {
michael@0 2090 if (followSymlinks)
michael@0 2091 rv = file->CopyToFollowingLinks(target, EmptyString());
michael@0 2092 else
michael@0 2093 rv = file->CopyTo(target, EmptyString());
michael@0 2094 if (NS_FAILED(rv))
michael@0 2095 return rv;
michael@0 2096 }
michael@0 2097 }
michael@0 2098 }
michael@0 2099 // we've finished moving all the children of this directory
michael@0 2100 // in the new directory. so now delete the directory
michael@0 2101 // note, we don't need to do a recursive delete.
michael@0 2102 // MoveTo() is recursive. At this point,
michael@0 2103 // we've already moved the children of the current folder
michael@0 2104 // to the new location. nothing should be left in the folder.
michael@0 2105 if (move)
michael@0 2106 {
michael@0 2107 rv = Remove(false /* recursive */);
michael@0 2108 if (NS_FAILED(rv))
michael@0 2109 return rv;
michael@0 2110 }
michael@0 2111 }
michael@0 2112
michael@0 2113
michael@0 2114 // If we moved, we want to adjust this.
michael@0 2115 if (move)
michael@0 2116 {
michael@0 2117 MakeDirty();
michael@0 2118
michael@0 2119 nsAutoString newParentPath;
michael@0 2120 newParentDir->GetPath(newParentPath);
michael@0 2121
michael@0 2122 if (newParentPath.IsEmpty())
michael@0 2123 return NS_ERROR_FAILURE;
michael@0 2124
michael@0 2125 if (newName.IsEmpty())
michael@0 2126 {
michael@0 2127 nsAutoString aFileName;
michael@0 2128 GetLeafName(aFileName);
michael@0 2129
michael@0 2130 InitWithPath(newParentPath);
michael@0 2131 Append(aFileName);
michael@0 2132 }
michael@0 2133 else
michael@0 2134 {
michael@0 2135 InitWithPath(newParentPath);
michael@0 2136 Append(newName);
michael@0 2137 }
michael@0 2138 }
michael@0 2139
michael@0 2140 return NS_OK;
michael@0 2141 }
michael@0 2142
michael@0 2143 NS_IMETHODIMP
michael@0 2144 nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName)
michael@0 2145 {
michael@0 2146 return CopyMove(newParentDir, newName, 0);
michael@0 2147 }
michael@0 2148
michael@0 2149 NS_IMETHODIMP
michael@0 2150 nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName)
michael@0 2151 {
michael@0 2152 return CopyMove(newParentDir, newName, FollowSymlinks);
michael@0 2153 }
michael@0 2154
michael@0 2155 NS_IMETHODIMP
michael@0 2156 nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName)
michael@0 2157 {
michael@0 2158 return CopyMove(newParentDir, newName, Move);
michael@0 2159 }
michael@0 2160
michael@0 2161 NS_IMETHODIMP
michael@0 2162 nsLocalFile::RenameTo(nsIFile *newParentDir, const nsAString & newName)
michael@0 2163 {
michael@0 2164 nsCOMPtr<nsIFile> targetParentDir = newParentDir;
michael@0 2165 // check to see if this exists, otherwise return an error.
michael@0 2166 // we will check this by resolving. If the user wants us
michael@0 2167 // to follow links, then we are talking about the target,
michael@0 2168 // hence we can use the |followSymlinks| parameter.
michael@0 2169 nsresult rv = ResolveAndStat();
michael@0 2170 if (NS_FAILED(rv)) {
michael@0 2171 return rv;
michael@0 2172 }
michael@0 2173
michael@0 2174 if (!targetParentDir) {
michael@0 2175 // no parent was specified. We must rename.
michael@0 2176 if (newName.IsEmpty()) {
michael@0 2177 return NS_ERROR_INVALID_ARG;
michael@0 2178 }
michael@0 2179 rv = GetParent(getter_AddRefs(targetParentDir));
michael@0 2180 if (NS_FAILED(rv)) {
michael@0 2181 return rv;
michael@0 2182 }
michael@0 2183 }
michael@0 2184
michael@0 2185 if (!targetParentDir) {
michael@0 2186 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
michael@0 2187 }
michael@0 2188
michael@0 2189 // make sure it exists and is a directory. Create it if not there.
michael@0 2190 bool exists;
michael@0 2191 targetParentDir->Exists(&exists);
michael@0 2192 if (!exists) {
michael@0 2193 rv = targetParentDir->Create(DIRECTORY_TYPE, 0644);
michael@0 2194 if (NS_FAILED(rv)) {
michael@0 2195 return rv;
michael@0 2196 }
michael@0 2197 } else {
michael@0 2198 bool isDir;
michael@0 2199 targetParentDir->IsDirectory(&isDir);
michael@0 2200 if (!isDir) {
michael@0 2201 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
michael@0 2202 }
michael@0 2203 }
michael@0 2204
michael@0 2205 uint32_t options = Rename;
michael@0 2206 if (!newParentDir) {
michael@0 2207 options |= SkipNtfsAclReset;
michael@0 2208 }
michael@0 2209 // Move single file, or move a directory
michael@0 2210 return CopySingleFile(this, targetParentDir, newName, options);
michael@0 2211 }
michael@0 2212
michael@0 2213 NS_IMETHODIMP
michael@0 2214 nsLocalFile::Load(PRLibrary * *_retval)
michael@0 2215 {
michael@0 2216 // Check we are correctly initialized.
michael@0 2217 CHECK_mWorkingPath();
michael@0 2218
michael@0 2219 bool isFile;
michael@0 2220 nsresult rv = IsFile(&isFile);
michael@0 2221
michael@0 2222 if (NS_FAILED(rv))
michael@0 2223 return rv;
michael@0 2224
michael@0 2225 if (! isFile)
michael@0 2226 return NS_ERROR_FILE_IS_DIRECTORY;
michael@0 2227
michael@0 2228 #ifdef NS_BUILD_REFCNT_LOGGING
michael@0 2229 nsTraceRefcnt::SetActivityIsLegal(false);
michael@0 2230 #endif
michael@0 2231
michael@0 2232 PRLibSpec libSpec;
michael@0 2233 libSpec.value.pathname_u = mResolvedPath.get();
michael@0 2234 libSpec.type = PR_LibSpec_PathnameU;
michael@0 2235 *_retval = PR_LoadLibraryWithFlags(libSpec, 0);
michael@0 2236
michael@0 2237 #ifdef NS_BUILD_REFCNT_LOGGING
michael@0 2238 nsTraceRefcnt::SetActivityIsLegal(true);
michael@0 2239 #endif
michael@0 2240
michael@0 2241 if (*_retval)
michael@0 2242 return NS_OK;
michael@0 2243 return NS_ERROR_NULL_POINTER;
michael@0 2244 }
michael@0 2245
michael@0 2246 NS_IMETHODIMP
michael@0 2247 nsLocalFile::Remove(bool recursive)
michael@0 2248 {
michael@0 2249 // NOTE:
michael@0 2250 //
michael@0 2251 // if the working path points to a shortcut, then we will only
michael@0 2252 // delete the shortcut itself. even if the shortcut points to
michael@0 2253 // a directory, we will not recurse into that directory or
michael@0 2254 // delete that directory itself. likewise, if the shortcut
michael@0 2255 // points to a normal file, we will not delete the real file.
michael@0 2256 // this is done to be consistent with the other platforms that
michael@0 2257 // behave this way. we do this even if the followLinks attribute
michael@0 2258 // is set to true. this helps protect against misuse that could
michael@0 2259 // lead to security bugs (e.g., bug 210588).
michael@0 2260 //
michael@0 2261 // Since shortcut files are no longer permitted to be used as unix-like
michael@0 2262 // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
michael@0 2263 // this processing is a lot simpler. Even if the shortcut file is
michael@0 2264 // pointing to a directory, only the mWorkingPath value is used and so
michael@0 2265 // only the shortcut file will be deleted.
michael@0 2266
michael@0 2267 // Check we are correctly initialized.
michael@0 2268 CHECK_mWorkingPath();
michael@0 2269
michael@0 2270 bool isDir, isLink;
michael@0 2271 nsresult rv;
michael@0 2272
michael@0 2273 isDir = false;
michael@0 2274 rv = IsSymlink(&isLink);
michael@0 2275 if (NS_FAILED(rv))
michael@0 2276 return rv;
michael@0 2277
michael@0 2278 // only check to see if we have a directory if it isn't a link
michael@0 2279 if (!isLink)
michael@0 2280 {
michael@0 2281 rv = IsDirectory(&isDir);
michael@0 2282 if (NS_FAILED(rv))
michael@0 2283 return rv;
michael@0 2284 }
michael@0 2285
michael@0 2286 if (isDir)
michael@0 2287 {
michael@0 2288 if (recursive)
michael@0 2289 {
michael@0 2290 nsDirEnumerator dirEnum;
michael@0 2291
michael@0 2292 rv = dirEnum.Init(this);
michael@0 2293 if (NS_FAILED(rv))
michael@0 2294 return rv;
michael@0 2295
michael@0 2296 bool more = false;
michael@0 2297 while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more)
michael@0 2298 {
michael@0 2299 nsCOMPtr<nsISupports> item;
michael@0 2300 dirEnum.GetNext(getter_AddRefs(item));
michael@0 2301 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
michael@0 2302 if (file)
michael@0 2303 file->Remove(recursive);
michael@0 2304 }
michael@0 2305 }
michael@0 2306 if (RemoveDirectoryW(mWorkingPath.get()) == 0)
michael@0 2307 return ConvertWinError(GetLastError());
michael@0 2308 }
michael@0 2309 else
michael@0 2310 {
michael@0 2311 if (DeleteFileW(mWorkingPath.get()) == 0)
michael@0 2312 return ConvertWinError(GetLastError());
michael@0 2313 }
michael@0 2314
michael@0 2315 MakeDirty();
michael@0 2316 return rv;
michael@0 2317 }
michael@0 2318
michael@0 2319 NS_IMETHODIMP
michael@0 2320 nsLocalFile::GetLastModifiedTime(PRTime *aLastModifiedTime)
michael@0 2321 {
michael@0 2322 // Check we are correctly initialized.
michael@0 2323 CHECK_mWorkingPath();
michael@0 2324
michael@0 2325 if (NS_WARN_IF(!aLastModifiedTime))
michael@0 2326 return NS_ERROR_INVALID_ARG;
michael@0 2327
michael@0 2328 // get the modified time of the target as determined by mFollowSymlinks
michael@0 2329 // If true, then this will be for the target of the shortcut file,
michael@0 2330 // otherwise it will be for the shortcut file itself (i.e. the same
michael@0 2331 // results as GetLastModifiedTimeOfLink)
michael@0 2332
michael@0 2333 nsresult rv = ResolveAndStat();
michael@0 2334 if (NS_FAILED(rv))
michael@0 2335 return rv;
michael@0 2336
michael@0 2337 // microseconds -> milliseconds
michael@0 2338 *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC;
michael@0 2339 return NS_OK;
michael@0 2340 }
michael@0 2341
michael@0 2342
michael@0 2343 NS_IMETHODIMP
michael@0 2344 nsLocalFile::GetLastModifiedTimeOfLink(PRTime *aLastModifiedTime)
michael@0 2345 {
michael@0 2346 // Check we are correctly initialized.
michael@0 2347 CHECK_mWorkingPath();
michael@0 2348
michael@0 2349 if (NS_WARN_IF(!aLastModifiedTime))
michael@0 2350 return NS_ERROR_INVALID_ARG;
michael@0 2351
michael@0 2352 // The caller is assumed to have already called IsSymlink
michael@0 2353 // and to have found that this file is a link.
michael@0 2354
michael@0 2355 PRFileInfo64 info;
michael@0 2356 nsresult rv = GetFileInfo(mWorkingPath, &info);
michael@0 2357 if (NS_FAILED(rv))
michael@0 2358 return rv;
michael@0 2359
michael@0 2360 // microseconds -> milliseconds
michael@0 2361 *aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC;
michael@0 2362 return NS_OK;
michael@0 2363 }
michael@0 2364
michael@0 2365
michael@0 2366 NS_IMETHODIMP
michael@0 2367 nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime)
michael@0 2368 {
michael@0 2369 // Check we are correctly initialized.
michael@0 2370 CHECK_mWorkingPath();
michael@0 2371
michael@0 2372 nsresult rv = ResolveAndStat();
michael@0 2373 if (NS_FAILED(rv))
michael@0 2374 return rv;
michael@0 2375
michael@0 2376 // set the modified time of the target as determined by mFollowSymlinks
michael@0 2377 // If true, then this will be for the target of the shortcut file,
michael@0 2378 // otherwise it will be for the shortcut file itself (i.e. the same
michael@0 2379 // results as SetLastModifiedTimeOfLink)
michael@0 2380
michael@0 2381 rv = SetModDate(aLastModifiedTime, mResolvedPath.get());
michael@0 2382 if (NS_SUCCEEDED(rv))
michael@0 2383 MakeDirty();
michael@0 2384
michael@0 2385 return rv;
michael@0 2386 }
michael@0 2387
michael@0 2388
michael@0 2389 NS_IMETHODIMP
michael@0 2390 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime)
michael@0 2391 {
michael@0 2392 // The caller is assumed to have already called IsSymlink
michael@0 2393 // and to have found that this file is a link.
michael@0 2394
michael@0 2395 nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get());
michael@0 2396 if (NS_SUCCEEDED(rv))
michael@0 2397 MakeDirty();
michael@0 2398
michael@0 2399 return rv;
michael@0 2400 }
michael@0 2401
michael@0 2402 nsresult
michael@0 2403 nsLocalFile::SetModDate(PRTime aLastModifiedTime, const wchar_t *filePath)
michael@0 2404 {
michael@0 2405 // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
michael@0 2406 // modification time for directories.
michael@0 2407 HANDLE file = ::CreateFileW(filePath, // pointer to name of the file
michael@0 2408 GENERIC_WRITE, // access (write) mode
michael@0 2409 0, // share mode
michael@0 2410 nullptr, // pointer to security attributes
michael@0 2411 OPEN_EXISTING, // how to create
michael@0 2412 FILE_FLAG_BACKUP_SEMANTICS, // file attributes
michael@0 2413 nullptr);
michael@0 2414
michael@0 2415 if (file == INVALID_HANDLE_VALUE)
michael@0 2416 {
michael@0 2417 return ConvertWinError(GetLastError());
michael@0 2418 }
michael@0 2419
michael@0 2420 FILETIME ft;
michael@0 2421 SYSTEMTIME st;
michael@0 2422 PRExplodedTime pret;
michael@0 2423
michael@0 2424 // PR_ExplodeTime expects usecs...
michael@0 2425 PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret);
michael@0 2426 st.wYear = pret.tm_year;
michael@0 2427 st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
michael@0 2428 st.wDayOfWeek = pret.tm_wday;
michael@0 2429 st.wDay = pret.tm_mday;
michael@0 2430 st.wHour = pret.tm_hour;
michael@0 2431 st.wMinute = pret.tm_min;
michael@0 2432 st.wSecond = pret.tm_sec;
michael@0 2433 st.wMilliseconds = pret.tm_usec/1000;
michael@0 2434
michael@0 2435 nsresult rv = NS_OK;
michael@0 2436 // if at least one of these fails...
michael@0 2437 if (!(SystemTimeToFileTime(&st, &ft) != 0 &&
michael@0 2438 SetFileTime(file, nullptr, &ft, &ft) != 0))
michael@0 2439 {
michael@0 2440 rv = ConvertWinError(GetLastError());
michael@0 2441 }
michael@0 2442
michael@0 2443 CloseHandle(file);
michael@0 2444 return rv;
michael@0 2445 }
michael@0 2446
michael@0 2447 NS_IMETHODIMP
michael@0 2448 nsLocalFile::GetPermissions(uint32_t *aPermissions)
michael@0 2449 {
michael@0 2450 if (NS_WARN_IF(!aPermissions))
michael@0 2451 return NS_ERROR_INVALID_ARG;
michael@0 2452
michael@0 2453 // get the permissions of the target as determined by mFollowSymlinks
michael@0 2454 // If true, then this will be for the target of the shortcut file,
michael@0 2455 // otherwise it will be for the shortcut file itself (i.e. the same
michael@0 2456 // results as GetPermissionsOfLink)
michael@0 2457 nsresult rv = ResolveAndStat();
michael@0 2458 if (NS_FAILED(rv))
michael@0 2459 return rv;
michael@0 2460
michael@0 2461 bool isWritable, isExecutable;
michael@0 2462 IsWritable(&isWritable);
michael@0 2463 IsExecutable(&isExecutable);
michael@0 2464
michael@0 2465 *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
michael@0 2466 if (isWritable)
michael@0 2467 *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
michael@0 2468 if (isExecutable)
michael@0 2469 *aPermissions |= PR_IXUSR|PR_IXGRP|PR_IXOTH; // all execute
michael@0 2470
michael@0 2471 return NS_OK;
michael@0 2472 }
michael@0 2473
michael@0 2474 NS_IMETHODIMP
michael@0 2475 nsLocalFile::GetPermissionsOfLink(uint32_t *aPermissions)
michael@0 2476 {
michael@0 2477 // Check we are correctly initialized.
michael@0 2478 CHECK_mWorkingPath();
michael@0 2479
michael@0 2480 if (NS_WARN_IF(!aPermissions))
michael@0 2481 return NS_ERROR_INVALID_ARG;
michael@0 2482
michael@0 2483 // The caller is assumed to have already called IsSymlink
michael@0 2484 // and to have found that this file is a link. It is not
michael@0 2485 // possible for a link file to be executable.
michael@0 2486
michael@0 2487 DWORD word = ::GetFileAttributesW(mWorkingPath.get());
michael@0 2488 if (word == INVALID_FILE_ATTRIBUTES)
michael@0 2489 return NS_ERROR_FILE_INVALID_PATH;
michael@0 2490
michael@0 2491 bool isWritable = !(word & FILE_ATTRIBUTE_READONLY);
michael@0 2492 *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
michael@0 2493 if (isWritable)
michael@0 2494 *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
michael@0 2495
michael@0 2496 return NS_OK;
michael@0 2497 }
michael@0 2498
michael@0 2499
michael@0 2500 NS_IMETHODIMP
michael@0 2501 nsLocalFile::SetPermissions(uint32_t aPermissions)
michael@0 2502 {
michael@0 2503 // Check we are correctly initialized.
michael@0 2504 CHECK_mWorkingPath();
michael@0 2505
michael@0 2506 // set the permissions of the target as determined by mFollowSymlinks
michael@0 2507 // If true, then this will be for the target of the shortcut file,
michael@0 2508 // otherwise it will be for the shortcut file itself (i.e. the same
michael@0 2509 // results as SetPermissionsOfLink)
michael@0 2510 nsresult rv = ResolveAndStat();
michael@0 2511 if (NS_FAILED(rv))
michael@0 2512 return rv;
michael@0 2513
michael@0 2514 // windows only knows about the following permissions
michael@0 2515 int mode = 0;
michael@0 2516 if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
michael@0 2517 mode |= _S_IREAD;
michael@0 2518 if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
michael@0 2519 mode |= _S_IWRITE;
michael@0 2520
michael@0 2521 if (_wchmod(mResolvedPath.get(), mode) == -1)
michael@0 2522 return NS_ERROR_FAILURE;
michael@0 2523
michael@0 2524 return NS_OK;
michael@0 2525 }
michael@0 2526
michael@0 2527 NS_IMETHODIMP
michael@0 2528 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions)
michael@0 2529 {
michael@0 2530 // The caller is assumed to have already called IsSymlink
michael@0 2531 // and to have found that this file is a link.
michael@0 2532
michael@0 2533 // windows only knows about the following permissions
michael@0 2534 int mode = 0;
michael@0 2535 if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
michael@0 2536 mode |= _S_IREAD;
michael@0 2537 if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
michael@0 2538 mode |= _S_IWRITE;
michael@0 2539
michael@0 2540 if (_wchmod(mWorkingPath.get(), mode) == -1)
michael@0 2541 return NS_ERROR_FAILURE;
michael@0 2542
michael@0 2543 return NS_OK;
michael@0 2544 }
michael@0 2545
michael@0 2546
michael@0 2547 NS_IMETHODIMP
michael@0 2548 nsLocalFile::GetFileSize(int64_t *aFileSize)
michael@0 2549 {
michael@0 2550 if (NS_WARN_IF(!aFileSize))
michael@0 2551 return NS_ERROR_INVALID_ARG;
michael@0 2552
michael@0 2553 nsresult rv = ResolveAndStat();
michael@0 2554 if (NS_FAILED(rv))
michael@0 2555 return rv;
michael@0 2556
michael@0 2557 *aFileSize = mFileInfo64.size;
michael@0 2558 return NS_OK;
michael@0 2559 }
michael@0 2560
michael@0 2561
michael@0 2562 NS_IMETHODIMP
michael@0 2563 nsLocalFile::GetFileSizeOfLink(int64_t *aFileSize)
michael@0 2564 {
michael@0 2565 // Check we are correctly initialized.
michael@0 2566 CHECK_mWorkingPath();
michael@0 2567
michael@0 2568 if (NS_WARN_IF(!aFileSize))
michael@0 2569 return NS_ERROR_INVALID_ARG;
michael@0 2570
michael@0 2571 // The caller is assumed to have already called IsSymlink
michael@0 2572 // and to have found that this file is a link.
michael@0 2573
michael@0 2574 PRFileInfo64 info;
michael@0 2575 if (NS_FAILED(GetFileInfo(mWorkingPath, &info)))
michael@0 2576 return NS_ERROR_FILE_INVALID_PATH;
michael@0 2577
michael@0 2578 *aFileSize = info.size;
michael@0 2579 return NS_OK;
michael@0 2580 }
michael@0 2581
michael@0 2582 NS_IMETHODIMP
michael@0 2583 nsLocalFile::SetFileSize(int64_t aFileSize)
michael@0 2584 {
michael@0 2585 // Check we are correctly initialized.
michael@0 2586 CHECK_mWorkingPath();
michael@0 2587
michael@0 2588 nsresult rv = ResolveAndStat();
michael@0 2589 if (NS_FAILED(rv))
michael@0 2590 return rv;
michael@0 2591
michael@0 2592 HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file
michael@0 2593 GENERIC_WRITE, // access (write) mode
michael@0 2594 FILE_SHARE_READ, // share mode
michael@0 2595 nullptr, // pointer to security attributes
michael@0 2596 OPEN_EXISTING, // how to create
michael@0 2597 FILE_ATTRIBUTE_NORMAL, // file attributes
michael@0 2598 nullptr);
michael@0 2599 if (hFile == INVALID_HANDLE_VALUE)
michael@0 2600 {
michael@0 2601 return ConvertWinError(GetLastError());
michael@0 2602 }
michael@0 2603
michael@0 2604 // seek the file pointer to the new, desired end of file
michael@0 2605 // and then truncate the file at that position
michael@0 2606 rv = NS_ERROR_FAILURE;
michael@0 2607 aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN);
michael@0 2608 if (aFileSize != -1 && SetEndOfFile(hFile))
michael@0 2609 {
michael@0 2610 MakeDirty();
michael@0 2611 rv = NS_OK;
michael@0 2612 }
michael@0 2613
michael@0 2614 CloseHandle(hFile);
michael@0 2615 return rv;
michael@0 2616 }
michael@0 2617
michael@0 2618 NS_IMETHODIMP
michael@0 2619 nsLocalFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable)
michael@0 2620 {
michael@0 2621 // Check we are correctly initialized.
michael@0 2622 CHECK_mWorkingPath();
michael@0 2623
michael@0 2624 if (NS_WARN_IF(!aDiskSpaceAvailable))
michael@0 2625 return NS_ERROR_INVALID_ARG;
michael@0 2626
michael@0 2627 ResolveAndStat();
michael@0 2628
michael@0 2629 if (mFileInfo64.type == PR_FILE_FILE) {
michael@0 2630 // Since GetDiskFreeSpaceExW works only on directories, use the parent.
michael@0 2631 nsCOMPtr<nsIFile> parent;
michael@0 2632 if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) {
michael@0 2633 return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable);
michael@0 2634 }
michael@0 2635 }
michael@0 2636
michael@0 2637 ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes;
michael@0 2638 if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller,
michael@0 2639 &liTotalNumberOfBytes, nullptr))
michael@0 2640 {
michael@0 2641 *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart;
michael@0 2642 return NS_OK;
michael@0 2643 }
michael@0 2644 *aDiskSpaceAvailable = 0;
michael@0 2645 return NS_OK;
michael@0 2646 }
michael@0 2647
michael@0 2648 NS_IMETHODIMP
michael@0 2649 nsLocalFile::GetParent(nsIFile * *aParent)
michael@0 2650 {
michael@0 2651 // Check we are correctly initialized.
michael@0 2652 CHECK_mWorkingPath();
michael@0 2653
michael@0 2654 if (NS_WARN_IF(!aParent))
michael@0 2655 return NS_ERROR_INVALID_ARG;
michael@0 2656
michael@0 2657 // A two-character path must be a drive such as C:, so it has no parent
michael@0 2658 if (mWorkingPath.Length() == 2) {
michael@0 2659 *aParent = nullptr;
michael@0 2660 return NS_OK;
michael@0 2661 }
michael@0 2662
michael@0 2663 int32_t offset = mWorkingPath.RFindChar(char16_t('\\'));
michael@0 2664 // adding this offset check that was removed in bug 241708 fixes mail
michael@0 2665 // directories that aren't relative to/underneath the profile dir.
michael@0 2666 // e.g., on a different drive. Before you remove them, please make
michael@0 2667 // sure local mail directories that aren't underneath the profile dir work.
michael@0 2668 if (offset == kNotFound)
michael@0 2669 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
michael@0 2670
michael@0 2671 // A path of the form \\NAME is a top-level path and has no parent
michael@0 2672 if (offset == 1 && mWorkingPath[0] == L'\\') {
michael@0 2673 *aParent = nullptr;
michael@0 2674 return NS_OK;
michael@0 2675 }
michael@0 2676
michael@0 2677 nsAutoString parentPath(mWorkingPath);
michael@0 2678
michael@0 2679 if (offset > 0)
michael@0 2680 parentPath.Truncate(offset);
michael@0 2681 else
michael@0 2682 parentPath.AssignLiteral("\\\\.");
michael@0 2683
michael@0 2684 nsCOMPtr<nsIFile> localFile;
michael@0 2685 nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile));
michael@0 2686
michael@0 2687 if (NS_FAILED(rv)) {
michael@0 2688 return rv;
michael@0 2689 }
michael@0 2690
michael@0 2691 localFile.forget(aParent);
michael@0 2692 return NS_OK;
michael@0 2693 }
michael@0 2694
michael@0 2695 NS_IMETHODIMP
michael@0 2696 nsLocalFile::Exists(bool *_retval)
michael@0 2697 {
michael@0 2698 // Check we are correctly initialized.
michael@0 2699 CHECK_mWorkingPath();
michael@0 2700
michael@0 2701 if (NS_WARN_IF(!_retval))
michael@0 2702 return NS_ERROR_INVALID_ARG;
michael@0 2703 *_retval = false;
michael@0 2704
michael@0 2705 MakeDirty();
michael@0 2706 nsresult rv = ResolveAndStat();
michael@0 2707 *_retval = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED;
michael@0 2708
michael@0 2709 return NS_OK;
michael@0 2710 }
michael@0 2711
michael@0 2712 NS_IMETHODIMP
michael@0 2713 nsLocalFile::IsWritable(bool *aIsWritable)
michael@0 2714 {
michael@0 2715 // Check we are correctly initialized.
michael@0 2716 CHECK_mWorkingPath();
michael@0 2717
michael@0 2718 // The read-only attribute on a FAT directory only means that it can't
michael@0 2719 // be deleted. It is still possible to modify the contents of the directory.
michael@0 2720 nsresult rv = IsDirectory(aIsWritable);
michael@0 2721 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
michael@0 2722 *aIsWritable = true;
michael@0 2723 return NS_OK;
michael@0 2724 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
michael@0 2725 // If the file is normally allowed write access
michael@0 2726 // we should still return that the file is writable.
michael@0 2727 } else if (NS_FAILED(rv)) {
michael@0 2728 return rv;
michael@0 2729 }
michael@0 2730 if (*aIsWritable)
michael@0 2731 return NS_OK;
michael@0 2732
michael@0 2733 // writable if the file doesn't have the readonly attribute
michael@0 2734 rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable);
michael@0 2735 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
michael@0 2736 *aIsWritable = false;
michael@0 2737 return NS_OK;
michael@0 2738 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
michael@0 2739 // If the file is normally allowed write access
michael@0 2740 // we should still return that the file is writable.
michael@0 2741 } else if (NS_FAILED(rv)) {
michael@0 2742 return rv;
michael@0 2743 }
michael@0 2744 *aIsWritable = !*aIsWritable;
michael@0 2745
michael@0 2746 // If the read only attribute is not set, check to make sure
michael@0 2747 // we can open the file with write access.
michael@0 2748 if (*aIsWritable) {
michael@0 2749 PRFileDesc* file;
michael@0 2750 rv = OpenFile(mResolvedPath, PR_WRONLY, 0, &file);
michael@0 2751 if (NS_SUCCEEDED(rv)) {
michael@0 2752 PR_Close(file);
michael@0 2753 } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
michael@0 2754 *aIsWritable = false;
michael@0 2755 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
michael@0 2756 // If it is locked and read only we would have
michael@0 2757 // gotten access denied
michael@0 2758 *aIsWritable = true;
michael@0 2759 } else {
michael@0 2760 return rv;
michael@0 2761 }
michael@0 2762 }
michael@0 2763 return NS_OK;
michael@0 2764 }
michael@0 2765
michael@0 2766 NS_IMETHODIMP
michael@0 2767 nsLocalFile::IsReadable(bool *_retval)
michael@0 2768 {
michael@0 2769 // Check we are correctly initialized.
michael@0 2770 CHECK_mWorkingPath();
michael@0 2771
michael@0 2772 if (NS_WARN_IF(!_retval))
michael@0 2773 return NS_ERROR_INVALID_ARG;
michael@0 2774 *_retval = false;
michael@0 2775
michael@0 2776 nsresult rv = ResolveAndStat();
michael@0 2777 if (NS_FAILED(rv))
michael@0 2778 return rv;
michael@0 2779
michael@0 2780 *_retval = true;
michael@0 2781 return NS_OK;
michael@0 2782 }
michael@0 2783
michael@0 2784
michael@0 2785 NS_IMETHODIMP
michael@0 2786 nsLocalFile::IsExecutable(bool *_retval)
michael@0 2787 {
michael@0 2788 // Check we are correctly initialized.
michael@0 2789 CHECK_mWorkingPath();
michael@0 2790
michael@0 2791 if (NS_WARN_IF(!_retval))
michael@0 2792 return NS_ERROR_INVALID_ARG;
michael@0 2793 *_retval = false;
michael@0 2794
michael@0 2795 nsresult rv;
michael@0 2796
michael@0 2797 // only files can be executables
michael@0 2798 bool isFile;
michael@0 2799 rv = IsFile(&isFile);
michael@0 2800 if (NS_FAILED(rv))
michael@0 2801 return rv;
michael@0 2802 if (!isFile)
michael@0 2803 return NS_OK;
michael@0 2804
michael@0 2805 //TODO: shouldn't we be checking mFollowSymlinks here?
michael@0 2806 bool symLink;
michael@0 2807 rv = IsSymlink(&symLink);
michael@0 2808 if (NS_FAILED(rv))
michael@0 2809 return rv;
michael@0 2810
michael@0 2811 nsAutoString path;
michael@0 2812 if (symLink)
michael@0 2813 GetTarget(path);
michael@0 2814 else
michael@0 2815 GetPath(path);
michael@0 2816
michael@0 2817 // kill trailing dots and spaces.
michael@0 2818 int32_t filePathLen = path.Length() - 1;
michael@0 2819 while(filePathLen > 0 && (path[filePathLen] == L' ' || path[filePathLen] == L'.'))
michael@0 2820 {
michael@0 2821 path.Truncate(filePathLen--);
michael@0 2822 }
michael@0 2823
michael@0 2824 // Get extension.
michael@0 2825 int32_t dotIdx = path.RFindChar(char16_t('.'));
michael@0 2826 if ( dotIdx != kNotFound ) {
michael@0 2827 // Convert extension to lower case.
michael@0 2828 char16_t *p = path.BeginWriting();
michael@0 2829 for( p+= dotIdx + 1; *p; p++ )
michael@0 2830 *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
michael@0 2831
michael@0 2832 // Search for any of the set of executable extensions.
michael@0 2833 static const char * const executableExts[] = {
michael@0 2834 "ad",
michael@0 2835 "ade", // access project extension
michael@0 2836 "adp",
michael@0 2837 "air", // Adobe AIR installer
michael@0 2838 "app", // executable application
michael@0 2839 "application", // from bug 348763
michael@0 2840 "asp",
michael@0 2841 "bas",
michael@0 2842 "bat",
michael@0 2843 "chm",
michael@0 2844 "cmd",
michael@0 2845 "com",
michael@0 2846 "cpl",
michael@0 2847 "crt",
michael@0 2848 "exe",
michael@0 2849 "fxp", // FoxPro compiled app
michael@0 2850 "hlp",
michael@0 2851 "hta",
michael@0 2852 "inf",
michael@0 2853 "ins",
michael@0 2854 "isp",
michael@0 2855 "jar", // java application bundle
michael@0 2856 "js",
michael@0 2857 "jse",
michael@0 2858 "lnk",
michael@0 2859 "mad", // Access Module Shortcut
michael@0 2860 "maf", // Access
michael@0 2861 "mag", // Access Diagram Shortcut
michael@0 2862 "mam", // Access Macro Shortcut
michael@0 2863 "maq", // Access Query Shortcut
michael@0 2864 "mar", // Access Report Shortcut
michael@0 2865 "mas", // Access Stored Procedure
michael@0 2866 "mat", // Access Table Shortcut
michael@0 2867 "mau", // Media Attachment Unit
michael@0 2868 "mav", // Access View Shortcut
michael@0 2869 "maw", // Access Data Access Page
michael@0 2870 "mda", // Access Add-in, MDA Access 2 Workgroup
michael@0 2871 "mdb",
michael@0 2872 "mde",
michael@0 2873 "mdt", // Access Add-in Data
michael@0 2874 "mdw", // Access Workgroup Information
michael@0 2875 "mdz", // Access Wizard Template
michael@0 2876 "msc",
michael@0 2877 "msh", // Microsoft Shell
michael@0 2878 "mshxml", // Microsoft Shell
michael@0 2879 "msi",
michael@0 2880 "msp",
michael@0 2881 "mst",
michael@0 2882 "ops", // Office Profile Settings
michael@0 2883 "pcd",
michael@0 2884 "pif",
michael@0 2885 "plg", // Developer Studio Build Log
michael@0 2886 "prf", // windows system file
michael@0 2887 "prg",
michael@0 2888 "pst",
michael@0 2889 "reg",
michael@0 2890 "scf", // Windows explorer command
michael@0 2891 "scr",
michael@0 2892 "sct",
michael@0 2893 "shb",
michael@0 2894 "shs",
michael@0 2895 "url",
michael@0 2896 "vb",
michael@0 2897 "vbe",
michael@0 2898 "vbs",
michael@0 2899 "vsd",
michael@0 2900 "vsmacros", // Visual Studio .NET Binary-based Macro Project
michael@0 2901 "vss",
michael@0 2902 "vst",
michael@0 2903 "vsw",
michael@0 2904 "ws",
michael@0 2905 "wsc",
michael@0 2906 "wsf",
michael@0 2907 "wsh"};
michael@0 2908 nsDependentSubstring ext = Substring(path, dotIdx + 1);
michael@0 2909 for ( size_t i = 0; i < ArrayLength(executableExts); i++ ) {
michael@0 2910 if ( ext.EqualsASCII(executableExts[i])) {
michael@0 2911 // Found a match. Set result and quit.
michael@0 2912 *_retval = true;
michael@0 2913 break;
michael@0 2914 }
michael@0 2915 }
michael@0 2916 }
michael@0 2917
michael@0 2918 return NS_OK;
michael@0 2919 }
michael@0 2920
michael@0 2921
michael@0 2922 NS_IMETHODIMP
michael@0 2923 nsLocalFile::IsDirectory(bool *_retval)
michael@0 2924 {
michael@0 2925 return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval);
michael@0 2926 }
michael@0 2927
michael@0 2928 NS_IMETHODIMP
michael@0 2929 nsLocalFile::IsFile(bool *_retval)
michael@0 2930 {
michael@0 2931 nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval);
michael@0 2932 if (NS_SUCCEEDED(rv)) {
michael@0 2933 *_retval = !*_retval;
michael@0 2934 }
michael@0 2935 return rv;
michael@0 2936 }
michael@0 2937
michael@0 2938 NS_IMETHODIMP
michael@0 2939 nsLocalFile::IsHidden(bool *_retval)
michael@0 2940 {
michael@0 2941 return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, _retval);
michael@0 2942 }
michael@0 2943
michael@0 2944 nsresult
michael@0 2945 nsLocalFile::HasFileAttribute(DWORD fileAttrib, bool *_retval)
michael@0 2946 {
michael@0 2947 if (NS_WARN_IF(!_retval))
michael@0 2948 return NS_ERROR_INVALID_ARG;
michael@0 2949
michael@0 2950 nsresult rv = Resolve();
michael@0 2951 if (NS_FAILED(rv)) {
michael@0 2952 return rv;
michael@0 2953 }
michael@0 2954
michael@0 2955 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
michael@0 2956 if (INVALID_FILE_ATTRIBUTES == attributes) {
michael@0 2957 return ConvertWinError(GetLastError());
michael@0 2958 }
michael@0 2959
michael@0 2960 *_retval = ((attributes & fileAttrib) != 0);
michael@0 2961 return NS_OK;
michael@0 2962 }
michael@0 2963
michael@0 2964 NS_IMETHODIMP
michael@0 2965 nsLocalFile::IsSymlink(bool *_retval)
michael@0 2966 {
michael@0 2967 // Check we are correctly initialized.
michael@0 2968 CHECK_mWorkingPath();
michael@0 2969
michael@0 2970 if (NS_WARN_IF(!_retval))
michael@0 2971 return NS_ERROR_INVALID_ARG;
michael@0 2972
michael@0 2973 // unless it is a valid shortcut path it's not a symlink
michael@0 2974 if (!IsShortcutPath(mWorkingPath)) {
michael@0 2975 *_retval = false;
michael@0 2976 return NS_OK;
michael@0 2977 }
michael@0 2978
michael@0 2979 // we need to know if this is a file or directory
michael@0 2980 nsresult rv = ResolveAndStat();
michael@0 2981 if (NS_FAILED(rv)) {
michael@0 2982 return rv;
michael@0 2983 }
michael@0 2984
michael@0 2985 // We should not check mFileInfo64.type here for PR_FILE_FILE because lnk
michael@0 2986 // files can point to directories or files. Important security checks
michael@0 2987 // depend on correctly identifying lnk files. mFileInfo64 now holds info
michael@0 2988 // about the target of the lnk file, not the actual lnk file!
michael@0 2989 *_retval = true;
michael@0 2990 return NS_OK;
michael@0 2991 }
michael@0 2992
michael@0 2993 NS_IMETHODIMP
michael@0 2994 nsLocalFile::IsSpecial(bool *_retval)
michael@0 2995 {
michael@0 2996 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, _retval);
michael@0 2997 }
michael@0 2998
michael@0 2999 NS_IMETHODIMP
michael@0 3000 nsLocalFile::Equals(nsIFile *inFile, bool *_retval)
michael@0 3001 {
michael@0 3002 if (NS_WARN_IF(!inFile))
michael@0 3003 return NS_ERROR_INVALID_ARG;
michael@0 3004 if (NS_WARN_IF(!_retval))
michael@0 3005 return NS_ERROR_INVALID_ARG;
michael@0 3006
michael@0 3007 EnsureShortPath();
michael@0 3008
michael@0 3009 nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(inFile));
michael@0 3010 if (!lf) {
michael@0 3011 *_retval = false;
michael@0 3012 return NS_OK;
michael@0 3013 }
michael@0 3014
michael@0 3015 nsAutoString inFilePath;
michael@0 3016 lf->GetCanonicalPath(inFilePath);
michael@0 3017
michael@0 3018 // Ok : Win9x
michael@0 3019 *_retval = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0;
michael@0 3020
michael@0 3021 return NS_OK;
michael@0 3022 }
michael@0 3023
michael@0 3024
michael@0 3025 NS_IMETHODIMP
michael@0 3026 nsLocalFile::Contains(nsIFile *inFile, bool recur, bool *_retval)
michael@0 3027 {
michael@0 3028 // Check we are correctly initialized.
michael@0 3029 CHECK_mWorkingPath();
michael@0 3030
michael@0 3031 *_retval = false;
michael@0 3032
michael@0 3033 nsAutoString myFilePath;
michael@0 3034 if (NS_FAILED(GetTarget(myFilePath)))
michael@0 3035 GetPath(myFilePath);
michael@0 3036
michael@0 3037 uint32_t myFilePathLen = myFilePath.Length();
michael@0 3038
michael@0 3039 nsAutoString inFilePath;
michael@0 3040 if (NS_FAILED(inFile->GetTarget(inFilePath)))
michael@0 3041 inFile->GetPath(inFilePath);
michael@0 3042
michael@0 3043 // make sure that the |inFile|'s path has a trailing separator.
michael@0 3044 if (inFilePath.Length() >= myFilePathLen && inFilePath[myFilePathLen] == L'\\')
michael@0 3045 {
michael@0 3046 if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0)
michael@0 3047 {
michael@0 3048 *_retval = true;
michael@0 3049 }
michael@0 3050
michael@0 3051 }
michael@0 3052
michael@0 3053 return NS_OK;
michael@0 3054 }
michael@0 3055
michael@0 3056
michael@0 3057 NS_IMETHODIMP
michael@0 3058 nsLocalFile::GetTarget(nsAString &_retval)
michael@0 3059 {
michael@0 3060 _retval.Truncate();
michael@0 3061 #if STRICT_FAKE_SYMLINKS
michael@0 3062 bool symLink;
michael@0 3063
michael@0 3064 nsresult rv = IsSymlink(&symLink);
michael@0 3065 if (NS_FAILED(rv))
michael@0 3066 return rv;
michael@0 3067
michael@0 3068 if (!symLink)
michael@0 3069 {
michael@0 3070 return NS_ERROR_FILE_INVALID_PATH;
michael@0 3071 }
michael@0 3072 #endif
michael@0 3073 ResolveAndStat();
michael@0 3074
michael@0 3075 _retval = mResolvedPath;
michael@0 3076 return NS_OK;
michael@0 3077 }
michael@0 3078
michael@0 3079
michael@0 3080 /* attribute bool followLinks; */
michael@0 3081 NS_IMETHODIMP
michael@0 3082 nsLocalFile::GetFollowLinks(bool *aFollowLinks)
michael@0 3083 {
michael@0 3084 *aFollowLinks = mFollowSymlinks;
michael@0 3085 return NS_OK;
michael@0 3086 }
michael@0 3087 NS_IMETHODIMP
michael@0 3088 nsLocalFile::SetFollowLinks(bool aFollowLinks)
michael@0 3089 {
michael@0 3090 MakeDirty();
michael@0 3091 mFollowSymlinks = aFollowLinks;
michael@0 3092 return NS_OK;
michael@0 3093 }
michael@0 3094
michael@0 3095
michael@0 3096 NS_IMETHODIMP
michael@0 3097 nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries)
michael@0 3098 {
michael@0 3099 nsresult rv;
michael@0 3100
michael@0 3101 *entries = nullptr;
michael@0 3102 if (mWorkingPath.EqualsLiteral("\\\\.")) {
michael@0 3103 nsDriveEnumerator *drives = new nsDriveEnumerator;
michael@0 3104 if (!drives)
michael@0 3105 return NS_ERROR_OUT_OF_MEMORY;
michael@0 3106 NS_ADDREF(drives);
michael@0 3107 rv = drives->Init();
michael@0 3108 if (NS_FAILED(rv)) {
michael@0 3109 NS_RELEASE(drives);
michael@0 3110 return rv;
michael@0 3111 }
michael@0 3112 *entries = drives;
michael@0 3113 return NS_OK;
michael@0 3114 }
michael@0 3115
michael@0 3116 nsDirEnumerator* dirEnum = new nsDirEnumerator();
michael@0 3117 if (dirEnum == nullptr)
michael@0 3118 return NS_ERROR_OUT_OF_MEMORY;
michael@0 3119 NS_ADDREF(dirEnum);
michael@0 3120 rv = dirEnum->Init(this);
michael@0 3121 if (NS_FAILED(rv))
michael@0 3122 {
michael@0 3123 NS_RELEASE(dirEnum);
michael@0 3124 return rv;
michael@0 3125 }
michael@0 3126
michael@0 3127 *entries = dirEnum;
michael@0 3128
michael@0 3129 return NS_OK;
michael@0 3130 }
michael@0 3131
michael@0 3132 NS_IMETHODIMP
michael@0 3133 nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
michael@0 3134 {
michael@0 3135 CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor);
michael@0 3136 return NS_OK;
michael@0 3137 }
michael@0 3138
michael@0 3139 NS_IMETHODIMP
michael@0 3140 nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
michael@0 3141 {
michael@0 3142 if (IsUTF8(aPersistentDescriptor))
michael@0 3143 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor));
michael@0 3144 else
michael@0 3145 return InitWithNativePath(aPersistentDescriptor);
michael@0 3146 }
michael@0 3147
michael@0 3148 /* attrib unsigned long fileAttributesWin; */
michael@0 3149 NS_IMETHODIMP
michael@0 3150 nsLocalFile::GetFileAttributesWin(uint32_t *aAttribs)
michael@0 3151 {
michael@0 3152 *aAttribs = 0;
michael@0 3153 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
michael@0 3154 if (dwAttrs == INVALID_FILE_ATTRIBUTES)
michael@0 3155 return NS_ERROR_FILE_INVALID_PATH;
michael@0 3156
michael@0 3157 if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED))
michael@0 3158 *aAttribs |= WFA_SEARCH_INDEXED;
michael@0 3159
michael@0 3160 return NS_OK;
michael@0 3161 }
michael@0 3162
michael@0 3163 NS_IMETHODIMP
michael@0 3164 nsLocalFile::SetFileAttributesWin(uint32_t aAttribs)
michael@0 3165 {
michael@0 3166 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
michael@0 3167 if (dwAttrs == INVALID_FILE_ATTRIBUTES)
michael@0 3168 return NS_ERROR_FILE_INVALID_PATH;
michael@0 3169
michael@0 3170 if (aAttribs & WFA_SEARCH_INDEXED) {
michael@0 3171 dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
michael@0 3172 } else {
michael@0 3173 dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
michael@0 3174 }
michael@0 3175
michael@0 3176 if (aAttribs & WFA_READONLY) {
michael@0 3177 dwAttrs |= FILE_ATTRIBUTE_READONLY;
michael@0 3178 } else if ((aAttribs & WFA_READWRITE) &&
michael@0 3179 (dwAttrs & FILE_ATTRIBUTE_READONLY)) {
michael@0 3180 dwAttrs &= ~FILE_ATTRIBUTE_READONLY;
michael@0 3181 }
michael@0 3182
michael@0 3183 if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0)
michael@0 3184 return NS_ERROR_FAILURE;
michael@0 3185 return NS_OK;
michael@0 3186 }
michael@0 3187
michael@0 3188
michael@0 3189 NS_IMETHODIMP
michael@0 3190 nsLocalFile::Reveal()
michael@0 3191 {
michael@0 3192 // This API should be main thread only
michael@0 3193 MOZ_ASSERT(NS_IsMainThread());
michael@0 3194
michael@0 3195 // make sure mResolvedPath is set
michael@0 3196 nsresult rv = Resolve();
michael@0 3197 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
michael@0 3198 return rv;
michael@0 3199 }
michael@0 3200
michael@0 3201 // To create a new thread, get the thread manager
michael@0 3202 nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
michael@0 3203 nsCOMPtr<nsIThread> mythread;
michael@0 3204 rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
michael@0 3205 if (NS_FAILED(rv)) {
michael@0 3206 return rv;
michael@0 3207 }
michael@0 3208
michael@0 3209 nsCOMPtr<nsIRunnable> runnable =
michael@0 3210 new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp,
michael@0 3211 mResolvedPath);
michael@0 3212
michael@0 3213 // After the dispatch, the result runnable will shut down the worker
michael@0 3214 // thread, so we can let it go.
michael@0 3215 mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
michael@0 3216 return NS_OK;
michael@0 3217 }
michael@0 3218
michael@0 3219 NS_IMETHODIMP
michael@0 3220 nsLocalFile::Launch()
michael@0 3221 {
michael@0 3222 // This API should be main thread only
michael@0 3223 MOZ_ASSERT(NS_IsMainThread());
michael@0 3224
michael@0 3225 // make sure mResolvedPath is set
michael@0 3226 nsresult rv = Resolve();
michael@0 3227 if (NS_FAILED(rv))
michael@0 3228 return rv;
michael@0 3229
michael@0 3230 // To create a new thread, get the thread manager
michael@0 3231 nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
michael@0 3232 nsCOMPtr<nsIThread> mythread;
michael@0 3233 rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
michael@0 3234 if (NS_FAILED(rv)) {
michael@0 3235 return rv;
michael@0 3236 }
michael@0 3237
michael@0 3238 nsCOMPtr<nsIRunnable> runnable =
michael@0 3239 new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::LaunchOp,
michael@0 3240 mResolvedPath);
michael@0 3241
michael@0 3242 // After the dispatch, the result runnable will shut down the worker
michael@0 3243 // thread, so we can let it go.
michael@0 3244 mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
michael@0 3245 return NS_OK;
michael@0 3246 }
michael@0 3247
michael@0 3248 nsresult
michael@0 3249 NS_NewLocalFile(const nsAString &path, bool followLinks, nsIFile* *result)
michael@0 3250 {
michael@0 3251 nsLocalFile* file = new nsLocalFile();
michael@0 3252 if (file == nullptr)
michael@0 3253 return NS_ERROR_OUT_OF_MEMORY;
michael@0 3254 NS_ADDREF(file);
michael@0 3255
michael@0 3256 file->SetFollowLinks(followLinks);
michael@0 3257
michael@0 3258 if (!path.IsEmpty()) {
michael@0 3259 nsresult rv = file->InitWithPath(path);
michael@0 3260 if (NS_FAILED(rv)) {
michael@0 3261 NS_RELEASE(file);
michael@0 3262 return rv;
michael@0 3263 }
michael@0 3264 }
michael@0 3265
michael@0 3266 *result = file;
michael@0 3267 return NS_OK;
michael@0 3268 }
michael@0 3269
michael@0 3270 //-----------------------------------------------------------------------------
michael@0 3271 // Native (lossy) interface
michael@0 3272 //-----------------------------------------------------------------------------
michael@0 3273
michael@0 3274 NS_IMETHODIMP
michael@0 3275 nsLocalFile::InitWithNativePath(const nsACString &filePath)
michael@0 3276 {
michael@0 3277 nsAutoString tmp;
michael@0 3278 nsresult rv = NS_CopyNativeToUnicode(filePath, tmp);
michael@0 3279 if (NS_SUCCEEDED(rv))
michael@0 3280 return InitWithPath(tmp);
michael@0 3281
michael@0 3282 return rv;
michael@0 3283 }
michael@0 3284
michael@0 3285 NS_IMETHODIMP
michael@0 3286 nsLocalFile::AppendNative(const nsACString &node)
michael@0 3287 {
michael@0 3288 nsAutoString tmp;
michael@0 3289 nsresult rv = NS_CopyNativeToUnicode(node, tmp);
michael@0 3290 if (NS_SUCCEEDED(rv))
michael@0 3291 return Append(tmp);
michael@0 3292
michael@0 3293 return rv;
michael@0 3294 }
michael@0 3295
michael@0 3296 NS_IMETHODIMP
michael@0 3297 nsLocalFile::AppendRelativeNativePath(const nsACString &node)
michael@0 3298 {
michael@0 3299 nsAutoString tmp;
michael@0 3300 nsresult rv = NS_CopyNativeToUnicode(node, tmp);
michael@0 3301 if (NS_SUCCEEDED(rv))
michael@0 3302 return AppendRelativePath(tmp);
michael@0 3303 return rv;
michael@0 3304 }
michael@0 3305
michael@0 3306
michael@0 3307 NS_IMETHODIMP
michael@0 3308 nsLocalFile::GetNativeLeafName(nsACString &aLeafName)
michael@0 3309 {
michael@0 3310 //NS_WARNING("This API is lossy. Use GetLeafName !");
michael@0 3311 nsAutoString tmp;
michael@0 3312 nsresult rv = GetLeafName(tmp);
michael@0 3313 if (NS_SUCCEEDED(rv))
michael@0 3314 rv = NS_CopyUnicodeToNative(tmp, aLeafName);
michael@0 3315
michael@0 3316 return rv;
michael@0 3317 }
michael@0 3318
michael@0 3319 NS_IMETHODIMP
michael@0 3320 nsLocalFile::SetNativeLeafName(const nsACString &aLeafName)
michael@0 3321 {
michael@0 3322 nsAutoString tmp;
michael@0 3323 nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp);
michael@0 3324 if (NS_SUCCEEDED(rv))
michael@0 3325 return SetLeafName(tmp);
michael@0 3326
michael@0 3327 return rv;
michael@0 3328 }
michael@0 3329
michael@0 3330
michael@0 3331 NS_IMETHODIMP
michael@0 3332 nsLocalFile::GetNativePath(nsACString &_retval)
michael@0 3333 {
michael@0 3334 //NS_WARNING("This API is lossy. Use GetPath !");
michael@0 3335 nsAutoString tmp;
michael@0 3336 nsresult rv = GetPath(tmp);
michael@0 3337 if (NS_SUCCEEDED(rv))
michael@0 3338 rv = NS_CopyUnicodeToNative(tmp, _retval);
michael@0 3339
michael@0 3340 return rv;
michael@0 3341 }
michael@0 3342
michael@0 3343
michael@0 3344 NS_IMETHODIMP
michael@0 3345 nsLocalFile::GetNativeCanonicalPath(nsACString &aResult)
michael@0 3346 {
michael@0 3347 NS_WARNING("This method is lossy. Use GetCanonicalPath !");
michael@0 3348 EnsureShortPath();
michael@0 3349 NS_CopyUnicodeToNative(mShortWorkingPath, aResult);
michael@0 3350 return NS_OK;
michael@0 3351 }
michael@0 3352
michael@0 3353
michael@0 3354 NS_IMETHODIMP
michael@0 3355 nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName)
michael@0 3356 {
michael@0 3357 // Check we are correctly initialized.
michael@0 3358 CHECK_mWorkingPath();
michael@0 3359
michael@0 3360 if (newName.IsEmpty())
michael@0 3361 return CopyTo(newParentDir, EmptyString());
michael@0 3362
michael@0 3363 nsAutoString tmp;
michael@0 3364 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
michael@0 3365 if (NS_SUCCEEDED(rv))
michael@0 3366 return CopyTo(newParentDir, tmp);
michael@0 3367
michael@0 3368 return rv;
michael@0 3369 }
michael@0 3370
michael@0 3371 NS_IMETHODIMP
michael@0 3372 nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName)
michael@0 3373 {
michael@0 3374 if (newName.IsEmpty())
michael@0 3375 return CopyToFollowingLinks(newParentDir, EmptyString());
michael@0 3376
michael@0 3377 nsAutoString tmp;
michael@0 3378 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
michael@0 3379 if (NS_SUCCEEDED(rv))
michael@0 3380 return CopyToFollowingLinks(newParentDir, tmp);
michael@0 3381
michael@0 3382 return rv;
michael@0 3383 }
michael@0 3384
michael@0 3385 NS_IMETHODIMP
michael@0 3386 nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName)
michael@0 3387 {
michael@0 3388 // Check we are correctly initialized.
michael@0 3389 CHECK_mWorkingPath();
michael@0 3390
michael@0 3391 if (newName.IsEmpty())
michael@0 3392 return MoveTo(newParentDir, EmptyString());
michael@0 3393
michael@0 3394 nsAutoString tmp;
michael@0 3395 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
michael@0 3396 if (NS_SUCCEEDED(rv))
michael@0 3397 return MoveTo(newParentDir, tmp);
michael@0 3398
michael@0 3399 return rv;
michael@0 3400 }
michael@0 3401
michael@0 3402 NS_IMETHODIMP
michael@0 3403 nsLocalFile::GetNativeTarget(nsACString &_retval)
michael@0 3404 {
michael@0 3405 // Check we are correctly initialized.
michael@0 3406 CHECK_mWorkingPath();
michael@0 3407
michael@0 3408 NS_WARNING("This API is lossy. Use GetTarget !");
michael@0 3409 nsAutoString tmp;
michael@0 3410 nsresult rv = GetTarget(tmp);
michael@0 3411 if (NS_SUCCEEDED(rv))
michael@0 3412 rv = NS_CopyUnicodeToNative(tmp, _retval);
michael@0 3413
michael@0 3414 return rv;
michael@0 3415 }
michael@0 3416
michael@0 3417 nsresult
michael@0 3418 NS_NewNativeLocalFile(const nsACString &path, bool followLinks, nsIFile* *result)
michael@0 3419 {
michael@0 3420 nsAutoString buf;
michael@0 3421 nsresult rv = NS_CopyNativeToUnicode(path, buf);
michael@0 3422 if (NS_FAILED(rv)) {
michael@0 3423 *result = nullptr;
michael@0 3424 return rv;
michael@0 3425 }
michael@0 3426 return NS_NewLocalFile(buf, followLinks, result);
michael@0 3427 }
michael@0 3428
michael@0 3429 void
michael@0 3430 nsLocalFile::EnsureShortPath()
michael@0 3431 {
michael@0 3432 if (!mShortWorkingPath.IsEmpty())
michael@0 3433 return;
michael@0 3434
michael@0 3435 WCHAR shortPath[MAX_PATH + 1];
michael@0 3436 DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath,
michael@0 3437 ArrayLength(shortPath));
michael@0 3438 // If an error occurred then lengthNeeded is set to 0 or the length of the
michael@0 3439 // needed buffer including null termination. If it succeeds the number of
michael@0 3440 // wide characters not including null termination is returned.
michael@0 3441 if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath))
michael@0 3442 mShortWorkingPath.Assign(shortPath);
michael@0 3443 else
michael@0 3444 mShortWorkingPath.Assign(mWorkingPath);
michael@0 3445 }
michael@0 3446
michael@0 3447 // nsIHashable
michael@0 3448
michael@0 3449 NS_IMETHODIMP
michael@0 3450 nsLocalFile::Equals(nsIHashable* aOther, bool *aResult)
michael@0 3451 {
michael@0 3452 nsCOMPtr<nsIFile> otherfile(do_QueryInterface(aOther));
michael@0 3453 if (!otherfile) {
michael@0 3454 *aResult = false;
michael@0 3455 return NS_OK;
michael@0 3456 }
michael@0 3457
michael@0 3458 return Equals(otherfile, aResult);
michael@0 3459 }
michael@0 3460
michael@0 3461 NS_IMETHODIMP
michael@0 3462 nsLocalFile::GetHashCode(uint32_t *aResult)
michael@0 3463 {
michael@0 3464 // In order for short and long path names to hash to the same value we
michael@0 3465 // always hash on the short pathname.
michael@0 3466 EnsureShortPath();
michael@0 3467
michael@0 3468 *aResult = HashString(mShortWorkingPath);
michael@0 3469 return NS_OK;
michael@0 3470 }
michael@0 3471
michael@0 3472 //-----------------------------------------------------------------------------
michael@0 3473 // nsLocalFile <static members>
michael@0 3474 //-----------------------------------------------------------------------------
michael@0 3475
michael@0 3476 void
michael@0 3477 nsLocalFile::GlobalInit()
michael@0 3478 {
michael@0 3479 DebugOnly<nsresult> rv = NS_CreateShortcutResolver();
michael@0 3480 NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created");
michael@0 3481 }
michael@0 3482
michael@0 3483 void
michael@0 3484 nsLocalFile::GlobalShutdown()
michael@0 3485 {
michael@0 3486 NS_DestroyShortcutResolver();
michael@0 3487 }
michael@0 3488
michael@0 3489 NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsISimpleEnumerator)
michael@0 3490
michael@0 3491 nsDriveEnumerator::nsDriveEnumerator()
michael@0 3492 {
michael@0 3493 }
michael@0 3494
michael@0 3495 nsDriveEnumerator::~nsDriveEnumerator()
michael@0 3496 {
michael@0 3497 }
michael@0 3498
michael@0 3499 nsresult nsDriveEnumerator::Init()
michael@0 3500 {
michael@0 3501 /* If the length passed to GetLogicalDriveStrings is smaller
michael@0 3502 * than the length of the string it would return, it returns
michael@0 3503 * the length required for the string. */
michael@0 3504 DWORD length = GetLogicalDriveStringsW(0, 0);
michael@0 3505 /* The string is null terminated */
michael@0 3506 if (!mDrives.SetLength(length+1, fallible_t()))
michael@0 3507 return NS_ERROR_OUT_OF_MEMORY;
michael@0 3508 if (!GetLogicalDriveStringsW(length, wwc(mDrives.BeginWriting())))
michael@0 3509 return NS_ERROR_FAILURE;
michael@0 3510 mDrives.BeginReading(mStartOfCurrentDrive);
michael@0 3511 mDrives.EndReading(mEndOfDrivesString);
michael@0 3512 return NS_OK;
michael@0 3513 }
michael@0 3514
michael@0 3515 NS_IMETHODIMP nsDriveEnumerator::HasMoreElements(bool *aHasMore)
michael@0 3516 {
michael@0 3517 *aHasMore = *mStartOfCurrentDrive != L'\0';
michael@0 3518 return NS_OK;
michael@0 3519 }
michael@0 3520
michael@0 3521 NS_IMETHODIMP nsDriveEnumerator::GetNext(nsISupports **aNext)
michael@0 3522 {
michael@0 3523 /* GetLogicalDrives stored in mDrives is a concatenation
michael@0 3524 * of null terminated strings, followed by a null terminator.
michael@0 3525 * mStartOfCurrentDrive is an iterator pointing at the first
michael@0 3526 * character of the current drive. */
michael@0 3527 if (*mStartOfCurrentDrive == L'\0') {
michael@0 3528 *aNext = nullptr;
michael@0 3529 return NS_OK;
michael@0 3530 }
michael@0 3531
michael@0 3532 nsAString::const_iterator driveEnd = mStartOfCurrentDrive;
michael@0 3533 FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString);
michael@0 3534 nsString drive(Substring(mStartOfCurrentDrive, driveEnd));
michael@0 3535 mStartOfCurrentDrive = ++driveEnd;
michael@0 3536
michael@0 3537 nsIFile *file;
michael@0 3538 nsresult rv = NS_NewLocalFile(drive, false, &file);
michael@0 3539
michael@0 3540 *aNext = file;
michael@0 3541 return rv;
michael@0 3542 }

mercurial