/*++

Microsoft Windows
Copyright (C) Microsoft Corporation, 1981 - 1998

Module Name:

    utils.cxx

Abstract:



Author:

    Rahul Thombre (RahulTh) 4/8/1998

Revision History:

    4/8/1998    RahulTh

    Created this module.

--*/
#include "precomp.hxx"

BOOL IsSpecialDescendant (const long nID, UINT* parentID /*= NULL*/)
{
    BOOL fRetVal;
    int prntID = -1;

    switch (nID)
    {
    case IDS_MYPICS:
        prntID = IDS_MYDOCS;
        break;
    case IDS_PROGRAMS:
        prntID = IDS_STARTMENU;
        break;
    case IDS_STARTUP:
        prntID = IDS_PROGRAMS;
        break;
    default:
        prntID = -1;
        break;
    }

    if (fRetVal = (-1 != prntID))
    {
        if (parentID)
            *parentID = prntID;
    }

    return fRetVal;
}

//this is a helper function for ConvertOldStyleSection(...) which is used
//to convert Beta3 style ini files to Win2K style ini files.
void SplitRHS (CString& szValue, unsigned long & flags, CString& szPath)
{
    int index;

    //take some precautions
    szValue.TrimRight();
    szValue.TrimLeft();
    szPath.Empty();
    flags = 0;

    if (szValue.IsEmpty())
        return;

    //if we are here, szValue at least contains the flags
    swscanf ((LPCTSTR)szValue, TEXT("%x"), &flags);

    //check if there is a path too.
    index = szValue.Find(' ');  //we will find a space only if there is a path too.
    if (-1 != index)    //there is a path too.
    {
        szPath = szValue.Mid (index + 1);
        szPath.TrimLeft();
        szPath.TrimRight();
        ASSERT (!szPath.IsEmpty());
    }
}

//////////////////////////////////////////////////////////////////////////
// Given a full path name, this routine extracts its display name, viz.
// the part of which follows the final \. If there are no \'s in the
// full name, then it sets the display name to the full name
//////////////////////////////////////////////////////////////////////////
void ExtractDisplayName (const CString& szFullname, CString& szDisplayname)
{
    CString szName;
    szName = szFullname;
    //first get rid of any trailing spaces; this might happen in cases
    //where one is trying to create a shortcut to a network drive and
    //when resolved to a UNC path, it yields a path ending in a slash

    //reverse the string so that any trailing slashes will now be at the
    //head of the string
    szName.MakeReverse();
    //get rid of the leading slashes and spaces of the reversed string
    szName = szName.Mid ((szName.SpanIncluding(TEXT("\\ "))).GetLength());
    //reverse the string again and we will have a string without
    //any trailing '\' or ' '
    szName.MakeReverse();

    //with the trailing '\' and spaces removed, we can go about the
    //business of getting the display name

    //if \ cannot be found, ReverseFind returns -1 which gives 0 on adding
    //1, therefore szDisplayname gets the entire name if no \ is found.
    szDisplayname = szName.Mid (szName.ReverseFind('\\') + 1);
}

//+--------------------------------------------------------------------------
//
//  Function:   SplitProfileString
//
//  Synopsis:   This function takes in a string of the type key=value and
//              extracts the key and the value from it.
//
//  Arguments:  [in] szPair : the key value pair
//              [out] szKey : the key
//              [out] szValue : the value
//
//  Returns:    S_OK : if everything goes well.
//              E_FAIL: if the '=' sign cannot be found
//
//  History:    9/28/1998  RahulTh  created
//
//  Notes:
//
//---------------------------------------------------------------------------
HRESULT SplitProfileString (CString szPair, CString& szKey, CString& szValue)
{
    int nEqPos;

    nEqPos = szPair.Find ('=');

    if (-1 == nEqPos)
        return E_FAIL;

    szKey = szPair.Left(nEqPos);
    szKey.TrimLeft();
    szKey.TrimRight();

    szValue = szPair.Mid (nEqPos + 1);
    szValue.TrimLeft();
    szValue.TrimRight();

    return S_OK;
}

