windows-nt/Source/XPSP1/NT/shell/ext/shfolder/folder.c
2020-09-26 16:20:57 +08:00

1270 lines
37 KiB
C

#define _SHFOLDER_
#define NO_SHLWAPI_PATH
#include <windows.h>
#include <shlwapi.h>
#include <shlobj.h>
#include <shfolder.h>
#include <platform.h>
#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);
}
LPWSTR _lstrcpyW(LPWSTR pwszDest, LPCWSTR pwszOrig)
{
if (pwszDest && pwszOrig)
{
do
{
*pwszDest = *pwszOrig;
pwszDest ++;
} while ( *pwszOrig++);
return pwszDest;
}
return 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 == RegOpenKeyA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", &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_READ | KEY_WRITE,
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 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 && ((lstrlenW(pszPath) + lstrlenW(sz)) < MAX_PATH))
_lstrcpyW(PathAddBackslash(pszPath),sz);
}
void PathAppend(LPWSTR pszPath, LPCWSTR pszAppend)
{
if (pszPath && pszAppend)
{
if (((lstrlenW(pszPath) + lstrlenW(pszAppend)) < MAX_PATH))
_lstrcpyW(PathAddBackslash(pszPath), pszAppend);
}
}
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, MAX_PATH);
err = RegCreateKeyExA(hkeyProfRec, pszName, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_WRITE, NULL, &hSubKey, NULL);
if (err == ERROR_SUCCESS)
{
CHAR szDefaultPath[MAX_PATH];
DWORD dwOne = 1;
LPCSTR pszEnd = szPath + cchProfile + 1;
szDefaultPath[0] = 0;
lstrcpyA(szDefaultPath, "*windir");
lstrcatA(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)
if (lstrlenW(pszPath) + ARRAYSIZE(c_wszAllUsers) < MAX_PATH)
_lstrcpyW(PathFindFileName(pszPath), c_wszAllUsers);
}
else
{
// Win95 case
SHGetWindowsDirectory(pszPath);
// yes, non localized "All Users" per ericflo (NT4 behavior)
_lstrcpyW(PathAddBackslash(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 cbResult)
{
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)cbResult)
{
_lstrcpyW(pszResult, pszEnvVar);
_lstrcpyW(pszResult + lstrlenW(pszResult), 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 bRet = _SHCreateDirectory(pszPath);
if (!bRet && (lstrlenW(pszPath) < MAX_PATH))
{
WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1]; // +1 for PathAddBackslash()
// 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;
}
_lstrcpyW(szTemp, pszPath);
pEnd = PathAddBackslash(szTemp); // for the loop below
// 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
bRet = _SHCreateDirectory(szTemp);
}
*pSlash++ = CH_WHACK; // put the seperator back
}
}
return bRet;
}
// 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_READ, 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)))
lstrcpynW(pszPath, szExpand, MAX_PATH);
}
else
{
CHAR szExpand[MAX_PATH];
szExpand[0] = 0;
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)))
{
_lstrcpyW(szDefaultPath, pszPath);
}
}
err2 = RegCreateKeyExA(pfi->hkRoot, c_szUSF, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_WRITE, 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_READ | KEY_WRITE, 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, MAX_PATH);
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;
if (IsBadWritePtr(pszPath, MAX_PATH * sizeof(WCHAR)))
return E_INVALIDARG;
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)
{
WCHAR wsz[MAX_PATH];
HRESULT hr;
wsz[0] = 0;
if (IsBadWritePtr(pszPath, MAX_PATH * sizeof(*pszPath)))
return E_INVALIDARG;
pszPath[0] = 0;
hr = SHGetFolderPathW(hwnd, csidl, NULL, 0, wsz);
if (_SHUnicodeToAnsi(wsz, pszPath, MAX_PATH))
return hr;
else
return HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION);
}
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;
}