/*
 * Process Hacker Plugins -
 *   Update Checker Plugin
 *
 * Copyright (C) 2011-2016 dmex
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "updater.h"
#include <shellapi.h>
#include <shlobj.h>

static HANDLE UpdateDialogThreadHandle = NULL;
static HWND UpdateDialogHandle = NULL;
static PH_EVENT InitializedEvent = PH_EVENT_INIT;

mxml_type_t QueryXmlDataCallback(
    _In_ mxml_node_t *node
    )
{
    return MXML_OPAQUE;
}

BOOLEAN LastUpdateCheckExpired(
    VOID
    )
{
    ULONG64 lastUpdateTimeTicks = 0;
    LARGE_INTEGER currentUpdateTimeTicks;
    PPH_STRING lastUpdateTimeString;

    // Get the last update check time 
    lastUpdateTimeString = PhGetStringSetting(SETTING_NAME_LAST_CHECK);
    PhStringToInteger64(&lastUpdateTimeString->sr, 0, &lastUpdateTimeTicks);

    // Query the current time
    PhQuerySystemTime(&currentUpdateTimeTicks);

    // Check if the last update check was more than 7 days ago
    if (currentUpdateTimeTicks.QuadPart - lastUpdateTimeTicks >= 7 * PH_TICKS_PER_DAY)
    {
        PPH_STRING currentUpdateTimeString = PhFormatUInt64(currentUpdateTimeTicks.QuadPart, FALSE);

        // Save the current time
        PhSetStringSetting2(SETTING_NAME_LAST_CHECK, &currentUpdateTimeString->sr);

        // Cleanup
        PhDereferenceObject(currentUpdateTimeString);
        PhDereferenceObject(lastUpdateTimeString);
        return TRUE;
    }

    // Cleanup
    PhDereferenceObject(lastUpdateTimeString);
    return FALSE;
}

PPH_STRING UpdateVersionString(
    VOID
    )
{
    ULONG majorVersion;
    ULONG minorVersion;
    ULONG revisionVersion;
    PPH_STRING currentVersion = NULL;
    PPH_STRING versionHeader = NULL;

    PhGetPhVersionNumbers(
        &majorVersion,
        &minorVersion,
        NULL,
        &revisionVersion
        );

    currentVersion = PhFormatString(
        L"%lu.%lu.%lu",
        majorVersion,
        minorVersion,
        revisionVersion
        );

    if (currentVersion)
    {
        versionHeader = PhConcatStrings2(L"ProcessHacker-Build: ", currentVersion->Buffer);
        PhDereferenceObject(currentVersion);
    }

    return versionHeader;
}

PPH_STRING UpdateWindowsString(
    VOID
    )
{
    static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion");

    HANDLE keyHandle = NULL;
    PPH_STRING buildLabHeader = NULL;

    if (NT_SUCCESS(PhOpenKey(
        &keyHandle,
        KEY_READ,
        PH_KEY_LOCAL_MACHINE,
        &keyName,
        0
        )))
    {
        PPH_STRING buildLabString;
        
        if (buildLabString = PhQueryRegistryString(keyHandle, L"BuildLabEx"))
        {
            buildLabHeader = PhConcatStrings2(L"ProcessHacker-OsBuild: ", buildLabString->Buffer);
            PhDereferenceObject(buildLabString);
        }
        else if (buildLabString = PhQueryRegistryString(keyHandle, L"BuildLab"))
        {
            buildLabHeader = PhConcatStrings2(L"ProcessHacker-OsBuild: ", buildLabString->Buffer);
            PhDereferenceObject(buildLabString);
        }
        
        NtClose(keyHandle);
    }

    return buildLabHeader;
}

BOOLEAN ParseVersionString(
    _Inout_ PPH_UPDATER_CONTEXT Context
    )
{
    PH_STRINGREF sr, majorPart, minorPart, revisionPart;
    ULONG64 majorInteger = 0, minorInteger = 0, revisionInteger = 0;

    PhInitializeStringRef(&sr, Context->Version->Buffer);
    PhInitializeStringRef(&revisionPart, Context->RevVersion->Buffer);

    if (PhSplitStringRefAtChar(&sr, '.', &majorPart, &minorPart))
    {
        PhStringToInteger64(&majorPart, 10, &majorInteger);
        PhStringToInteger64(&minorPart, 10, &minorInteger);
        PhStringToInteger64(&revisionPart, 10, &revisionInteger);

        Context->MajorVersion = (ULONG)majorInteger;
        Context->MinorVersion = (ULONG)minorInteger;
        Context->RevisionVersion = (ULONG)revisionInteger;

        return TRUE;
    }

    return FALSE;
}

BOOLEAN ReadRequestString(
    _In_ HINTERNET Handle,
    _Out_ _Deref_post_z_cap_(*DataLength) PSTR *Data,
    _Out_ ULONG *DataLength
    )
{
    PSTR data;
    ULONG allocatedLength;
    ULONG dataLength;
    ULONG returnLength;
    BYTE buffer[PAGE_SIZE];

    allocatedLength = sizeof(buffer);
    data = (PSTR)PhAllocate(allocatedLength);
    dataLength = 0;

    // Zero the buffer
    memset(buffer, 0, PAGE_SIZE);

    while (WinHttpReadData(Handle, buffer, PAGE_SIZE, &returnLength))
    {
        if (returnLength == 0)
            break;

        if (allocatedLength < dataLength + returnLength)
        {
            allocatedLength *= 2;
            data = (PSTR)PhReAllocate(data, allocatedLength);
        }

        // Copy the returned buffer into our pointer
        memcpy(data + dataLength, buffer, returnLength);
        // Zero the returned buffer for the next loop
        //memset(buffer, 0, returnLength);

        dataLength += returnLength;
    }

    if (allocatedLength < dataLength + 1)
    {
        allocatedLength++;
        data = (PSTR)PhReAllocate(data, allocatedLength);
    }

    // Ensure that the buffer is null-terminated.
    data[dataLength] = 0;

    *DataLength = dataLength;
    *Data = data;

    return TRUE;
}

PPH_UPDATER_CONTEXT CreateUpdateContext(
    VOID
    )
{
    PPH_UPDATER_CONTEXT context;
    
    context = (PPH_UPDATER_CONTEXT)PhAllocate(sizeof(PH_UPDATER_CONTEXT));
    memset(context, 0, sizeof(PH_UPDATER_CONTEXT));

    return context;
}

VOID FreeUpdateContext(
    _In_ _Post_invalid_ PPH_UPDATER_CONTEXT Context
    )
{
    if (!Context)
        return;

    Context->HaveData = FALSE;
    Context->UpdaterState = PhUpdateMaximum;

    Context->MinorVersion = 0;
    Context->MajorVersion = 0;
    Context->RevisionVersion = 0;
    Context->CurrentMinorVersion = 0;
    Context->CurrentMajorVersion = 0;
    Context->CurrentRevisionVersion = 0;

    PhClearReference(&Context->Version);
    PhClearReference(&Context->RevVersion);
    PhClearReference(&Context->RelDate);
    PhClearReference(&Context->Size);
    PhClearReference(&Context->Hash);
    PhClearReference(&Context->ReleaseNotesUrl);
    PhClearReference(&Context->SetupFilePath);
    PhClearReference(&Context->SetupFileDownloadUrl);

    if (Context->FontHandle)
    {
        DeleteObject(Context->FontHandle);
        Context->FontHandle = NULL;
    }

    if (Context->IconBitmap)
    {
        DeleteObject(Context->IconBitmap);
        Context->IconBitmap = NULL;
    }

    if (Context->IconHandle)
    {
        DestroyIcon(Context->IconHandle);
        Context->IconHandle = NULL;
    }

    PhFree(Context);
}

BOOLEAN QueryUpdateData(
    _Inout_ PPH_UPDATER_CONTEXT Context,
    _In_ BOOLEAN UseFailServer
    )
{
    BOOLEAN isSuccess = FALSE;
    HINTERNET httpSessionHandle = NULL;
    HINTERNET httpConnectionHandle = NULL;
    HINTERNET httpRequestHandle = NULL;
    WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 };
    mxml_node_t* xmlNode = NULL;
    ULONG xmlStringBufferLength = 0;
    PSTR xmlStringBuffer = NULL;
    PPH_STRING versionHeader = UpdateVersionString();
    PPH_STRING windowsHeader = UpdateWindowsString();

    // Get the current Process Hacker version
    PhGetPhVersionNumbers(
        &Context->CurrentMajorVersion,
        &Context->CurrentMinorVersion,
        NULL,
        &Context->CurrentRevisionVersion
        );

    __try
    {
        // Query the current system proxy
        WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig);

        // Open the HTTP session with the system proxy configuration if available
        if (!(httpSessionHandle = WinHttpOpen(
            NULL,
            proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
            proxyConfig.lpszProxy,
            proxyConfig.lpszProxyBypass,
            0
            )))
        {
            __leave;
        }

        if (WindowsVersion >= WINDOWS_8_1)
        {
            // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags.
            ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE;

            WinHttpSetOption(
                httpSessionHandle, 
                WINHTTP_OPTION_DECOMPRESSION, 
                &httpFlags,
                sizeof(ULONG)
                );
        }

        if (UseFailServer)
        {
            if (!(httpConnectionHandle = WinHttpConnect(
                httpSessionHandle,
                L"processhacker.sourceforge.net",
                INTERNET_DEFAULT_HTTP_PORT,
                0
                )))
            {
                __leave;
            }

            if (!(httpRequestHandle = WinHttpOpenRequest(
                httpConnectionHandle,
                NULL,
                L"/update.php",
                NULL,
                WINHTTP_NO_REFERER,
                WINHTTP_DEFAULT_ACCEPT_TYPES,
                WINHTTP_FLAG_REFRESH
                )))
            {
                __leave;
            }
        }
        else
        {
            if (!(httpConnectionHandle = WinHttpConnect(
                httpSessionHandle,
                L"wj32.org",
                INTERNET_DEFAULT_HTTPS_PORT,
                0
                )))
            {
                __leave;
            }

            if (!(httpRequestHandle = WinHttpOpenRequest(
                httpConnectionHandle,
                NULL,
                L"/processhacker/update.php",
                NULL,
                WINHTTP_NO_REFERER,
                WINHTTP_DEFAULT_ACCEPT_TYPES,
                WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE
                )))
            {
                __leave;
            }
        }

        if (versionHeader)
        {
            WinHttpAddRequestHeaders(
                httpRequestHandle, 
                versionHeader->Buffer, 
                (ULONG)versionHeader->Length / sizeof(WCHAR), 
                WINHTTP_ADDREQ_FLAG_ADD
                );
        }

        if (windowsHeader)
        {
            WinHttpAddRequestHeaders(
                httpRequestHandle, 
                windowsHeader->Buffer,
                (ULONG)windowsHeader->Length / sizeof(WCHAR),
                WINHTTP_ADDREQ_FLAG_ADD
                );
        }

        if (!WinHttpSendRequest(
            httpRequestHandle,
            WINHTTP_NO_ADDITIONAL_HEADERS, 
            0,
            WINHTTP_NO_REQUEST_DATA, 
            0,
            WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 
            0
            ))
        {
            __leave;
        }

        if (!WinHttpReceiveResponse(httpRequestHandle, NULL))
            __leave;

        // Read the resulting xml into our buffer.
        if (!ReadRequestString(httpRequestHandle, &xmlStringBuffer, &xmlStringBufferLength))
            __leave;

        // Check the buffer for valid data.
        if (xmlStringBuffer == NULL || xmlStringBuffer[0] == '\0')
            __leave;

        // Load our XML
        xmlNode = mxmlLoadString(NULL, xmlStringBuffer, QueryXmlDataCallback);
        if (xmlNode == NULL || xmlNode->type != MXML_ELEMENT)
            __leave;

        // Find the version node
        Context->Version = PhGetOpaqueXmlNodeText(
            mxmlFindElement(xmlNode->child, xmlNode, "ver", NULL, NULL, MXML_DESCEND)
            );
        if (PhIsNullOrEmptyString(Context->Version))
            __leave;

        // Find the revision node
        Context->RevVersion = PhGetOpaqueXmlNodeText(
            mxmlFindElement(xmlNode->child, xmlNode, "rev", NULL, NULL, MXML_DESCEND)
            );
        if (PhIsNullOrEmptyString(Context->RevVersion))
            __leave;

        // Find the release date node
        Context->RelDate = PhGetOpaqueXmlNodeText(
            mxmlFindElement(xmlNode->child, xmlNode, "reldate", NULL, NULL, MXML_DESCEND)
            );
        if (PhIsNullOrEmptyString(Context->RelDate))
            __leave;

        // Find the size node
        Context->Size = PhGetOpaqueXmlNodeText(
            mxmlFindElement(xmlNode->child, xmlNode, "size", NULL, NULL, MXML_DESCEND)
            );
        if (PhIsNullOrEmptyString(Context->Size))
            __leave;

        //Find the hash node
        Context->Hash = PhGetOpaqueXmlNodeText(
            mxmlFindElement(xmlNode->child, xmlNode, "sha1", NULL, NULL, MXML_DESCEND)
            );
        if (PhIsNullOrEmptyString(Context->Hash))
            __leave;

        // Find the release notes URL
        Context->ReleaseNotesUrl = PhGetOpaqueXmlNodeText(
            mxmlFindElement(xmlNode->child, xmlNode, "relnotes", NULL, NULL, MXML_DESCEND)
            );
        if (PhIsNullOrEmptyString(Context->ReleaseNotesUrl))
            __leave;

        // Find the installer download URL
        Context->SetupFileDownloadUrl = PhGetOpaqueXmlNodeText(
            mxmlFindElement(xmlNode->child, xmlNode, "setupurl", NULL, NULL, MXML_DESCEND)
            );
        if (PhIsNullOrEmptyString(Context->SetupFileDownloadUrl))
            __leave;

        if (!ParseVersionString(Context))
            __leave;

        isSuccess = TRUE;
    }
    __finally
    {
        if (httpRequestHandle)
            WinHttpCloseHandle(httpRequestHandle);

        if (httpConnectionHandle)
            WinHttpCloseHandle(httpConnectionHandle);

        if (httpSessionHandle)
            WinHttpCloseHandle(httpSessionHandle);

        if (xmlNode)
            mxmlDelete(xmlNode);

        if (xmlStringBuffer)
            PhFree(xmlStringBuffer);

        PhClearReference(&versionHeader);
        PhClearReference(&windowsHeader);
    }

    return isSuccess;
}

NTSTATUS UpdateCheckSilentThread(
    _In_ PVOID Parameter
    )
{
    PPH_UPDATER_CONTEXT context = NULL;
    ULONGLONG currentVersion = 0;
    ULONGLONG latestVersion = 0;

    context = CreateUpdateContext();

    __try
    {
        if (!LastUpdateCheckExpired())
        {
            __leave;
        }

        if (!QueryUpdateData(context, FALSE))
        {
            if (!QueryUpdateData(context, TRUE))
            {
                __leave;
            }
        }

        currentVersion = MAKEDLLVERULL(
            context->CurrentMajorVersion,
            context->CurrentMinorVersion,
            0,
            context->CurrentRevisionVersion
            );

#ifdef DEBUG_UPDATE
        latestVersion = MAKEDLLVERULL(
            9999,
            9999,
            0,
            9999
            );
#else
        latestVersion = MAKEDLLVERULL(
            context->MajorVersion,
            context->MinorVersion,
            0,
            context->RevisionVersion
            );
#endif

        if (WindowsVersion < WINDOWS_7 && context->MajorVersion != 2)
        { 
            // If we're running on XP or Vista, we only support 2.x builds and below.
            latestVersion = currentVersion;
        }

        // Compare the current version against the latest available version
        if (currentVersion < latestVersion)
        {
            // Don't spam the user the second they open PH, delay dialog creation for 3 seconds.
            Sleep(3000);

            if (!UpdateDialogHandle)
            {
                // We have data we're going to cache and pass into the dialog
                context->HaveData = TRUE;

                // Show the dialog asynchronously on a new thread.
                ShowUpdateDialog(context);
            }
        }
    }
    __finally
    {
        // Check the dialog doesn't own the window context...
        if (!context->HaveData)
        {
            FreeUpdateContext(context);
        }
    }

    return STATUS_SUCCESS;
}

NTSTATUS UpdateCheckThread(
    _In_ PVOID Parameter
    )
{
    PPH_UPDATER_CONTEXT context = NULL;
    ULONGLONG currentVersion = 0;
    ULONGLONG latestVersion = 0;

    context = (PPH_UPDATER_CONTEXT)Parameter;

    // Check if we have cached update data
    if (!context->HaveData)
    {
        context->HaveData = QueryUpdateData(context, FALSE);

        if (!context->HaveData)
        {
            context->HaveData = QueryUpdateData(context, TRUE);
        }
    }

    // sanity check
    if (!context->HaveData)
    {
        PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0);
        return STATUS_SUCCESS;
    }

    currentVersion = MAKEDLLVERULL(
        context->CurrentMajorVersion,
        context->CurrentMinorVersion,
        0,
        context->CurrentRevisionVersion
        );

#ifdef DEBUG_UPDATE
    latestVersion = MAKEDLLVERULL(
        9999,
        9999,
        0,
        9999
        );
#else
    latestVersion = MAKEDLLVERULL(
        context->MajorVersion,
        context->MinorVersion,
        0,
        context->RevisionVersion
        );
#endif

    if (WindowsVersion < WINDOWS_7 && context->MajorVersion != 2)
    {
        // If we're running on XP or Vista, we only support 2.x builds and below.
        PostMessage(context->DialogHandle, PH_UPDATENOTSUPPORTED, 0, 0);
    }
    else if (currentVersion == latestVersion)
    {
        // User is running the latest version
        PostMessage(context->DialogHandle, PH_UPDATEISCURRENT, 0, 0);
    }
    else if (currentVersion > latestVersion)
    {
        // User is running a newer version
        PostMessage(context->DialogHandle, PH_UPDATENEWER, 0, 0);
    }
    else
    {
        // User is running an older version
        PostMessage(context->DialogHandle, PH_UPDATEAVAILABLE, 0, 0);
    }

    return STATUS_SUCCESS;
}

NTSTATUS UpdateDownloadThread(
    _In_ PVOID Parameter
    )
{
    BOOLEAN downloadSuccess = FALSE;
    BOOLEAN hashSuccess = FALSE;
    BOOLEAN verifySuccess = FALSE;
    HANDLE tempFileHandle = NULL;
    HINTERNET httpSessionHandle = NULL;
    HINTERNET httpConnectionHandle = NULL;
    HINTERNET httpRequestHandle = NULL;
    PPH_STRING setupTempPath = NULL;
    PPH_STRING downloadHostPath = NULL;
    PPH_STRING downloadUrlPath = NULL;
    PPH_STRING userAgentString = NULL;
    PPH_STRING fullSetupPath = NULL;
    PPH_STRING randomGuidString = NULL;
    ULONG indexOfFileName = -1;
    GUID randomGuid;
    URL_COMPONENTS httpUrlComponents = { sizeof(URL_COMPONENTS) };
    WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 };

    PPH_UPDATER_CONTEXT context = (PPH_UPDATER_CONTEXT)Parameter;

    __try
    {
        // Create a user agent string.
        userAgentString = PhFormatString(
            L"PH_%lu.%lu_%lu",
            context->CurrentMajorVersion,
            context->CurrentMinorVersion,
            context->CurrentRevisionVersion
            );
        if (PhIsNullOrEmptyString(userAgentString))
            __leave;

        // Allocate the GetTempPath buffer
        setupTempPath = PhCreateStringEx(NULL, GetTempPath(0, NULL) * sizeof(WCHAR));
        if (PhIsNullOrEmptyString(setupTempPath))
            __leave;

        // Get the temp path
        if (GetTempPath((ULONG)setupTempPath->Length / sizeof(WCHAR), setupTempPath->Buffer) == 0)
            __leave;
        if (PhIsNullOrEmptyString(setupTempPath))
            __leave;

        // Generate random guid for our directory path.
        PhGenerateGuid(&randomGuid);
        
        if (randomGuidString = PhFormatGuid(&randomGuid))
        {
            PPH_STRING guidSubString;

            // Strip the left and right curly brackets.
            guidSubString = PhSubstring(randomGuidString, 1, randomGuidString->Length / sizeof(WCHAR) - 2);

            PhSwapReference(&randomGuidString, guidSubString);
        }

        // Append the tempath to our string: %TEMP%RandomString\\processhacker-%lu.%lu-setup.exe
        // Example: C:\\Users\\dmex\\AppData\\Temp\\ABCD\\processhacker-2.90-setup.exe
        context->SetupFilePath = PhFormatString(
            L"%s%s\\processhacker-%lu.%lu-setup.exe",
            setupTempPath->Buffer,
            randomGuidString->Buffer,
            context->MajorVersion,
            context->MinorVersion
            );
        if (PhIsNullOrEmptyString(context->SetupFilePath))
            __leave;

        // Create the directory if it does not exist.
        if (fullSetupPath = PhGetFullPath(context->SetupFilePath->Buffer, &indexOfFileName))
        {
            PPH_STRING directoryPath;

            if (indexOfFileName == -1)
                __leave;
            
            if (directoryPath = PhSubstring(fullSetupPath, 0, indexOfFileName))
            {
                SHCreateDirectoryEx(NULL, directoryPath->Buffer, NULL);
                PhDereferenceObject(directoryPath);
            }
        }

        // Create output file
        if (!NT_SUCCESS(PhCreateFileWin32(
            &tempFileHandle,
            context->SetupFilePath->Buffer,
            FILE_GENERIC_READ | FILE_GENERIC_WRITE,
            FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_TEMPORARY,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            FILE_OVERWRITE_IF,
            FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
            )))
        {
            __leave;
        }

        // Set lengths to non-zero enabling these params to be cracked.
        httpUrlComponents.dwSchemeLength = (ULONG)-1;
        httpUrlComponents.dwHostNameLength = (ULONG)-1;
        httpUrlComponents.dwUrlPathLength = (ULONG)-1;

        if (!WinHttpCrackUrl(
            context->SetupFileDownloadUrl->Buffer,
            (ULONG)context->SetupFileDownloadUrl->Length,
            0,
            &httpUrlComponents
            ))
        {
            __leave;
        }

        // Create the Host string.
        downloadHostPath = PhCreateStringEx(
            httpUrlComponents.lpszHostName,
            httpUrlComponents.dwHostNameLength * sizeof(WCHAR)
            );
        if (PhIsNullOrEmptyString(downloadHostPath))
            __leave;

        // Create the Path string.
        downloadUrlPath = PhCreateStringEx(
            httpUrlComponents.lpszUrlPath,
            httpUrlComponents.dwUrlPathLength * sizeof(WCHAR)
            );
        if (PhIsNullOrEmptyString(downloadUrlPath))
            __leave;

        SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Connecting...");

        // Query the current system proxy
        WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig);

        // Open the HTTP session with the system proxy configuration if available
        if (!(httpSessionHandle = WinHttpOpen(
            userAgentString->Buffer,
            proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
            proxyConfig.lpszProxy,
            proxyConfig.lpszProxyBypass,
            0
            )))
        {
            __leave;
        }

        if (WindowsVersion >= WINDOWS_8_1)
        {
            // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags.
            ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE;

            WinHttpSetOption(
                httpSessionHandle,
                WINHTTP_OPTION_DECOMPRESSION,
                &httpFlags,
                sizeof(ULONG)
                );
        }

        if (!(httpConnectionHandle = WinHttpConnect(
            httpSessionHandle,
            downloadHostPath->Buffer,
            httpUrlComponents.nScheme == INTERNET_SCHEME_HTTP ? INTERNET_DEFAULT_HTTP_PORT : INTERNET_DEFAULT_HTTPS_PORT,
            0
            )))
        {
            __leave;
        }

        if (!(httpRequestHandle = WinHttpOpenRequest(
            httpConnectionHandle,
            NULL,
            downloadUrlPath->Buffer,
            NULL,
            WINHTTP_NO_REFERER,
            WINHTTP_DEFAULT_ACCEPT_TYPES,
            WINHTTP_FLAG_REFRESH | (httpUrlComponents.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0)
            )))
        {
            __leave;
        }

        SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Sending request...");

        if (!WinHttpSendRequest(
            httpRequestHandle,
            WINHTTP_NO_ADDITIONAL_HEADERS,
            0,
            WINHTTP_NO_REQUEST_DATA,
            0,
            WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH,
            0
            ))
        {
            __leave;
        }

        SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Waiting for response...");

        if (WinHttpReceiveResponse(httpRequestHandle, NULL))
        {
            ULONG bytesDownloaded = 0;
            ULONG downloadedBytes = 0;
            ULONG contentLengthSize = sizeof(ULONG);
            ULONG contentLength = 0;
            BYTE buffer[PAGE_SIZE];
            BYTE hashBuffer[20];

            PH_HASH_CONTEXT hashContext;
            IO_STATUS_BLOCK isb;

            if (!WinHttpQueryHeaders(
                httpRequestHandle,
                WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
                WINHTTP_HEADER_NAME_BY_INDEX,
                &contentLength,
                &contentLengthSize,
                0
                ))
            {
                __leave;
            }

            // Initialize hash algorithm.
            PhInitializeHash(&hashContext, Sha1HashAlgorithm);

            // Zero the buffer.
            memset(buffer, 0, PAGE_SIZE);

            // Download the data.
            while (WinHttpReadData(httpRequestHandle, buffer, PAGE_SIZE, &bytesDownloaded))
            {
                // If we get zero bytes, the file was uploaded or there was an error
                if (bytesDownloaded == 0)
                    break;

                // If the dialog was closed, just cleanup and exit
                //if (context->UpdaterState == PhUpdateMaximum)
                if (!UpdateDialogThreadHandle)
                    __leave;

                // Update the hash of bytes we downloaded.
                PhUpdateHash(&hashContext, buffer, bytesDownloaded);

                // Write the downloaded bytes to disk.
                if (!NT_SUCCESS(NtWriteFile(
                    tempFileHandle,
                    NULL,
                    NULL,
                    NULL,
                    &isb,
                    buffer,
                    bytesDownloaded,
                    NULL,
                    NULL
                    )))
                {
                    __leave;
                }

                downloadedBytes += (DWORD)isb.Information;

                // Check the number of bytes written are the same we downloaded.
                if (bytesDownloaded != isb.Information)
                    __leave;

                // Update the GUI progress.
                // TODO: Update on GUI thread.
                {
                    //int percent = MulDiv(100, downloadedBytes, contentLength);
                    FLOAT percent = ((FLOAT)downloadedBytes / contentLength * 100);
                    PPH_STRING totalDownloaded = PhFormatSize(downloadedBytes, -1);
                    PPH_STRING totalLength = PhFormatSize(contentLength, -1);

                    PPH_STRING dlLengthString = PhFormatString(
                        L"%s of %s (%.0f%%)",
                        totalDownloaded->Buffer,
                        totalLength->Buffer,
                        percent
                        );

                    // Update the progress bar position
                    SendMessage(context->ProgressHandle, PBM_SETPOS, (ULONG)percent, 0);
                    Static_SetText(context->StatusHandle, dlLengthString->Buffer);

                    PhDereferenceObject(dlLengthString);
                    PhDereferenceObject(totalDownloaded);
                    PhDereferenceObject(totalLength);
                }
            }

            // Compute hash result (will fail if file not downloaded correctly).
            if (PhFinalHash(&hashContext, &hashBuffer, 20, NULL))
            {
                // Allocate our hash string, hex the final hash result in our hashBuffer.
                PPH_STRING hexString = PhBufferToHexString(hashBuffer, 20);

                if (PhEqualString(hexString, context->Hash, TRUE))
                {
                    hashSuccess = TRUE;
                }

                PhDereferenceObject(hexString);
            }
        }

        downloadSuccess = TRUE;
    }
    __finally
    {
        if (tempFileHandle)
            NtClose(tempFileHandle);

        if (httpRequestHandle)
            WinHttpCloseHandle(httpRequestHandle);

        if (httpConnectionHandle)
            WinHttpCloseHandle(httpConnectionHandle);

        if (httpSessionHandle)
            WinHttpCloseHandle(httpSessionHandle);

        PhClearReference(&randomGuidString);
        PhClearReference(&fullSetupPath);
        PhClearReference(&setupTempPath);
        PhClearReference(&downloadHostPath);
        PhClearReference(&downloadUrlPath);
        PhClearReference(&userAgentString);
    }

    if (WindowsVersion < WINDOWS_8)
    {
        // Disable signature checking on XP, Vista and Win7 due to SHA2 certificate issues.
        verifySuccess = TRUE;
    }
    else
    {
        // Check the digital signature of the installer...
        if (context->SetupFilePath && PhVerifyFile(context->SetupFilePath->Buffer, NULL) == VrTrusted)
        {
            verifySuccess = TRUE;
        }
    }

    if (downloadSuccess && hashSuccess && verifySuccess)
    {
        PostMessage(context->DialogHandle, PH_UPDATESUCCESS, 0, 0);
    }
    else if (downloadSuccess)
    {
        PostMessage(context->DialogHandle, PH_UPDATEFAILURE, verifySuccess, hashSuccess);
    }
    else
    {
        PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0);
    }

    return STATUS_SUCCESS;
}

INT_PTR CALLBACK UpdaterWndProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    )
{
    PPH_UPDATER_CONTEXT context = NULL;

    if (uMsg == WM_INITDIALOG)
    {
        context = (PPH_UPDATER_CONTEXT)lParam;
        SetProp(hwndDlg, L"Context", (HANDLE)context);
    }
    else
    {
        context = (PPH_UPDATER_CONTEXT)GetProp(hwndDlg, L"Context");

        if (uMsg == WM_NCDESTROY)
        {
            RemoveProp(hwndDlg, L"Context");
            FreeUpdateContext(context);

            PostQuitMessage(0);
        }
    }

    if (context == NULL)
        return FALSE;

    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            LOGFONT logFont;
            HWND parentWindow = GetParent(hwndDlg);

            context->DialogHandle = hwndDlg;
            context->StatusHandle = GetDlgItem(hwndDlg, IDC_STATUS);
            context->ProgressHandle = GetDlgItem(hwndDlg, IDC_PROGRESS);

            if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0))
            {
                HDC hdc;

                if (hdc = GetDC(hwndDlg))
                {
                    // Create the font handle
                    context->FontHandle = CreateFont(
                        -MulDiv(-14, GetDeviceCaps(hdc, LOGPIXELSY), 72),
                        0,
                        0,
                        0,
                        FW_MEDIUM,
                        FALSE,
                        FALSE,
                        FALSE,
                        ANSI_CHARSET,
                        OUT_DEFAULT_PRECIS,
                        CLIP_DEFAULT_PRECIS,
                        CLEARTYPE_QUALITY | ANTIALIASED_QUALITY,
                        DEFAULT_PITCH,
                        logFont.lfFaceName
                        );

                    ReleaseDC(hwndDlg, hdc);
                }
            }

            // Load the Process Hacker icon.
            context->IconHandle = (HICON)LoadImage(
                NtCurrentPeb()->ImageBaseAddress,
                MAKEINTRESOURCE(PHAPP_IDI_PROCESSHACKER),
                IMAGE_ICON,
                GetSystemMetrics(SM_CXICON),
                GetSystemMetrics(SM_CYICON),
                0
                );
            
            context->IconBitmap = PhIconToBitmap(context->IconHandle, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));

            // Set the window icons
            if (context->IconHandle)
                SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)context->IconHandle);
            // Set the text font
            if (context->FontHandle)
                SetWindowFont(GetDlgItem(hwndDlg, IDC_MESSAGE), context->FontHandle, FALSE);
            // Set the window image
            if (context->IconBitmap)
                SendMessage(GetDlgItem(hwndDlg, IDC_UPDATEICON), STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)context->IconBitmap);

            // Center the update window on PH if it's visible else we center on the desktop.
            PhCenterWindow(hwndDlg, (IsWindowVisible(parentWindow) && !IsMinimized(parentWindow)) ? parentWindow : NULL);

            // Show new version info (from the background update check)
            if (context->HaveData)
            {
                HANDLE updateCheckThread = NULL;

                // Create the update check thread.
                if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context))
                    NtClose(updateCheckThread);
            }
        }
        break;
    case WM_SHOWDIALOG:
        {
            if (IsMinimized(hwndDlg))
                ShowWindow(hwndDlg, SW_RESTORE);
            else
                ShowWindow(hwndDlg, SW_SHOW);

            SetForegroundWindow(hwndDlg);
        }
        break;
    case WM_CTLCOLORBTN:
    case WM_CTLCOLORDLG:
    case WM_CTLCOLORSTATIC:
        {
            HDC hDC = (HDC)wParam;
            HWND hwndChild = (HWND)lParam;

            // Check for our static label and change the color.
            if (GetWindowID(hwndChild) == IDC_MESSAGE)
            {
                SetTextColor(hDC, RGB(19, 112, 171));
            }

            // Set a transparent background for the control backcolor.
            SetBkMode(hDC, TRANSPARENT);

            // set window background color.
            return (INT_PTR)GetSysColorBrush(COLOR_WINDOW);
        }
        break;
    case WM_COMMAND:
        {
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
            case IDCANCEL:
            case IDOK:
                DestroyWindow(hwndDlg);
                break;
            case IDC_DOWNLOAD:
                {
                    switch (context->UpdaterState)
                    {
                    case PhUpdateDefault:
                        {
                            HANDLE updateCheckThread = NULL;

                            SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Checking for new releases...");
                            SetDlgItemText(hwndDlg, IDC_RELDATE, L"");
                            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);

                            if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context))
                                NtClose(updateCheckThread);
                        }
                        break;
                    case PhUpdateDownload:
                        {
                            // We can't download updates below Vista due to SHA2 and the
                            // WINHTTP_OPTION_SECURITY_FLAGS option doesn't work on XP for some reason.
                            // Just show the downloads page when there is a new release.
                            if (WindowsVersion >= WINDOWS_VISTA && PhInstalledUsingSetup())
                            {
                                HANDLE downloadThreadHandle = NULL;

                                // Disable the download button
                                Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);

                                // Reset the progress bar (might be a download retry)
                                SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETPOS, 0, 0);

                                if (WindowsVersion >= WINDOWS_VISTA)
                                    SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_NORMAL, 0);

                                // Start file download thread
                                if (downloadThreadHandle = PhCreateThread(0, (PUSER_THREAD_START_ROUTINE)UpdateDownloadThread, context))
                                    NtClose(downloadThreadHandle);
                            }
                            else
                            {
                                // Let the user handle non-setup installation, show the homepage and close this dialog.
                                PhShellExecute(hwndDlg, L"http://processhacker.sourceforge.net/downloads.php", NULL);
                                DestroyWindow(hwndDlg);
                            }
                        }
                        break;
                    case PhUpdateInstall:
                        {
                            SHELLEXECUTEINFO info = { sizeof(SHELLEXECUTEINFO) };

                            if (PhIsNullOrEmptyString(context->SetupFilePath))
                                break;

                            info.lpFile = context->SetupFilePath->Buffer;
                            info.lpVerb = PhGetOwnTokenAttributes().Elevated ? NULL : L"runas";
                            info.nShow = SW_SHOW;
                            info.hwnd = hwndDlg;

                            ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle);

                            if (!ShellExecuteEx(&info))
                            {
                                // Install failed, cancel the shutdown.
                                ProcessHacker_CancelEarlyShutdown(PhMainWndHandle);

                                // Set button text for next action
                                Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry");
                            }
                            else
                            {
                                ProcessHacker_Destroy(PhMainWndHandle);
                            }
                        }
                        break;
                    }
                }
                break;
            }
            break;
        }
        break;
    case PH_UPDATEAVAILABLE:
        {
            // Set updater state
            context->UpdaterState = PhUpdateDownload;

            // Set the UI text
            SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString(
                L"Process Hacker %lu.%lu.%lu",
                context->MajorVersion,
                context->MinorVersion,
                context->RevisionVersion
                )->Buffer);
            SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString(
                L"Released: %s",
                context->RelDate->Buffer
                )->Buffer);
            SetDlgItemText(hwndDlg, IDC_STATUS, PhaFormatString(
                L"Size: %s",
                context->Size->Buffer
                )->Buffer);
            Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Download");

            // Enable the controls
            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
            Control_Visible(GetDlgItem(hwndDlg, IDC_PROGRESS), TRUE);
            Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE);
        }
        break;
    case PH_UPDATEISCURRENT:
        {
            // Set updater state
            context->UpdaterState = PhUpdateMaximum;

            // Set the UI text
            SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running the latest version.");
            SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString(
                L"Stable release build: v%lu.%lu.%lu",
                context->CurrentMajorVersion,
                context->CurrentMinorVersion,
                context->CurrentRevisionVersion
                )->Buffer);

            // Disable the download button
            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);
            // Enable the changelog link
            Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE);
        }
        break;
    case PH_UPDATENEWER:
        {
            context->UpdaterState = PhUpdateMaximum;

            // Set the UI text
            SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running a newer version!");
            SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString(
                L"Pre-release build: v%lu.%lu.%lu",
                context->CurrentMajorVersion,
                context->CurrentMinorVersion,
                context->CurrentRevisionVersion
                )->Buffer);

            // Disable the download button
            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);
            // Disable the changelog link
            Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), FALSE);
        }
        break;
    case PH_UPDATESUCCESS:
        {
            context->UpdaterState = PhUpdateInstall;

            // If PH is not elevated, set the UAC shield for the install button as the setup requires elevation.
            if (!PhGetOwnTokenAttributes().Elevated)
                SendMessage(GetDlgItem(hwndDlg, IDC_DOWNLOAD), BCM_SETSHIELD, 0, TRUE);

            // Set the download result, don't include hash status since it succeeded.
            SetDlgItemText(hwndDlg, IDC_STATUS, L"Click Install to continue update...");

            // Set button text for next action
            Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Install");
            // Enable the Download/Install button so the user can install the update
            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
        }
        break;
    case PH_UPDATEFAILURE:
        {
            context->UpdaterState = PhUpdateDefault;

            if (WindowsVersion >= WINDOWS_VISTA)
                SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_ERROR, 0);

            SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again...");
            SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates.");

            if ((BOOLEAN)wParam)
                SetDlgItemText(hwndDlg, IDC_STATUS, L"Hash check failed.");
            else if ((BOOLEAN)lParam)
                SetDlgItemText(hwndDlg, IDC_STATUS, L"Signature check failed.");

            // Set button text for next action
            Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry");
            // Enable the Install button
            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
            // Hash failed, reset state to downloading so user can redownload the file.
        }
        break;
    case PH_UPDATEISERRORED:
        {
            context->UpdaterState = PhUpdateDefault;

            SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again...");
            SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates.");

            Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry");
            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
        }
        break;
    case PH_UPDATENOTSUPPORTED:
        {
            // Set updater state
            context->UpdaterState = PhUpdateMaximum;

            // Set the UI text
            SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString(
                L"You're running the latest version: v%lu.%lu.%lu",
                context->CurrentMajorVersion,
                context->CurrentMinorVersion,
                context->CurrentRevisionVersion
                )->Buffer);
            SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString(
                L"v%lu.%lu.%lu is available for Windows 7 and above.",
                context->MajorVersion,
                context->MinorVersion,
                context->RevisionVersion
                )->Buffer);

            // Disable the download button
            Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);
            // Enable the changelog link
            Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE);
        }
        break;
    case WM_NOTIFY:
        {
            switch (((LPNMHDR)lParam)->code)
            {
            case NM_CLICK: // Mouse
            case NM_RETURN: // Keyboard
                {
                    // Launch the ReleaseNotes URL (if it exists) with the default browser
                    if (!PhIsNullOrEmptyString(context->ReleaseNotesUrl))
                        PhShellExecute(hwndDlg, context->ReleaseNotesUrl->Buffer, NULL);
                }
                break;
            }
        }
        break;
    }

    return FALSE;
}

NTSTATUS ShowUpdateDialogThread(
    _In_ PVOID Parameter
    )
{
    BOOL result;
    MSG message;
    PH_AUTO_POOL autoPool;
    PPH_UPDATER_CONTEXT context = NULL;

    if (Parameter)
        context = (PPH_UPDATER_CONTEXT)Parameter;
    else
        context = CreateUpdateContext();

    PhInitializeAutoPool(&autoPool);

    UpdateDialogHandle = CreateDialogParam(
        PluginInstance->DllBase,
        MAKEINTRESOURCE(IDD_UPDATE),
        PhMainWndHandle,
        UpdaterWndProc,
        (LPARAM)context
        );

    PhSetEvent(&InitializedEvent);

    while (result = GetMessage(&message, NULL, 0, 0))
    {
        if (result == -1)
            break;

        if (!IsDialogMessage(UpdateDialogHandle, &message))
        {
            TranslateMessage(&message);
            DispatchMessage(&message);
        }

        PhDrainAutoPool(&autoPool);
    }

    PhDeleteAutoPool(&autoPool);
    PhResetEvent(&InitializedEvent);

    if (UpdateDialogThreadHandle)
    {
        NtClose(UpdateDialogThreadHandle);
        UpdateDialogThreadHandle = NULL;
    }

    return STATUS_SUCCESS;
}

VOID ShowUpdateDialog(
    _In_opt_ PPH_UPDATER_CONTEXT Context
    )
{
    if (!UpdateDialogThreadHandle)
    {
        if (!(UpdateDialogThreadHandle = PhCreateThread(0, ShowUpdateDialogThread, Context)))
        {
            PhShowStatus(PhMainWndHandle, L"Unable to create the updater window.", 0, GetLastError());
            return;
        }

        PhWaitForEvent(&InitializedEvent, NULL);
    }

    PostMessage(UpdateDialogHandle, WM_SHOWDIALOG, 0, 0);
}

VOID StartInitialCheck(
    VOID
    )
{
    HANDLE silentCheckThread = NULL;

    if (silentCheckThread = PhCreateThread(0, UpdateCheckSilentThread, NULL))
        NtClose(silentCheckThread);
}