Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
#include <windows.h>
#include <winnetwk.h>
#include "mozilla/FileUtilsWin.h"
#include "mozilla/DebugOnly.h"
#include "nsCRTGlue.h"
#include "gtest/gtest.h"
class DriveMapping {
public:
explicit DriveMapping(const nsAString& aRemoteUNCPath);
~DriveMapping();
bool Init();
bool ChangeDriveLetter();
wchar_t GetDriveLetter() { return mDriveLetter; }
private:
bool DoMapping();
void Disconnect(wchar_t aDriveLetter);
wchar_t mDriveLetter;
nsString mRemoteUNCPath;
};
DriveMapping::DriveMapping(const nsAString& aRemoteUNCPath)
: mDriveLetter(0), mRemoteUNCPath(aRemoteUNCPath) {}
bool DriveMapping::Init() {
if (mDriveLetter) {
return false;
}
return DoMapping();
}
bool DriveMapping::DoMapping() {
wchar_t drvTemplate[] = L" :";
NETRESOURCEW netRes = {0};
netRes.dwType = RESOURCETYPE_DISK;
netRes.lpLocalName = drvTemplate;
netRes.lpRemoteName =
reinterpret_cast<wchar_t*>(mRemoteUNCPath.BeginWriting());
wchar_t driveLetter = L'D';
DWORD result = NO_ERROR;
do {
drvTemplate[0] = driveLetter;
result = WNetAddConnection2W(&netRes, nullptr, nullptr, CONNECT_TEMPORARY);
} while (result == ERROR_ALREADY_ASSIGNED && ++driveLetter <= L'Z');
if (result != NO_ERROR) {
return false;
}
mDriveLetter = driveLetter;
return true;
}
bool DriveMapping::ChangeDriveLetter() {
wchar_t prevDriveLetter = mDriveLetter;
bool result = DoMapping();
MOZ_RELEASE_ASSERT(mDriveLetter != prevDriveLetter);
if (result && prevDriveLetter) {
Disconnect(prevDriveLetter);
}
return result;
}
void DriveMapping::Disconnect(wchar_t aDriveLetter) {
wchar_t drvTemplate[] = {aDriveLetter, L':', L'\0'};
DWORD result = WNetCancelConnection2W(drvTemplate, 0, TRUE);
MOZ_RELEASE_ASSERT(result == NO_ERROR);
}
DriveMapping::~DriveMapping() {
if (mDriveLetter) {
Disconnect(mDriveLetter);
}
}
bool DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath) {
const wchar_t drvTpl[] = {aDriveLetter, L':', L'\0'};
aNtPath.SetLength(MAX_PATH);
DWORD pathLen;
while (true) {
pathLen = QueryDosDeviceW(
drvTpl, reinterpret_cast<wchar_t*>(aNtPath.BeginWriting()),
aNtPath.Length());
if (pathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
break;
}
aNtPath.SetLength(aNtPath.Length() * 2);
}
if (!pathLen) {
return false;
}
// aNtPath contains embedded NULLs, so we need to figure out the real length
// via wcslen.
aNtPath.SetLength(NS_strlen(aNtPath.BeginReading()));
return true;
}
bool TestNtPathToDosPath(const wchar_t* aNtPath,
const wchar_t* aExpectedDosPath) {
nsAutoString output;
bool result = mozilla::NtPathToDosPath(nsDependentString(aNtPath), output);
return result && output == reinterpret_cast<const nsAString::char_type*>(
aExpectedDosPath);
}
TEST(NtPathToDosPath, Tests)
{
nsAutoString cDrive;
ASSERT_TRUE(DriveToNtPath(L'C', cDrive));
// empty string
EXPECT_TRUE(TestNtPathToDosPath(L"", L""));
// non-existent device, must fail
EXPECT_FALSE(
TestNtPathToDosPath(L"\\Device\\ThisDeviceDoesNotExist\\Foo", nullptr));
// base case
nsAutoString testPath(cDrive);
testPath.Append(L"\\Program Files");
EXPECT_TRUE(TestNtPathToDosPath(testPath.get(), L"C:\\Program Files"));
// short filename
nsAutoString ntShortName(cDrive);
ntShortName.Append(L"\\progra~1");
EXPECT_TRUE(TestNtPathToDosPath(ntShortName.get(), L"C:\\Program Files"));
// drive letters as symbolic links (NtCreateFile uses these)
EXPECT_TRUE(TestNtPathToDosPath(L"\\??\\C:\\Foo", L"C:\\Foo"));
// other symbolic links (should fail)
EXPECT_FALSE(TestNtPathToDosPath(L"\\??\\MountPointManager", nullptr));
// socket (should fail)
EXPECT_FALSE(TestNtPathToDosPath(L"\\Device\\Afd\\Endpoint", nullptr));
// UNC path (using MUP)
EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\Mup\\127.0.0.1\\C$",
L"\\\\127.0.0.1\\C$"));
// UNC path (using LanmanRedirector)
EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\LanmanRedirector\\127.0.0.1\\C$",
L"\\\\127.0.0.1\\C$"));
DriveMapping drvMapping(u"\\\\127.0.0.1\\C$"_ns);
// Only run these tests if we were able to map; some machines don't have perms
if (drvMapping.Init()) {
wchar_t expected[] = L" :\\";
expected[0] = drvMapping.GetDriveLetter();
nsAutoString networkPath;
ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath));
networkPath += u"\\";
EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected));
// NtPathToDosPath must correctly handle paths whose drive letter mapping
// has changed. We need to test this because the APIs called by
// NtPathToDosPath return different info if this has happened.
ASSERT_TRUE(drvMapping.ChangeDriveLetter());
expected[0] = drvMapping.GetDriveLetter();
ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath));
networkPath += u"\\";
EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected));
}
}