//+--------------------------------------------------------------------------
//
//  Function:   ConvertOldStyleSection
//
//  Synopsis:   this function looks at an ini file and if does not have the
//              new ini file format, it reads the old redirect section and
//              transforms it into the new ini file format which supports
//              scaleability
//
//  Arguments:  [in] szGPTPath : the directory where the ini file resides
//              [in] pScope    : pointer to the scope pane
//
//  Returns:    S_OK if it was successful
//              an error code if it fails
//
//  History:    9/28/1998  RahulTh  created
//
//  Notes:      This function exists primarily for backward compatibility
//              with Win2K betas. Might be okay to remove it.
//
//---------------------------------------------------------------------------
HRESULT ConvertOldStyleSection (
                                const CString& szGPTPath
                                )
{
    AFX_MANAGE_STATE (AfxGetStaticModuleState());

    CString szIniFile;
    TCHAR*  lpszSection;
    TCHAR*  szEntry;
    DWORD   cbSize = 1024;
    DWORD   cbCopied;
    CString SectionEntry;
    CString Key;
    CString Dir;
    CString Value;
    CString Path;
    CString szStartMenu;
    CString szPrograms;
    CString szStartup;
    ULONG   flags;
    DWORD   Status;
    BOOL    bStatus;
    HRESULT hr;
    const TCHAR   szEveryOne[] = TEXT("s-1-1-0");

    //derive the full path of the ini file.
    szIniFile.LoadString (IDS_INIFILE);
    szIniFile = szGPTPath + '\\' + szIniFile;

    //create an empty section
    lpszSection = new TCHAR [cbSize];
    lpszSection[0] = lpszSection[1] = '\0';


    switch (CheckIniFormat (szIniFile))
    {
    case S_OK:
        //this section has already been converted
        goto ConOldStlSec_CleanupAndQuit;

    case S_FALSE:
        break;          //has the Redirect section but not the FolderStatus
                        //section, so there is processing to do.
    case REGDB_E_KEYMISSING:
        //this means that the function has neither the FolderStatus section
        //nor the Redirect section, so we just create an empty FolderStatus
        //section to make processing simpler in future.
        //ignore any errors here because they don't really cause any harm
        //however, make sure that the file is pre-created in unicode so that
        //the WritePrivateProfile* functions don't puke in ANSI
        PrecreateUnicodeIniFile ((LPCTSTR)szIniFile);
        WritePrivateProfileSection (TEXT("FolderStatus"),
                                    lpszSection,
                                    (LPCTSTR) szIniFile);
        hr = S_OK;
        goto ConOldStlSec_CleanupAndQuit;
    }

    //this means that we need to convert the section ourselves
    //first load the redirect section
    do
    {
        cbCopied = GetPrivateProfileSection (TEXT("Redirect"),
                                             lpszSection,
                                             cbSize,
                                             (LPCTSTR) szIniFile
                                             );
        if (cbSize - 2 == cbCopied)
        {
            delete [] lpszSection;
            cbSize *= 2;
            lpszSection = new TCHAR [cbSize];
            continue;
        }

        //the section has been successfully loaded if we are here.
        break;
    } while (TRUE);

    //start the conversion process:
    for (szEntry = lpszSection; *szEntry; szEntry += (lstrlen(szEntry) + 1))
    {
        SectionEntry = szEntry;
        if (FAILED(hr = SplitProfileString (SectionEntry, Key, Value)))
            goto ConOldStlSec_CleanupAndQuit;

        SplitRHS (Value, flags, Path);
        Path.TrimLeft();
        Path.TrimRight();
        if (Path.IsEmpty())
            Path = TEXT("%USERPROFILE%") + ('\\' + Key);  //we used the relative paths for keys in the old style section
        ExtractDisplayName (Key, Dir);

        //set the new flags or modify the existing flags to reflect new behavior
        szStartMenu.LoadString (IDS_STARTMENU);
        szPrograms.LoadString (IDS_PROGRAMS);
        szStartup.LoadString (IDS_STARTUP);
        if (Dir.CompareNoCase (szStartMenu) && //it is not the start menu and
            Dir.CompareNoCase (szPrograms) &&  //it is not programs and
            Dir.CompareNoCase (szStartup))  //it is not the startup folder
        {
            flags |= REDIR_SETACLS;         //apply acls. this was the default behavior earlier, but not any more
        }
        else    //it is one of start menu/programs/startup
        {
            //move contents is not allowed for start menu and its descendants
            flags &= ~REDIR_MOVE_CONTENTS;
        }

        if ((flags & REDIR_DONT_CARE) && (flags & REDIR_FOLLOW_PARENT))
        {
            //if both flags were present, this implies they are linked together
            //in the new format, in order to express this, only follow_parent
            //is required
            flags &= ~REDIR_DONT_CARE;
        }

        Value.Format (TEXT("%x"), flags);

        bStatus = WritePrivateProfileString (TEXT("FolderStatus"),
                                             Dir,
                                             Value,
                                             (LPCTSTR)szIniFile
                                             );

        if (bStatus && (!(flags & REDIR_DONT_CARE)) && (!(flags & REDIR_FOLLOW_PARENT)))
            bStatus = WritePrivateProfileString ((LPCTSTR) Dir,
                                                 szEveryOne,
                                                 (LPCTSTR) Path,
                                                 (LPCTSTR) szIniFile
                                                 );

        if (!bStatus)
        {
            Status = GetLastError();
            hr = HRESULT_FROM_WIN32 (Status);
            goto ConOldStlSec_CleanupAndQuit;
        }
    }

ConOldStlSec_CleanupAndQuit:
    delete [] lpszSection;
    return hr;
}

