diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/crashreporter/client/crashreporter_win.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/crashreporter/client/crashreporter_win.cpp')
-rw-r--r-- | toolkit/crashreporter/client/crashreporter_win.cpp | 1568 |
1 files changed, 1568 insertions, 0 deletions
diff --git a/toolkit/crashreporter/client/crashreporter_win.cpp b/toolkit/crashreporter/client/crashreporter_win.cpp new file mode 100644 index 000000000..57ca495ba --- /dev/null +++ b/toolkit/crashreporter/client/crashreporter_win.cpp @@ -0,0 +1,1568 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#endif + +#include "crashreporter.h" + +#include <windows.h> +#include <versionhelpers.h> +#include <commctrl.h> +#include <richedit.h> +#include <shellapi.h> +#include <shlobj.h> +#include <shlwapi.h> +#include <math.h> +#include <set> +#include <algorithm> +#include "resource.h" +#include "client/windows/sender/crash_report_sender.h" +#include "common/windows/string_utils-inl.h" + +#define CRASH_REPORTER_VALUE L"Enabled" +#define SUBMIT_REPORT_VALUE L"SubmitCrashReport" +#define SUBMIT_REPORT_OLD L"SubmitReport" +#define INCLUDE_URL_VALUE L"IncludeURL" +#define EMAIL_ME_VALUE L"EmailMe" +#define EMAIL_VALUE L"Email" +#define MAX_EMAIL_LENGTH 1024 + +#define SENDURL_ORIGINAL L"https://crash-reports.mozilla.com/submit" +#define SENDURL_XPSP2 L"https://crash-reports-xpsp2.mozilla.com/submit" + +#define WM_UPLOADCOMPLETE WM_APP + +// Thanks, Windows.h :( +#undef min +#undef max + +using std::string; +using std::wstring; +using std::map; +using std::vector; +using std::set; +using std::ios; +using std::ifstream; +using std::ofstream; + +using namespace CrashReporter; + +typedef struct { + HWND hDlg; + map<wstring,wstring> queryParameters; + map<wstring,wstring> files; + wstring sendURL; + + wstring serverResponse; +} SendThreadData; + +/* + * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx + * "The DLGTEMPLATEEX structure is not defined in any standard header file. + * The structure definition is provided here to explain the format of an + * extended template for a dialog box. +*/ +typedef struct { + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + // There's more to this struct, but it has weird variable-length + // members, and I only actually need to touch exStyle on an existing + // instance, so I've omitted the rest. +} DLGTEMPLATEEX; + +static HANDLE gThreadHandle; +static SendThreadData gSendData = { 0, }; +static vector<string> gRestartArgs; +static map<wstring,wstring> gQueryParameters; +static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter"); +static wstring gURLParameter; +static int gCheckboxPadding = 6; +static bool gRTLlayout = false; + +// When vertically resizing the dialog, these items should move down +static set<UINT> gAttachedBottom; + +// Default set of items for gAttachedBottom +static const UINT kDefaultAttachedBottom[] = { + IDC_SUBMITREPORTCHECK, + IDC_VIEWREPORTBUTTON, + IDC_COMMENTTEXT, + IDC_INCLUDEURLCHECK, + IDC_EMAILMECHECK, + IDC_EMAILTEXT, + IDC_PROGRESSTEXT, + IDC_THROBBER, + IDC_CLOSEBUTTON, + IDC_RESTARTBUTTON, +}; + +static wstring UTF8ToWide(const string& utf8, bool *success = 0); +static DWORD WINAPI SendThreadProc(LPVOID param); + +static wstring Str(const char* key) +{ + return UTF8ToWide(gStrings[key]); +} + +/* === win32 helper functions === */ + +static void DoInitCommonControls() +{ + INITCOMMONCONTROLSEX ic; + ic.dwSize = sizeof(INITCOMMONCONTROLSEX); + ic.dwICC = ICC_PROGRESS_CLASS; + InitCommonControlsEx(&ic); + // also get the rich edit control + LoadLibrary(L"Msftedit.dll"); +} + +static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) +{ + DWORD type, dataSize; + dataSize = sizeof(DWORD); + if (RegQueryValueEx(hRegKey, valueName, nullptr, + &type, (LPBYTE)value, &dataSize) == ERROR_SUCCESS && + type == REG_DWORD) + return true; + + return false; +} + +// Removes a value from HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER, if it exists. +static void RemoveUnusedValues(const wchar_t* key, LPCTSTR valueName) +{ + HKEY hRegKey; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_SET_VALUE, &hRegKey) + == ERROR_SUCCESS) { + RegDeleteValue(hRegKey, valueName); + RegCloseKey(hRegKey); + } + + if (RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_SET_VALUE, &hRegKey) + == ERROR_SUCCESS) { + RegDeleteValue(hRegKey, valueName); + RegCloseKey(hRegKey); + } +} + +static bool CheckBoolKey(const wchar_t* key, + const wchar_t* valueName, + bool* enabled) +{ + /* + * NOTE! This code needs to stay in sync with the preference checking + * code in in nsExceptionHandler.cpp. + */ + *enabled = false; + bool found = false; + HKEY hRegKey; + DWORD val; + // see if our reg key is set globally + if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) { + if (GetBoolValue(hRegKey, valueName, &val)) { + *enabled = (val == 1); + found = true; + } + RegCloseKey(hRegKey); + } else { + // look for it in user settings + if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { + if (GetBoolValue(hRegKey, valueName, &val)) { + *enabled = (val == 1); + found = true; + } + RegCloseKey(hRegKey); + } + } + + return found; +} + +static void SetBoolKey(const wchar_t* key, const wchar_t* value, bool enabled) +{ + /* + * NOTE! This code needs to stay in sync with the preference setting + * code in in nsExceptionHandler.cpp. + */ + HKEY hRegKey; + + // remove the old value from the registry if it exists + RemoveUnusedValues(key, SUBMIT_REPORT_OLD); + + if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { + DWORD data = (enabled ? 1 : 0); + RegSetValueEx(hRegKey, value, 0, REG_DWORD, (LPBYTE)&data, sizeof(data)); + RegCloseKey(hRegKey); + } +} + +static bool GetStringValue(HKEY hRegKey, LPCTSTR valueName, wstring& value) +{ + DWORD type, dataSize; + wchar_t buf[2048]; + dataSize = sizeof(buf); + if (RegQueryValueEx(hRegKey, valueName, nullptr, + &type, (LPBYTE)buf, &dataSize) == ERROR_SUCCESS && + type == REG_SZ) { + value = buf; + return true; + } + + return false; +} + +static bool GetStringKey(const wchar_t* key, + const wchar_t* valueName, + wstring& value) +{ + value = L""; + bool found = false; + HKEY hRegKey; + // see if our reg key is set globally + if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) { + if (GetStringValue(hRegKey, valueName, value)) { + found = true; + } + RegCloseKey(hRegKey); + } else { + // look for it in user settings + if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { + if (GetStringValue(hRegKey, valueName, value)) { + found = true; + } + RegCloseKey(hRegKey); + } + } + + return found; +} + +static void SetStringKey(const wchar_t* key, + const wchar_t* valueName, + const wstring& value) +{ + HKEY hRegKey; + if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { + RegSetValueEx(hRegKey, valueName, 0, REG_SZ, + (LPBYTE)value.c_str(), + (value.length() + 1) * sizeof(wchar_t)); + RegCloseKey(hRegKey); + } +} + +static string FormatLastError() +{ + DWORD err = GetLastError(); + LPWSTR s; + string message = "Crash report submission failed: "; + // odds are it's a WinInet error + HANDLE hInetModule = GetModuleHandle(L"WinInet.dll"); + if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FROM_HMODULE, + hInetModule, + err, + 0, + (LPWSTR)&s, + 0, + nullptr) != 0) { + message += WideToUTF8(s, nullptr); + LocalFree(s); + // strip off any trailing newlines + string::size_type n = message.find_last_not_of("\r\n"); + if (n < message.size() - 1) { + message.erase(n+1); + } + } + else { + char buf[64]; + sprintf(buf, "Unknown error, error code: 0x%08x", err); + message += buf; + } + return message; +} + +#define TS_DRAW 2 +#define BP_CHECKBOX 3 + +typedef HANDLE (WINAPI*OpenThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList); +typedef HRESULT (WINAPI*CloseThemeDataPtr)(HANDLE hTheme); +typedef HRESULT (WINAPI*GetThemePartSizePtr)(HANDLE hTheme, HDC hdc, int iPartId, + int iStateId, RECT* prc, int ts, + SIZE* psz); +typedef HRESULT (WINAPI*GetThemeContentRectPtr)(HANDLE hTheme, HDC hdc, int iPartId, + int iStateId, const RECT* pRect, + RECT* pContentRect); + + +static void GetThemeSizes(HWND hwnd) +{ + HMODULE themeDLL = LoadLibrary(L"uxtheme.dll"); + + if (!themeDLL) + return; + + OpenThemeDataPtr openTheme = + (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData"); + CloseThemeDataPtr closeTheme = + (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData"); + GetThemePartSizePtr getThemePartSize = + (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize"); + + if (!openTheme || !closeTheme || !getThemePartSize) { + FreeLibrary(themeDLL); + return; + } + + HANDLE buttonTheme = openTheme(hwnd, L"Button"); + if (!buttonTheme) { + FreeLibrary(themeDLL); + return; + } + HDC hdc = GetDC(hwnd); + SIZE s; + getThemePartSize(buttonTheme, hdc, BP_CHECKBOX, 0, nullptr, TS_DRAW, &s); + gCheckboxPadding = s.cx; + closeTheme(buttonTheme); + FreeLibrary(themeDLL); +} + +// Gets the position of a window relative to another window's client area +static void GetRelativeRect(HWND hwnd, HWND hwndParent, RECT* r) +{ + GetWindowRect(hwnd, r); + MapWindowPoints(nullptr, hwndParent, (POINT*)r, 2); +} + +static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible) +{ + HWND hwnd = GetDlgItem(hwndDlg, item); + + ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE); +} + +static void SetDlgItemDisabled(HWND hwndDlg, UINT item, bool disabled) +{ + HWND hwnd = GetDlgItem(hwndDlg, item); + LONG style = GetWindowLong(hwnd, GWL_STYLE); + if (!disabled) + style |= WS_DISABLED; + else + style &= ~WS_DISABLED; + + SetWindowLong(hwnd, GWL_STYLE, style); +} + +/* === Crash Reporting Dialog === */ + +static void StretchDialog(HWND hwndDlg, int ydiff) +{ + RECT r; + GetWindowRect(hwndDlg, &r); + r.bottom += ydiff; + MoveWindow(hwndDlg, r.left, r.top, + r.right - r.left, r.bottom - r.top, TRUE); +} + +static void ReflowDialog(HWND hwndDlg, int ydiff) +{ + // Move items attached to the bottom down/up by as much as + // the window resize + for (set<UINT>::const_iterator item = gAttachedBottom.begin(); + item != gAttachedBottom.end(); + item++) { + RECT r; + HWND hwnd = GetDlgItem(hwndDlg, *item); + GetRelativeRect(hwnd, hwndDlg, &r); + r.top += ydiff; + r.bottom += ydiff; + MoveWindow(hwnd, r.left, r.top, + r.right - r.left, r.bottom - r.top, TRUE); + } +} + +static DWORD WINAPI SendThreadProc(LPVOID param) +{ + bool finishedOk; + SendThreadData* td = (SendThreadData*)param; + + if (td->sendURL.empty()) { + finishedOk = false; + LogMessage("No server URL, not sending report"); + } else { + google_breakpad::CrashReportSender sender(L""); + finishedOk = (sender.SendCrashReport(td->sendURL, + td->queryParameters, + td->files, + &td->serverResponse) + == google_breakpad::RESULT_SUCCEEDED); + if (finishedOk) { + LogMessage("Crash report submitted successfully"); + } + else { + // get an error string and print it to the log + //XXX: would be nice to get the HTTP status code here, filed: + // http://code.google.com/p/google-breakpad/issues/detail?id=220 + LogMessage(FormatLastError()); + } + } + + PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0); + + return 0; +} + +static void EndCrashReporterDialog(HWND hwndDlg, int code) +{ + // Save the current values to the registry + wchar_t email[MAX_EMAIL_LENGTH]; + GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email, + sizeof(email) / sizeof(email[0])); + SetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email); + + SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, + IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0); + SetBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, + IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK) != 0); + SetBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE, + IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0); + + EndDialog(hwndDlg, code); +} + +static void MaybeResizeProgressText(HWND hwndDlg) +{ + HWND hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESSTEXT); + HDC hdc = GetDC(hwndProgress); + HFONT hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0); + if (hfont) + SelectObject(hdc, hfont); + SIZE size; + RECT rect; + GetRelativeRect(hwndProgress, hwndDlg, &rect); + + wchar_t text[1024]; + GetWindowText(hwndProgress, text, 1024); + + if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size)) + return; + + if (size.cx < (rect.right - rect.left)) + return; + + // Figure out how much we need to resize things vertically + // This is sort of a fudge, but it should be good enough. + int wantedHeight = size.cy * + (int)ceil((float)size.cx / (float)(rect.right - rect.left)); + int diff = wantedHeight - (rect.bottom - rect.top); + if (diff <= 0) + return; + + MoveWindow(hwndProgress, rect.left, rect.top, + rect.right - rect.left, + wantedHeight, + TRUE); + + gAttachedBottom.clear(); + gAttachedBottom.insert(IDC_CLOSEBUTTON); + gAttachedBottom.insert(IDC_RESTARTBUTTON); + + StretchDialog(hwndDlg, diff); + + for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) { + gAttachedBottom.insert(kDefaultAttachedBottom[i]); + } +} + +static void MaybeSendReport(HWND hwndDlg) +{ + if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) { + EndCrashReporterDialog(hwndDlg, 0); + return; + } + + // disable all the form controls + EnableWindow(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_CLOSEBUTTON), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON), false); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTDURINGSUBMIT).c_str()); + MaybeResizeProgressText(hwndDlg); + // start throbber + // play entire AVI, and loop + Animate_Play(GetDlgItem(hwndDlg, IDC_THROBBER), 0, -1, -1); + SetDlgItemVisible(hwndDlg, IDC_THROBBER, true); + gThreadHandle = nullptr; + gSendData.hDlg = hwndDlg; + gSendData.queryParameters = gQueryParameters; + + gThreadHandle = CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0, + nullptr); +} + +static void RestartApplication() +{ + wstring cmdLine; + + for (unsigned int i = 0; i < gRestartArgs.size(); i++) { + cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" "; + } + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + ZeroMemory(&pi, sizeof(pi)); + + if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE, + 0, nullptr, nullptr, &si, &pi)) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } +} + +static void ShowReportInfo(HWND hwndDlg) +{ + wstring description; + + for (map<wstring,wstring>::const_iterator i = gQueryParameters.begin(); + i != gQueryParameters.end(); + i++) { + description += i->first; + description += L": "; + description += i->second; + description += L"\n"; + } + + description += L"\n"; + description += Str(ST_EXTRAREPORTINFO); + + SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str()); +} + +static void UpdateURL(HWND hwndDlg) +{ + if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) { + gQueryParameters[L"URL"] = gURLParameter; + } else { + gQueryParameters.erase(L"URL"); + } +} + +static void UpdateEmail(HWND hwndDlg) +{ + if (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)) { + wchar_t email[MAX_EMAIL_LENGTH]; + GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email, + sizeof(email) / sizeof(email[0])); + gQueryParameters[L"Email"] = email; + if (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) + EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), true); + } else { + gQueryParameters.erase(L"Email"); + EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false); + } +} + +static void UpdateComment(HWND hwndDlg) +{ + wchar_t comment[MAX_COMMENT_LENGTH + 1]; + GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment, + sizeof(comment) / sizeof(comment[0])); + if (wcslen(comment) > 0) + gQueryParameters[L"Comments"] = comment; + else + gQueryParameters.erase(L"Comments"); +} + +/* + * Dialog procedure for the "view report" dialog. + */ +static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message, + WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_INITDIALOG: { + SetWindowText(hwndDlg, Str(ST_VIEWREPORTTITLE).c_str()); + SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str()); + SendDlgItemMessage(hwndDlg, IDC_VIEWREPORTTEXT, + EM_SETTARGETDEVICE, (WPARAM)nullptr, 0); + ShowReportInfo(hwndDlg); + SetFocus(GetDlgItem(hwndDlg, IDOK)); + return FALSE; + } + + case WM_COMMAND: { + if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK) + EndDialog(hwndDlg, 0); + return FALSE; + } + } + return FALSE; +} + +// Return the number of bytes this string will take encoded +// in UTF-8 +static inline int BytesInUTF8(wchar_t* str) +{ + // Just count size of buffer for UTF-8, minus one + // (we don't need to count the null terminator) + return WideCharToMultiByte(CP_UTF8, 0, str, -1, + nullptr, 0, nullptr, nullptr) - 1; +} + +// Calculate the length of the text in this edit control (in bytes, +// in the UTF-8 encoding) after replacing the current selection +// with |insert|. +static int NewTextLength(HWND hwndEdit, wchar_t* insert) +{ + wchar_t current[MAX_COMMENT_LENGTH + 1]; + + GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1); + DWORD selStart, selEnd; + SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd); + + int selectionLength = 0; + if (selEnd - selStart > 0) { + wchar_t selection[MAX_COMMENT_LENGTH + 1]; + google_breakpad::WindowsStringUtils::safe_wcsncpy(selection, + MAX_COMMENT_LENGTH + 1, + current + selStart, + selEnd - selStart); + selection[selEnd - selStart] = '\0'; + selectionLength = BytesInUTF8(selection); + } + + // current string length + replacement text length + // - replaced selection length + return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength; +} + +// Window procedure for subclassing edit controls +static LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) +{ + static WNDPROC super = nullptr; + + if (super == nullptr) + super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (uMsg) { + case WM_PAINT: { + HDC hdc; + PAINTSTRUCT ps; + RECT r; + wchar_t windowText[1024]; + + GetWindowText(hwnd, windowText, 1024); + // if the control contains text or is focused, draw it normally + if (GetFocus() == hwnd || windowText[0] != '\0') + return CallWindowProc(super, hwnd, uMsg, wParam, lParam); + + GetClientRect(hwnd, &r); + hdc = BeginPaint(hwnd, &ps); + FillRect(hdc, &r, GetSysColorBrush(IsWindowEnabled(hwnd) + ? COLOR_WINDOW : COLOR_BTNFACE)); + SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); + SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT)); + SetBkMode(hdc, TRANSPARENT); + wchar_t* txt = (wchar_t*)GetProp(hwnd, L"PROP_GRAYTEXT"); + // Get the actual edit control rect + CallWindowProc(super, hwnd, EM_GETRECT, 0, (LPARAM)&r); + UINT format = DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL; + if (gRTLlayout) + format |= DT_RIGHT; + if (txt) + DrawText(hdc, txt, wcslen(txt), &r, format); + EndPaint(hwnd, &ps); + return 0; + } + + // We handle WM_CHAR and WM_PASTE to limit the comment box to 500 + // bytes in UTF-8. + case WM_CHAR: { + // Leave accelerator keys and non-printing chars (except LF) alone + if (wParam & (1<<24) || wParam & (1<<29) || + (wParam < ' ' && wParam != '\n')) + break; + + wchar_t ch[2] = { (wchar_t)wParam, 0 }; + if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH) + return 0; + + break; + } + + case WM_PASTE: { + if (IsClipboardFormatAvailable(CF_UNICODETEXT) && + OpenClipboard(hwnd)) { + HGLOBAL hg = GetClipboardData(CF_UNICODETEXT); + wchar_t* pastedText = (wchar_t*)GlobalLock(hg); + int newSize = 0; + + if (pastedText) + newSize = NewTextLength(hwnd, pastedText); + + GlobalUnlock(hg); + CloseClipboard(); + + if (newSize > MAX_COMMENT_LENGTH) + return 0; + } + break; + } + + case WM_SETFOCUS: + case WM_KILLFOCUS: { + RECT r; + GetClientRect(hwnd, &r); + InvalidateRect(hwnd, &r, TRUE); + break; + } + + case WM_DESTROY: { + // cleanup our property + HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT"); + if (hData) + GlobalFree(hData); + } + } + + return CallWindowProc(super, hwnd, uMsg, wParam, lParam); +} + +// Resize a control to fit this text +static int ResizeControl(HWND hwndButton, RECT& rect, wstring text, + bool shiftLeft, int userDefinedPadding) +{ + HDC hdc = GetDC(hwndButton); + HFONT hfont = (HFONT)SendMessage(hwndButton, WM_GETFONT, 0, 0); + if (hfont) + SelectObject(hdc, hfont); + SIZE size, oldSize; + int sizeDiff = 0; + + wchar_t oldText[1024]; + GetWindowText(hwndButton, oldText, 1024); + + if (GetTextExtentPoint32(hdc, text.c_str(), text.length(), &size) + // default text on the button + && GetTextExtentPoint32(hdc, oldText, wcslen(oldText), &oldSize)) { + /* + Expand control widths to accomidate wider text strings. For most + controls (including buttons) the text padding is defined by the + dialog's rc file. Some controls (such as checkboxes) have padding + that extends to the end of the dialog, in which case we ignore the + rc padding and rely on a user defined value passed in through + userDefinedPadding. + */ + int textIncrease = size.cx - oldSize.cx; + if (textIncrease < 0) + return 0; + int existingTextPadding; + if (userDefinedPadding == 0) + existingTextPadding = (rect.right - rect.left) - oldSize.cx; + else + existingTextPadding = userDefinedPadding; + sizeDiff = textIncrease + existingTextPadding; + + if (shiftLeft) { + // shift left by the amount the button should grow + rect.left -= sizeDiff; + } + else { + // grow right instead + rect.right += sizeDiff; + } + MoveWindow(hwndButton, rect.left, rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + TRUE); + } + return sizeDiff; +} + +// The window was resized horizontally, so widen some of our +// controls to make use of the space +static void StretchControlsToFit(HWND hwndDlg) +{ + int controls[] = { + IDC_DESCRIPTIONTEXT, + IDC_SUBMITREPORTCHECK, + IDC_COMMENTTEXT, + IDC_INCLUDEURLCHECK, + IDC_EMAILMECHECK, + IDC_EMAILTEXT, + IDC_PROGRESSTEXT + }; + + RECT dlgRect; + GetClientRect(hwndDlg, &dlgRect); + + for (int i=0; i<sizeof(controls)/sizeof(controls[0]); i++) { + RECT r; + HWND hwndControl = GetDlgItem(hwndDlg, controls[i]); + GetRelativeRect(hwndControl, hwndDlg, &r); + // 6 pixel spacing on the right + if (r.right + 6 != dlgRect.right) { + r.right = dlgRect.right - 6; + MoveWindow(hwndControl, r.left, r.top, + r.right - r.left, + r.bottom - r.top, + TRUE); + } + } +} + +static void SubmitReportChecked(HWND hwndDlg) +{ + bool enabled = (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), + enabled && (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK) + != 0)); + SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled); +} + +static INT_PTR DialogBoxParamMaybeRTL(UINT idd, HWND hwndParent, + DLGPROC dlgProc, LPARAM param) +{ + INT_PTR rv = 0; + if (gRTLlayout) { + // We need to toggle the WS_EX_LAYOUTRTL style flag on the dialog + // template. + HRSRC hDialogRC = FindResource(nullptr, MAKEINTRESOURCE(idd), + RT_DIALOG); + HGLOBAL hDlgTemplate = LoadResource(nullptr, hDialogRC); + DLGTEMPLATEEX* pDlgTemplate = (DLGTEMPLATEEX*)LockResource(hDlgTemplate); + unsigned long sizeDlg = SizeofResource(nullptr, hDialogRC); + HGLOBAL hMyDlgTemplate = GlobalAlloc(GPTR, sizeDlg); + DLGTEMPLATEEX* pMyDlgTemplate = + (DLGTEMPLATEEX*)GlobalLock(hMyDlgTemplate); + memcpy(pMyDlgTemplate, pDlgTemplate, sizeDlg); + + pMyDlgTemplate->exStyle |= WS_EX_LAYOUTRTL; + + rv = DialogBoxIndirectParam(nullptr, (LPCDLGTEMPLATE)pMyDlgTemplate, + hwndParent, dlgProc, param); + GlobalUnlock(hMyDlgTemplate); + GlobalFree(hMyDlgTemplate); + } + else { + rv = DialogBoxParam(nullptr, MAKEINTRESOURCE(idd), hwndParent, + dlgProc, param); + } + + return rv; +} + + +static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message, + WPARAM wParam, LPARAM lParam) +{ + static int sHeight = 0; + + bool success; + bool enabled; + + switch (message) { + case WM_INITDIALOG: { + GetThemeSizes(hwndDlg); + RECT r; + GetClientRect(hwndDlg, &r); + sHeight = r.bottom - r.top; + + SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str()); + HICON hIcon = LoadIcon(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDI_MAINICON)); + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + + // resize the "View Report" button based on the string length + RECT rect; + HWND hwnd = GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON); + GetRelativeRect(hwnd, hwndDlg, &rect); + ResizeControl(hwnd, rect, Str(ST_VIEWREPORT), false, 0); + SetDlgItemText(hwndDlg, IDC_VIEWREPORTBUTTON, Str(ST_VIEWREPORT).c_str()); + + hwnd = GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK); + GetRelativeRect(hwnd, hwndDlg, &rect); + long maxdiff = ResizeControl(hwnd, rect, Str(ST_CHECKSUBMIT), false, + gCheckboxPadding); + SetDlgItemText(hwndDlg, IDC_SUBMITREPORTCHECK, + Str(ST_CHECKSUBMIT).c_str()); + + if (!CheckBoolKey(gCrashReporterKey.c_str(), + SUBMIT_REPORT_VALUE, &enabled)) + enabled = ShouldEnableSending(); + + CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, enabled ? BST_CHECKED + : BST_UNCHECKED); + SubmitReportChecked(hwndDlg); + + HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT); + WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr(hwndComment, + GWLP_WNDPROC, + (LONG_PTR)EditSubclassProc); + + // Subclass comment edit control to get placeholder text + SetWindowLongPtr(hwndComment, GWLP_USERDATA, (LONG_PTR)OldWndProc); + wstring commentGrayText = Str(ST_COMMENTGRAYTEXT); + wchar_t* hMem = (wchar_t*)GlobalAlloc(GPTR, (commentGrayText.length() + 1)*sizeof(wchar_t)); + wcscpy(hMem, commentGrayText.c_str()); + SetProp(hwndComment, L"PROP_GRAYTEXT", hMem); + + hwnd = GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK); + GetRelativeRect(hwnd, hwndDlg, &rect); + long diff = ResizeControl(hwnd, rect, Str(ST_CHECKURL), false, + gCheckboxPadding); + maxdiff = std::max(diff, maxdiff); + SetDlgItemText(hwndDlg, IDC_INCLUDEURLCHECK, Str(ST_CHECKURL).c_str()); + + // want this on by default + if (CheckBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, &enabled) && + !enabled) { + CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_UNCHECKED); + } else { + CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_CHECKED); + } + + hwnd = GetDlgItem(hwndDlg, IDC_EMAILMECHECK); + GetRelativeRect(hwnd, hwndDlg, &rect); + diff = ResizeControl(hwnd, rect, Str(ST_CHECKEMAIL), false, + gCheckboxPadding); + maxdiff = std::max(diff, maxdiff); + SetDlgItemText(hwndDlg, IDC_EMAILMECHECK, Str(ST_CHECKEMAIL).c_str()); + + if (CheckBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, &enabled) && + enabled) { + CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED); + } else { + CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_UNCHECKED); + } + + wstring email; + if (GetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email)) { + SetDlgItemText(hwndDlg, IDC_EMAILTEXT, email.c_str()); + } + + // Subclass email edit control to get placeholder text + HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT); + OldWndProc = (WNDPROC)SetWindowLongPtr(hwndEmail, + GWLP_WNDPROC, + (LONG_PTR)EditSubclassProc); + SetWindowLongPtr(hwndEmail, GWLP_USERDATA, (LONG_PTR)OldWndProc); + wstring emailGrayText = Str(ST_EMAILGRAYTEXT); + hMem = (wchar_t*)GlobalAlloc(GPTR, (emailGrayText.length() + 1)*sizeof(wchar_t)); + wcscpy(hMem, emailGrayText.c_str()); + SetProp(hwndEmail, L"PROP_GRAYTEXT", hMem); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTPRESUBMIT).c_str()); + + RECT closeRect; + HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON); + GetRelativeRect(hwndClose, hwndDlg, &closeRect); + + RECT restartRect; + HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON); + GetRelativeRect(hwndRestart, hwndDlg, &restartRect); + + // set the close button text and shift the buttons around + // since the size may need to change + int sizeDiff = ResizeControl(hwndClose, closeRect, Str(ST_QUIT), + true, 0); + restartRect.left -= sizeDiff; + restartRect.right -= sizeDiff; + SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str()); + + if (gRestartArgs.size() > 0) { + // Resize restart button to fit text + ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0); + SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str()); + } else { + // No restart arguments, so just hide the restart button + SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false); + } + // See if we need to widen the window + // Leave 6 pixels on either side + 6 pixels between the buttons + int neededSize = closeRect.right - closeRect.left + + restartRect.right - restartRect.left + 6 * 3; + GetClientRect(hwndDlg, &r); + // We may already have resized one of the checkboxes above + maxdiff = std::max(maxdiff, neededSize - (r.right - r.left)); + + if (maxdiff > 0) { + // widen window + GetWindowRect(hwndDlg, &r); + r.right += maxdiff; + MoveWindow(hwndDlg, r.left, r.top, + r.right - r.left, r.bottom - r.top, TRUE); + // shift both buttons right + if (restartRect.left + maxdiff < 6) + maxdiff += 6; + closeRect.left += maxdiff; + closeRect.right += maxdiff; + restartRect.left += maxdiff; + restartRect.right += maxdiff; + MoveWindow(hwndClose, closeRect.left, closeRect.top, + closeRect.right - closeRect.left, + closeRect.bottom - closeRect.top, + TRUE); + StretchControlsToFit(hwndDlg); + } + // need to move the restart button regardless + MoveWindow(hwndRestart, restartRect.left, restartRect.top, + restartRect.right - restartRect.left, + restartRect.bottom - restartRect.top, + TRUE); + + // Resize the description text last, in case the window was resized + // before this. + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, + EM_SETEVENTMASK, (WPARAM)nullptr, + ENM_REQUESTRESIZE); + + wstring description = Str(ST_CRASHREPORTERHEADER); + description += L"\n\n"; + description += Str(ST_CRASHREPORTERDESCRIPTION); + SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str()); + + + // Make the title bold. + CHARFORMAT fmt = { 0, }; + fmt.cbSize = sizeof(fmt); + fmt.dwMask = CFM_BOLD; + fmt.dwEffects = CFE_BOLD; + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, + 0, Str(ST_CRASHREPORTERHEADER).length()); + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETCHARFORMAT, + SCF_SELECTION, (LPARAM)&fmt); + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0); + // Force redraw. + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, + EM_SETTARGETDEVICE, (WPARAM)nullptr, 0); + // Force resize. + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, + EM_REQUESTRESIZE, 0, 0); + + // if no URL was given, hide the URL checkbox + if (gQueryParameters.find(L"URL") == gQueryParameters.end()) { + RECT urlCheckRect, emailCheckRect; + GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect); + GetWindowRect(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), &emailCheckRect); + + SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false); + + gAttachedBottom.erase(IDC_VIEWREPORTBUTTON); + gAttachedBottom.erase(IDC_SUBMITREPORTCHECK); + gAttachedBottom.erase(IDC_COMMENTTEXT); + + StretchDialog(hwndDlg, urlCheckRect.top - emailCheckRect.top); + + gAttachedBottom.insert(IDC_VIEWREPORTBUTTON); + gAttachedBottom.insert(IDC_SUBMITREPORTCHECK); + gAttachedBottom.insert(IDC_COMMENTTEXT); + } + + MaybeResizeProgressText(hwndDlg); + + // Open the AVI resource for the throbber + Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER), + MAKEINTRESOURCE(IDR_THROBBER)); + + UpdateURL(hwndDlg); + UpdateEmail(hwndDlg); + + SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK)); + return FALSE; + } + case WM_SIZE: { + ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight); + sHeight = HIWORD(lParam); + InvalidateRect(hwndDlg, nullptr, TRUE); + return FALSE; + } + case WM_NOTIFY: { + NMHDR* notification = reinterpret_cast<NMHDR*>(lParam); + if (notification->code == EN_REQUESTRESIZE) { + // Resizing the rich edit control to fit the description text. + REQRESIZE* reqresize = reinterpret_cast<REQRESIZE*>(lParam); + RECT newSize = reqresize->rc; + RECT oldSize; + GetRelativeRect(notification->hwndFrom, hwndDlg, &oldSize); + + // resize the text box as requested + MoveWindow(notification->hwndFrom, newSize.left, newSize.top, + newSize.right - newSize.left, newSize.bottom - newSize.top, + TRUE); + + // Resize the dialog to fit (the WM_SIZE handler will move the controls) + StretchDialog(hwndDlg, newSize.bottom - oldSize.bottom); + } + return FALSE; + } + case WM_COMMAND: { + if (HIWORD(wParam) == BN_CLICKED) { + switch(LOWORD(wParam)) { + case IDC_VIEWREPORTBUTTON: + DialogBoxParamMaybeRTL(IDD_VIEWREPORTDIALOG, hwndDlg, + (DLGPROC)ViewReportDialogProc, 0); + break; + case IDC_SUBMITREPORTCHECK: + SubmitReportChecked(hwndDlg); + break; + case IDC_INCLUDEURLCHECK: + UpdateURL(hwndDlg); + break; + case IDC_EMAILMECHECK: + UpdateEmail(hwndDlg); + break; + case IDC_CLOSEBUTTON: + MaybeSendReport(hwndDlg); + break; + case IDC_RESTARTBUTTON: + RestartApplication(); + MaybeSendReport(hwndDlg); + break; + } + } else if (HIWORD(wParam) == EN_CHANGE) { + switch(LOWORD(wParam)) { + case IDC_EMAILTEXT: + UpdateEmail(hwndDlg); + break; + case IDC_COMMENTTEXT: + UpdateComment(hwndDlg); + } + } + + return FALSE; + } + case WM_UPLOADCOMPLETE: { + WaitForSingleObject(gThreadHandle, INFINITE); + success = (wParam == 1); + SendCompleted(success, WideToUTF8(gSendData.serverResponse)); + // hide throbber + Animate_Stop(GetDlgItem(hwndDlg, IDC_THROBBER)); + SetDlgItemVisible(hwndDlg, IDC_THROBBER, false); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, + success ? + Str(ST_REPORTSUBMITSUCCESS).c_str() : + Str(ST_SUBMITFAILED).c_str()); + MaybeResizeProgressText(hwndDlg); + // close dialog after 5 seconds + SetTimer(hwndDlg, 0, 5000, nullptr); + // + return TRUE; + } + + case WM_LBUTTONDOWN: { + HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT); + POINT p = { LOWORD(lParam), HIWORD(lParam) }; + // if the email edit control is clicked, enable it, + // check the email checkbox, and focus the email edit control + if (ChildWindowFromPoint(hwndDlg, p) == hwndEmail && + IsWindowEnabled(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON)) && + !IsWindowEnabled(hwndEmail) && + IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0) { + CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED); + UpdateEmail(hwndDlg); + SetFocus(hwndEmail); + } + break; + } + + case WM_TIMER: { + // The "1" gets used down in UIShowCrashUI to indicate that we at least + // tried to send the report. + EndCrashReporterDialog(hwndDlg, 1); + return FALSE; + } + + case WM_CLOSE: { + EndCrashReporterDialog(hwndDlg, 0); + return FALSE; + } + } + return FALSE; +} + +static wstring UTF8ToWide(const string& utf8, bool *success) +{ + wchar_t* buffer = nullptr; + int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), + -1, nullptr, 0); + if(buffer_size == 0) { + if (success) + *success = false; + return L""; + } + + buffer = new wchar_t[buffer_size]; + if(buffer == nullptr) { + if (success) + *success = false; + return L""; + } + + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), + -1, buffer, buffer_size); + wstring str = buffer; + delete [] buffer; + + if (success) + *success = true; + + return str; +} + +static string WideToMBCP(const wstring& wide, + unsigned int cp, + bool* success = nullptr) +{ + char* buffer = nullptr; + int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), + -1, nullptr, 0, nullptr, nullptr); + if(buffer_size == 0) { + if (success) + *success = false; + return ""; + } + + buffer = new char[buffer_size]; + if(buffer == nullptr) { + if (success) + *success = false; + return ""; + } + + WideCharToMultiByte(cp, 0, wide.c_str(), + -1, buffer, buffer_size, nullptr, nullptr); + string mb = buffer; + delete [] buffer; + + if (success) + *success = true; + + return mb; +} + +string WideToUTF8(const wstring& wide, bool* success) +{ + return WideToMBCP(wide, CP_UTF8, success); +} + +/* === Crashreporter UI Functions === */ + +bool UIInit() +{ + for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) { + gAttachedBottom.insert(kDefaultAttachedBottom[i]); + } + + DoInitCommonControls(); + + return true; +} + +void UIShutdown() +{ +} + +void UIShowDefaultUI() +{ + MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(), + L"Crash Reporter", + MB_OK | MB_ICONSTOP); +} + +static bool CanUseMainCrashReportServer() +{ + // Any NT from 6.0 and above is fine. + if (IsWindowsVersionOrGreater(6, 0, 0)) { + return true; + } + + // On NT 5 servers, we need Server 2003 SP2. + if (IsWindowsServer()) { + return IsWindowsVersionOrGreater(5, 2, 2); + } + + // Otherwise we have an NT 5 client. + // We need exactly XP SP3 (version 5.1 SP3 but not version 5.2). + return (IsWindowsVersionOrGreater(5, 1, 3) && + !IsWindowsVersionOrGreater(5, 2, 0)); +} + +bool UIShowCrashUI(const StringTable& files, + const StringTable& queryParameters, + const string& sendURL, + const vector<string>& restartArgs) +{ + gSendData.hDlg = nullptr; + gSendData.sendURL = UTF8ToWide(sendURL); + + // Older Windows don't support the crash report server's crypto. + // This is a hack to use an alternate server. + if (!CanUseMainCrashReportServer() && + gSendData.sendURL.find(SENDURL_ORIGINAL) == 0) { + gSendData.sendURL.replace(0, ARRAYSIZE(SENDURL_ORIGINAL) - 1, + SENDURL_XPSP2); + } + + for (StringTable::const_iterator i = files.begin(); + i != files.end(); + i++) { + gSendData.files[UTF8ToWide(i->first)] = UTF8ToWide(i->second); + } + + for (StringTable::const_iterator i = queryParameters.begin(); + i != queryParameters.end(); + i++) { + gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second); + } + + if (gQueryParameters.find(L"Vendor") != gQueryParameters.end()) { + gCrashReporterKey = L"Software\\"; + if (!gQueryParameters[L"Vendor"].empty()) { + gCrashReporterKey += gQueryParameters[L"Vendor"] + L"\\"; + } + gCrashReporterKey += gQueryParameters[L"ProductName"] + L"\\Crash Reporter"; + } + + if (gQueryParameters.find(L"URL") != gQueryParameters.end()) + gURLParameter = gQueryParameters[L"URL"]; + + gRestartArgs = restartArgs; + + if (gStrings.find("isRTL") != gStrings.end() && + gStrings["isRTL"] == "yes") + gRTLlayout = true; + + return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr, + (DLGPROC)CrashReporterDialogProc, 0); +} + +void UIError_impl(const string& message) +{ + wstring title = Str(ST_CRASHREPORTERTITLE); + if (title.empty()) + title = L"Crash Reporter Error"; + + MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(), + MB_OK | MB_ICONSTOP); +} + +bool UIGetIniPath(string& path) +{ + wchar_t fileName[MAX_PATH]; + if (GetModuleFileName(nullptr, fileName, MAX_PATH)) { + // get crashreporter ini + wchar_t* s = wcsrchr(fileName, '.'); + if (s) { + wcscpy(s, L".ini"); + path = WideToUTF8(fileName); + return true; + } + } + + return false; +} + +bool UIGetSettingsPath(const string& vendor, + const string& product, + string& settings_path) +{ + wchar_t path[MAX_PATH]; + HRESULT hRes = SHGetFolderPath(nullptr, + CSIDL_APPDATA, + nullptr, + 0, + path); + if (FAILED(hRes)) { + // This provides a fallback for getting the path to APPDATA by querying the + // registry when the call to SHGetFolderPath is unable to provide this path + // (Bug 513958). + HKEY key; + DWORD type, size, dwRes; + dwRes = ::RegOpenKeyExW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", + 0, + KEY_READ, + &key); + if (dwRes != ERROR_SUCCESS) + return false; + + dwRes = RegQueryValueExW(key, + L"AppData", + nullptr, + &type, + (LPBYTE)&path, + &size); + ::RegCloseKey(key); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (dwRes != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) + return false; + } + + if (!vendor.empty()) { + PathAppend(path, UTF8ToWide(vendor).c_str()); + } + PathAppend(path, UTF8ToWide(product).c_str()); + PathAppend(path, L"Crash Reports"); + settings_path = WideToUTF8(path); + return true; +} + +bool UIEnsurePathExists(const string& path) +{ + if (CreateDirectory(UTF8ToWide(path).c_str(), nullptr) == 0) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + return false; + } + + return true; +} + +bool UIFileExists(const string& path) +{ + DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str()); + return (attrs != INVALID_FILE_ATTRIBUTES); +} + +bool UIMoveFile(const string& oldfile, const string& newfile) +{ + if (oldfile == newfile) + return true; + + return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str()) + == TRUE; +} + +bool UIDeleteFile(const string& oldfile) +{ + return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE; +} + +ifstream* UIOpenRead(const string& filename) +{ + // adapted from breakpad's src/common/windows/http_upload.cc + +#if defined(_MSC_VER) + ifstream* file = new ifstream(); + file->open(UTF8ToWide(filename).c_str(), ios::in); +#else // GCC + ifstream* file = new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), + ios::in); +#endif // _MSC_VER + + return file; +} + +ofstream* UIOpenWrite(const string& filename, + bool append, // append=false + bool binary) // binary=false +{ + // adapted from breakpad's src/common/windows/http_upload.cc + std::ios_base::openmode mode = ios::out; + if (append) { + mode = mode | ios::app; + } + if (binary) { + mode = mode | ios::binary; + } + +#if defined(_MSC_VER) + ofstream* file = new ofstream(); + file->open(UTF8ToWide(filename).c_str(), mode); +#else // GCC + ofstream* file = new ofstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), + mode); +#endif // _MSC_VER + + return file; +} + +struct FileData +{ + FILETIME timestamp; + wstring path; +}; + +static bool CompareFDTime(const FileData& fd1, const FileData& fd2) +{ + return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0; +} + +void UIPruneSavedDumps(const std::string& directory) +{ + wstring wdirectory = UTF8ToWide(directory); + + WIN32_FIND_DATA fdata; + wstring findpath = wdirectory + L"\\*.dmp"; + HANDLE dirlist = FindFirstFile(findpath.c_str(), &fdata); + if (dirlist == INVALID_HANDLE_VALUE) + return; + + vector<FileData> dumpfiles; + + for (BOOL ok = true; ok; ok = FindNextFile(dirlist, &fdata)) { + FileData fd = {fdata.ftLastWriteTime, wdirectory + L"\\" + fdata.cFileName}; + dumpfiles.push_back(fd); + } + + sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime); + + while (dumpfiles.size() > kSaveCount) { + // get the path of the oldest file + wstring path = (--dumpfiles.end())->path; + DeleteFile(path.c_str()); + + // s/.dmp/.extra/ + path.replace(path.size() - 4, 4, L".extra"); + DeleteFile(path.c_str()); + + dumpfiles.pop_back(); + } +} + +void UIRunMinidumpAnalyzer(const string& exename, const string& filename) +{ + wstring cmdLine; + + cmdLine += L"\"" + UTF8ToWide(exename) + L"\" "; + cmdLine += L"\"" + UTF8ToWide(filename) + L"\" "; + + STARTUPINFO si = {}; + PROCESS_INFORMATION pi = {}; + + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + + if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE, + 0, nullptr, nullptr, &si, &pi)) { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } +} |