#define _SHFOLDER_ #define NO_SHLWAPI_PATH #include #include #include #include #include #include #include "resource.h" #ifdef DBG #define ASSERT(x) if (!(x)) DebugBreak(); #else #define ASSERT(x) #endif #define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) // We can't rely on shlwapi SHUnicodeToAnsi/SHAnsiToUnicode in this module #define SHAnsiToUnicode(psz, pwsz, cchwsz) MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, psz, -1, pwsz, cchwsz) #define SHUnicodeToAnsi _SHUnicodeToAnsi // // Global array of static system SIDs, corresponding to UI_SystemSid // struct { SID sid; // contains 1 subauthority DWORD dwSubAuth[1]; // we currently need at most 2 subauthorities } c_StaticSids[] = { {{SID_REVISION, 1, SECURITY_CREATOR_SID_AUTHORITY, {SECURITY_CREATOR_OWNER_RID}}, {0} }, {{SID_REVISION, 1, SECURITY_NT_AUTHORITY, {SECURITY_AUTHENTICATED_USER_RID}}, {0} }, {{SID_REVISION, 1, SECURITY_NT_AUTHORITY, {SECURITY_LOCAL_SYSTEM_RID}}, {0} }, {{SID_REVISION, 2, SECURITY_NT_AUTHORITY, {SECURITY_BUILTIN_DOMAIN_RID}}, {DOMAIN_ALIAS_RID_ADMINS} }, {{SID_REVISION, 2, SECURITY_NT_AUTHORITY, {SECURITY_BUILTIN_DOMAIN_RID}}, {DOMAIN_ALIAS_RID_POWER_USERS} }, }; #define SSI_CREATOROWNER 0 #define SSI_AUTHUSER 1 #define SSI_SYSTEM 2 #define SSI_ADMIN 3 #define SSI_POWERUSER 4 typedef struct tagACEPARAMLIST { DWORD dwSidIndex; DWORD AccessMask; DWORD dwAceFlags; } ACEPARAMLIST; #define ACE_INHERIT (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE) #define FILE_MODIFY (FILE_ALL_ACCESS & ~(WRITE_DAC | WRITE_OWNER)) const ACEPARAMLIST c_paplUnsecure[] = { SSI_SYSTEM, FILE_ALL_ACCESS, 0, SSI_SYSTEM, GENERIC_ALL, ACE_INHERIT, SSI_AUTHUSER, FILE_MODIFY, 0, SSI_AUTHUSER, FILE_MODIFY, ACE_INHERIT, }; // // CSIDL_COMMON_DOCUMENTS // Admins, System, Creator Owner: Full Control - Container Inherit, Object Inherit // Users, Power Users: Read - Container Inherit, Object Inherit // Users, Power Users: Write - Container Inherit // // Non admin users can create files and directories. They have full control over // the files they create. All other users can read those files by default, but // they cannot modify the files unless the original creator gives them explicit // permissions to do so. // const ACEPARAMLIST c_paplCommonDocs[] = { SSI_SYSTEM, FILE_ALL_ACCESS, 0, SSI_SYSTEM, GENERIC_ALL, ACE_INHERIT, SSI_ADMIN, FILE_ALL_ACCESS, 0, SSI_ADMIN, GENERIC_ALL, ACE_INHERIT, SSI_CREATOROWNER, GENERIC_ALL, ACE_INHERIT, SSI_AUTHUSER, FILE_GENERIC_READ, 0, SSI_AUTHUSER, GENERIC_READ, ACE_INHERIT, SSI_AUTHUSER, FILE_GENERIC_WRITE, 0, SSI_AUTHUSER, GENERIC_WRITE, (CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE), SSI_POWERUSER, FILE_GENERIC_READ, 0, SSI_POWERUSER, GENERIC_READ, ACE_INHERIT, SSI_POWERUSER, FILE_GENERIC_WRITE, 0, SSI_POWERUSER, GENERIC_WRITE, (CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE), }; // // CSIDL_COMMON_APPDATA // Admins, System, Creator Owner: Full Control - Container Inherit, Object Inherit // Power Users: Modify - Container Inherit, Object Inherit // Users: Read - Container Inherit, Object Inherit // // Users can only read common appdata which is presumably created by admins or // power users during setup. // const ACEPARAMLIST c_paplCommonAppData[] = { SSI_SYSTEM, FILE_ALL_ACCESS, 0, SSI_SYSTEM, GENERIC_ALL, ACE_INHERIT, SSI_ADMIN, FILE_ALL_ACCESS, 0, SSI_ADMIN, GENERIC_ALL, ACE_INHERIT, SSI_CREATOROWNER, GENERIC_ALL, ACE_INHERIT, SSI_AUTHUSER, FILE_GENERIC_READ, 0, SSI_AUTHUSER, GENERIC_READ, ACE_INHERIT, SSI_POWERUSER, FILE_MODIFY, 0, SSI_POWERUSER, FILE_MODIFY, ACE_INHERIT, }; long _SHUnicodeToAnsi(LPCWSTR pwsz, LPSTR psz, long cchCount) { psz[0] = 0; return WideCharToMultiByte(CP_ACP, 0, pwsz, -1, psz, cchCount, 0, 0); } BOOL _SetDirAccess(LPCWSTR pszFile, const ACEPARAMLIST* papl, ULONG cPapl); HINSTANCE g_hinst = NULL; typedef void (__stdcall * PFNSHFLUSHSFCACHE)(); BOOL IsNewShlwapi(HMODULE hmod) { DLLGETVERSIONPROC pfnGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hmod, "DllGetVersion"); if (pfnGetVersion) { DLLVERSIONINFO dllinfo; dllinfo.cbSize = sizeof(dllinfo); if (pfnGetVersion(&dllinfo) == NOERROR) { return (dllinfo.dwMajorVersion > 5) || ((dllinfo.dwMajorVersion == 5) && ((dllinfo.dwMinorVersion > 0) || ((dllinfo.dwMinorVersion == 0) && (dllinfo.dwBuildNumber > 2012)))); } } return 0; } void FlushShellFolderCache() { // We could link directly now, but this is a smaller delta... HMODULE hmod = LoadLibraryA("shlwapi.dll"); if (hmod) { // avoid IE5 beta1 shlwapi.dll that has an export here but // not what we expect if (IsNewShlwapi(hmod)) { PFNSHFLUSHSFCACHE pfn = (PFNSHFLUSHSFCACHE)GetProcAddress(hmod, (CHAR *) MAKEINTRESOURCE(419)); if (pfn) pfn(); } FreeLibrary(hmod); } } HRESULT _SHGetFolderPath(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath) { HRESULT hr = E_NOTIMPL; HMODULE hmod = LoadLibraryA("shell32.dll"); if (hmod) { PFNSHGETFOLDERPATHW pfn = (PFNSHGETFOLDERPATHW)GetProcAddress(hmod, "SHGetFolderPathW"); if (pfn) hr = pfn(hwnd, csidl, hToken, dwFlags, pszPath); FreeLibrary(hmod); } return hr; } BOOL RunningOnNT() { static BOOL s_fRunningOnNT = 42; if (s_fRunningOnNT == 42) { OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx(&osvi); s_fRunningOnNT = (VER_PLATFORM_WIN32_NT == osvi.dwPlatformId); } return s_fRunningOnNT; } // shell32.SHGetSpecialFolderPath (175) // undocumented API, but the only one that exists on all platforms // // this thunk deals with the A/W issues based on the platform as // the export was TCHAR // typedef BOOL(__stdcall * PFNSHGETSPECIALFOLDERPATH)(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate); BOOL _SHGetSpecialFolderPath(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate) { BOOL bRet = FALSE; HMODULE hmod = LoadLibraryA("shell32.dll"); if (hmod) { PFNSHGETSPECIALFOLDERPATH pfn = (PFNSHGETSPECIALFOLDERPATH)GetProcAddress(hmod, (CHAR*) MAKEINTRESOURCE(175)); if (pfn) { if (RunningOnNT()) // compute from Get { bRet = pfn(hwnd, pszPath, csidl, fCreate); } else { CHAR szPath[MAX_PATH]; szPath[0] = 0; bRet = pfn(hwnd, (LPWSTR)szPath, csidl, fCreate); if (bRet) SHAnsiToUnicode(szPath, pszPath, MAX_PATH); // WideCharToMultiByte wrapper } } FreeLibrary(hmod); } return bRet; } BOOL GetProgramFiles(LPCWSTR pszValue, LPWSTR pszPath) { HKEY hkey; DWORD cbPath = MAX_PATH; *pszPath = 0; if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", 0, KEY_QUERY_VALUE, &hkey)) { if (RunningOnNT()) { cbPath *= sizeof(WCHAR); RegQueryValueExW(hkey, pszValue, NULL, NULL, (LPBYTE) pszPath, &cbPath); } else { CHAR szPath[MAX_PATH], szValue[64]; szPath[0] = 0; _SHUnicodeToAnsi(pszValue, szValue, ARRAYSIZE(szValue)); RegQueryValueExA(hkey, szValue, NULL, NULL, szPath, &cbPath); SHAnsiToUnicode(szPath, pszPath, MAX_PATH); } RegCloseKey(hkey); } return (BOOL)*pszPath; } // get the equiv of %USERPROFILE% on both win95 and NT // // on Win95 without user profiles turned on this will fail // out: // phkey optional out param // // returns: // length of the profile path UINT GetProfilePath(LPWSTR pszPath, HKEY *phkey, UINT *pcchProfile) { if (phkey) *phkey = NULL; if (pcchProfile) *pcchProfile = 0; if (RunningOnNT()) { ExpandEnvironmentStringsW(L"%USERPROFILE%", pszPath, MAX_PATH); if (pszPath[0] == L'%') pszPath[0] = 0; } else { HKEY hkeyProfRec; LONG err; CHAR szProfileDir[MAX_PATH]; szProfileDir [0] = 0; err = RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\ProfileReconciliation", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE, NULL, &hkeyProfRec, NULL); if (err == ERROR_SUCCESS) { DWORD cbData = sizeof(szProfileDir); RegQueryValueExA(hkeyProfRec, "ProfileDirectory", 0, NULL, (LPBYTE)szProfileDir, &cbData); if (phkey) *phkey = hkeyProfRec; else RegCloseKey(hkeyProfRec); if (pcchProfile) *pcchProfile = lstrlenA(szProfileDir); SHAnsiToUnicode(szProfileDir, pszPath, MAX_PATH); } } return lstrlenW(pszPath); } void SHGetWindowsDirectory(LPWSTR pszPath) { if (RunningOnNT()) GetWindowsDirectoryW(pszPath, MAX_PATH); else { CHAR szPath[MAX_PATH]; if (GetWindowsDirectoryA(szPath, ARRAYSIZE(szPath)-1)) SHAnsiToUnicode(szPath, pszPath, MAX_PATH); } } #define CH_WHACK FILENAME_SEPARATOR_W // add a backslash to a qualified path // // in: // pszPath path (A:, C:\foo, etc) // // out: // pszPath A:\, C:\foo\ ; // // returns: // pointer to the NULL that terminates the path STDAPI_(LPWSTR) PathAddBackslash(LPWSTR pszPath) { LPWSTR pszEnd; // try to keep us from tromping over MAX_PATH in size. // if we find these cases, return NULL. Note: We need to // check those places that call us to handle their GP fault // if they try to use the NULL! int ichPath = lstrlenW(pszPath); if (ichPath >= (MAX_PATH - 1)) return NULL; pszEnd = pszPath + ichPath; // this is really an error, caller shouldn't pass // an empty string if (!*pszPath) return pszEnd; /* Get the end of the source directory */ switch(* (pszEnd-1)) { case CH_WHACK: break; default: *pszEnd++ = CH_WHACK; *pszEnd = 0; } return pszEnd; } // Returns a pointer to the last component of a path string. // // in: // path name, either fully qualified or not // // returns: // pointer into the path where the path is. if none is found // returns a poiter to the start of the path // // c:\foo\bar -> bar // c:\foo -> foo // c:\foo\ -> c:\foo\ (REVIEW: is this case busted?) // c:\ -> c:\ (REVIEW: this case is strange) // c: -> c: // foo -> foo STDAPI_(LPWSTR) PathFindFileName(LPCWSTR pPath) { LPCWSTR pT; for (pT = pPath; *pPath; ++pPath) { if ((pPath[0] == L'\\' || pPath[0] == L':' || pPath[0] == L'/') && pPath[1] && pPath[1] != L'\\' && pPath[1] != L'/') pT = pPath + 1; } return (LPWSTR)pT; // const -> non const } STDAPI_(LPWSTR) PathFindSecondFileName(LPCWSTR pPath) { LPCWSTR pT, pRet = NULL; for (pT = pPath; *pPath; ++pPath) { if ((pPath[0] == L'\\' || pPath[0] == L':' || pPath[0] == L'/') && pPath[1] && pPath[1] != L'\\' && pPath[1] != L'/') { pRet = pT; // remember last pT = pPath + 1; } } return (LPWSTR)pRet; // const -> non const } // This function is modified in that if the string's length is 0, the null terminator is NOT copied to the buffer. int _LoadStringExW( UINT wID, LPWSTR lpBuffer, // Unicode buffer int cchBufferMax, // cch in Unicode buffer WORD wLangId) { HRSRC hResInfo; HANDLE hStringSeg; LPWSTR lpsz; int cch; cch = 0; // String Tables are broken up into 16 string segments. Find the segment // containing the string we are interested in. if (hResInfo = FindResourceExW(g_hinst, (LPCWSTR)RT_STRING, (LPWSTR)((LONG_PTR)(((USHORT)wID >> 4) + 1)), wLangId)) { // Load that segment. hStringSeg = LoadResource(g_hinst, hResInfo); // Lock the resource. if (lpsz = (LPWSTR)LockResource(hStringSeg)) { // Move past the other strings in this segment. // (16 strings in a segment -> & 0x0F) wID &= 0x0F; while (TRUE) { cch = *((WORD *)lpsz++); // PASCAL like string count // first UTCHAR is count if TCHARs if (wID-- == 0) break; lpsz += cch; // Step to start if next string } // Account for the NULL cchBufferMax--; // Don't copy more than the max allowed. if (cch > cchBufferMax) cch = cchBufferMax; // Copy the string into the buffer. CopyMemory(lpBuffer, lpsz, cch * sizeof(WCHAR)); // Attach Null terminator. lpBuffer[cch] = 0; } } return cch; } BOOL CALLBACK EnumResLangProc(HINSTANCE hinst, LPCWSTR lpszType, LPCWSTR lpszName, LANGID wLangId, LPARAM lParam) { *(LANGID *)lParam = wLangId; return FALSE; } BOOL CALLBACK EnumResNameProc(HINSTANCE hinst, LPCWSTR lpszType, LPCWSTR lpszName, LPARAM lParam) { EnumResourceLanguagesW(hinst, lpszType, lpszName, EnumResLangProc, lParam); return FALSE; } LANGID GetShellLangId() { static LANGID wShellLangID=0xffff; if (0xffff == wShellLangID) { BOOL fSuccess; HINSTANCE hShell; hShell = LoadLibraryA("shell32.dll"); if (hShell) { EnumResourceNamesW(hShell, (LPWSTR) RT_VERSION, EnumResNameProc, (LPARAM) &wShellLangID); FreeLibrary(hShell); } if (0xffff == wShellLangID) wShellLangID = GetSystemDefaultLangID(); } return wShellLangID; } void PathAppend(LPWSTR pszPath, LPCWSTR pszAppend) { if (pszPath && pszAppend && PathAddBackslash(pszPath)) { StringCchCatW(pszPath, MAX_PATH, pszAppend); } } void PathAppendResource(LPWSTR pszPath, UINT id) { WCHAR sz[MAX_PATH]; sz[0] = 0; if (!_LoadStringExW(id, sz, ARRAYSIZE(sz), GetShellLangId())) { _LoadStringExW(id, sz, ARRAYSIZE(sz), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); } if (*sz) { PathAppend(pszPath, sz); } } const CHAR c_szUSF[] = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"; const CHAR c_szSF[] = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; LONG RegSetStrW(HKEY hkey, LPCWSTR pszValueName, LPCWSTR pszValue) { return RegSetValueExW(hkey, pszValueName, 0, REG_SZ, (LPBYTE)pszValue, (lstrlenW(pszValue) + 1) * sizeof(WCHAR)); } LONG RegSetStrA(HKEY hkey, LPCSTR pszValueName, LPCSTR pszValue) { return RegSetValueExA(hkey, pszValueName, 0, REG_SZ, (LPBYTE)pszValue, (lstrlenA(pszValue) + 1) * sizeof(CHAR)); } void MakeFolderRoam(HKEY hkeyProfRec, LPCSTR pszName, LPCWSTR pszPath, UINT cchProfile) { HKEY hSubKey; LONG err; CHAR szPath[MAX_PATH]; ASSERT(!RunningOnNT()); _SHUnicodeToAnsi(pszPath, szPath, ARRAYSIZE(szPath)); err = RegCreateKeyExA(hkeyProfRec, pszName, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hSubKey, NULL); if (err == ERROR_SUCCESS) { CHAR szDefaultPath[MAX_PATH]; DWORD dwOne = 1; LPCSTR pszEnd = szPath + cchProfile + 1; szDefaultPath[0] = 0; StringCchCopy(szDefaultPath, ARRAYSIZE(szDefaultPath), "*windir"); StringCchCat(szDefaultPath, ARRAYSIZE(szDefaultPath), szPath + cchProfile); RegSetStrA(hSubKey, "CentralFile", pszEnd); RegSetStrA(hSubKey, "LocalFile", pszEnd); RegSetStrA(hSubKey, "Name", "*.*"); RegSetStrA(hSubKey, "DefaultDir", szDefaultPath); RegSetValueExA(hSubKey, "MustBeRelative", 0, REG_DWORD, (LPBYTE)&dwOne, sizeof(dwOne)); RegSetValueExA(hSubKey, "Default", 0, REG_DWORD, (LPBYTE)&dwOne, sizeof(dwOne)); RegSetStrA(hSubKey, "RegKey", c_szUSF); RegSetStrA(hSubKey, "RegValue", pszName); RegCloseKey(hSubKey); } } typedef struct _FOLDER_INFO { int id; // CSIDL value HKEY hkRoot; // per user, per machine UINT idsDirName; // esource ID for directory name LPCSTR pszRegValue; // Name of reg value and ProfileReconciliation subkey BOOL (*pfnGetPath)(const struct _FOLDER_INFO *, LPWSTR); // compute the path if not found const ACEPARAMLIST* papl; ULONG cApl; } FOLDER_INFO; typedef struct _NT_FOLDER_INFO { const FOLDER_INFO *pfi; WCHAR wszRegValue[60]; // this should be long enough to hold the longest member of FOLDER_INFO.pszRegValue } NT_FOLDER_INFO; BOOL DownLevelRoaming(const FOLDER_INFO *pfi, LPWSTR pszPath) { HKEY hkeyProfRec; UINT cchProfile; UINT cwchProfile = GetProfilePath(pszPath, &hkeyProfRec, &cchProfile); if (cwchProfile) { PathAppendResource(pszPath, pfi->idsDirName); if (hkeyProfRec) { MakeFolderRoam(hkeyProfRec, pfi->pszRegValue, pszPath, cchProfile); RegCloseKey(hkeyProfRec); } } else { SHGetWindowsDirectory(pszPath); if (pfi->id == CSIDL_PERSONAL) { if (pszPath[1] == TEXT(':') && pszPath[2] == TEXT('\\')) { pszPath[3] = 0; // strip to "C:\" } } PathAppendResource(pszPath, pfi->idsDirName); } return (BOOL)*pszPath; } BOOL DownLevelNonRoaming(const FOLDER_INFO *pfi, LPWSTR pszPath) { UINT cchProfile = GetProfilePath(pszPath, NULL, 0); if (cchProfile) { PathAppendResource(pszPath, pfi->idsDirName); } else { SHGetWindowsDirectory(pszPath); PathAppendResource(pszPath, pfi->idsDirName); } return (BOOL)*pszPath; } BOOL DownLevelRelative(UINT csidl, UINT id, LPWSTR pszPath) { *pszPath = 0; // assume error // since this is inside MyDocs make sure MyDocs exists first (for the create call) if (SHGetFolderPathW(NULL, csidl | CSIDL_FLAG_CREATE, NULL, 0, pszPath) == S_OK) { PathAppendResource(pszPath, id); } return (BOOL)*pszPath; } // we explictly don't want the MyPics folder to roam. the reasonaing being // that the contents of this are typically too large to give a good roaming // experience. but of course NT4 (< SP4) still roams everyting in the profile // dir thus this will roam on those platforms. BOOL DownLevelMyPictures(const FOLDER_INFO *pfi, LPWSTR pszPath) { return DownLevelRelative(CSIDL_PERSONAL, IDS_CSIDL_MYPICTURES, pszPath); } BOOL DownLevelMyMusic(const FOLDER_INFO *pfi, LPWSTR pszPath) { return DownLevelRelative(CSIDL_PERSONAL, IDS_CSIDL_MYMUSIC, pszPath); } BOOL DownLevelAdminTools(const FOLDER_INFO *pfi, LPWSTR pszPath) { return DownLevelRelative(CSIDL_PROGRAMS, IDS_CSIDL_ADMINTOOLS, pszPath); } BOOL DownLevelCommonAdminTools(const FOLDER_INFO *pfi, LPWSTR pszPath) { return DownLevelRelative(CSIDL_COMMON_PROGRAMS, IDS_CSIDL_ADMINTOOLS, pszPath); } const WCHAR c_wszAllUsers[] = L"All Users"; // not localized BOOL GetAllUsersRoot(LPWSTR pszPath) { if (GetProfilePath(pszPath, NULL, 0)) { // yes, non localized "All Users" per ericflo (NT4 behavior) LPWSTR pszFileName = PathFindFileName(pszPath); StringCchCopyW(pszFileName, MAX_PATH - (lstrlenW(pszPath) - lstrlenW(pszFileName)), c_wszAllUsers); } else { // Win95 case SHGetWindowsDirectory(pszPath); // yes, non localized "All Users" per ericflo (NT4 behavior) PathAppend(pszPath, c_wszAllUsers); } return *pszPath; } BOOL DownLevelCommon(const FOLDER_INFO *pfi, LPWSTR pszPath) { if (GetAllUsersRoot(pszPath)) { PathAppendResource(pszPath, pfi->idsDirName); } return (BOOL)*pszPath; } BOOL DownLevelCommonPrograms(const FOLDER_INFO *pfi, LPWSTR pszPath) { WCHAR szPath[MAX_PATH]; if (S_OK == SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, 0, szPath)) { if (GetAllUsersRoot(pszPath)) { PathAppend(pszPath, PathFindSecondFileName(szPath)); } } return (BOOL)*pszPath; } #define HKLM HKEY_LOCAL_MACHINE #define HKCU HKEY_CURRENT_USER const FOLDER_INFO c_rgFolders[] = { { CSIDL_PERSONAL, HKCU, IDS_CSIDL_PERSONAL, "Personal", DownLevelRoaming, NULL, 0 }, { CSIDL_MYPICTURES, HKCU, IDS_CSIDL_MYPICTURES, "My Pictures", DownLevelMyPictures, NULL, 0 }, { CSIDL_MYMUSIC, HKCU, IDS_CSIDL_MYMUSIC, "My Music", DownLevelMyMusic, NULL, 0 }, { CSIDL_APPDATA, HKCU, IDS_CSIDL_APPDATA, "AppData", DownLevelRoaming, NULL, 0 }, { CSIDL_LOCAL_APPDATA, HKCU, IDS_CSIDL_LOCAL_APPDATA, "Local AppData", DownLevelNonRoaming, NULL, 0 }, { CSIDL_INTERNET_CACHE, HKCU, IDS_CSIDL_CACHE, "Cache", DownLevelNonRoaming, NULL, 0 }, { CSIDL_COOKIES, HKCU, IDS_CSIDL_COOKIES, "Cookies", DownLevelRoaming, NULL, 0 }, { CSIDL_HISTORY, HKCU, IDS_CSIDL_HISTORY, "History", DownLevelRoaming, NULL, 0 }, { CSIDL_ADMINTOOLS, HKCU, IDS_CSIDL_ADMINTOOLS, "Administrative Tools", DownLevelAdminTools, NULL, 0 }, { CSIDL_COMMON_APPDATA, HKLM, IDS_CSIDL_APPDATA, "Common AppData", DownLevelCommon, c_paplCommonAppData, ARRAYSIZE(c_paplCommonAppData) }, { CSIDL_COMMON_DOCUMENTS, HKLM, IDS_CSIDL_COMMON_DOCUMENTS, "Common Documents", DownLevelCommon, c_paplCommonDocs, ARRAYSIZE(c_paplCommonDocs) }, { CSIDL_COMMON_PROGRAMS, HKLM, 0, "Common Programs", DownLevelCommonPrograms, c_paplUnsecure, ARRAYSIZE(c_paplUnsecure) }, { CSIDL_COMMON_ADMINTOOLS, HKLM, IDS_CSIDL_ADMINTOOLS, "Common Administrative Tools", DownLevelCommonAdminTools, c_paplUnsecure, ARRAYSIZE(c_paplUnsecure) }, { -1, HKCU, 0, NULL, NULL, NULL } }; BOOL UnExpandEnvironmentString(LPCWSTR pszPath, LPCWSTR pszEnvVar, LPWSTR pszResult, UINT cchResult) { DWORD nToCmp; WCHAR szEnvVar[MAX_PATH]; szEnvVar[0] = 0; ASSERT(RunningOnNT()); ExpandEnvironmentStringsW(pszEnvVar, szEnvVar, ARRAYSIZE(szEnvVar)); // don't count the NULL nToCmp = lstrlenW(szEnvVar); if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, szEnvVar, nToCmp, pszPath, nToCmp) == 2) { if (lstrlenW(pszPath) - (int)nToCmp + lstrlenW(pszEnvVar) < (int)cchResult) { StringCchCopyW(pszResult, cchResult, pszEnvVar); StringCchCatW(pszResult, cchResult, pszPath + nToCmp); return TRUE; } } return FALSE; } const FOLDER_INFO *FindFolderInfo(int csidl) { const FOLDER_INFO *pfi; for (pfi = c_rgFolders; pfi->id != -1; pfi++) { if (pfi->id == csidl) return pfi; } return NULL; } BOOL _SHCreateDirectory(LPCWSTR pszPath) { if (RunningOnNT()) return CreateDirectoryW(pszPath, NULL); else { // no check for Unicode -> Ansi needed here, because we validated // the path in _EnsureExistsOrCreate() CHAR szPath[MAX_PATH]; _SHUnicodeToAnsi(pszPath, szPath, ARRAYSIZE(szPath)); return CreateDirectoryA(szPath, NULL); } } BOOL _CreateDirectoryDeep(LPCWSTR pszPath) { BOOL fRet = _SHCreateDirectory(pszPath); if (!fRet && (lstrlenW(pszPath) < MAX_PATH)) { WCHAR *pSlash, szTemp[MAX_PATH]; // There are certain error codes that we should bail out here // before going through and walking up the tree... switch (GetLastError()) { case ERROR_FILENAME_EXCED_RANGE: case ERROR_FILE_EXISTS: return FALSE; } StringCchCopyW(szTemp, ARRAYSIZE(szTemp), pszPath); fRet = (PathAddBackslash(szTemp) != NULL); // for the loop below if (fRet) { // assume we have 'X:\' to start this should even work // on UNC names because will will ignore the first error pSlash = szTemp + 3; // create each part of the dir in order while (*pSlash) { while (*pSlash && *pSlash != CH_WHACK) pSlash ++; if (*pSlash) { *pSlash = 0; // terminate path at seperator fRet = _SHCreateDirectory(szTemp); } *pSlash++ = CH_WHACK; // put the seperator back } } } return fRet; } // check for // X:\foo // \\foo BOOL PathIsFullyQualified(LPCWSTR pszPath) { return pszPath[0] && pszPath[1] && (pszPath[1] == ':' || (pszPath[0] == '\\' && pszPath[1] == '\\')); } HRESULT GetPathFromRegOrDefault(const NT_FOLDER_INFO *npfi, LPWSTR pszPath) { HRESULT hr; HKEY hkeyUserShellFolders; LONG err; CHAR szPath[MAX_PATH]; const FOLDER_INFO *pfi = npfi->pfi; szPath[0] = 0; err = RegCreateKeyExA(pfi->hkRoot, c_szUSF, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE, NULL, &hkeyUserShellFolders, NULL); if (err == ERROR_SUCCESS) { DWORD dwType, cbData = MAX_PATH * sizeof(*pszPath); if (RunningOnNT()) { err = RegQueryValueExW(hkeyUserShellFolders, npfi->wszRegValue, NULL, &dwType, (LPBYTE)pszPath, &cbData); } else { err = RegQueryValueExA(hkeyUserShellFolders, pfi->pszRegValue, NULL, &dwType, (LPBYTE)szPath, &cbData); SHAnsiToUnicode(szPath, pszPath, MAX_PATH); } if (err == ERROR_SUCCESS && cbData) { if (dwType == REG_EXPAND_SZ) { if (RunningOnNT()) { WCHAR szExpand[MAX_PATH]; szExpand[0] = 0; if (ExpandEnvironmentStringsW(pszPath, szExpand, ARRAYSIZE(szExpand))) { StringCchCopyW(pszPath, MAX_PATH, szExpand); } } else { CHAR szExpand[MAX_PATH]; szExpand[0] = 0; if (ExpandEnvironmentStringsA(szPath, szExpand, ARRAYSIZE(szExpand))) { SHAnsiToUnicode(szExpand, pszPath, MAX_PATH); } } } } else if (pfi->pfnGetPath && pfi->pfnGetPath(pfi, pszPath)) { err = ERROR_SUCCESS; // store results back to "User Shell Folders" on NT, but not on Win95 if (RunningOnNT()) { WCHAR szDefaultPath[MAX_PATH]; HKEY hkeyWriteUserShellFolders; LONG err2; szDefaultPath[0] = 0; if (!UnExpandEnvironmentString(pszPath, L"%USERPROFILE%", szDefaultPath, ARRAYSIZE(szDefaultPath))) { if (!UnExpandEnvironmentString(pszPath, L"%SYSTEMROOT%", szDefaultPath, ARRAYSIZE(szDefaultPath))) { StringCchCopyW(szDefaultPath, ARRAYSIZE(szDefaultPath), pszPath); } } err2 = RegCreateKeyExA(pfi->hkRoot, c_szUSF, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hkeyWriteUserShellFolders, NULL); if (err2 == ERROR_SUCCESS) { RegSetValueExW(hkeyWriteUserShellFolders, npfi->wszRegValue, 0, REG_EXPAND_SZ, (LPBYTE)szDefaultPath, (lstrlenW(szDefaultPath) + 1) * sizeof(szDefaultPath[0])); RegCloseKey(hkeyWriteUserShellFolders); } } } else err = ERROR_PATH_NOT_FOUND; // validate the returned path here if (err == ERROR_SUCCESS) { // expand failed (or some app messed up and did not use REG_EXPAND_SZ) if (*pszPath == L'%') { err = ERROR_ENVVAR_NOT_FOUND; *pszPath = 0; } else if (!PathIsFullyQualified(pszPath)) { err = ERROR_PATH_NOT_FOUND; *pszPath = 0; } } RegCloseKey(hkeyUserShellFolders); } return HRESULT_FROM_WIN32(err); } HRESULT _EnsureExistsOrCreate(LPWSTR pszPath, BOOL bCreate, const ACEPARAMLIST* papl, ULONG cApl) { HRESULT hr; DWORD dwFileAttributes; if (RunningOnNT()) dwFileAttributes = GetFileAttributesW(pszPath); else { CHAR szPath[MAX_PATH]; if (_SHUnicodeToAnsi(pszPath, szPath, ARRAYSIZE(szPath))) dwFileAttributes = GetFileAttributesA(szPath); else { pszPath[0] = 0; return HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION); } } if (dwFileAttributes == -1) { if (bCreate) { if (_CreateDirectoryDeep(pszPath)) { hr = S_OK; if (papl && RunningOnNT()) { _SetDirAccess(pszPath, papl, cApl); } } else { hr = HRESULT_FROM_WIN32(GetLastError()); *pszPath = 0; } } else { hr = S_FALSE; *pszPath = 0; } } else hr = S_OK; return hr; } HRESULT _DownLevelGetFolderPath(int csidl, LPWSTR pszPath, BOOL bCreate) { const FOLDER_INFO *pfi; HRESULT hr = E_INVALIDARG; *pszPath = 0; // assume error pfi = FindFolderInfo(csidl); if (pfi) { NT_FOLDER_INFO nfi; nfi.pfi = pfi; SHAnsiToUnicode(pfi->pszRegValue, nfi.wszRegValue, ARRAYSIZE(nfi.wszRegValue)); // get default value from "User Shell Folders" hr = GetPathFromRegOrDefault(&nfi, pszPath); if (SUCCEEDED(hr)) { hr = _EnsureExistsOrCreate(pszPath, bCreate, pfi->papl, pfi->cApl); if (hr == S_OK) { HKEY hkeyShellFolders; LONG err; // store to "Shell Folders" err = RegCreateKeyExA(pfi->hkRoot, c_szSF, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hkeyShellFolders, NULL); if (err == ERROR_SUCCESS) { if (RunningOnNT()) { RegSetStrW(hkeyShellFolders, nfi.wszRegValue, pszPath); } else { CHAR szPath[MAX_PATH]; _SHUnicodeToAnsi(pszPath, szPath, ARRAYSIZE(szPath)); RegSetStrA(hkeyShellFolders, pfi->pszRegValue, szPath); } RegCloseKey(hkeyShellFolders); } FlushShellFolderCache(); } } } else { if (csidl == CSIDL_WINDOWS) { SHGetWindowsDirectory(pszPath); hr = S_OK; } else if (csidl == CSIDL_SYSTEM) { if (RunningOnNT()) { GetSystemDirectoryW(pszPath, MAX_PATH); } else { CHAR szPath[MAX_PATH]; szPath[0] = 0; GetSystemDirectoryA(szPath, ARRAYSIZE(szPath)); SHAnsiToUnicode(szPath, pszPath, MAX_PATH); } hr = S_OK; } else if (csidl == CSIDL_PROGRAM_FILES) { hr = GetProgramFiles(L"ProgramFilesDir", pszPath) ? S_OK : S_FALSE; } else if (csidl == CSIDL_PROGRAM_FILES_COMMON) { hr = GetProgramFiles(L"CommonFilesDir", pszPath) ? S_OK : S_FALSE; } } return hr; } // We pass csidl to _SHGetSpecialFolderPath only for NT 4 English folders // NT bug # 60970 // NT bug # 222510 // NT bug # 221492 STDAPI SHGetFolderPathW(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath) { HRESULT hr = E_INVALIDARG; if (pszPath) { pszPath[0] = 0; hr = _SHGetFolderPath(hwnd, csidl, hToken, dwFlags, pszPath); if (hr == E_NOTIMPL || hr == E_INVALIDARG) { BOOL bCreate = csidl & CSIDL_FLAG_CREATE; csidl &= ~CSIDL_FLAG_MASK; // strip the flags if (hToken || dwFlags) return E_INVALIDARG; if ((csidl < CSIDL_LOCAL_APPDATA) && _SHGetSpecialFolderPath(hwnd, pszPath, csidl, bCreate)) { hr = S_OK; } else { hr = _DownLevelGetFolderPath(csidl, pszPath, bCreate); } } } return hr; } STDAPI SHGetFolderPathA(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPSTR pszPath) { HRESULT hr = E_INVALIDARG; if (pszPath) { WCHAR wsz[MAX_PATH]; pszPath[0] = 0; hr = SHGetFolderPathW(hwnd, csidl, NULL, 0, wsz); if (SUCCEEDED(hr)) { if (!_SHUnicodeToAnsi(wsz, pszPath, MAX_PATH)) { hr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION); } } } return hr; } BOOL APIENTRY DllMain(IN HANDLE hDll, IN DWORD dwReason, IN LPVOID lpReserved) { switch(dwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hDll); g_hinst = hDll; break; default: break; } return TRUE; } BOOL _AddAccessAllowedAce(PACL pAcl, DWORD dwAceRevision, DWORD AccessMask, PSID pSid) { // // First verify that the SID is valid on this platform // WCHAR szName[MAX_PATH], szDomain[MAX_PATH]; DWORD cbName = ARRAYSIZE(szName); DWORD cbDomain = ARRAYSIZE(szDomain); SID_NAME_USE snu; if (LookupAccountSidW(NULL, pSid, szName, &cbName, szDomain, &cbDomain, &snu)) { // // Yes, it's valid; now add the ACE // return AddAccessAllowedAce(pAcl, dwAceRevision, AccessMask, pSid); } return FALSE; } BOOL _AddAces(PACL pAcl, const ACEPARAMLIST* papl, ULONG cPapl) { ULONG i; for (i = 0; i < cPapl; i++) { PSID psid = &c_StaticSids[papl[i].dwSidIndex]; if (_AddAccessAllowedAce(pAcl, ACL_REVISION, papl[i].AccessMask, psid)) { if (papl[i].dwAceFlags) { ACE_HEADER* pAceHeader; if (GetAce(pAcl, i, &pAceHeader)) { pAceHeader->AceFlags |= papl[i].dwAceFlags; } else { return FALSE; } } } else { return FALSE; } } return TRUE; } PACL _CreateAcl(ULONG cPapl) { // Allocate space for the ACL DWORD cbAcl = (cPapl * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + sizeof(c_StaticSids[0]))) + sizeof(ACL); PACL pAcl = (PACL) GlobalAlloc(GPTR, cbAcl); if (pAcl) { InitializeAcl(pAcl, cbAcl, ACL_REVISION); } return pAcl; } BOOL _SetDirAccess(LPCWSTR pszDir, const ACEPARAMLIST* papl, ULONG cPapl) { BOOL bRetVal = FALSE; PACL pAcl; ASSERT(RunningOnNT()); pAcl = _CreateAcl(cPapl); if (pAcl) { if (_AddAces(pAcl, papl, cPapl)) { SECURITY_DESCRIPTOR sd; // Put together the security descriptor if (InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { if (SetSecurityDescriptorDacl(&sd, TRUE, pAcl, FALSE)) { // Set the security bRetVal = SetFileSecurityW(pszDir, DACL_SECURITY_INFORMATION, &sd); } } } GlobalFree(pAcl); } return bRetVal; }