diff --git a/xpcom/io/FilePreferences.h b/xpcom/io/FilePreferences.h index 4f6597df91bf..a0d2f3ff97b4 100644 --- a/xpcom/io/FilePreferences.h +++ b/xpcom/io/FilePreferences.h @@ -19,6 +19,8 @@ bool IsAllowedPath(const nsAString& aFilePath); bool IsAllowedPath(const nsACString& aFilePath); #endif +extern const char kPathSeparator; + namespace testing { void SetBlockUNCPaths(bool aBlock); diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp index ef9d18dae33b..8c257bbf4e8a 100644 --- a/xpcom/io/nsLocalFileWin.cpp +++ b/xpcom/io/nsLocalFileWin.cpp @@ -59,6 +59,7 @@ #include "mozilla/WidgetUtils.h" using namespace mozilla; +using mozilla::FilePreferences::kPathSeparator; #define CHECK_mWorkingPath() \ do { \ @@ -78,6 +79,39 @@ using namespace mozilla; # define DRIVE_REMOTE 4 #endif +constexpr auto kDevicePathSpecifier = NS_LITERAL_STRING("\\\\?\\"); + +namespace { + +bool StartsWithDiskDesignatorAndBackslash(const nsAString& aAbsolutePath) { + // aAbsolutePath can only be (in regular expression): + // UNC path: ^\\\\.* + // A single backslash: ^\\.* + // A disk designator with a backslash: ^[A-Za-z]:\\.* + return aAbsolutePath.Length() >= 3 && aAbsolutePath.CharAt(1) == L':' && + aAbsolutePath.CharAt(2) == kPathSeparator; +} + +nsresult NewLocalFile(const nsAString& aPath, bool aFollowLinks, + bool aUseDOSDevicePathSyntax, nsIFile** aResult) { + RefPtr file = new nsLocalFile(); + + file->SetFollowLinks(aFollowLinks); + file->SetUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithPath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + + file.forget(aResult); + return NS_OK; +} + +} // anonymous namespace + static HWND GetMostRecentNavigatorHWND() { nsresult rv; nsCOMPtr winMediator( @@ -164,7 +198,7 @@ nsresult nsLocalFile::RevealFile(const nsString& aResolvedPath) { class nsDriveEnumerator : public nsSimpleEnumerator, public nsIDirectoryEnumerator { public: - nsDriveEnumerator(); + explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax); NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSISIMPLEENUMERATOR NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) @@ -200,6 +234,7 @@ class nsDriveEnumerator : public nsSimpleEnumerator, nsString mDrives; nsAString::const_iterator mStartOfCurrentDrive; nsAString::const_iterator mEndOfDrivesString; + const bool mUseDOSDevicePathSyntax; }; //----------------------------------------------------------------------------- @@ -445,12 +480,21 @@ static void FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm) { // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64 static nsresult GetFileInfo(const nsString& aName, PRFileInfo64* aInfo) { - WIN32_FILE_ATTRIBUTE_DATA fileData; - - if (aName.IsEmpty() || aName.FindCharInSet(u"?*") != kNotFound) { + if (aName.IsEmpty()) { return NS_ERROR_INVALID_ARG; } + // Checking u"?*" for the file path excluding the kDevicePathSpecifier. + // ToDo: Check if checking "?" for the file path is still needed. + const int32_t offset = StringBeginsWith(aName, kDevicePathSpecifier) + ? kDevicePathSpecifier.Length() + : 0; + + if (aName.FindCharInSet(u"?*", offset) != kNotFound) { + return NS_ERROR_INVALID_ARG; + } + + WIN32_FILE_ATTRIBUTE_DATA fileData; if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) { return ConvertWinError(GetLastError()); } @@ -488,9 +532,6 @@ static nsresult OpenDir(const nsString& aName, nsDir** aDir) { } *aDir = nullptr; - if (aName.Length() + 3 >= MAX_PATH) { - return NS_ERROR_FILE_NAME_TOO_LONG; - } nsDir* d = new nsDir(); nsAutoString filename(aName); @@ -704,9 +745,13 @@ NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumerator, nsSimpleEnumerator, //----------------------------------------------------------------------------- nsLocalFile::nsLocalFile() - : mDirty(true), mResolveDirty(true), mFollowSymlinks(false) {} + : mDirty(true), + mResolveDirty(true), + mFollowSymlinks(false), + mUseDOSDevicePathSyntax(false) {} -nsLocalFile::nsLocalFile(const nsAString& aFilePath) : mFollowSymlinks(false) { +nsLocalFile::nsLocalFile(const nsAString& aFilePath) + : mFollowSymlinks(false), mUseDOSDevicePathSyntax(false) { InitWithPath(aFilePath); } @@ -743,6 +788,7 @@ nsLocalFile::nsLocalFile(const nsLocalFile& aOther) : mDirty(true), mResolveDirty(true), mFollowSymlinks(aOther.mFollowSymlinks), + mUseDOSDevicePathSyntax(aOther.mUseDOSDevicePathSyntax), mWorkingPath(aOther.mWorkingPath) {} // Resolve the shortcut file from mWorkingPath and write the path @@ -937,6 +983,15 @@ nsLocalFile::InitWithPath(const nsAString& aFilePath) { mWorkingPath.Truncate(mWorkingPath.Length() - 1); } + // Bug 1626514: make sure that we don't end up with multiple prefixes. + + // Prepend the "\\?\" prefix if the useDOSDevicePathSyntax is set and the path + // starts with a disk designator and backslash. + if (mUseDOSDevicePathSyntax && + StartsWithDiskDesignatorAndBackslash(mWorkingPath)) { + mWorkingPath = kDevicePathSpecifier + mWorkingPath; + } + return NS_OK; } @@ -1468,6 +1523,8 @@ nsLocalFile::SetLeafName(const nsAString& aLeafName) { NS_IMETHODIMP nsLocalFile::GetPath(nsAString& aResult) { + MOZ_ASSERT_IF(mUseDOSDevicePathSyntax, + !StartsWithDiskDesignatorAndBackslash(mWorkingPath)); aResult = mWorkingPath; return NS_OK; } @@ -1618,6 +1675,24 @@ nsresult nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent, return rv; } +#ifdef DEBUG + nsCOMPtr srcWinFile = do_QueryInterface(aSourceFile); + MOZ_ASSERT(srcWinFile); + + bool srcUseDOSDevicePathSyntax; + srcWinFile->GetUseDOSDevicePathSyntax(&srcUseDOSDevicePathSyntax); + + nsCOMPtr destWinFile = do_QueryInterface(aDestParent); + MOZ_ASSERT(destWinFile); + + bool destUseDOSDevicePathSyntax; + destWinFile->GetUseDOSDevicePathSyntax(&destUseDOSDevicePathSyntax); + + MOZ_ASSERT(srcUseDOSDevicePathSyntax == destUseDOSDevicePathSyntax, + "Copy or Move files with different values for " + "useDOSDevicePathSyntax would fail"); +#endif + if (FilePreferences::IsBlockedUNCPath(destPath)) { return NS_ERROR_FILE_ACCESS_DENIED; } @@ -2547,8 +2622,8 @@ nsLocalFile::GetParent(nsIFile** aParent) { nsCOMPtr localFile; nsresult rv = - NS_NewLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile)); - + NewLocalFile(parentPath, mFollowSymlinks, mUseDOSDevicePathSyntax, + getter_AddRefs(localFile)); if (NS_FAILED(rv)) { return rv; } @@ -2810,8 +2885,32 @@ nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) { nsAutoString inFilePath; lf->GetCanonicalPath(inFilePath); + bool inUseDOSDevicePathSyntax; + lf->GetUseDOSDevicePathSyntax(&inUseDOSDevicePathSyntax); + + // Remove the prefix for both inFilePath and mShortWorkingPath if the + // useDOSDevicePathSyntax from them are not the same. + // This is added because of Omnijar. It compare files from different moduals + // with itself + nsAutoString shortWorkingPath; + if (inUseDOSDevicePathSyntax == mUseDOSDevicePathSyntax) { + shortWorkingPath = mShortWorkingPath; + } else if (inUseDOSDevicePathSyntax && + StringBeginsWith(inFilePath, kDevicePathSpecifier)) { + MOZ_ASSERT(!StringBeginsWith(mShortWorkingPath, kDevicePathSpecifier)); + + shortWorkingPath = mShortWorkingPath; + inFilePath = Substring(inFilePath, kDevicePathSpecifier.Length()); + } else if (mUseDOSDevicePathSyntax && + StringBeginsWith(mShortWorkingPath, kDevicePathSpecifier)) { + MOZ_ASSERT(!StringBeginsWith(inFilePath, kDevicePathSpecifier)); + + shortWorkingPath = + Substring(mShortWorkingPath, kDevicePathSpecifier.Length()); + } + // Ok : Win9x - *aResult = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0; + *aResult = _wcsicmp(shortWorkingPath.get(), inFilePath.get()) == 0; return NS_OK; } @@ -2851,6 +2950,9 @@ nsLocalFile::GetTarget(nsAString& aResult) { aResult.Truncate(); Resolve(); + MOZ_ASSERT_IF(mUseDOSDevicePathSyntax, + !StartsWithDiskDesignatorAndBackslash(mResolvedPath)); + aResult = mResolvedPath; return NS_OK; } @@ -2873,7 +2975,8 @@ nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { *aEntries = nullptr; if (mWorkingPath.EqualsLiteral("\\\\.")) { - RefPtr drives = new nsDriveEnumerator; + RefPtr drives = + new nsDriveEnumerator(mUseDOSDevicePathSyntax); rv = drives->Init(); if (NS_FAILED(rv)) { return rv; @@ -2959,6 +3062,24 @@ nsLocalFile::GetUseDOSDevicePathSyntax(bool* aUseDOSDevicePathSyntax) { NS_IMETHODIMP nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax) { + if (mUseDOSDevicePathSyntax == aUseDOSDevicePathSyntax) { + return NS_OK; + } + + if (mUseDOSDevicePathSyntax) { + if (StringBeginsWith(mWorkingPath, kDevicePathSpecifier)) { + MakeDirty(); + // Remove the prefix + mWorkingPath = Substring(mWorkingPath, kDevicePathSpecifier.Length()); + } + } else { + if (StartsWithDiskDesignatorAndBackslash(mWorkingPath)) { + MakeDirty(); + // Prepend the prefix + mWorkingPath = kDevicePathSpecifier + mWorkingPath; + } + } + mUseDOSDevicePathSyntax = aUseDOSDevicePathSyntax; return NS_OK; } @@ -3282,7 +3403,8 @@ void nsLocalFile::EnsureShortPath() { NS_IMPL_ISUPPORTS_INHERITED(nsDriveEnumerator, nsSimpleEnumerator, nsIDirectoryEnumerator) -nsDriveEnumerator::nsDriveEnumerator() {} +nsDriveEnumerator::nsDriveEnumerator(bool aUseDOSDevicePathSyntax) + : mUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax) {} nsDriveEnumerator::~nsDriveEnumerator() {} @@ -3326,7 +3448,7 @@ nsDriveEnumerator::GetNext(nsISupports** aNext) { mStartOfCurrentDrive = ++driveEnd; nsIFile* file; - nsresult rv = NS_NewLocalFile(drive, false, &file); + nsresult rv = NewLocalFile(drive, false, mUseDOSDevicePathSyntax, &file); *aNext = file; return rv; diff --git a/xpcom/io/nsLocalFileWin.h b/xpcom/io/nsLocalFileWin.h index ea4dd84a9047..872f38e2f17f 100644 --- a/xpcom/io/nsLocalFileWin.h +++ b/xpcom/io/nsLocalFileWin.h @@ -65,7 +65,7 @@ class nsLocalFile final : public nsILocalFileWin { bool mResolveDirty; bool mFollowSymlinks; // should we follow symlinks when working on this file - bool mUseDOSDevicePathSyntax = false; + bool mUseDOSDevicePathSyntax; // this string will always be in native format! nsString mWorkingPath;