//+--------------------------------------------------------------------------
//
//  Function:   GetFolderIndex
//
//  Synopsis:   given the name of a folder, this function returns its index
//              in the array of CFileInfo objects in the scope pane
//
//  Arguments:  [in] szName : name of the folder
//
//  Returns:    the index of the folder or -1 if the name is invalid
//
//  History:    9/28/1998  RahulTh  created
//
//  Notes:
//
//---------------------------------------------------------------------------
LONG GetFolderIndex (const CString& szName)
{
    LONG i;
    CString szBuiltinFolder;

    for (i = IDS_DIRS_START; i < IDS_DIRS_END; i++)
    {
        szBuiltinFolder.LoadString (i);
        if (szName.CompareNoCase((LPCTSTR)szBuiltinFolder))
            break;
    }

    return GETINDEX (i);
}

//+--------------------------------------------------------------------------
//
//  Function:   CheckIniFormat
//
//  Synopsis:   this function examines the sections of an ini file to see
//              if it supports the new ini file format (that allows for
//              scaleability)
//
//  Arguments:  [in] szIniFile : the full path of the ini file
//
//  Returns:    S_OK : if it finds the FolderStatus section
//              S_FALSE : if it does not find the FolderStatus section but
//                        finds the Redirect section
//              REGDB_E_KEYMISSING : if it finds neither the FolderStatus
//                                   section nor the Redirect section
//
//  History:    9/28/1998  RahulTh  created
//
//  Notes:      this function exists for backward compatibility with Win2K
//              Betas. Might be okay to get rid of this eventually.
//
//---------------------------------------------------------------------------
HRESULT CheckIniFormat (LPCTSTR szIniFile)
{
    DWORD   cbSize = 1024;
    DWORD   cbCopied;
    TCHAR*  lpszNames;
    TCHAR*  szSectionName;
    BOOL    fHasFolderStatus = FALSE;
    BOOL    fHasRedirect = FALSE;

    do
    {
        lpszNames = new TCHAR [cbSize];
        if (! lpszNames)
            return E_OUTOFMEMORY;
        
        *lpszNames = L'\0';
        cbCopied = GetPrivateProfileSectionNames (lpszNames, cbSize, szIniFile);

        if (cbSize - 2 == cbCopied) //the buffer was not enough.
        {
            delete [] lpszNames;
            cbSize *= 2;            //increase the buffer size
            continue;
        }

        break;  //if we are here, we are done.

    } while (TRUE);

    for (szSectionName = lpszNames;
         *szSectionName;
         szSectionName += (lstrlen(szSectionName) + 1))
    {
        if (0 == lstrcmpi(TEXT("FolderStatus"), szSectionName))
        {
            fHasFolderStatus = TRUE;
            continue;
        }

        if (0 == lstrcmpi (TEXT("Redirect"), szSectionName))
        {
            fHasRedirect = TRUE;
            continue;
        }
    }

    //cleanup dynamically allocated memory before quitting
    delete [] lpszNames;

    if (fHasFolderStatus)
        return S_OK;

    //if we are here, the file does not have the FolderStatus section
    if (fHasRedirect)
        return S_FALSE;

    //if we are here, then the file has neither the folder status section
    //nor the Redirect section
    return REGDB_E_KEYMISSING;
}

