#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;
}
