#include "precomp.hxx"
#pragma hdrstop

#include <shguidp.h>    // CLSID_MyDocuments, CLSID_ShellFSFolder
#include <shlobjp.h>    // SHFlushSFCache()
#include "util.h"
#include "dll.h"
#include "resource.h"
#include "sddl.h"

HRESULT GetFolderDisplayName(UINT csidl, LPTSTR pszPath, UINT cch)
{
    *pszPath = 0;

    LPITEMIDLIST pidl;
    if (SUCCEEDED(SHGetFolderLocation(NULL, csidl | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, &pidl)))
    {
        SHGetNameAndFlags(pidl, SHGDN_NORMAL, pszPath, cch, NULL);
        ILFree(pidl);
    }
    return *pszPath ? S_OK : E_FAIL;
}

#define MYDOCS_CLSID  TEXT("{450d8fba-ad25-11d0-98a8-0800361b1103}") // CLSID_MyDocuments

// Create/Updates file in SendTo directory to have current display name

void UpdateSendToFile()
{
    TCHAR szSendToDir[MAX_PATH];
    
    if (S_OK == SHGetFolderPath(NULL, CSIDL_SENDTO, NULL, SHGFP_TYPE_CURRENT, szSendToDir))
    {
        // Create c:\winnt\profile\chrisg\sendto\<display name>.mydocs
        BOOL bDeleteOnly = FALSE;
        TCHAR szNewFile[MAX_PATH];
        TCHAR szName[MAX_PATH];
        if (SUCCEEDED(GetFolderDisplayName(CSIDL_PERSONAL, szName, ARRAYSIZE(szName))))
        {
            PathCleanupSpec(NULL, szName);  // map any illegal chars to file sys chars
            PathRemoveBlanks(szName);

            PathCombine(szNewFile, szSendToDir, szName);
            lstrcat(szNewFile, TEXT(".mydocs"));
        }
        else
        {
            // we can't create a new file, because we don't have a name
            bDeleteOnly = TRUE;
        }
        
        TCHAR szFile[MAX_PATH];
        WIN32_FIND_DATA fd;

        // delete c:\winnt\profile\chrisg\sendto\*.mydocs

        PathCombine(szFile, szSendToDir, TEXT("*.mydocs"));

        HANDLE hFind = FindFirstFile(szFile, &fd);
        if (hFind != INVALID_HANDLE_VALUE)
        {
            do
            {
                PathCombine(szFile, szSendToDir, fd.cFileName);
                if (0 == lstrcmp(szFile, szNewFile))
                {
                    // The file that we needed to create already exists,
                    // just leave it in place instead of deleting it and
                    // then creating it again below (this fixes
                    // app compat problems - see NT bug 246932)
                    bDeleteOnly = TRUE;
                    // file now has the exact display name, MUI adjusts the return from GetFolderDisplayName and
                    // since we run this every time we dont have to worry about localizing the sendto target.
                }
                else
                {
                    DeleteFile(szFile);
                }
            } while (FindNextFile(hFind, &fd));
            FindClose(hFind);
        }

        if (!bDeleteOnly)
        {
            hFind = CreateFile(szNewFile, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
            if (hFind != INVALID_HANDLE_VALUE)
            {
                CloseHandle(hFind);
                // file now has the exact display name, MUI adjusts the return from GetFolderDisplayName and
                // since we run this every time we dont have to worry about localizing the sendto target.
            }
            else
            {
                // might be illegal chars in the file name, fall back to the default MyDocs name here
            }
        }
    }
}

// test pszChild against pszParent to see if
// pszChild is equal (PATH_IS_EQUAL) or 
// a DIRECT child (PATH_IS_CHILD)

DWORD ComparePaths(LPCTSTR pszChild, LPCTSTR pszParent)
{
    DWORD dwRet = PATH_IS_DIFFERENT;

    TCHAR szParent[MAX_PATH];
    StrCpyN(szParent, pszParent, ARRAYSIZE(szParent));

    if (PathIsRoot(szParent) && (-1 != PathGetDriveNumber(szParent)))
    {
        szParent[2] = 0;    // trip D:\ -> D: to make code below work
    }

    INT cchParent = lstrlen(szParent);
    INT cchChild = lstrlen(pszChild);

    if (cchParent <= cchChild)
    {
        TCHAR szChild[MAX_PATH];
        lstrcpyn(szChild, pszChild, ARRAYSIZE(szChild));

        LPTSTR pszChildSlice = szChild + cchParent;
        if (TEXT('\\') == *pszChildSlice)
        {
            *pszChildSlice = 0;
        }

        if (lstrcmpi(szChild, szParent) == 0)
        {
            if (cchParent < cchChild)
            {
                LPTSTR pTmp = pszChildSlice + 1;

                while (*pTmp && *pTmp != TEXT('\\'))
                {
                    pTmp++; // find second level path segments
                }

                if (!(*pTmp))
                {
                    dwRet = PATH_IS_CHILD;  // direct child
                }
            }
            else
            {
                dwRet = PATH_IS_EQUAL;
            }
        }
    }

    return dwRet;
}

// Checks the path to see if it is marked as system or read only and
// then check desktop.ini for CLSID or CLSID2 entry...

BOOL IsPathAlreadyShellFolder(LPCTSTR pszPath, DWORD dwAttrib)
{
    BOOL bIsShellFolder = FALSE;

    if (PathIsSystemFolder(pszPath, dwAttrib))
    {
        TCHAR szDesktopIni[MAX_PATH];
        PathCombine(szDesktopIni, pszPath, TEXT("desktop.ini"));

        // Check for CLSID entry...
        TCHAR szBuffer[MAX_PATH];
        GetPrivateProfileString(TEXT(".ShellClassInfo"), TEXT("CLSID"), TEXT("foo"), szBuffer, ARRAYSIZE(szBuffer), szDesktopIni);

        if ((lstrcmpi(szBuffer, TEXT("foo")) !=0) &&
             (lstrcmpi(szBuffer, MYDOCS_CLSID) !=0))
        {
            bIsShellFolder = TRUE;
        }

        // Check for CLSID2 entry...
        GetPrivateProfileString(TEXT(".ShellClassInfo"), TEXT("CLSID2"), TEXT("foo"), szBuffer, ARRAYSIZE(szBuffer), szDesktopIni);

        if ((lstrcmpi(szBuffer, TEXT("foo")) != 0) &&
             (lstrcmpi(szBuffer, MYDOCS_CLSID) != 0))
        {
            bIsShellFolder = TRUE;
        }
    }
    return bIsShellFolder;
}

const struct
{
    DWORD dwDir;
    DWORD dwFlags;
    DWORD dwRet;
}
_adirs[] =
{
    { CSIDL_DESKTOP,            PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_DESKTOP   },
    { CSIDL_PERSONAL,           PATH_IS_EQUAL                , PATH_IS_MYDOCS    },
    { CSIDL_SENDTO,             PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_SENDTO    },
    { CSIDL_RECENT,             PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_RECENT    },
    { CSIDL_HISTORY,            PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_HISTORY   },
    { CSIDL_COOKIES,            PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_COOKIES   },
    { CSIDL_PRINTHOOD,          PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_PRINTHOOD },
    { CSIDL_NETHOOD,            PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_NETHOOD   },
    { CSIDL_STARTMENU,          PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_STARTMENU },
    { CSIDL_TEMPLATES,          PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_TEMPLATES },
    { CSIDL_FAVORITES,          PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_FAVORITES },
    { CSIDL_FONTS,              PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_FONTS     },
    { CSIDL_APPDATA,            PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_APPDATA   },
    { CSIDL_INTERNET_CACHE,     PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_TEMP_INET },
    { CSIDL_COMMON_STARTMENU,   PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_STARTMENU },
    { CSIDL_COMMON_DESKTOPDIRECTORY, PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_DESKTOP },
    { CSIDL_WINDOWS,            PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_WINDOWS },
    { CSIDL_SYSTEM,             PATH_IS_EQUAL | PATH_IS_CHILD, PATH_IS_SYSTEM },
    { CSIDL_PROFILE,            PATH_IS_EQUAL                , PATH_IS_PROFILE },
};

BOOL PathEndsInDot(LPCTSTR pszPath)
{
    // CreateDirectory("c:\foo.") or CreateDirectory("c:\foo.....")
    // will succeed but create a directory named "c:\foo", which isn't
    // what the user asked for.  So we use this function to guard
    // against those cases.
    //
    // Note that this simple test also picks off "c:\foo\." -- ok for
    // our purposes.

    UINT cLen = lstrlen(pszPath);
    return (cLen >= 1) && (pszPath[cLen - 1] == TEXT('.'));
}

//
// Checks the path to see if it is okay as a MyDocs path
//
DWORD IsPathGoodMyDocsPath(HWND hwnd, LPCTSTR pszPath)
{
    if (NULL == pszPath)
    {
        return PATH_IS_ERROR;
    }
    
    TCHAR szRootPath[MAX_PATH];
    lstrcpyn(szRootPath, pszPath, ARRAYSIZE(szRootPath));
    if (!PathStripToRoot(szRootPath))
    {
        return PATH_IS_ERROR;
    }

    if (PathEndsInDot(pszPath))
    {
        return PATH_IS_ERROR;
    }
    
    DWORD dwRes, dwAttr = GetFileAttributes(pszPath);
    if (dwAttr == 0xFFFFFFFF)
    {
        if (0xFFFFFFFF == GetFileAttributes(szRootPath))
        {
            // If the root path doesn't exist, then we're not going
            // to be able to create a path:
            return PATH_IS_ERROR;
        }
        else
        {
            return PATH_IS_NONEXISTENT;
        }
    }

    if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
    {
        return PATH_IS_NONDIR;
    }

    for (int i = 0; i < ARRAYSIZE(_adirs); i++)
    {
        TCHAR szPathToCheck[MAX_PATH];
        //
        // Check for various special shell folders
        //
        if (S_OK == SHGetFolderPath(hwnd, _adirs[i].dwDir | CSIDL_FLAG_DONT_VERIFY, NULL, SHGFP_TYPE_CURRENT, szPathToCheck))
        {
            dwRes = ComparePaths(pszPath, szPathToCheck);

            if (dwRes & _adirs[i].dwFlags)
            {
                //
                // The inevitable exceptions
                //
                switch (_adirs[i].dwDir) 
                {
                case CSIDL_DESKTOP:
                    if (PATH_IS_CHILD == dwRes) 
                    {
                        continue;   // allowing subfolder of CSIDL_DESKTOP
                    }
                    break;

                default:
                    break;
                } // switch

                return _adirs[i].dwRet;
            }
        }
    }
    
    //
    // Make sure path isn't set as a system or some other kind of
    // folder that already has a CLSID or CLSID2 entry...
    //
    if (IsPathAlreadyShellFolder(pszPath, dwAttr))
    {
        return PATH_IS_SHELLFOLDER;
    }

    return PATH_IS_GOOD;
}