//+--------------------------------------------------------------------------
//
//  Function:   GetIntfromUnicodeString
//
//  Synopsis:   converts a unicode string into an integer
//
//  Arguments:  [in] szNum : the number represented as a unicode string
//              [in] Base : the base in which the resultant int is desired
//              [out] pValue : pointer to the integer representation of the
//                             number
//
//  Returns:    STATUS_SUCCESS if successful.
//              or some other error code
//
//  History:    9/29/1998  RahulTh  created
//
//  Notes:
//
//---------------------------------------------------------------------------
NTSTATUS GetIntFromUnicodeString (const WCHAR* szNum, ULONG Base, PULONG pValue)
{
    CString StrNum;
    UNICODE_STRING StringW;
    size_t len;
    NTSTATUS Status;

    StrNum = szNum;
    len = StrNum.GetLength();
    StringW.Length = len * sizeof(WCHAR);
    StringW.MaximumLength = sizeof(WCHAR) * (len + 1);
    StringW.Buffer = StrNum.GetBuffer(len);

    Status = RtlUnicodeStringToInteger (&StringW, Base, pValue);

    return Status;
}


//+--------------------------------------------------------------------------
//
//  Function:   GetUNCPath
//
//  Synopsis:   this function tries to retrieve the UNC path of an item
//              given its PIDL
//
//  Arguments:  [in] lpszPath : the full path to the selected file.
//              [out] szUNC : the UNC path of the item
//
//  Returns:    NO_ERROR if the conversion was successful.
//              other error codes if not...
//
//  History:    10/1/1998  RahulTh  created
//              4/12/1999  RahulTh  added error code. changed params.
//                                  (item id list is no longer passed in)
//
//  Notes:      if this function is unsuccessful, then szUNC will contain an
//              empty string
//
//---------------------------------------------------------------------------
DWORD GetUNCPath (LPCTSTR lpszPath, CString& szUNC)
{
    TCHAR* lpszUNCName;
    UNIVERSAL_NAME_INFO* pUNCInfo;
    DWORD lBufferSize;
    DWORD retVal = NO_ERROR;

    szUNC.Empty();  //precautionary measures

    //we have a path, now we shall try to get a UNC path
    lpszUNCName = new TCHAR[MAX_PATH];
    pUNCInfo = (UNIVERSAL_NAME_INFO*)lpszUNCName;
    lBufferSize = MAX_PATH * sizeof(TCHAR);
    retVal = WNetGetUniversalName (lpszPath,
                                   UNIVERSAL_NAME_INFO_LEVEL,
                                   (LPVOID)pUNCInfo,
                                   &lBufferSize);
    if (ERROR_MORE_DATA == retVal)  //MAX_PATH was insufficient to hold the UNC path
    {
        delete [] lpszUNCName;
        lpszUNCName = new TCHAR[lBufferSize/(sizeof(TCHAR)) + 1];
        pUNCInfo = (UNIVERSAL_NAME_INFO*)lpszUNCName;
        retVal = WNetGetUniversalName (lpszPath,
                                       UNIVERSAL_NAME_INFO_LEVEL,
                                       (LPVOID)pUNCInfo,
                                       &lBufferSize);
    }

    //at this point we may or may not have a UNC path.
    //if we do, we return that, or we return whatever we already have
    if (NO_ERROR == retVal)
        szUNC = pUNCInfo->lpUniversalName;

    delete [] lpszUNCName;

    return retVal;
}

