/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
HRESULT hr; if (attributes & FILE_ATTRIBUTE_DIRECTORY) { // We have a directory so we should open the directory itself.
LPITEMIDLIST dir = ILCreateFromPathW(aResolvedPath.get()); if (!dir) { return NS_ERROR_FAILURE;
}
// Perform the open of the directory.
hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
CoTaskMemFree(dir);
} else {
int32_t len = aResolvedPath.Length(); // We don't currently handle UNC long paths of the form \\?\ anywhere so // this should be fine. if (len > MAX_PATH) { return NS_ERROR_FILE_INVALID_PATH;
}
WCHAR parentDirectoryPath[MAX_PATH + 1] = {0};
wcsncpy(parentDirectoryPath, aResolvedPath.get(), MAX_PATH);
PathRemoveFileSpecW(parentDirectoryPath);
// We have a file so we should open the parent directory.
LPITEMIDLIST dir = ILCreateFromPathW(parentDirectoryPath); if (!dir) { return NS_ERROR_FAILURE;
}
// Set the item in the directory to select to the file we want to reveal.
LPITEMIDLIST item = ILCreateFromPathW(aResolvedPath.get()); if (!item) {
CoTaskMemFree(dir); return NS_ERROR_FAILURE;
}
for (const nsLiteralString& forbiddenName : forbiddenNames) { if (StringBeginsWith(aFileName, forbiddenName,
nsASCIICaseInsensitiveStringComparator)) { // invalid name is either the entire string, or a prefix with a period if (aFileName.Length() == forbiddenName.Length() ||
aFileName.CharAt(forbiddenName.Length()) == char16_t('.')) { returntrue;
}
}
}
returnfalse;
}
class nsDriveEnumerator : public nsSimpleEnumerator, public nsIDirectoryEnumerator { public: explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSISIMPLEENUMERATOR
NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
nsresult Init();
/** * While not comprehensive, this will map many common Windows error codes to a * corresponding nsresult. If an unmapped error is encountered, the hex error * code will be logged to stderr. Error codes, names, and descriptions can be * found at the following MSDN page: * https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes * * \note When adding more mappings here, it must be checked if there's code that * depends on the current generic NS_ERROR_MODULE_WIN32 mapping for such error * codes.
*/ static nsresult ConvertWinError(DWORD aWinErr) {
nsresult rv;
switch (aWinErr) { case ERROR_FILE_NOT_FOUND:
[[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND case ERROR_PATH_NOT_FOUND:
[[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND case ERROR_INVALID_DRIVE:
rv = NS_ERROR_FILE_NOT_FOUND; break; case ERROR_ACCESS_DENIED:
[[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED case ERROR_NOT_SAME_DEVICE:
[[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED case ERROR_CANNOT_MAKE:
[[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED case ERROR_CONTENT_BLOCKED:
rv = NS_ERROR_FILE_ACCESS_DENIED; break; case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags
[[fallthrough]]; // to NS_ERROR_FILE_IS_LOCKED case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx
rv = NS_ERROR_FILE_IS_LOCKED; break; case ERROR_NOT_ENOUGH_MEMORY:
[[fallthrough]]; // to NS_ERROR_OUT_OF_MEMORY case ERROR_NO_SYSTEM_RESOURCES:
rv = NS_ERROR_OUT_OF_MEMORY; break; case ERROR_DIR_NOT_EMPTY:
[[fallthrough]]; // to NS_ERROR_FILE_DIR_NOT_EMPTY case ERROR_CURRENT_DIRECTORY:
rv = NS_ERROR_FILE_DIR_NOT_EMPTY; break; case ERROR_WRITE_PROTECT:
rv = NS_ERROR_FILE_READ_ONLY; break; case ERROR_HANDLE_DISK_FULL:
[[fallthrough]]; // to NS_ERROR_FILE_NO_DEVICE_SPACE case ERROR_DISK_FULL:
rv = NS_ERROR_FILE_NO_DEVICE_SPACE; break; case ERROR_FILE_EXISTS:
[[fallthrough]]; // to NS_ERROR_FILE_ALREADY_EXISTS case ERROR_ALREADY_EXISTS:
rv = NS_ERROR_FILE_ALREADY_EXISTS; break; case ERROR_FILENAME_EXCED_RANGE:
rv = NS_ERROR_FILE_NAME_TOO_LONG; break; case ERROR_DIRECTORY:
rv = NS_ERROR_FILE_NOT_DIRECTORY; break; case ERROR_FILE_CORRUPT:
[[fallthrough]]; // to NS_ERROR_FILE_FS_CORRUPTED case ERROR_DISK_CORRUPT:
rv = NS_ERROR_FILE_FS_CORRUPTED; break; case ERROR_DEVICE_HARDWARE_ERROR:
[[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE case ERROR_DEVICE_NOT_CONNECTED:
[[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE case ERROR_DEV_NOT_EXIST:
[[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE case ERROR_INVALID_FUNCTION:
[[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE case ERROR_IO_DEVICE:
rv = NS_ERROR_FILE_DEVICE_FAILURE; break; case ERROR_NOT_READY:
rv = NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE; break; case ERROR_INVALID_NAME:
rv = NS_ERROR_FILE_INVALID_PATH; break; case ERROR_INVALID_BLOCK:
[[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE case ERROR_INVALID_HANDLE:
[[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE case ERROR_ARENA_TRASHED:
rv = NS_ERROR_FILE_INVALID_HANDLE; break; case 0:
rv = NS_OK; break; default:
printf_stderr( "ConvertWinError received an unrecognized WinError: 0x%" PRIx32 "\n", static_cast<uint32_t>(aWinErr));
MOZ_ASSERT((aWinErr & 0xFFFF) == aWinErr);
rv = NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_WIN32, aWinErr & 0xFFFF); break;
} return rv;
}
// Check whether a path is a volume root. Expects paths to be \-terminated. staticbool IsRootPath(const nsAString& aPath) { // Easy cases first: if (aPath.Last() != L'\\') { returnfalse;
} if (StringEndsWith(aPath, u":\\"_ns)) { returntrue;
}
nsAString::const_iterator begin, end;
aPath.BeginReading(begin);
aPath.EndReading(end); // We know we've got a trailing slash, skip that:
end--; // Find the next last slash: if (RFindInReadable(u"\\"_ns, begin, end)) { // Reset iterator:
aPath.EndReading(end);
end--; auto lastSegment = Substring(++begin, end); if (lastSegment.IsEmpty()) { returnfalse;
}
// Check if we end with e.g. "c$", a drive letter in UNC or network shares if (lastSegment.Last() == L'$' && lastSegment.Length() == 2 &&
IsAsciiAlpha(lastSegment.First())) { returntrue;
} // Volume GUID paths: if (StringBeginsWith(lastSegment, u"Volume{"_ns) &&
lastSegment.Last() == L'}') { returntrue;
}
} returnfalse;
}
staticauto kSpecialNTFSFilesInRoot = {
u"$MFT"_ns, u"$MFTMirr"_ns, u"$LogFile"_ns, u"$Volume"_ns,
u"$AttrDef"_ns, u"$Bitmap"_ns, u"$Boot"_ns, u"$BadClus"_ns,
u"$Secure"_ns, u"$UpCase"_ns, u"$Extend"_ns}; staticbool IsSpecialNTFSPath(const nsAString& aFilePath) {
nsAString::const_iterator begin, end;
aFilePath.BeginReading(begin);
aFilePath.EndReading(end); auto iter = begin; // Early exit if there's no '$' (common case) if (!FindCharInReadable(L'$', iter, end)) { returnfalse;
}
iter = begin; // Any use of ':$' is illegal in filenames anyway; while we support some // ADS stuff (ie ":Zone.Identifier"), none of them use the ':$' syntax: if (FindInReadable(u":$"_ns, iter, end)) { returntrue;
}
auto normalized = mozilla::MakeUniqueFallible<wchar_t[]>(MAX_PATH); if (!normalized) { returntrue;
} auto flatPath = PromiseFlatString(aFilePath); auto fullPathRV =
GetFullPathNameW(flatPath.get(), MAX_PATH - 1, normalized.get(), nullptr); if (fullPathRV == 0 || fullPathRV > MAX_PATH - 1) { returnfalse;
}
nsString normalizedPath(normalized.get());
normalizedPath.BeginReading(begin);
normalizedPath.EndReading(end);
iter = begin; auto kDelimiters = u"\\:"_ns; while (iter != end && FindCharInReadable(L'$', iter, end)) { for (auto str : kSpecialNTFSFilesInRoot) { if (StringBeginsWith(Substring(iter, end), str,
nsCaseInsensitiveStringComparator)) { // If we're enclosed by separators or the beginning/end of the string, // this is one of the special files. Check if we're on a volume root. auto iterCopy = iter;
iterCopy.advance(str.Length()); // We check for both \ and : here because the filename could be // followd by a colon and a stream name/type, which shouldn't affect // our check: if (iterCopy == end || kDelimiters.Contains(*iterCopy)) {
iterCopy = iter; // At the start of this path component, we don't need to care about // colons: we would have caught those in the check for `:$` above. if (iterCopy == begin || *(--iterCopy) == L'\\') { return IsRootPath(Substring(begin, iter));
}
}
}
}
iter++;
}
returnfalse;
}
//----------------------------------------------------------------------------- // We need the following three definitions to make |OpenFile| convert a file // handle to an NSPR file descriptor correctly when |O_APPEND| flag is // specified. It is defined in a private header of NSPR (primpl.h) we can't // include. As a temporary workaround until we decide how to extend // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER| // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied. // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h. // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc| // need to be changed to match the definitions for WinNT. //----------------------------------------------------------------------------- typedefenum {
_PR_TRI_TRUE = 1,
_PR_TRI_FALSE = 0,
_PR_TRI_UNKNOWN = -1
} _PRTriStateBool;
struct _MDFileDesc {
PROsfd osfd;
};
struct PRFilePrivate {
int32_t state; bool nonblocking;
_PRTriStateBool inheritable;
PRFileDesc* next; int lockCount; /* 0: not locked * -1: a native lockfile call is in progress
* > 0: # times the file is locked */ bool appendMode;
_MDFileDesc md;
};
//----------------------------------------------------------------------------- // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo, // OpenDir, CloseDir, ReadDir) should go away once the corresponding // UTF-16 APIs are implemented on all the supported platforms (or at least // Windows 9x/ME) in NSPR. Currently, they're only implemented on // Windows NT4 or later. (bug 330665) //-----------------------------------------------------------------------------
// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : // PR_Open and _PR_MD_OPEN
nsresult OpenFile(const nsString& aName, int aOsflags, int aMode, bool aShareDelete, PRFileDesc** aFd) {
int32_t access = 0;
if (aOsflags & nsIFile::DELETE_ON_CLOSE) {
attributes |= FILE_FLAG_DELETE_ON_CLOSE;
}
if (aOsflags & nsIFile::OS_READAHEAD) {
attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
}
// If no write permissions are requested, and if we are possibly creating // the file, then set the new file as read only. // The flag has no effect if we happen to open the file. if (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) &&
disposition != OPEN_EXISTING) {
attributes |= FILE_ATTRIBUTE_READONLY;
}
*aFd = PR_ImportFile((PROsfd)file); if (*aFd) { // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
(*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? true : false; return NS_OK;
}
// 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,
nsLocalFile::FileInfo* aInfo) { 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());
}
nsDir* d = new nsDir();
nsAutoString filename(aName);
// If |aName| ends in a slash or backslash, do not append another backslash. if (filename.Last() == L'/' || filename.Last() == L'\\') {
filename.Append('*');
} else {
filename.AppendLiteral("\\*");
}
filename.ReplaceChar(L'/', L'\\');
// FindFirstFileExW Will have a last error of ERROR_DIRECTORY if // <file_path>\* is passed in. If <unknown_path>\* is passed in then // ERROR_PATH_NOT_FOUND will be the last error.
d->handle = ::FindFirstFileExW(filename.get(), FindExInfoBasic, &(d->data),
FindExSearchNameMatch, nullptr,
FIND_FIRST_EX_LARGE_FETCH);
if (filepath.IsEmpty()) {
aParent->GetPath(filepath);
}
if (filepath.IsEmpty()) { return NS_ERROR_UNEXPECTED;
}
// IsDirectory is not needed here because OpenDir will return // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
nsresult rv = OpenDir(filepath, &mDir); if (NS_FAILED(rv)) { return rv;
}
mParent = aParent; return NS_OK;
}
NS_IMETHOD HasMoreElements(bool* aResult) override {
nsresult rv; if (!mNext && mDir) {
nsString name;
rv = ReadDir(mDir, PR_SKIP_BOTH, name); if (NS_FAILED(rv)) { return rv;
} if (name.IsEmpty()) { // end of dir entries
rv = CloseDir(mDir); if (NS_FAILED(rv)) { return rv;
}
// Resolve any shortcuts and stat the resolved path. After a successful return // the path is guaranteed valid and the members of mFileInfo can be used.
nsresult nsLocalFile::ResolveAndStat() { // if we aren't dirty then we are already done if (!mDirty) { return NS_OK;
}
AUTO_PROFILER_LABEL("nsLocalFile::ResolveAndStat", OTHER); // we can't resolve/stat anything that isn't a valid NSPR addressable path if (mWorkingPath.IsEmpty()) { return NS_ERROR_FILE_INVALID_PATH;
}
// this is usually correct
mResolvedPath.Assign(mWorkingPath);
// Make sure root paths have a trailing slash.
nsAutoString nsprPath(mWorkingPath); if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == u':') {
nsprPath.Append('\\');
}
// first we will see if the working path exists. If it doesn't then // there is nothing more that can be done
nsresult rv = GetFileInfo(nsprPath, &mFileInfo); if (NS_FAILED(rv)) { return rv;
}
// OTHER from GetFileInfo currently means a symlink
rv = ResolveSymlink(); // Even if it fails we need to have the resolved path equal to working path // for those functions that always use the resolved path. if (NS_FAILED(rv)) {
mResolvedPath.Assign(mWorkingPath); return rv;
}
mResolveDirty = false; // get the details of the resolved path
rv = GetFileInfo(mResolvedPath, &mFileInfo); if (NS_FAILED(rv)) { return rv;
}
mDirty = false; return NS_OK;
}
/** * Fills the mResolvedPath member variable with the file or symlink target * if follow symlinks is on. This is a copy of the Resolve parts from * ResolveAndStat. ResolveAndStat is much slower though because of the stat. * * @return NS_OK on success.
*/
nsresult nsLocalFile::Resolve() { // if we aren't dirty then we are already done if (!mResolveDirty) { return NS_OK;
}
// we can't resolve/stat anything that isn't a valid NSPR addressable path if (mWorkingPath.IsEmpty()) { return NS_ERROR_FILE_INVALID_PATH;
}
// this is usually correct
mResolvedPath.Assign(mWorkingPath);
// just do a sanity check. if it has any forward slashes, it is not a Native // path on windows. Also, it must have a colon at after the first char. if (FindCharInReadable(L'/', begin, end)) { return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
if (FilePreferences::IsBlockedUNCPath(aFilePath)) { return NS_ERROR_FILE_ACCESS_DENIED;
}
if (secondChar == L':') { // Make sure we have a valid drive, later code assumes the drive letter // is a single char a-z or A-Z. if (MozPathGetDriveNumber<wchar_t>(aFilePath.Data()) == -1) { return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
}
if (IsSpecialNTFSPath(aFilePath)) { return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
mWorkingPath = aFilePath; // kill any trailing '\' if (mWorkingPath.Last() == L'\\') {
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 &&
FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) {
mWorkingPath = kDevicePathSpecifier + mWorkingPath;
}
return NS_OK;
}
// Strip a handler command string of its quotes and parameters. staticvoid CleanupHandlerPath(nsString& aPath) { // Example command strings passed into this routine:
int32_t lastCommaPos = aPath.RFindChar(','); if (lastCommaPos != kNotFound) aPath.Truncate(lastCommaPos);
aPath.Append(' ');
// case insensitive
int32_t index = aPath.LowerCaseFindASCII(".exe "); if (index == kNotFound) index = aPath.LowerCaseFindASCII(".dll "); if (index == kNotFound) index = aPath.LowerCaseFindASCII(".cpl ");
// Strip the windows host process bootstrap executable rundll32.exe // from a handler's command string if it exists. staticvoid StripRundll32(nsString& aCommandString) { // Example rundll formats: // C:\Windows\System32\rundll32.exe "path to dll" // rundll32.exe "path to dll" // C:\Windows\System32\rundll32.exe "path to dll", var var // rundll32.exe "path to dll", var var
constexpr auto rundllSegment = "rundll32.exe "_ns;
constexpr auto rundllSegmentShort = "rundll32 "_ns;
// case insensitive
int32_t strLen = rundllSegment.Length();
int32_t index = aCommandString.LowerCaseFindASCII(rundllSegment); if (index == kNotFound) {
strLen = rundllSegmentShort.Length();
index = aCommandString.LowerCaseFindASCII(rundllSegmentShort);
}
if (index != kNotFound) {
uint32_t rundllSegmentLength = index + strLen;
aCommandString.Cut(0, rundllSegmentLength);
}
}
// Returns the fully qualified path to an application handler based on // a parameterized command string. Note this routine should not be used // to launch the associated application as it strips parameters and // rundll.exe from the string. Designed for retrieving display information // on a particular handler. /* static */ bool nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler) {
nsAutoString handlerCommand(aCommandHandler);
// Straight command path: // // %SystemRoot%\system32\NOTEPAD.EXE var // "C:\Program Files\iTunes\iTunes.exe" var var // C:\Program Files\iTunes\iTunes.exe var var // // Example rundll handlers: // // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll" // C:\Windows\System32\rundll32.exe "path to dll", var var // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo // Viewer.dll", var var
// Expand environment variables so we have full path strings.
uint32_t bufLength =
::ExpandEnvironmentStringsW(handlerCommand.get(), nullptr, 0); if (bufLength == 0) // Error returnfalse;
auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength); if (!destination) returnfalse; if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(),
bufLength)) returnfalse;
handlerCommand.Assign(destination.get());
// Remove quotes around paths
handlerCommand.StripChars(u"\"");
// Strip windows host process bootstrap so we can get to the actual // handler.
StripRundll32(handlerCommand);
// Trim any command parameters so that we have a native path we can // initialize a local file with.
CleanupHandlerPath(handlerCommand);
// create directories to target // // A given local file can be either one of these forms: // // - normal: X:\some\path\on\this\drive // ^--- start here // // - UNC path: \\machine\volume\some\path\on\this\drive // ^--- start here // // Skip the first 'X:\' for the first form, and skip the first full // '\\machine\volume\' segment for the second form.
if (path[0] == L'\\' && path[1] == L'\\') { // dealing with a UNC path here; skip past '\\machine\'
path = wcschr(path + 2, L'\\'); if (!path) { return NS_ERROR_FILE_INVALID_PATH;
}
++path;
}
// search for first slash after the drive (or volume) name wchar_t* slash = wcschr(path, L'\\');
nsresult directoryCreateError = NS_OK; if (slash) { // skip the first '\\'
++slash;
slash = wcschr(slash, L'\\');
while (slash) {
*slash = L'\0';
if (!::CreateDirectoryW(mWorkingPath.get(), nullptr)) {
rv = ConvertWinError(GetLastError()); if (NS_ERROR_FILE_NOT_FOUND == rv &&
NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { // If a previous CreateDirectory failed due to access, return that. return NS_ERROR_FILE_ACCESS_DENIED;
} // perhaps the base path already exists, or perhaps we don't have // permissions to create the directory. NOTE: access denied could // occur on a parent directory even though it exists. elseif (rv != NS_ERROR_FILE_ALREADY_EXISTS &&
rv != NS_ERROR_FILE_ACCESS_DENIED) { return rv;
}
// If our last CreateDirectory failed due to access, return that. if (NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { return directoryCreateError;
}
NS_IMETHODIMP
nsLocalFile::Normalize() { // XXX See bug 187957 comment 18 for possible problems with this // implementation.
if (mWorkingPath.IsEmpty()) { return NS_OK;
}
nsAutoString path(mWorkingPath);
// find the index of the root backslash for the path. Everything before // this is considered fully normalized and cannot be ascended beyond // using ".." For a local drive this is the first slash (e.g. "c:\"). // For a UNC path it is the slash following the share name // (e.g. "\\server\share\").
int32_t rootIdx = 2; // default to local drive if (path.First() == L'\\') { // if a share then calculate the rootIdx
rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server if (rootIdx == kNotFound) { return NS_OK; // already normalized
}
rootIdx = path.FindChar(L'\\', rootIdx + 1); if (rootIdx == kNotFound) { return NS_OK; // already normalized
}
} elseif (path.CharAt(rootIdx) != L'\\') { // The path has been specified relative to the current working directory // for that drive. To normalize it, the current working directory for // that drive needs to be inserted before the supplied relative path // which will provide an absolute path (and the rootIdx will still be 2).
WCHAR cwd[MAX_PATH];
WCHAR* pcwd = cwd; int drive = TOUPPER(path.First()) - 'A' + 1; /* We need to worry about IPH, for details read bug 419326. * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx * uses a bitmask, bit 0 is 'a:' * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx * take an int, 1 is 'a:'. * * Because of this, we need to do some math. Subtract 1 to convert from * _chdrive/_getdcwd format to _getdrives drive numbering. * Shift left x bits to convert from integer indexing to bitfield indexing. * And of course, we need to find out if the drive is in the bitmask. * * If we're really unlucky, we can still lose, but only if the user * manages to eject the drive between our call to _getdrives() and * our *calls* to _wgetdcwd.
*/ if (!((1 << (drive - 1)) & _getdrives())) { return NS_ERROR_FILE_INVALID_PATH;
} if (!_wgetdcwd(drive, pcwd, MAX_PATH)) {
pcwd = _wgetdcwd(drive, 0, 0);
} if (!pcwd) { return NS_ERROR_OUT_OF_MEMORY;
}
nsAutoString currentDir(pcwd); if (pcwd != cwd) {
free(pcwd);
}
MOZ_ASSERT(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid");
MOZ_ASSERT(path.CharAt(rootIdx) == '\\', "rootIdx is invalid");
// if there is nothing following the root path then it is already normalized if (rootIdx + 1 == (int32_t)path.Length()) { return NS_OK;
}
// assign the root const char16_t* pathBuffer = path.get(); // simplify access to the buffer
mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer
mWorkingPath.Assign(pathBuffer, rootIdx);
// Normalize the path components. The actions taken are: // // "\\" condense to single backslash // "." remove from path // ".." up a directory // "..." remove from path (any number of dots > 2) // // The last form is something that Windows 95 and 98 supported and // is a shortcut for changing up multiple directories. Windows XP // and ilk ignore it in a path, as is done here.
int32_t len, begin, end = rootIdx; while (end < (int32_t)path.Length()) { // find the current segment (text between the backslashes) to // be examined, this will set the following variables: // begin == index of first char in segment // end == index 1 char after last char in segment // len == length of segment
begin = end + 1;
end = path.FindChar('\\', begin); if (end == kNotFound) {
end = path.Length();
}
len = end - begin;
// len != 0, and interesting paths always begin with a dot if (pathBuffer[begin] == '.') { // ignore single dots if (len == 1) { continue;
}
// handle multiple dots if (len >= 2 && pathBuffer[begin + 1] == L'.') { // back up a path component on double dot if (len == 2) {
int32_t prev = mWorkingPath.RFindChar('\\'); if (prev >= rootIdx) {
mWorkingPath.Truncate(prev);
} continue;
}
// length is > 2 and the first two characters are dots. // if the rest of the string is dots, then ignore it. int idx = len - 1; for (; idx >= 2; --idx) { if (pathBuffer[begin + idx] != L'.') { break;
}
}
// this is true if the loop above didn't break // and all characters in this segment are dots. if (idx < 2) { continue;
}
}
}
// add the current component to the path, including the preceding backslash
mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
}
// kill trailing dots and spaces.
int32_t filePathLen = mWorkingPath.Length() - 1; while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
mWorkingPath[filePathLen] == L'.')) {
mWorkingPath.Truncate(filePathLen--);
}
if (mWorkingPath.IsEmpty()) { return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
int32_t offset = mWorkingPath.RFindChar(L'\\');
// if the working path is just a node without any lashes. if (offset == kNotFound) {
aLeafName = mWorkingPath;
} else {
aLeafName = Substring(mWorkingPath, offset + 1);
}
if (mWorkingPath.IsEmpty()) { return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
SHFILEINFOW sfi = {};
DWORD_PTR result = ::SHGetFileInfoW(mWorkingPath.get(), 0, &sfi, sizeof(sfi),
SHGFI_DISPLAYNAME); // If we found a display name, return that: if (result) {
aLeafName.Assign(sfi.szDisplayName); return NS_OK;
} // Nope - fall back to the regular leaf name. return GetLeafName(aLeafName);
}
/** * Determines if the drive type for the specified file is rmeote or local. * * @param path The path of the file to check * @param remote Out parameter, on function success holds true if the specified * file path is remote, or false if the file path is local. * @return true on success. The return value implies absolutely nothing about * wether the file is local or remote.
*/ staticbool IsRemoteFilePath(LPCWSTR aPath, bool& aRemote) { // Obtain the parent directory path and make sure it ends with // a trailing backslash.
WCHAR dirPath[MAX_PATH + 1] = {0};
wcsncpy(dirPath, aPath, MAX_PATH); if (!PathRemoveFileSpecW(dirPath)) { returnfalse;
}
size_t len = wcslen(dirPath); // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we // recheck the required length here since we need to terminate it with // a backslash. if (len >= MAX_PATH) { returnfalse;
}
// get the path that we are going to copy to. // Since windows does not know how to auto // resolve shortcuts, we must work with the // target.
nsAutoString destPath;
rv = aDestParent->GetTarget(destPath); if (NS_FAILED(rv)) { return rv;
}
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;
}
int copyOK = 0; if (move) {
copyOK = ::MoveFileExW(filePath.get(), destPath.get(),
MOVEFILE_REPLACE_EXISTING);
}
// If we either failed to move the file, or this is a copy, try copying: if (!copyOK && (!move || GetLastError() == ERROR_NOT_SAME_DEVICE)) { // Failed renames here should just return access denied. if (move && (aOptions & Rename)) { return NS_ERROR_FILE_ACCESS_DENIED;
}
// Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying // to a SMBV2 remote drive. Without this parameter subsequent append mode // file writes can cause the resultant file to become corrupt. We only need // to do this if the major version of Windows is > 5(Only Windows Vista and // above can support SMBV2). With a 7200RPM hard drive: Copying a 1KB file // with COPY_FILE_NO_BUFFERING takes about 30-60ms. Copying a 1KB file // without COPY_FILE_NO_BUFFERING takes < 1ms. So we only use // COPY_FILE_NO_BUFFERING when we have a remote drive.
DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; bool path1Remote, path2Remote; if (!IsRemoteFilePath(filePath.get(), path1Remote) ||
!IsRemoteFilePath(destPath.get(), path2Remote) || path1Remote ||
path2Remote) {
dwCopyFlags |= COPY_FILE_NO_BUFFERING;
}
copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr,
nullptr, dwCopyFlags); // On Windows 10, copying without buffering has started failing, so try // with buffering... if (!copyOK && (dwCopyFlags & COPY_FILE_NO_BUFFERING) &&
GetLastError() == ERROR_INVALID_PARAMETER) {
dwCopyFlags &= ~COPY_FILE_NO_BUFFERING;
copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr,
nullptr, dwCopyFlags);
}
if (move && copyOK) {
DeleteFileW(filePath.get());
}
}
if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure.
rv = ConvertWinError(GetLastError());
} elseif (move && !(aOptions & SkipNtfsAclReset)) { // Set security permissions to inherit from parent. // Note: propagates to all children: slow for big file trees
PACL pOldDACL = nullptr;
PSECURITY_DESCRIPTOR pSD = nullptr;
::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION, nullptr, nullptr,
&pOldDACL, nullptr, &pSD);
UniquePtr<VOID, LocalFreeDeleter> autoFreeSecDesc(pSD); if (pOldDACL) { // Test the current DACL, if we find one that is inherited then we can // skip the reset. This avoids a request for SeTcbPrivilege, which can // cause a lot of audit events if enabled (Bug 1816694). bool inherited = false; for (DWORD i = 0; i < pOldDACL->AceCount; ++i) { VOID* pAce = nullptr; if (::GetAce(pOldDACL, i, &pAce) && static_cast<PACE_HEADER>(pAce)->AceFlags & INHERITED_ACE) {
inherited = true; break;
}
}
nsresult nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName,
uint32_t aOptions) { bool move = aOptions & (Move | Rename); bool followSymlinks = aOptions & FollowSymlinks; // If we're not provided with a new parent, we're copying or moving to // another file in the same directory and can safely skip checking if the // destination directory exists: bool targetInSameDirectory = !aParentDir;
nsCOMPtr<nsIFile> newParentDir = aParentDir; // check to see if this exists, otherwise return an error. // we will check this by resolving. If the user wants us // to follow links, then we are talking about the target, // hence we can use the |FollowSymlinks| option.
nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) { return rv;
}
if (!newParentDir) { // no parent was specified. We must rename. if (aNewName.IsEmpty()) { return NS_ERROR_INVALID_ARG;
}
rv = GetParent(getter_AddRefs(newParentDir)); if (NS_FAILED(rv)) { return rv;
}
}
if (!newParentDir) { return NS_ERROR_FILE_DESTINATION_NOT_DIR;
}
if (!targetInSameDirectory) { // make sure it exists and is a directory. Create it if not there. bool exists = false;
rv = newParentDir->Exists(&exists); if (NS_FAILED(rv)) { return rv;
}
if (!exists) {
rv = newParentDir->Create(DIRECTORY_TYPE,
0644); // TODO, what permissions should we use if (NS_FAILED(rv)) { return rv;
}
} else { bool isDir = false;
rv = newParentDir->IsDirectory(&isDir); if (NS_FAILED(rv)) { return rv;
}
if (!isDir) { if (followSymlinks) { bool isLink = false;
rv = newParentDir->IsSymlink(&isLink); if (NS_FAILED(rv)) { return rv;
}
if (isLink) {
nsAutoString target;
rv = newParentDir->GetTarget(target); if (NS_FAILED(rv)) { return rv;
}
nsCOMPtr<nsIFile> realDest = new nsLocalFile();
rv = realDest->InitWithPath(target); if (NS_FAILED(rv)) { return rv;
}
// Try to move the file or directory, or try to copy a single file (or // non-followed symlink) if (move || !isDir || (isSymlink && !followSymlinks)) { // Copy/Move single file, or move a directory if (!aParentDir) {
aOptions |= SkipNtfsAclReset;
}
rv = CopySingleFile(this, newParentDir, aNewName, aOptions);
done = NS_SUCCEEDED(rv); // If we are moving a directory and that fails, fallback on directory // enumeration. See bug 231300 for details. if (!done && !(move && isDir)) { return rv;
}
}
// Not able to copy or move directly, so enumerate it if (!done) { // create a new target destination in the new parentDir;
nsCOMPtr<nsIFile> target;
rv = newParentDir->Clone(getter_AddRefs(target)); if (NS_FAILED(rv)) { return rv;
}
nsAutoString allocatedNewName; if (aNewName.IsEmpty()) { bool isLink = false;
rv = IsSymlink(&isLink); if (NS_FAILED(rv)) { return rv;
}
if (isLink) {
nsAutoString temp;
rv = GetTarget(temp); if (NS_FAILED(rv)) { return rv;
}
int32_t offset = temp.RFindChar(L'\\'); if (offset == kNotFound) {
allocatedNewName = temp;
} else {
allocatedNewName = Substring(temp, offset + 1);
}
} else {
GetLeafName(allocatedNewName); // this should be the leaf name of the
}
} else {
allocatedNewName = aNewName;
}
rv = target->Append(allocatedNewName); if (NS_FAILED(rv)) { return rv;
}
allocatedNewName.Truncate();
bool exists = false; // check if the destination directory already exists
rv = target->Exists(&exists); if (NS_FAILED(rv)) { return rv;
}
if (!exists) { // if the destination directory cannot be created, return an error
rv = target->Create(DIRECTORY_TYPE,
0644); // TODO, what permissions should we use if (NS_FAILED(rv)) { return rv;
}
} else { // check if the destination directory is writable and empty bool isWritable = false;
rv = target->IsWritable(&isWritable); if (NS_FAILED(rv)) { return rv;
}
if (!isWritable) { return NS_ERROR_FILE_ACCESS_DENIED;
}
bool more;
targetIterator->HasMoreElements(&more); // return error if target directory is not empty if (more) { return NS_ERROR_FILE_DIR_NOT_EMPTY;
}
}
RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
if (move) { if (followSymlinks) { return NS_ERROR_FAILURE;
}
rv = file->MoveTo(target, u""_ns); if (NS_FAILED(rv)) { return rv;
}
} else { if (followSymlinks) {
rv = file->CopyToFollowingLinks(target, u""_ns);
} else {
rv = file->CopyTo(target, u""_ns);
} if (NS_FAILED(rv)) { return rv;
}
}
} // we've finished moving all the children of this directory // in the new directory. so now delete the directory // note, we don't need to do a recursive delete. // MoveTo() is recursive. At this point, // we've already moved the children of the current folder // to the new location. nothing should be left in the folder. if (move) {
rv = Remove(false/* recursive */); if (NS_FAILED(rv)) { return rv;
}
}
}
// If we moved, we want to adjust this. if (move) {
MakeDirty();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.