// // LauncherUtils.cpp // // Created by Luis Cuenca on 6/5/2019. // Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "stdafx.h" #include #include #include #include #pragma comment(lib, "winhttp") #include "LauncherApp.h" #include "LauncherUtils.h" CString LauncherUtils::urlEncodeString(const CString& url) { std::map specialCharsMap = { { _T("$"), _T("%24") }, { _T(" "), _T("%20") }, { _T("#"), _T("%23") }, { _T("@"), _T("%40") }, { _T("`"), _T("%60") }, { _T("&"), _T("%26") }, { _T("/"), _T("%2F") }, { _T(":"), _T("%3A") }, { _T(";"), _T("%3B") }, { _T("<"), _T("%3C") }, { _T(">"), _T("%3E") }, { _T("="), _T("%3D") }, { _T("?"), _T("%3F") }, { _T("["), _T("%5B") }, { _T("\\"), _T("%5C") }, { _T("]"), _T("%5D") }, { _T("^"), _T("%5E") }, { _T("{"), _T("%7B") }, { _T("|"), _T("%7C") }, { _T("}"), _T("%7D") }, { _T("~"), _T("%7E") }, { _T("“"), _T("%22") }, { _T("‘"), _T("%27") }, { _T("+"), _T("%2B") }, { _T(","), _T("%2C") } }; CString stringOut = url; stringOut.Replace(_T("%"), _T("%25")); for (auto& itr = specialCharsMap.begin(); itr != specialCharsMap.end(); itr++) { stringOut.Replace(itr->first, itr->second); } return stringOut; } BOOL LauncherUtils::shutdownProcess(DWORD dwProcessId, UINT uExitCode) { DWORD dwDesiredAccess = PROCESS_TERMINATE; BOOL bInheritHandle = FALSE; HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); if (hProcess == NULL) { return FALSE; } BOOL result = TerminateProcess(hProcess, uExitCode); CloseHandle(hProcess); return result; } BOOL CALLBACK LauncherUtils::isWindowOpenedCallback(HWND hWnd, LPARAM lparam) { ProcessData* processData = reinterpret_cast(lparam); if (processData) { DWORD idptr; GetWindowThreadProcessId(hWnd, &idptr); if (idptr && (int)(idptr) == processData->processID) { processData->isOpened = IsWindowVisible(hWnd); return FALSE; } } return TRUE; } BOOL LauncherUtils::isProcessWindowOpened(const wchar_t *processName) { ProcessData processData; BOOL result = isProcessRunning(processName, processData.processID); if (result) { EnumWindows(LauncherUtils::isWindowOpenedCallback, reinterpret_cast(&processData)); return processData.isOpened; } return result; } BOOL LauncherUtils::isProcessRunning(const wchar_t *processName, int& processID) { bool exists = false; PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (!_wcsicmp(entry.szExeFile, processName)) { exists = true; processID = entry.th32ProcessID; break; } } } CloseHandle(snapshot); return exists; } HRESULT LauncherUtils::createLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs) { IShellLink* psl; // Get a pointer to the IShellLink interface. It is assumed that CoInitialize // has already been called. CoInitialize(NULL); HRESULT hres = E_INVALIDARG; if ((lpszPathObj != NULL) && (wcslen(lpszPathObj) > 0) && (lpszDesc != NULL) && (lpszPathLink != NULL) && (strlen(lpszPathLink) > 0)) { hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); if (SUCCEEDED(hres)) { IPersistFile* ppf; // Set the path to the shortcut target and add the description. psl->SetPath(lpszPathObj); psl->SetDescription(lpszDesc); psl->SetArguments(lpszArgs); // Query IShellLink for the IPersistFile interface, used for saving the // shortcut in persistent storage. hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); if (SUCCEEDED(hres)) { WCHAR wsz[MAX_PATH]; // Ensure that the string is Unicode. MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH); // Add code here to check return value from MultiByteWideChar // for success. // Save the link by calling IPersistFile::Save. hres = ppf->Save(wsz, TRUE); ppf->Release(); } psl->Release(); } } CoUninitialize(); return SUCCEEDED(hres); } std::string LauncherUtils::cStringToStd(CString cstring) { CT2CA convertedString(cstring); std::string strStd(convertedString); return strStd; } BOOL LauncherUtils::launchApplication(LPCWSTR lpApplicationName, LPTSTR cmdArgs) { // additional information STARTUPINFO si; PROCESS_INFORMATION pi; // set the size of the structures ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // start the program up BOOL success = CreateProcess( lpApplicationName, // the path cmdArgs, // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE CREATE_NEW_CONSOLE, // Opens file in a separate console NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION structure ); // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return success; } BOOL LauncherUtils::deleteRegistryKey(const CString& registryPath) { TCHAR szDelKey[MAX_PATH * 2]; StringCchCopy(szDelKey, MAX_PATH * 2, registryPath); auto status = RegDeleteKey(HKEY_CURRENT_USER, szDelKey); if (status == ERROR_SUCCESS) { return TRUE; } return FALSE; } LauncherUtils::ResponseError LauncherUtils::makeHTTPCall(const CString& callerName, bool useHTTPS, const CString& mainUrl, const CString& dirUrl, const CString& contentType, CStringA& postData, CString& response, bool isPost = false) { HINTERNET hopen = WinHttpOpen(callerName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hopen) { return ResponseError::Open; } HINTERNET hconnect = WinHttpConnect(hopen, mainUrl, useHTTPS ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0); if (!hconnect) { return ResponseError::Connect; } HINTERNET hrequest = WinHttpOpenRequest(hconnect, isPost ? L"POST" : L"GET", dirUrl, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, useHTTPS ? WINHTTP_FLAG_SECURE : 0); if (!hrequest) { return ResponseError::OpenRequest; } if (isPost) { if (!WinHttpSendRequest(hrequest, contentType, -1, postData.GetBuffer(postData.GetLength()), (DWORD)strlen(postData), (DWORD)strlen(postData), NULL)) { return ResponseError::SendRequest; } } else { if (!WinHttpSendRequest(hrequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { return ResponseError::SendRequest; } } if (!WinHttpReceiveResponse(hrequest, 0)) { return ResponseError::ReceiveRequest; } // query remote file size, set haveContentLength on success and dwContentLength to the length wchar_t szContentLength[32]; DWORD bufferBytes = 4096; DWORD dwHeaderIndex = WINHTTP_NO_HEADER_INDEX; BOOL haveContentLength = WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_CONTENT_LENGTH, NULL, &szContentLength, &bufferBytes, &dwHeaderIndex); DWORD statusCode; DWORD statusCodeSize = sizeof(statusCode); WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, NULL, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX); CString msg; msg.Format(_T("Status code response (%s%s): %lu"), mainUrl, dirUrl, statusCode); theApp._manager.addToLog(msg); DWORD dwContentLength; if (haveContentLength) { dwContentLength = _wtoi(szContentLength); } byte p[4096]; if (!WinHttpQueryDataAvailable(hrequest, &bufferBytes)) { return ResponseError::ReadResponse; } WinHttpReadData(hrequest, p, bufferBytes, &bufferBytes); response = CString((const char*)p, (int)bufferBytes); return ResponseError::NoError; } BOOL LauncherUtils::parseJSON(const CString& jsonTxt, Json::Value& root) { Json::CharReaderBuilder CharBuilder; std::string jsonString = cStringToStd(jsonTxt); std::string errs; Json::CharReader* nreader = CharBuilder.newCharReader(); bool parsingSuccessful = false; if (nreader != NULL) { parsingSuccessful = nreader->parse(jsonString.c_str(), jsonString.c_str() + jsonString.length(), &root, &errs); delete nreader; } return parsingSuccessful; } BOOL LauncherUtils::getFont(const CString& fontName, int fontSize, bool isBold, CFont& fontOut) { LOGFONT lf; memset(&lf, 0, sizeof(lf)); lf.lfHeight = fontSize; lf.lfWeight = isBold ? FW_BOLD : FW_NORMAL; lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; lf.lfQuality = ANTIALIASED_QUALITY; wcscpy_s(lf.lfFaceName, fontName); if (!fontOut.CreateFontIndirect(&lf)) { return FALSE; } return TRUE; } uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string& path, std::vector& files, std::function progressCallback) { { CString msg; msg.Format(_T("Reading zip file %s, extracting to %s"), CString(zipFile.c_str()), CString(path.c_str())); theApp._manager.addToLog(msg); } mz_zip_archive zip_archive; memset(&zip_archive, 0, sizeof(zip_archive)); auto status = mz_zip_reader_init_file(&zip_archive, zipFile.c_str(), 0); if (!status) { auto zip_error = mz_zip_get_last_error(&zip_archive); auto zip_error_msg = mz_zip_get_error_string(zip_error); CString msg; msg.Format(_T("Failed to initialize miniz: %d %s"), zip_error, CString(zip_error_msg)); theApp._manager.addToLog(msg); return 0; } int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive); { CString msg; msg.Format(_T("Zip archive has a file count of %d"), fileCount); theApp._manager.addToLog(msg); } if (fileCount == 0) { mz_zip_reader_end(&zip_archive); return 0; } mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) { theApp._manager.addToLog(_T("Zip archive cannot be stat'd")); mz_zip_reader_end(&zip_archive); return 0; } // Get root folder CString lastDir = _T(""); uint64_t totalSize = 0; uint64_t totalCompressedSize = 0; bool _shouldFail = false; for (int i = 0; i < fileCount; i++) { if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue; std::string filename = file_stat.m_filename; std::replace(filename.begin(), filename.end(), '/', '\\'); CString fullFilename = CString(path.c_str()) + "\\" + CString(filename.c_str()); if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) { int error = SHCreateDirectoryEx(NULL, fullFilename, NULL); if (error == ERROR_BAD_PATHNAME || error == ERROR_FILENAME_EXCED_RANGE || error == ERROR_PATH_NOT_FOUND || error == ERROR_CANCELLED) { CString msg; msg.Format(_T("Unzipping error: %d creating folder: %s"), error, fullFilename); theApp._manager.addToLog(msg); mz_zip_reader_end(&zip_archive); return 0; } continue; } CT2A destFile(fullFilename); if (mz_zip_reader_extract_to_file(&zip_archive, i, destFile, 0)) { totalCompressedSize += (uint64_t)file_stat.m_comp_size; totalSize += (uint64_t)file_stat.m_uncomp_size; progressCallback((float)totalCompressedSize / (float)zip_archive.m_archive_size); files.emplace_back(destFile); } else { CString msg; msg.Format(_T("Error unzipping the file: %s"), fullFilename); theApp._manager.addToLog(msg); _shouldFail = true; } } { CString msg; msg.Format(_T("Done unzipping archive, total size: %llu"), totalSize); theApp._manager.addToLog(msg); } // Close the archive, freeing any resources it was using mz_zip_reader_end(&zip_archive); return totalSize; } BOOL LauncherUtils::insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value) { HKEY key; auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); if (status == ERROR_SUCCESS) { status = RegSetValueExA(key, name.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)(value.size() + 1)); return status == ERROR_SUCCESS; } RegCloseKey(key); return FALSE; } BOOL LauncherUtils::insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value) { HKEY key; auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); if (status == ERROR_SUCCESS) { status = RegSetValueExA(key, name.c_str(), 0, REG_DWORD, (const BYTE*)&value, sizeof(value)); return TRUE; } RegCloseKey(key); return FALSE; } BOOL LauncherUtils::deleteFileOrDirectory(const CString& dirPath, bool noRecycleBin) { CString dir = dirPath; // Add extra null to string dir.AppendChar(0); SHFILEOPSTRUCT fileop; fileop.hwnd = NULL; // no status display fileop.wFunc = FO_DELETE; // delete operation fileop.pFrom = dir; // source file name as double null terminated string fileop.pTo = NULL; // no destination needed fileop.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; // do not prompt the user if (!noRecycleBin) { fileop.fFlags |= FOF_ALLOWUNDO; } fileop.fAnyOperationsAborted = FALSE; fileop.lpszProgressTitle = NULL; fileop.hNameMappings = NULL; int ret = SHFileOperation(&fileop); return (ret == 0); } BOOL LauncherUtils::hMac256(const CString& cmessage, const char* keystr, CString& hashOut) { char message[256]; strcpy_s(message, CStringA(cmessage).GetString()); char key[256]; strcpy_s(key, keystr); HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHmacHash = 0; BYTE pbHash[32]; HMAC_INFO HmacInfo; int err = 0; typedef struct blob { BLOBHEADER header; DWORD len; BYTE key[1]; } m_blob; ZeroMemory(&HmacInfo, sizeof(HmacInfo)); HmacInfo.HashAlgid = CALG_SHA_256; ZeroMemory(&pbHash, 32); m_blob* kb = NULL; DWORD kbSize = DWORD(sizeof(m_blob) + strlen(key)); kb = (m_blob*)malloc(kbSize); kb->header.bType = PLAINTEXTKEYBLOB; kb->header.bVersion = CUR_BLOB_VERSION; kb->header.reserved = 0; kb->header.aiKeyAlg = CALG_RC2; memcpy(&kb->key, key, strlen(key)); kb->len = (DWORD)strlen(key); BOOL success = false; DWORD datalen = (DWORD)32; if (CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET) && CryptImportKey(hProv, (BYTE*)kb, kbSize, 0, CRYPT_IPSEC_HMAC_KEY, &hKey) && CryptCreateHash(hProv, CALG_HMAC, hKey, 0, &hHmacHash) && CryptSetHashParam(hHmacHash, HP_HMAC_INFO, (BYTE*)&HmacInfo, 0) && CryptHashData(hHmacHash, (BYTE*)message, (DWORD)strlen(message), 0) && CryptGetHashParam(hHmacHash, HP_HASHVAL, pbHash, &datalen, 0)) { char *Hex = "0123456789abcdef"; char HashString[65] = { 0 }; for (int Count = 0; Count < 32; Count++) { HashString[Count * 2] = Hex[pbHash[Count] >> 4]; HashString[(Count * 2) + 1] = Hex[pbHash[Count] & 0xF]; } hashOut = CString(HashString); success = true; } free(kb); if (hHmacHash) CryptDestroyHash(hHmacHash); if (hKey) CryptDestroyKey(hKey); if (hHash) CryptDestroyHash(hHash); if (hProv) CryptReleaseContext(hProv, 0); return success; } DWORD WINAPI LauncherUtils::unzipThread(LPVOID lpParameter) { UnzipThreadData& data = *((UnzipThreadData*)lpParameter); uint64_t size = LauncherUtils::extractZip(data._zipFile, data._path, std::vector(), data._progressCallback); int mb_size = (int)(size * 0.001f); data._callback(data._type, mb_size); delete &data; return 0; } DWORD WINAPI LauncherUtils::downloadThread(LPVOID lpParameter) { DownloadThreadData& data = *((DownloadThreadData*)lpParameter); ProgressCallback progressCallback; progressCallback.setProgressCallback(data._progressCallback); auto hr = URLDownloadToFile(0, data._url, data._file, 0, static_cast(&progressCallback)); data._callback(data._type, hr != S_OK); return 0; } DWORD WINAPI LauncherUtils::deleteDirectoryThread(LPVOID lpParameter) { DeleteThreadData& data = *((DeleteThreadData*)lpParameter); BOOL success = LauncherUtils::deleteFileOrDirectory(data._dirPath); data._callback(!success); return 0; } DWORD WINAPI LauncherUtils::httpThread(LPVOID lpParameter) { HttpThreadData& data = *((HttpThreadData*)lpParameter); CString response; auto error = LauncherUtils::makeHTTPCall(data._callerName, data._useHTTPS, data._mainUrl, data._dirUrl, data._contentType, data._postData, response, data._isPost); data._callback(response, error); return 0; } BOOL LauncherUtils::unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, std::function callback, std::function progressCallback) { DWORD myThreadID; UnzipThreadData* unzipThreadData = new UnzipThreadData(); unzipThreadData->_type = type; unzipThreadData->_zipFile = zipFile; unzipThreadData->_path = path; unzipThreadData->setCallback(callback); unzipThreadData->setProgressCallback(progressCallback); HANDLE myHandle = CreateThread(0, 0, unzipThread, unzipThreadData, 0, &myThreadID); if (myHandle) { CloseHandle(myHandle); return TRUE; } return FALSE; } BOOL LauncherUtils::downloadFileOnThread(int type, const CString& url, const CString& file, std::function callback, std::function progressCallback) { DWORD myThreadID; DownloadThreadData* downloadThreadData = new DownloadThreadData(); downloadThreadData->_type = type; downloadThreadData->_url = url; downloadThreadData->_file = file; downloadThreadData->setCallback(callback); downloadThreadData->setProgressCallback(progressCallback); HANDLE myHandle = CreateThread(0, 0, downloadThread, downloadThreadData, 0, &myThreadID); if (myHandle) { CloseHandle(myHandle); return TRUE; } return FALSE; } BOOL LauncherUtils::deleteDirectoryOnThread(const CString& dirPath, std::function callback) { DWORD myThreadID; DeleteThreadData* deleteThreadData = new DeleteThreadData(); deleteThreadData->_dirPath = dirPath; deleteThreadData->setCallback(callback); HANDLE myHandle = CreateThread(0, 0, deleteDirectoryThread, deleteThreadData, 0, &myThreadID); if (myHandle) { CloseHandle(myHandle); return TRUE; } return FALSE; } BOOL LauncherUtils::httpCallOnThread(const CString& callerName, bool useHTTPS, const CString& mainUrl, const CString& dirUrl, const CString& contentType, CStringA& postData, bool isPost, std::function callback) { DWORD myThreadID; HttpThreadData* httpThreadData = new HttpThreadData(); httpThreadData->_callerName = callerName; httpThreadData->_useHTTPS = useHTTPS; httpThreadData->_mainUrl = mainUrl; httpThreadData->_dirUrl = dirUrl; httpThreadData->_contentType = contentType; httpThreadData->_postData = postData; httpThreadData->_isPost = isPost; httpThreadData->setCallback(callback); HANDLE myHandle = CreateThread(0, 0, httpThread, httpThreadData, 0, &myThreadID); if (myHandle) { CloseHandle(myHandle); return TRUE; } return FALSE; } HWND LauncherUtils::executeOnForeground(const CString& path, const CString& params) { SHELLEXECUTEINFO info; info.cbSize = sizeof(SHELLEXECUTEINFO); info.lpVerb = _T("open"); info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_WAITFORINPUTIDLE; info.hwnd = NULL; info.lpVerb = NULL; info.lpParameters = NULL; info.lpDirectory = NULL; info.nShow = SW_SHOWNORMAL; info.hInstApp = NULL; info.lpFile = path; info.lpParameters = params; HWND hwnd = NULL; if (!ShellExecuteEx(&info)) { return FALSE; } else { DWORD infopid = GetProcessId(info.hProcess); AllowSetForegroundWindow(infopid); hwnd = GetTopWindow(0); while (hwnd) { DWORD pid; DWORD dwTheardId = ::GetWindowThreadProcessId(hwnd, &pid); if (pid == infopid) { SetForegroundWindow(hwnd); SetActiveWindow(hwnd); break; } hwnd = ::GetNextWindow(hwnd, GW_HWNDNEXT); } CloseHandle(info.hProcess); } return hwnd; }