//+--------------------------------------------------------------------------
//
//  Function:   BrowseCallbackProc
//
//  Synopsis:   the callback function for SHBrowseForFolder
//
//  Arguments:  see Platform SDK
//
//  Returns:    see Platform SDK
//
//  History:    4/9/1999  RahulTh  created
//
//  Notes:
//
//---------------------------------------------------------------------------
int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg,
                                LPARAM lParam, LPARAM lpData
                                )
{
    CString * pszData;
    CString   szStart;
    int       index;
    LPITEMIDLIST    lpidl = NULL;
    TCHAR      lpszPath [MAX_PATH];

    pszData = (CString *) lpData;

    switch (uMsg)
    {
    case BFFM_INITIALIZED:
        if (pszData)
        {
            szStart = *pszData;
            szStart.TrimRight();
            szStart.TrimLeft();
            if (! szStart.IsEmpty())
            {
                index = szStart.ReverseFind (L'\\');
                if (-1 != index && index > 1)
                    szStart = szStart.Left (index);
                SendMessage (hwnd, BFFM_SETSELECTION, TRUE,
                             (LPARAM)(LPCTSTR)szStart);
            }
        }
        break;
    case BFFM_SELCHANGED:
        //we need to check if we can get the full path to the selected folder.
        //e.g. if the full path exceeds MAX_PATH, we cannot obtain the path
        //from the item id list. if we cannot get the path, we should not
        //enable the OK button. So, over here, as a precaution, we first
        //disable the OK button. We will enable it only after we get the path.
        SendMessage (hwnd, BFFM_ENABLEOK, FALSE, FALSE);
        if (SHGetPathFromIDList((LPCITEMIDLIST)lParam, lpszPath))
        {
            //set the path into the data member and enable the OK button
            if (lpData)
            {
                SendMessage (hwnd, BFFM_ENABLEOK, TRUE, TRUE);
                *((CString *) lpData) = lpszPath;
            }
        }
        break;
    default:
        break;
    }

    return 0;
}

//+--------------------------------------------------------------------------
//
//  Function:   PrecreateUnicodeIniFile
//
//  Synopsis:   The WritePrivateProfile* functions do not write in unicode
//              unless the file already exists in unicode format. Therefore,
//              this function is used to precreate a unicode file so that
//              the WritePrivateProfile* functions can preserve the unicodeness.
//
//  Arguments:  [in] lpszFilePath : the full path of the ini file.
//
//  Returns:    ERROR_SUCCESS if successful.
//              an error code otherwise.
//
//  History:    7/9/1999  RahulTh  created
//
//  Notes:
//
//---------------------------------------------------------------------------
DWORD PrecreateUnicodeIniFile (LPCTSTR lpszFilePath)
{
    HANDLE      hFile;
    WIN32_FILE_ATTRIBUTE_DATA   fad;
    DWORD       Status = ERROR_ALREADY_EXISTS;
    DWORD       dwWritten;

    if (!GetFileAttributesEx (lpszFilePath, GetFileExInfoStandard, &fad))
    {
        if (ERROR_FILE_NOT_FOUND == (Status = GetLastError()))
        {
            hFile = CreateFile(lpszFilePath, GENERIC_WRITE, 0, NULL,
                               CREATE_NEW, FILE_ATTRIBUTE_HIDDEN, NULL);

            if (hFile != INVALID_HANDLE_VALUE)
            {
                //add the unicode marker to the beginning of the file
                //so that APIs know for sure that it is a unicode file.
                WriteFile(hFile, L"\xfeff\r\n", 3 * sizeof(WCHAR),
                          &dwWritten, NULL);
                //add some unicode characters to the file.
                WriteFile(hFile, L"     \r\n", 7 * sizeof(WCHAR),
                          &dwWritten, NULL);
                CloseHandle(hFile);
                Status = ERROR_SUCCESS;
            }
            else
            {
                Status = GetLastError();
            }
        }
    }

    return Status;
}

