/*-----------------------------------------------------------------------------
Copyright (c) 1996  Microsoft Corporation

Module Name:  ckcnv.cxx

Abstract:
  Upgrades cookies to present urlcache format by enumerating cookie files in the
  cache cookies directory and creates cookie cache index entries in the format of 
  the current wininet.dll. 
    
Author:
    Adriaan Canter (adriaanc) 09-Jan-1997
        Created

    Adriaan Canter (adriaanc) 01-Feb-1997    
        Modified for per-user caches. The class CCookieLoader definition 
        can now be pasted into the urlcache build without re-definition
        and work correctly, as long as the HKLM and HKCU cache keys are
        not modified. BUGBUG - do this.

-----------------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <wininet.h>
#include "cachedef.h"

#define INET_ASSERT(condition) Assert(condition)

/*-----------------------------------------------------------------------------
    GetHKLMHistoryDirectory
  ----------------------------------------------------------------------------*/
DWORD GetHKLMHistoryDirectory(CHAR *szHistoryDirectory)
{
    HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
    DWORD dwError, dwKeyType, cbKeyLen = MAX_PATH;

    // Skip the level of indirection for url history.
    if (dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
        "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Url History",
        NULL, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
        goto exit;
    
    if (dwError = RegQueryValueEx(hKey, CACHE_DIRECTORY_VALUE, NULL, &dwKeyType, 
        (LPBYTE) szHistoryDirectory, &cbKeyLen) != ERROR_SUCCESS)
        goto exit;


exit:
    if (hKey != INVALID_HANDLE_VALUE)
        CloseHandle(hKey);

    return dwError;
}

/*-----------------------------------------------------------------------------
    GetHKCUHistoryDirectory
  ----------------------------------------------------------------------------*/
DWORD GetHKCUHistoryDirectory(CHAR *szHistoryDirectory)
{
    HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
    DWORD dwError, dwKeyType, cbKeyLen = MAX_PATH;

    CHAR szHistoryDirRegKey[MAX_PATH];
    strcpy(szHistoryDirRegKey, OLD_CACHE_KEY);
    strcat(szHistoryDirRegKey, "\\");
    strcat(szHistoryDirRegKey, HISTORY_PATH_KEY);

    if (dwError = RegOpenKeyEx(HKEY_CURRENT_USER, 
        szHistoryDirRegKey, NULL, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
        goto exit;
    
    if (dwError = RegQueryValueEx(hKey, CACHE_PATH_VALUE, NULL, &dwKeyType, 
        (LPBYTE) szHistoryDirectory, &cbKeyLen) != ERROR_SUCCESS)
        goto exit;

exit:
    if (hKey != INVALID_HANDLE_VALUE)
        CloseHandle(hKey);

    return dwError;
}


/*-----------------------------------------------------------------------------
    GetHKLMCookiesDirectory
  ----------------------------------------------------------------------------*/
DWORD GetHKLMCookiesDirectory(CHAR *szCookiesDirectory)
{
    HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
    DWORD dwError, dwKeyType, cbKeyLen = MAX_PATH;

    CHAR szCookiesDirRegKey[MAX_PATH];
    strcpy(szCookiesDirRegKey, OLD_CACHE_KEY);
    strcat(szCookiesDirRegKey, "\\");
    strcat(szCookiesDirRegKey, CACHE_SPECIAL_PATHS_KEY);
    strcat(szCookiesDirRegKey, "\\");
    strcat(szCookiesDirRegKey, COOKIE_PATH_KEY);

    if (dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
        szCookiesDirRegKey, NULL, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
        goto exit;
    
    if (dwError = RegQueryValueEx(hKey, CACHE_DIRECTORY_VALUE, NULL, &dwKeyType, 
        (LPBYTE) szCookiesDirectory, &cbKeyLen) != ERROR_SUCCESS)
        goto exit;

exit:
    if (hKey != INVALID_HANDLE_VALUE)
        CloseHandle(hKey);

    return dwError;
}

/*-----------------------------------------------------------------------------
    GetHKCUCookiesDirectory
  ----------------------------------------------------------------------------*/
DWORD GetHKCUCookiesDirectory(CHAR *szCookiesDirectory)
{
    HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
    DWORD dwError, dwKeyType, cbKeyLen = MAX_PATH;

    CHAR szCookiesDirRegKey[MAX_PATH];
    strcpy(szCookiesDirRegKey, OLD_CACHE_KEY);
    strcat(szCookiesDirRegKey, "\\");
    strcat(szCookiesDirRegKey, COOKIE_PATH_KEY);

    if (dwError = RegOpenKeyEx(HKEY_CURRENT_USER, 
        szCookiesDirRegKey, NULL, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
        goto exit;
    
    if (dwError = RegQueryValueEx(hKey, CACHE_PATH_VALUE, NULL, &dwKeyType, 
        (LPBYTE) szCookiesDirectory, &cbKeyLen) != ERROR_SUCCESS)
        goto exit;

exit:
    if (hKey != INVALID_HANDLE_VALUE)
        CloseHandle(hKey);

    return dwError;
}

/*-----------------------------------------------------------------------------
    GetHKLMContentDirectory
  ----------------------------------------------------------------------------*/
DWORD GetHKLMContentDirectory(CHAR *szContentDirectory)
{
    HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
    DWORD dwError, dwKeyType, cbKeyLen = MAX_PATH;

    CHAR szContentDirRegKey[MAX_PATH];
    strcpy(szContentDirRegKey, CACHE_PATHS_FULL_KEY);

    if (dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
        szContentDirRegKey, NULL, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
        goto exit;
    
    if (dwError = RegQueryValueEx(hKey, CACHE_DIRECTORY_VALUE, NULL, &dwKeyType, 
        (LPBYTE) szContentDirectory, &cbKeyLen) != ERROR_SUCCESS)
        goto exit;

exit:
    if (hKey != INVALID_HANDLE_VALUE)
        CloseHandle(hKey);

    return dwError;
}

/*-----------------------------------------------------------------------------
    GetHKCUContentDirectory
  ----------------------------------------------------------------------------*/
DWORD GetHKCUContentDirectory(CHAR *szContentDirectory)
{
    HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
    DWORD dwError, dwKeyType, cbKeyLen = MAX_PATH;

    CHAR szContentDirRegKey[MAX_PATH];
    strcpy(szContentDirRegKey, OLD_CACHE_KEY);
    strcat(szContentDirRegKey, "\\");
    strcat(szContentDirRegKey, CONTENT_PATH_KEY);

    if (dwError = RegOpenKeyEx(HKEY_CURRENT_USER, 
        szContentDirRegKey, NULL, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
        goto exit;
    
    if (dwError = RegQueryValueEx(hKey, CACHE_PATH_VALUE, NULL, &dwKeyType, 
        (LPBYTE) szContentDirectory, &cbKeyLen) != ERROR_SUCCESS)
        goto exit;

exit:
    if (hKey != INVALID_HANDLE_VALUE)
        CloseHandle(hKey);

    return dwError;
}



/*-----------------------------------------------------------------------------
    class CCookieLoader

    Class used to perform cookie conversion
  ----------------------------------------------------------------------------*/
class CCookieLoader
{
public:
    CHAR* ParseNextCookie(CHAR*, CHAR**, FILETIME*, FILETIME*);    
    DWORD LoadCookies(BOOL);
};


// Debug assert code.
#if DBG
#define Assert(Predicate) \
    { \
        if (!(Predicate)) \
            AssertFailed( #Predicate, __FILE__, __LINE__, NULL ); \
    }

VOID
AssertFailed(
    LPSTR FailedAssertion,
    LPSTR FileName,
    DWORD LineNumber,
    LPSTR Message
    )
{

    printf("Assert @ %s \n", FailedAssertion );
    printf("Assert Filename, %s \n", FileName );
    printf("Line Num. = %ld.\n", LineNumber );
    printf("Message is %s\n", Message );

    DebugBreak();
}
#else
#define Assert(_x_)
#endif // DBG


/*-----------------------------------------------------------------------------
    CCookieLoader::ParseNextCookie

    Upgrades cookies from Cache Version 3.2 to Cache Version 4.0 
  ----------------------------------------------------------------------------*/
CHAR* CCookieLoader::ParseNextCookie(CHAR* ptr, CHAR** ppszHash,
    FILETIME* pftExpire, FILETIME* pftLast)
{
    CHAR *pszName, *pszValue, *pszFlags,
         *pszExpireTimeLow, *pszExpireTimeHigh,
         *pszLastTimeHigh,  *pszLastTimeLow,
         *pszDelimiter, *pszNextCookie;
   
    __try
    {
        // Get the first token (cookie name).
        pszName           = strtok(ptr, "\n");
        if (!pszName)                               // Cookie name.
        {
            // Normal termination of the parse.
            pszNextCookie = 0;
            goto exit;
        }

        // Parse the rest of the cookie
        pszValue          = strtok(NULL, "\n");      // Cookie value.
        *ppszHash         = strtok(NULL, "\n");      // Combo of domain and path.
        pszFlags          = strtok(NULL, "\n");      // Cookie flags.
        pszExpireTimeLow  = strtok(NULL, "\n");      // Expire time.
        pszExpireTimeHigh = strtok(NULL, "\n");             
        pszLastTimeLow    = strtok(NULL, "\n");      // Last Modified time.
        pszLastTimeHigh   = strtok(NULL, "\n");
        pszDelimiter      = strtok(NULL, "\n");      // Delimiter should be "*"

    
        // Abnormal termination of parse.
        if (!pszDelimiter || pszDelimiter[0] != '*')
        {
            INET_ASSERT(FALSE);
            pszNextCookie = 0;
            goto exit;
        }

        // Set the times.
        pftExpire->dwLowDateTime  = atoi(pszExpireTimeLow);
        pftExpire->dwHighDateTime = atoi(pszExpireTimeHigh);
        pftLast->dwLowDateTime    = atoi(pszLastTimeLow);
        pftLast->dwHighDateTime   = atoi(pszLastTimeHigh);        

        pszNextCookie = pszDelimiter+2;
    }
    
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        INET_ASSERT(FALSE);
        pszNextCookie = 0;
        goto exit;
    }

exit:
    return pszNextCookie;

}


/*-----------------------------------------------------------------------------
    CCookieLoader::LoadCookies
  ----------------------------------------------------------------------------*/
DWORD CCookieLoader::LoadCookies(BOOL fConvertToPerUser)
{
    HANDLE             hFind = INVALID_HANDLE_VALUE;
    HANDLE             hFile = INVALID_HANDLE_VALUE;

    FILETIME           ftExpire, ftLast;
    CHAR               szCookieFileName        [MAX_PATH],
                       szCookieFileNamePattern [MAX_PATH],
                       szHKLMCookiesPath       [MAX_PATH],
                       szHKCUCookiesPath       [MAX_PATH],
                       szCookieName            [MAX_PATH],    
                       szHKLMCookieFileName    [MAX_PATH],
                       szHKCUCookieFileName    [MAX_PATH];
                    
    CHAR               *pszHash, *ptr, *pszCookiesPath,
                       *pszCurrentCookie, *szBuffer;

    WIN32_FIND_DATA    FindData;
    BOOL               bReturn;
    DWORD              cbRead = 0, dwError = ERROR_SUCCESS;

    // Data for a single cookie should fit in 2 pages.
    BYTE bCacheEntryInfoBuffer[2 * PAGE_SIZE];
    INTERNET_CACHE_ENTRY_INFO *pCacheEntryInfo;
    DWORD cbCacheEntryInfoBuffer;

    // Strictly enforced syntax: requires this to be initialized
    // before the try block is entered.
    szBuffer = 0;

    __try
    {   

        // Check to see if we are upgrading cookies
        // from local machine to per user.
        if (fConvertToPerUser)
        {
            DWORD cb = MAX_PATH;
            CHAR szUserName[MAX_PATH];

            // We are converting cookies from HKLM to HKCU.
            // This is done by enumerating the user's cookies
            // files and copying them to the per-user diretory.            
            // Once this is accomplished, cookie converting will
            // proceed normally.

            // Get the cookies directory as specified by HKLM.
            if (dwError = GetHKLMCookiesDirectory(szHKLMCookiesPath) != ERROR_SUCCESS)
            {
                INET_ASSERT(FALSE);
                goto exit;
            }
            strcpy(szCookieFileNamePattern, szHKLMCookiesPath);

            // Get the cookies directory as specified by HKCU.
            if (dwError = GetHKCUCookiesDirectory(szHKCUCookiesPath) != ERROR_SUCCESS)
            {
                INET_ASSERT(FALSE);
                goto exit;
            }

            // Get the current user name.
            GetUserName(szUserName, &cb);

            // szCookieFileNamePattern will look like c:\winnt\cookies\joeuser@*.txt
            strcat(szCookieFileNamePattern, "\\");
            strcat(szCookieFileNamePattern, szUserName);
            strcat(szCookieFileNamePattern, "@*.txt");
        
            // Enumerate the users cache files        
            hFind = FindFirstFile(szCookieFileNamePattern, &FindData);
            if (hFind == INVALID_HANDLE_VALUE)
            {
                // OK, No cookie files to upgrade.
                dwError = ERROR_SUCCESS;
                goto exit;
            }    
        
            // One or more cookie files exist.
            do
            {
                // Construct absolute path from HKLM to cookies file.
                strcpy(szHKLMCookieFileName, szHKLMCookiesPath);
                strcat(szHKLMCookieFileName, "\\");
                strcat(szHKLMCookieFileName, FindData.cFileName);
                
                // Construct absolute path from HKCU to cookies file.
                strcpy(szHKCUCookieFileName, szHKCUCookiesPath);
                strcat(szHKCUCookieFileName, "\\");
                strcat(szHKCUCookieFileName, FindData.cFileName);
            
                // Copy the file to the per-user directory.
                CopyFile(szHKLMCookieFileName, szHKCUCookieFileName, TRUE);

            } while (FindNextFile(hFind, &FindData)); 
        
            // Close the Find handle.
            if (hFind != INVALID_HANDLE_VALUE)
            {
                FindClose(hFind);        
                hFind = INVALID_HANDLE_VALUE;
            }

        } // Per-user upgrade.
        else
        {
            // No per-user upgrade. szCookieFileNamePattern will look like
            // c:\winnt\cookies\*@*.txt or c:\winnt\profiles\joeuser\cookies\*@*.txt.
            GetHKLMCookiesDirectory(szHKLMCookiesPath);
            strcpy(szCookieFileNamePattern, szHKLMCookiesPath);
            strcat(szCookieFileNamePattern, "\\*@*.txt");
        }

        // We now have the appropriate cookie filename pattern, also need a copy
        // of the cookies directory associated with the current user.
        pszCookiesPath = (fConvertToPerUser ? szHKCUCookiesPath : szHKLMCookiesPath);
        
        // Enumerate the cache files.
        hFind = FindFirstFile(szCookieFileNamePattern, &FindData);
        if (hFind == INVALID_HANDLE_VALUE)
        {
            // OK, No cookies files to upgrade.
            // BUGBUG - should we verify this?
            dwError = ERROR_SUCCESS;
            goto exit;
        }    

        // One or more cookie files exist.
        do
        {
            // Construct absolute path to cookie file.
            strcpy(szCookieFileName, pszCookiesPath);
            strcat(szCookieFileName, "\\");
            strcat(szCookieFileName, FindData.cFileName);
            
            // Open the cookie file.
            hFile = CreateFile(
                    szCookieFileName,       // Absolute path to cookies file.
                    GENERIC_READ,           // Read only.
                    FILE_SHARE_READ,        // Share.
                    0,                      // Security Attribute (ignored in W95).
                    OPEN_EXISTING,          // Fail if doesn't exist.
                    FILE_ATTRIBUTE_NORMAL,  // No special attributes.
                    0                       // Attribute template.
                    );                   

            // File handle must be valid.
            if (hFile != INVALID_HANDLE_VALUE)
            {
                // Allocate memory for cookie file contents.
                // BUGBUG - put an upper limit on this? -> 
                // 300 cookies * 4k/cookie = 1200k plus sundry.
                szBuffer = new CHAR[FindData.nFileSizeLow + 1];
                if (!szBuffer)
                {
                    dwError = ERROR_NOT_ENOUGH_MEMORY;
                    goto exit;
                }

                // Read the file into memory.
                bReturn = ReadFile(hFile, szBuffer, FindData.nFileSizeLow, &cbRead, NULL);

                // ReadFile must be successful.
                INET_ASSERT(bReturn);
                if (bReturn)
                {
                    // Null terminate buffer.
                    szBuffer[cbRead] = '\0';

                    // Parse each cookie out of the buffer.
                    pszCurrentCookie = szBuffer;
                    while (pszCurrentCookie = ParseNextCookie(pszCurrentCookie, 
                        &pszHash, &ftExpire, &ftLast))
                    {
                        // Construct the cookie name from the following strings:
                        // FindData.cFileName is like "user@foobar.txt"
                        // pszHash is like "foobar.com/"
                        // szCookieName should then be "Cookie:user@foobar.com/"            
                        strcpy(szCookieName, COOKIE_PREFIX);
                        strcat(szCookieName, FindData.cFileName);
                        ptr = strstr(szCookieName, "@");
                        strcpy(ptr+1, pszHash);

                        // Check to see if an earlier version of this cookie
                        // has already been added to the cache index file.
                        BOOL fAddToCache = TRUE;
                        pCacheEntryInfo = (INTERNET_CACHE_ENTRY_INFO*) bCacheEntryInfoBuffer;
                        cbCacheEntryInfoBuffer = sizeof(bCacheEntryInfoBuffer);

                        dwError = GetUrlCacheEntryInfo(szCookieName, pCacheEntryInfo, 
                            &cbCacheEntryInfoBuffer);

                        if (dwError == ERROR_SUCCESS 
                            && CompareFileTime(&pCacheEntryInfo->LastModifiedTime, &ftLast) > 0)
                            fAddToCache = FALSE;

                        if (fAddToCache)
                        {
                            // Either this cookie was not found in the index file or 
                            // it was found and the last modified time on it is 
                            // less than the currently parsed cookie. Proceed
                            // to add this cookie to the index file.
                            BOOL bCommit;
                            bCommit = CommitUrlCacheEntry(
                                szCookieName,           // cookie:user@foobar.com.
                                szCookieFileName,       // c:\winnt\cookies\user@foobar.txt.
                                ftExpire,               // Expire time.
                                ftLast,                 // Last modified time.
                                0,                      // CacheEntryType.
                                0,                      // HeaderInfo.
                                0,                      // HeaderSize.
                                0,                      // FileExtension.
                                0);                     // Reserved.

                            INET_ASSERT(bCommit);
                        } 

                    } // Successful next cookie field.

                } // Successful read.

                // Done with this cookie file. Delete the buffer.
                delete [] szBuffer;

                // And close the file
                CloseHandle(hFile);
                hFile = INVALID_HANDLE_VALUE;

            } // File handle is valid.
                    
        } while (FindNextFile(hFind, &FindData)); 

        
        // No more cookie files or an error occured.        
        if ((dwError = GetLastError()) != ERROR_NO_MORE_FILES)
            goto exit;

        // Normal termination.
        dwError = ERROR_SUCCESS;

    exit:

        // Close the file handle.
        if (hFile != INVALID_HANDLE_VALUE)
            CloseHandle(hFile);

        // Close the Find handle.
        if (hFind != INVALID_HANDLE_VALUE)
            FindClose(hFind);

        return dwError;

    } // try

    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        // Cleanup.
        delete [] szBuffer;

        if (hFind != INVALID_HANDLE_VALUE)
            FindClose(hFind);
        if (hFile != INVALID_HANDLE_VALUE)
            CloseHandle(hFile);
                
        INET_ASSERT(FALSE);
        dwError = ERROR_EXCEPTION_IN_SERVICE;
        return dwError;
    }

}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    DWORD dwError;
    CHAR szFilename[MAX_PATH];
    CHAR szHistoryIndexFile[MAX_PATH];
    CHAR szCookiesIndexFile[MAX_PATH];
    CHAR szContentIndexFile[MAX_PATH];
    CCookieLoader cc;

    __try
    {
    
        // Convert cookies. Note, for ckcnv, we never
        // upgrade to per-user.
        dwError = cc.LoadCookies(FALSE);

        // Nuke all index.dat files so that re-installations will init properly.
        // Take care of HKLM entries only.

        // Content
        if (GetHKLMContentDirectory(szContentIndexFile) == ERROR_SUCCESS)
        {
            strcat(szContentIndexFile, "\\");
            strcat(szContentIndexFile, MEMMAP_FILE_NAME);
            DeleteFile(szContentIndexFile);
        }

        // Cookies
        if (GetHKLMCookiesDirectory(szCookiesIndexFile) == ERROR_SUCCESS)
        {
            strcat(szCookiesIndexFile, "\\");
            strcat(szCookiesIndexFile, MEMMAP_FILE_NAME);
            DeleteFile(szCookiesIndexFile);
        }

        // History
        if (GetHKLMHistoryDirectory(szHistoryIndexFile) == ERROR_SUCCESS)
        {
            strcat(szHistoryIndexFile, "\\");
            strcat(szHistoryIndexFile, MEMMAP_FILE_NAME);
            DeleteFile(szHistoryIndexFile);
        }

        // See if we're supposed to delete this
        // executable after the user reboots.
        if (!_strnicmp(lpCmdLine, "/D", sizeof("/D")))
        {

            // Got this filename?
            if (GetModuleFileName(NULL, szFilename, MAX_PATH))
            {
                OSVERSIONINFO osVersionInfo;
                osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        
                if (GetVersionEx(&osVersionInfo))
                {
                    // Two different methods of deleting this file
                    // depending on the platform ID.
                    if (osVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
                    {
                        // Platform is Windows NT.
                        MoveFileEx(szFilename, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
                    }
                    else
                    {
                        // Platform is Windows 95.
                        CHAR szString[MAX_PATH];
                        strcpy(szString, "NUL=");
                        strcat(szString, szFilename);
                        WritePrivateProfileSection("Rename", szString, "wininit.ini");

                    }
                }
            }
        }
    }
    

    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        INET_ASSERT(FALSE);
        dwError = ERROR_EXTENDED_ERROR;
    }

    return (dwError == ERROR_SUCCESS ? 0 : 1);

}