diff options
Diffstat (limited to 'lib/resolve_path.cpp')
-rw-r--r-- | lib/resolve_path.cpp | 708 |
1 files changed, 0 insertions, 708 deletions
diff --git a/lib/resolve_path.cpp b/lib/resolve_path.cpp deleted file mode 100644 index 768876c9..00000000 --- a/lib/resolve_path.cpp +++ /dev/null @@ -1,708 +0,0 @@ -#include "resolve_path.h" -#include <set> //not necessarily included by <map>! -#include <map> -#include <zen/time.h> -#include <zen/thread.h> -#include <zen/utf.h> -#include <zen/scope_guard.h> -#include <wx/utils.h> //wxGetEnv - -#ifdef ZEN_WIN -#include <zen/long_path_prefix.h> -#include <zen/file_handling.h> -#include <zen/win.h> //includes "windows.h" -#include <zen/dll.h> -#include <Shlobj.h> -#ifdef _MSC_VER -#pragma comment(lib, "Mpr.lib") -#endif - -#elif defined ZEN_LINUX || defined ZEN_MAC -#include <stdlib.h> //getenv() -#include <unistd.h> //getcwd -#endif - -using namespace zen; - - -namespace -{ -#ifdef ZEN_WIN -Zstring resolveRelativePath(const Zstring& relativeName) //note: ::GetFullPathName() is documented not threadsafe! -{ - //don't use long path prefix! does not work with relative paths "." and ".." - const DWORD bufferSize = ::GetFullPathName(relativeName.c_str(), 0, nullptr, nullptr); - if (bufferSize > 0) - { - std::vector<wchar_t> buffer(bufferSize); - const DWORD charsWritten = ::GetFullPathName(relativeName.c_str(), //__in LPCTSTR lpFileName, - bufferSize, //__in DWORD nBufferLength, - &buffer[0], //__out LPTSTR lpBuffer, - nullptr); //__out LPTSTR *lpFilePart - if (0 < charsWritten && charsWritten < bufferSize) //theoretically, charsWritten can never be == "bufferSize" - return Zstring(&buffer[0], charsWritten); - } - return relativeName; //ERROR! Don't do anything -} - -#elif defined ZEN_LINUX || defined ZEN_MAC -Zstring resolveRelativePath(const Zstring& relativeName) -{ - //http://linux.die.net/man/2/path_resolution - if (!startsWith(relativeName, FILE_NAME_SEPARATOR)) //absolute names are exactly those starting with a '/' - { - /* - basic support for '~': strictly speaking this is a shell-layer feature, so "realpath()" won't handle it - http://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html - - http://linux.die.net/man/3/getpwuid: An application that wants to determine its user's home directory - should inspect the value of HOME (rather than the value getpwuid(getuid())->pw_dir) since this allows - the user to modify their notion of "the home directory" during a login session. - */ - if (startsWith(relativeName, "~/") || relativeName == "~") - { - const char* homeDir = ::getenv("HOME"); - if (!homeDir) - return relativeName; //error! no further processing! - - if (startsWith(relativeName, "~/")) - return appendSeparator(homeDir) + afterFirst(relativeName, '/'); - else if (relativeName == "~") - return homeDir; - } - - //we cannot use ::realpath() since it resolves *existing* relative paths only! - if (char* dirpath = ::getcwd(nullptr, 0)) - { - ZEN_ON_SCOPE_EXIT(::free(dirpath)); - return appendSeparator(dirpath) + relativeName; - } - } - return relativeName; -} -#endif - - -#ifdef ZEN_WIN -class CsidlConstants -{ -public: - typedef std::map<Zstring, Zstring, LessFilename> CsidlToDirMap; //case-insensitive comparison - - static const CsidlToDirMap& get() - { - static CsidlConstants inst; //potential MT solved by intializing at startup, see below - return inst.csidlToDir; - } - -private: - CsidlConstants() - { - auto addCsidl = [&](int csidl, const Zstring& paramName) - { - wchar_t buffer[MAX_PATH] = {}; - if (SUCCEEDED(::SHGetFolderPath(nullptr, //__in HWND hwndOwner, - csidl | CSIDL_FLAG_DONT_VERIFY, //__in int nFolder, - nullptr, //__in HANDLE hToken, - 0 /* == SHGFP_TYPE_CURRENT*/, //__in DWORD dwFlags, - buffer))) //__out LPTSTR pszPath - { - Zstring dirname = buffer; - if (!dirname.empty()) - csidlToDir.insert(std::make_pair(paramName, dirname)); - } - }; - - //================================================================================================ - //SHGetKnownFolderPath: API available only with Windows Vista and later: -#ifdef __MINGW32__ -#define KF_FLAG_DONT_VERIFY 0x00004000 -#endif - typedef HRESULT (STDAPICALLTYPE* SHGetKnownFolderPathFunc)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath); - const SysDllFun<SHGetKnownFolderPathFunc> shGetKnownFolderPath(L"Shell32.dll", "SHGetKnownFolderPath"); - - auto addFolderId = [&](REFKNOWNFOLDERID rfid, const Zstring& paramName) - { - if (shGetKnownFolderPath != nullptr) //[!] avoid bogus MSVC "performance warning" - { - PWSTR path = nullptr; - if (SUCCEEDED(shGetKnownFolderPath(rfid, //_In_ REFKNOWNFOLDERID rfid, - KF_FLAG_DONT_VERIFY, //_In_ DWORD dwFlags, - nullptr, //_In_opt_ HANDLE hToken, - &path))) //_Out_ PWSTR *ppszPath - { - ZEN_ON_SCOPE_EXIT(::CoTaskMemFree(path)); - - Zstring dirname = path; - if (!dirname.empty()) - csidlToDir.insert(std::make_pair(paramName, dirname)); - } - } - }; - - addCsidl(CSIDL_DESKTOPDIRECTORY, L"csidl_Desktop"); // C:\Users\<user>\Desktop - addCsidl(CSIDL_COMMON_DESKTOPDIRECTORY, L"csidl_PublicDesktop"); // C:\Users\All Users\Desktop - - addCsidl(CSIDL_FAVORITES, L"csidl_Favorites"); // C:\Users\<user>\Favorites - addCsidl(CSIDL_COMMON_FAVORITES, L"csidl_PublicFavorites"); // C:\Users\<user>\Favorites; unused? -> http://blogs.msdn.com/b/oldnewthing/archive/2012/09/04/10346022.aspx - - addCsidl(CSIDL_PERSONAL, L"csidl_MyDocuments"); // C:\Users\<user>\Documents - addCsidl(CSIDL_COMMON_DOCUMENTS, L"csidl_PublicDocuments"); // C:\Users\Public\Documents - - addCsidl(CSIDL_MYMUSIC, L"csidl_MyMusic"); // C:\Users\<user>\Music - addCsidl(CSIDL_COMMON_MUSIC, L"csidl_PublicMusic"); // C:\Users\Public\Music - - addCsidl(CSIDL_MYPICTURES, L"csidl_MyPictures"); // C:\Users\<user>\Pictures - addCsidl(CSIDL_COMMON_PICTURES, L"csidl_PublicPictures"); // C:\Users\Public\Pictures - - addCsidl(CSIDL_MYVIDEO, L"csidl_MyVideos"); // C:\Users\<user>\Videos - addCsidl(CSIDL_COMMON_VIDEO, L"csidl_PublicVideos"); // C:\Users\Public\Videos - - addCsidl(CSIDL_NETHOOD, L"csidl_Nethood"); // C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Network Shortcuts - - addCsidl(CSIDL_PROGRAMS, L"csidl_Programs"); // C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs - addCsidl(CSIDL_COMMON_PROGRAMS, L"csidl_PublicPrograms"); // C:\ProgramData\Microsoft\Windows\Start Menu\Programs - - addCsidl(CSIDL_RESOURCES, L"csidl_Resources"); // C:\Windows\Resources - - addCsidl(CSIDL_STARTMENU, L"csidl_StartMenu"); // C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu - addCsidl(CSIDL_COMMON_STARTMENU, L"csidl_PublicStartMenu"); // C:\ProgramData\Microsoft\Windows\Start Menu - - addCsidl(CSIDL_STARTUP, L"csidl_Startup"); // C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\StartUp - addCsidl(CSIDL_COMMON_STARTUP, L"csidl_PublicStartup"); // C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp - - addCsidl(CSIDL_TEMPLATES, L"csidl_Templates"); // C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Templates - addCsidl(CSIDL_COMMON_TEMPLATES, L"csidl_PublicTemplates"); // C:\ProgramData\Microsoft\Windows\Templates - - //Vista and later: - addFolderId(FOLDERID_Downloads, L"csidl_Downloads"); // C:\Users\<user>\Downloads - addFolderId(FOLDERID_PublicDownloads, L"csidl_PublicDownloads"); // C:\Users\Public\Downloads - - addFolderId(FOLDERID_QuickLaunch, L"csidl_QuickLaunch"); // C:\Users\<user>\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch - - /* - CSIDL_APPDATA covered by %AppData% - CSIDL_LOCAL_APPDATA covered by %LocalAppData% -> not on XP! - CSIDL_COMMON_APPDATA covered by %ProgramData% -> not on XP! - CSIDL_PROFILE covered by %UserProfile% - CSIDL_WINDOWS covered by %WinDir% - CSIDL_SYSTEM covered by %WinDir% - CSIDL_SYSTEMX86 covered by %WinDir% - CSIDL_PROGRAM_FILES covered by %ProgramFiles% - CSIDL_PROGRAM_FILES_COMMON covered by %CommonProgramFiles% - CSIDL_PROGRAM_FILESX86 covered by %ProgramFiles(x86)% -> not on XP! - CSIDL_PROGRAM_FILES_COMMONX86 covered by %CommonProgramFiles(x86)% -> not on XP! - CSIDL_ADMINTOOLS not relevant? - CSIDL_COMMON_ADMINTOOLS not relevant? - - FOLDERID_Public covered by %Public% - */ - } - - CsidlConstants(const CsidlConstants&); - CsidlConstants& operator=(const CsidlConstants&); - - CsidlToDirMap csidlToDir; -}; - -//caveat: function scope static initialization is not thread-safe in VS 2010! => make sure to call at app start! -struct Dummy { Dummy() { CsidlConstants::get(); }} blah; -#endif - - -std::unique_ptr<Zstring> getEnvironmentVar(const Zstring& envName) //return nullptr if not found -{ - wxString value; - if (!wxGetEnv(utfCvrtTo<wxString>(envName), &value)) - return nullptr; - - //some postprocessing: - trim(value); //remove leading, trailing blanks - - //remove leading, trailing double-quotes - if (startsWith(value, L"\"") && - endsWith (value, L"\"") && - value.length() >= 2) - value = wxString(value.c_str() + 1, value.length() - 2); - - return make_unique<Zstring>(utfCvrtTo<Zstring>(value)); -} - - -std::unique_ptr<Zstring> resolveMacro(const Zstring& macro, //macro without %-characters - const std::vector<std::pair<Zstring, Zstring>>& ext) //return nullptr if not resolved -{ - auto equalNoCase = [](const Zstring& lhs, const Zstring& rhs) { return utfCvrtTo<wxString>(lhs).CmpNoCase(utfCvrtTo<wxString>(rhs)) == 0; }; - - //there exist environment variables named %TIME%, %DATE% so check for our internal macros first! - if (equalNoCase(macro, Zstr("time"))) - return make_unique<Zstring>(formatTime<Zstring>(Zstr("%H%M%S"))); - - if (equalNoCase(macro, Zstr("date"))) - return make_unique<Zstring>(formatTime<Zstring>(FORMAT_ISO_DATE)); - - if (equalNoCase(macro, Zstr("timestamp"))) - return make_unique<Zstring>(formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S"))); //e.g. "2012-05-15 131513" - - std::unique_ptr<Zstring> cand; - auto processPhrase = [&](const Zchar* phrase, const Zchar* format) -> bool - { - if (!equalNoCase(macro, phrase)) - return false; - - cand = make_unique<Zstring>(formatTime<Zstring>(format)); - return true; - }; - - if (processPhrase(Zstr("weekday"), Zstr("%A"))) return cand; - if (processPhrase(Zstr("day" ), Zstr("%d"))) return cand; - if (processPhrase(Zstr("month" ), Zstr("%m"))) return cand; - if (processPhrase(Zstr("week" ), Zstr("%U"))) return cand; - if (processPhrase(Zstr("year" ), Zstr("%Y"))) return cand; - if (processPhrase(Zstr("hour" ), Zstr("%H"))) return cand; - if (processPhrase(Zstr("min" ), Zstr("%M"))) return cand; - if (processPhrase(Zstr("sec" ), Zstr("%S"))) return cand; - - //check domain-specific extensions - { - auto it = std::find_if(ext.begin(), ext.end(), [&](const std::pair<Zstring, Zstring>& p) { return equalNoCase(macro, p.first); }); - if (it != ext.end()) - return make_unique<Zstring>(it->second); - } - - //try to resolve as environment variable - if (std::unique_ptr<Zstring> value = getEnvironmentVar(macro)) - return value; - -#ifdef ZEN_WIN - //try to resolve as CSIDL value - { - const auto& csidlMap = CsidlConstants::get(); - auto it = csidlMap.find(macro); - if (it != csidlMap.end()) - return make_unique<Zstring>(it->second); - } -#endif - - return nullptr; -} - -const Zchar MACRO_SEP = Zstr('%'); - -//returns expanded or original string -Zstring expandMacros(const Zstring& text, const std::vector<std::pair<Zstring, Zstring>>& ext) -{ - if (contains(text, MACRO_SEP)) - { - Zstring prefix = beforeFirst(text, MACRO_SEP); - Zstring rest = afterFirst (text, MACRO_SEP); - if (contains(rest, MACRO_SEP)) - { - Zstring potentialMacro = beforeFirst(rest, MACRO_SEP); - Zstring postfix = afterFirst (rest, MACRO_SEP); //text == prefix + MACRO_SEP + potentialMacro + MACRO_SEP + postfix - - if (std::unique_ptr<Zstring> value = resolveMacro(potentialMacro, ext)) - return prefix + *value + expandMacros(postfix, ext); - else - return prefix + MACRO_SEP + potentialMacro + expandMacros(MACRO_SEP + postfix, ext); - } - } - return text; -} -} - - -Zstring zen::expandMacros(const Zstring& text) { return ::expandMacros(text, std::vector<std::pair<Zstring, Zstring>>()); } - - -namespace -{ -#ifdef ZEN_WIN -//networks and cdrom excluded - may still block for slow USB sticks! -Zstring getPathByVolumenName(const Zstring& volumeName) //return empty string on error -{ - //FindFirstVolume(): traverses volumes on local hard disks only! - //GetLogicalDriveStrings(): traverses all *logical* volumes, including CD-ROM, FreeOTFE virtual volumes - - const DWORD bufferSize = ::GetLogicalDriveStrings(0, nullptr); - std::vector<wchar_t> buffer(bufferSize); - - const DWORD rv = ::GetLogicalDriveStrings(bufferSize, //__in DWORD nBufferLength, - &buffer[0]); //__out LPTSTR lpBuffer - if (0 < rv && rv < bufferSize) - { - //search for matching path in parallel until first hit - RunUntilFirstHit<Zstring> findFirstMatch; - - for (const wchar_t* it = &buffer[0]; *it != 0; it += strLength(it) + 1) //list terminated by empty c-string - { - const Zstring path = it; - - findFirstMatch.addJob([path, volumeName]() -> std::unique_ptr<Zstring> - { - UINT type = ::GetDriveType(appendSeparator(path).c_str()); //non-blocking call! - if (type == DRIVE_REMOTE || type == DRIVE_CDROM) - return nullptr; - - //next call seriously blocks for non-existing network drives! - std::vector<wchar_t> volName(MAX_PATH + 1); //docu says so - - if (::GetVolumeInformation(appendSeparator(path).c_str(), //__in_opt LPCTSTR lpRootPathName, - &volName[0], //__out LPTSTR lpVolumeNameBuffer, - static_cast<DWORD>(volName.size()), //__in DWORD nVolumeNameSize, - nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - nullptr, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, - 0)) //__in DWORD nFileSystemNameSize - if (EqualFilename()(volumeName, Zstring(&volName[0]))) - return zen::make_unique<Zstring>(path); - return nullptr; - }); - } - if (auto result = findFirstMatch.get()) //blocks until ready - return *result; - } - - return Zstring(); -} - - -//networks and cdrom excluded - this should not block -Zstring getVolumeName(const Zstring& volumePath) //return empty string on error -{ - UINT rv = ::GetDriveType(appendSeparator(volumePath).c_str()); //non-blocking call! - if (rv != DRIVE_REMOTE && - rv != DRIVE_CDROM) - { - const DWORD bufferSize = MAX_PATH + 1; - std::vector<wchar_t> buffer(bufferSize); - if (::GetVolumeInformation(appendSeparator(volumePath).c_str(), //__in_opt LPCTSTR lpRootPathName, - &buffer[0], //__out LPTSTR lpVolumeNameBuffer, - bufferSize, //__in DWORD nVolumeNameSize, - nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - nullptr, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, - 0)) //__in DWORD nFileSystemNameSize - return &buffer[0]; - } - return Zstring(); -} -#endif - - -//expand volume name if possible, return original input otherwise -Zstring expandVolumeName(const Zstring& text) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder -{ - //this would be a nice job for a C++11 regex... - - //we only expect the [.*] pattern at the beginning => do not touch dir names like "C:\somedir\[stuff]" - Zstring textTmp = text; - trim(textTmp, true, false); - - if (startsWith(textTmp, Zstr("["))) - { - size_t posEnd = textTmp.find(Zstr("]")); - if (posEnd != Zstring::npos) - { - Zstring volname = Zstring(textTmp.c_str() + 1, posEnd - 1); - Zstring rest = Zstring(textTmp.c_str() + posEnd + 1); - - if (startsWith(rest, Zstr(':'))) - rest = afterFirst(rest, Zstr(':')); - if (startsWith(rest, FILE_NAME_SEPARATOR)) - rest = afterFirst(rest, FILE_NAME_SEPARATOR); -#ifdef ZEN_WIN - //[.*] pattern was found... - if (!volname.empty()) - { - Zstring volPath = getPathByVolumenName(volname); //may block for slow USB sticks! - if (!volPath.empty()) - return appendSeparator(volPath) + rest; //successfully replaced pattern - } - //error: did not find corresponding volume name: - - /*make sure directory creation will fail later if attempted, instead of inconveniently interpreting this string as a relative name! - [FFS USB]\FreeFileSync will be resolved as - ?:\[FFS USB]\FreeFileSync\ - Windows - /.../[FFS USB]/FreeFileSync/ - Linux - instead of: - C:\Program Files\FreeFileSync\[FFS USB]\FreeFileSync\ */ - return L"?:\\[" + volname + L"]\\" + rest; - -#elif defined ZEN_LINUX || defined ZEN_MAC //neither supported nor needed - return "/.../[" + volname + "]/" + rest; -#endif - } - } - return text; -} -} - - -void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, LessFilename>& output) -{ -#ifdef ZEN_WIN - //1. replace volume path by volume name: c:\dirname -> [SYSTEM]\dirname - if (dirname.size() >= 3 && - std::iswalpha(dirname[0]) && - dirname[1] == L':' && - dirname[2] == L'\\') - { - Zstring volname = getVolumeName(Zstring(dirname.c_str(), 3)); //should not block - if (!volname.empty()) - output.insert(L"[" + volname + L"]" + Zstring(dirname.c_str() + 2)); - } - - //2. replace volume name by volume path: [SYSTEM]\dirname -> c:\dirname - { - Zstring testVolname = expandVolumeName(dirname); //should not block - if (testVolname != dirname) - if (output.insert(testVolname).second) - getDirectoryAliasesRecursive(testVolname, output); //recurse! - } -#endif - - //3. environment variables: C:\Users\<user> -> %USERPROFILE% - { - std::map<Zstring, Zstring> envToDir; - - //get list of useful variables - auto addEnvVar = [&](const Zstring& envName) - { - if (std::unique_ptr<Zstring> value = getEnvironmentVar(envName)) - envToDir.insert(std::make_pair(envName, *value)); - }; -#ifdef ZEN_WIN - addEnvVar(L"AllUsersProfile"); // C:\ProgramData - addEnvVar(L"AppData"); // C:\Users\<user>\AppData\Roaming - addEnvVar(L"LocalAppData"); // C:\Users\<user>\AppData\Local - addEnvVar(L"ProgramData"); // C:\ProgramData - addEnvVar(L"ProgramFiles"); // C:\Program Files - addEnvVar(L"ProgramFiles(x86)");// C:\Program Files (x86) - addEnvVar(L"CommonProgramFiles"); // C:\Program Files\Common Files - addEnvVar(L"CommonProgramFiles(x86)"); // C:\Program Files (x86)\Common Files - addEnvVar(L"Public"); // C:\Users\Public - addEnvVar(L"UserProfile"); // C:\Users\<user> - addEnvVar(L"WinDir"); // C:\Windows - addEnvVar(L"Temp"); // C:\Windows\Temp - - //add CSIDL values: http://msdn.microsoft.com/en-us/library/bb762494(v=vs.85).aspx - const auto& csidlMap = CsidlConstants::get(); - envToDir.insert(csidlMap.begin(), csidlMap.end()); - -#elif defined ZEN_LINUX || defined ZEN_MAC - addEnvVar("HOME"); //Linux: /home/<user> Mac: /Users/<user> -#endif - //substitute paths by symbolic names - auto pathStartsWith = [](const Zstring& path, const Zstring& prefix) -> bool - { -#if defined ZEN_WIN || defined ZEN_MAC - Zstring tmp = path; - Zstring tmp2 = prefix; - ::makeUpper(tmp); - ::makeUpper(tmp2); - return startsWith(tmp, tmp2); -#elif defined ZEN_LINUX - return startsWith(path, prefix); -#endif - }; - for (const auto& entry : envToDir) - if (pathStartsWith(dirname, entry.second)) - output.insert(MACRO_SEP + entry.first + MACRO_SEP + (dirname.c_str() + entry.second.size())); - } - - //4. replace (all) macros: %USERPROFILE% -> C:\Users\<user> - { - Zstring testMacros = expandMacros(dirname); - if (testMacros != dirname) - if (output.insert(testMacros).second) - getDirectoryAliasesRecursive(testMacros, output); //recurse! - } -} - - -std::vector<Zstring> zen::getDirectoryAliases(const Zstring& dirString) -{ - Zstring dirname = dirString; - trim(dirname, true, false); - if (dirname.empty()) - return std::vector<Zstring>(); - - std::set<Zstring, LessFilename> tmp; - getDirectoryAliasesRecursive(dirname, tmp); - - tmp.erase(dirname); - tmp.erase(Zstring()); - - return std::vector<Zstring>(tmp.begin(), tmp.end()); -} - - -Zstring zen::getFormattedDirectoryName(const Zstring& dirString) // throw() -{ - //formatting is needed since functions expect the directory to end with '\' to be able to split the relative names. - - Zstring dirname = dirString; - - //remove leading/trailing whitespace before allowing misinterpretation in applyLongPathPrefix() - trim(dirname, true, false); - while (endsWith(dirname, Zstr(' '))) //don't remove all whitespace from right, e.g. 0xa0 may be used as part of dir name - dirname.resize(dirname.size() - 1); - - if (dirname.empty()) //an empty string would later be resolved as "\"; this is not desired - return Zstring(); - - dirname = expandMacros(dirname); - dirname = expandVolumeName(dirname); //may block for slow USB sticks! - - /* - need to resolve relative paths: - WINDOWS: - - \\?\-prefix which needs absolute names - - Volume Shadow Copy: volume name needs to be part of each filename - - file icon buffer (at least for extensions that are actually read from disk, like "exe") - - ::SHFileOperation(): Using relative path names is not thread safe - WINDOWS/LINUX: - - detection of dependent directories, e.g. "\" and "C:\test" - */ - dirname = resolveRelativePath(dirname); - - return appendSeparator(dirname); -} - - -#ifdef ZEN_WIN -void zen::loginNetworkShare(const Zstring& dirnameOrig, bool allowUserInteraction) //throw() - user interaction: show OS password prompt -{ - /* - ATTENTION: it is not safe to retrieve UNC path via ::WNetGetConnection() for every type of network share: - - network type |::WNetGetConnection rv | lpRemoteName | existing UNC path - -----------------------------|-------------------------|---------------------------------|---------------- - inactive local network share | ERROR_CONNECTION_UNAVAIL| \\192.168.1.27\new2 | YES - WebDrive | NO_ERROR | \\Webdrive-ZenJu\GNU | NO - Box.net (WebDav) | NO_ERROR | \\www.box.net\DavWWWRoot\dav | YES - NetDrive | ERROR_NOT_CONNECTED | <empty> | NO - ____________________________________________________________________________________________________________ - - Windows Login Prompt Naming Conventions: - user account: <Domain>\<user> e.g. WIN-XP\ZenJu - network share: \\<server>\<share> e.g. \\WIN-XP\test - - Windows Command Line: - - list *all* active network connections, including deviceless ones which are hidden in Explorer: - net use - - delete active connection: - net use /delete \\server\share - ____________________________________________________________________________________________________________ - - Scenario: XP-shared folder is accessed by Win 7 over LAN with access limited to a certain user - - Problems: - I. WNetAddConnection2() allows (at least certain) invalid credentials (e.g. username: a/password: a) and establishes an *unusable* connection - II. WNetAddConnection2() refuses to overwrite an existing (unusable) connection created in I), but shows prompt repeatedly - III. WNetAddConnection2() won't bring up the prompt if *wrong* credentials had been entered just recently, even with CONNECT_INTERACTIVE specified! => 2-step proccess - */ - - auto connect = [&](NETRESOURCE& trgRes) //blocks heavily if network is not reachable!!! - { - //1. first try to connect without user interaction - blocks! - DWORD rv = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource, - nullptr, // __in LPCTSTR lpPassword, - nullptr, // __in LPCTSTR lpUsername, - 0); //__in DWORD dwFlags - //53L ERROR_BAD_NETPATH The network path was not found. - //86L ERROR_INVALID_PASSWORD - //1219L ERROR_SESSION_CREDENTIAL_CONFLICT Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. - //1326L ERROR_LOGON_FAILURE Logon failure: unknown user name or bad password. - //1236L ERROR_CONNECTION_ABORTED - if (somethingExists(trgRes.lpRemoteName)) //blocks! - return; //success: connection usable! -> don't care about "rv" - - if (rv == ERROR_BAD_NETPATH || //Windows 7 - rv == ERROR_BAD_NET_NAME|| //XP - rv == ERROR_CONNECTION_ABORTED) //failed to connect to a network that existed not too long ago; will later return ERROR_BAD_NETPATH - return; //no need to show a prompt for an unreachable network device - - //2. if first attempt failed, we need to *force* prompt by using CONNECT_PROMPT - if (allowUserInteraction) - { - //avoid problem II.) - DWORD rv2= ::WNetCancelConnection2(trgRes.lpRemoteName, //_In_ LPCTSTR lpName, - 0, //_In_ DWORD dwFlags, - true); //_In_ BOOL fForce - //2250L ERROR_NOT_CONNECTED - - //enforce login prompt - DWORD rv3 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource, - nullptr, // __in LPCTSTR lpPassword, - nullptr, // __in LPCTSTR lpUsername, - CONNECT_INTERACTIVE | CONNECT_PROMPT); //__in DWORD dwFlags - (void)rv2; - (void)rv3; - } - }; - - - Zstring dirname = removeLongPathPrefix(dirnameOrig); - trim(dirname, true, false); - - //1. locally mapped network share - if (dirname.size() >= 2 && iswalpha(dirname[0]) && dirname[1] == L':') - { - Zstring driveLetter(dirname.c_str(), 2); //e.g.: "Q:" - { - DWORD bufferSize = 10000; - std::vector<wchar_t> remoteNameBuffer(bufferSize); - - //map local -> remote - - //note: following function call does NOT block! - DWORD rv = ::WNetGetConnection(driveLetter.c_str(), //__in LPCTSTR lpLocalName in the form "<driveletter>:" - &remoteNameBuffer[0], //__out LPTSTR lpRemoteName, - &bufferSize); //__inout LPDWORD lpnLength - if (rv == ERROR_CONNECTION_UNAVAIL) //remoteNameBuffer will be filled nevertheless! - { - //ERROR_CONNECTION_UNAVAIL: network mapping is existing, but not connected - - Zstring networkShare = &remoteNameBuffer[0]; - if (!networkShare.empty()) - { - NETRESOURCE trgRes = {}; - trgRes.dwType = RESOURCETYPE_DISK; - trgRes.lpLocalName = const_cast<LPWSTR>(driveLetter.c_str()); //lpNetResource is marked "__in", seems WNetAddConnection2 is not const correct! - trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); // - - connect(trgRes); //blocks! - } - } - } - } - //2. deviceless network connection - else if (startsWith(dirname, L"\\\\")) //UNC path - { - const Zstring networkShare = [&]() -> Zstring //extract prefix "\\server\share" - { - size_t pos = dirname.find('\\', 2); - if (pos == Zstring::npos) - return Zstring(); - pos = dirname.find('\\', pos + 1); - return pos == Zstring::npos ? dirname : Zstring(dirname.c_str(), pos); - }(); - - if (!networkShare.empty()) - { - //::WNetGetResourceInformation seems to fail with ERROR_BAD_NET_NAME even for existing unconnected network shares! - // => unconditionally try to connect to network share, seems we cannot reliably detect connection status otherwise - - NETRESOURCE trgRes = {}; - trgRes.dwType = RESOURCETYPE_DISK; - trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //trgRes is "__in" - - connect(trgRes); //blocks! - } - } -} -#endif |