//+--------------------------------------------------------------------------
//
//  Function:   IsValidPrefix
//
//  Synopsis:   Given a path, this function determines if it is a valid prefix
//
//  Arguments:  [in] pathType : the type of the path
//              [in] pwszPath : the supplied path
//
//  Returns:    TRUE: if the prefix is valid.
//              FALSE: otherwise
//
//  History:    3/14/2000  RahulTh  created
//
//  Notes:      A valid prefix is either a non-unc path or a UNC path which
//              has at least the server and the share component. It must also
//              be a non-empty path.
//
//---------------------------------------------------------------------------
BOOL IsValidPrefix (UINT pathType, LPCTSTR pwszPath)
{
    CString       szPath;
    const WCHAR * pwszProcessedPath;

    if (! pwszPath || L'\0' == *pwszPath)
        return FALSE;

    szPath = pwszPath;
    szPath.TrimLeft();
    szPath.TrimRight();
    szPath.TrimRight(L'\\');
    pwszProcessedPath = (LPCTSTR) szPath;

    if (PathIsUNC ((LPCTSTR) szPath))
    {
        // Make sure it has both the server and the share component
        if (lstrlen (pwszProcessedPath) <= 2 ||
            L'\\' != pwszProcessedPath[0] ||
            L'\\' != pwszProcessedPath[1] ||
            NULL ==  wcschr (&pwszProcessedPath[2], L'\\'))
        {
            return FALSE;
        }
    }

    //
    // If we are here, we just need to make sure that the path does not contain
    // any environment variables -- if it is not IDS_SPECIFIC_PATH
    //
    if (pathType != IDS_SPECIFIC_PATH &&
        NULL != wcschr(pwszProcessedPath, L'%'))
    {
        return FALSE;
    }

    // If we make it up to here, then the path is a valid prefix.

    return TRUE;
}

//+--------------------------------------------------------------------------
//
//  Function:   AlwaysShowMyPicsNode
//
//  Synopsis:   In WindowsXP, we now show the MyPics node in the scope pane
//              only if My Pics does not follow My Docs. However, if required
//              this MyPics can always be made visible by setting a reg. value
//              under HKLM\Software\Policies\Microsoft\Windows\System called 
//              FRAlwaysShowMyPicsNode. This is a DWORD value and if set to
//              non-zero, the MyPics node will always be displayed.
//
//  Arguments:  none.
//
//  Returns:    TRUE : if the value was found in the registry and was non-zero.
//              FALSE : otherwise.
//
//  History:    4/10/2001  RahulTh  created
//
//  Notes:      Note: In case of errors, the default value of FALSE is returned.
//
//---------------------------------------------------------------------------
BOOL AlwaysShowMyPicsNode (void)
{
    BOOL    bAlwaysShowMyPics = FALSE;
    DWORD   dwValue = 0;
    DWORD   dwType = REG_DWORD;
    DWORD   dwSize = sizeof(DWORD);
    HKEY    hKey = NULL;
    
    if (ERROR_SUCCESS != RegOpenKey (HKEY_LOCAL_MACHINE,
                                     TEXT("Software\\Policies\\Microsoft\\Windows\\System"),
                                     &hKey)
        )
    {
        return bAlwaysShowMyPics;
    }
    
    if (ERROR_SUCCESS == RegQueryValueEx (hKey,
                                          TEXT("FRAlwaysShowMyPicsNode"),
                                          NULL,
                                          &dwType,
                                          (LPBYTE)(&dwValue),
                                          &dwSize)
        )
    {
        if (REG_DWORD == dwType && dwValue)
            bAlwaysShowMyPics = TRUE;
    }
    
    RegCloseKey(hKey);
    
    return bAlwaysShowMyPics;
}

//+--------------------------------------------------------------------------
//
//  Function:   CreateThemedPropertyPage
//
//  Synopsis:   Helper function to make sure that property pages put up
//              by the snap-in are themed.
//
//  Arguments:
//
//  Returns:
//
//  History:    4/20/2001  RahulTh  created
//
//  Notes:
//
//---------------------------------------------------------------------------
HPROPSHEETPAGE CreateThemedPropertySheetPage(AFX_OLDPROPSHEETPAGE* psp)
{
    PROPSHEETPAGE_V3 sp_v3 = {0};
    CopyMemory (&sp_v3, psp, psp->dwSize);
    sp_v3.dwSize = sizeof(sp_v3);

    return (::CreatePropertySheetPage (&sp_v3));
}