1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #ifdef WIN32_LEAN_AND_MEAN
7 # undef WIN32_LEAN_AND_MEAN
8 #endif
9
10 #include "crashreporter.h"
11
12 #include <windows.h>
13 #include <versionhelpers.h>
14 #include <commctrl.h>
15 #include <richedit.h>
16 #include <shellapi.h>
17 #include <shlobj.h>
18 #include <shlwapi.h>
19 #include <math.h>
20 #include <set>
21 #include <algorithm>
22 #include "resource.h"
23 #include "windows/sender/crash_report_sender.h"
24 #include "common/windows/string_utils-inl.h"
25
26 #define CRASH_REPORTER_VALUE L"Enabled"
27 #define SUBMIT_REPORT_VALUE L"SubmitCrashReport"
28 #define SUBMIT_REPORT_OLD L"SubmitReport"
29 #define INCLUDE_URL_VALUE L"IncludeURL"
30
31 #define SENDURL_ORIGINAL L"https://crash-reports.mozilla.com/submit"
32 #define SENDURL_XPSP2 L"https://crash-reports-xpsp2.mozilla.com/submit"
33
34 #define WM_UPLOADCOMPLETE WM_APP
35
36 // Thanks, Windows.h :(
37 #undef min
38 #undef max
39
40 using std::ifstream;
41 using std::ios;
42 using std::ios_base;
43 using std::map;
44 using std::ofstream;
45 using std::set;
46 using std::string;
47 using std::vector;
48 using std::wstring;
49
50 using namespace CrashReporter;
51
52 typedef struct {
53 HWND hDlg;
54 Json::Value queryParameters;
55 map<wstring, wstring> files;
56 wstring sendURL;
57
58 wstring serverResponse;
59 } SendThreadData;
60
61 /*
62 * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx
63 * "The DLGTEMPLATEEX structure is not defined in any standard header file.
64 * The structure definition is provided here to explain the format of an
65 * extended template for a dialog box.
66 */
67 typedef struct {
68 WORD dlgVer;
69 WORD signature;
70 DWORD helpID;
71 DWORD exStyle;
72 // There's more to this struct, but it has weird variable-length
73 // members, and I only actually need to touch exStyle on an existing
74 // instance, so I've omitted the rest.
75 } DLGTEMPLATEEX;
76
77 static HANDLE gThreadHandle;
78 static SendThreadData gSendData = {
79 0,
80 };
81 static vector<string> gRestartArgs;
82 static Json::Value gQueryParameters;
83 static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter");
84 static string gURLParameter;
85 static int gCheckboxPadding = 6;
86 static bool gRTLlayout = false;
87
88 // When vertically resizing the dialog, these items should move down
89 static set<UINT> gAttachedBottom;
90
91 // Default set of items for gAttachedBottom
92 static const UINT kDefaultAttachedBottom[] = {
93 IDC_SUBMITREPORTCHECK, IDC_VIEWREPORTBUTTON, IDC_COMMENTTEXT,
94 IDC_INCLUDEURLCHECK, IDC_PROGRESSTEXT, IDC_THROBBER,
95 IDC_CLOSEBUTTON, IDC_RESTARTBUTTON,
96 };
97
98 static wstring UTF8ToWide(const string& utf8, bool* success = 0);
99 static DWORD WINAPI SendThreadProc(LPVOID param);
100
Str(const char * key)101 static wstring Str(const char* key) { return UTF8ToWide(gStrings[key]); }
102
103 /* === win32 helper functions === */
104
DoInitCommonControls()105 static void DoInitCommonControls() {
106 INITCOMMONCONTROLSEX ic;
107 ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
108 ic.dwICC = ICC_PROGRESS_CLASS;
109 InitCommonControlsEx(&ic);
110 // also get the rich edit control
111 LoadLibrary(L"Msftedit.dll");
112 }
113
GetBoolValue(HKEY hRegKey,LPCTSTR valueName,DWORD * value)114 static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) {
115 DWORD type, dataSize;
116 dataSize = sizeof(DWORD);
117 if (RegQueryValueEx(hRegKey, valueName, nullptr, &type, (LPBYTE)value,
118 &dataSize) == ERROR_SUCCESS &&
119 type == REG_DWORD)
120 return true;
121
122 return false;
123 }
124
125 // Removes a value from HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER, if it exists.
RemoveUnusedValues(const wchar_t * key,LPCTSTR valueName)126 static void RemoveUnusedValues(const wchar_t* key, LPCTSTR valueName) {
127 HKEY hRegKey;
128
129 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_SET_VALUE, &hRegKey) ==
130 ERROR_SUCCESS) {
131 RegDeleteValue(hRegKey, valueName);
132 RegCloseKey(hRegKey);
133 }
134
135 if (RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_SET_VALUE, &hRegKey) ==
136 ERROR_SUCCESS) {
137 RegDeleteValue(hRegKey, valueName);
138 RegCloseKey(hRegKey);
139 }
140 }
141
CheckBoolKey(const wchar_t * key,const wchar_t * valueName,bool * enabled)142 static bool CheckBoolKey(const wchar_t* key, const wchar_t* valueName,
143 bool* enabled) {
144 /*
145 * NOTE! This code needs to stay in sync with the preference checking
146 * code in in nsExceptionHandler.cpp.
147 */
148 *enabled = false;
149 bool found = false;
150 HKEY hRegKey;
151 DWORD val;
152 // see if our reg key is set globally
153 if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) {
154 if (GetBoolValue(hRegKey, valueName, &val)) {
155 *enabled = (val == 1);
156 found = true;
157 }
158 RegCloseKey(hRegKey);
159 } else {
160 // look for it in user settings
161 if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
162 if (GetBoolValue(hRegKey, valueName, &val)) {
163 *enabled = (val == 1);
164 found = true;
165 }
166 RegCloseKey(hRegKey);
167 }
168 }
169
170 return found;
171 }
172
SetBoolKey(const wchar_t * key,const wchar_t * value,bool enabled)173 static void SetBoolKey(const wchar_t* key, const wchar_t* value, bool enabled) {
174 /*
175 * NOTE! This code needs to stay in sync with the preference setting
176 * code in in nsExceptionHandler.cpp.
177 */
178 HKEY hRegKey;
179
180 // remove the old value from the registry if it exists
181 RemoveUnusedValues(key, SUBMIT_REPORT_OLD);
182
183 if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
184 DWORD data = (enabled ? 1 : 0);
185 RegSetValueEx(hRegKey, value, 0, REG_DWORD, (LPBYTE)&data, sizeof(data));
186 RegCloseKey(hRegKey);
187 }
188 }
189
FormatLastError()190 static string FormatLastError() {
191 DWORD err = GetLastError();
192 LPWSTR s;
193 string message = "Crash report submission failed: ";
194 // odds are it's a WinInet error
195 HANDLE hInetModule = GetModuleHandle(L"WinInet.dll");
196 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
197 FORMAT_MESSAGE_FROM_SYSTEM |
198 FORMAT_MESSAGE_FROM_HMODULE,
199 hInetModule, err, 0, (LPWSTR)&s, 0, nullptr) != 0) {
200 message += WideToUTF8(s, nullptr);
201 LocalFree(s);
202 // strip off any trailing newlines
203 string::size_type n = message.find_last_not_of("\r\n");
204 if (n < message.size() - 1) {
205 message.erase(n + 1);
206 }
207 } else {
208 char buf[64];
209 sprintf(buf, "Unknown error, error code: 0x%08x",
210 static_cast<unsigned int>(err));
211 message += buf;
212 }
213 return message;
214 }
215
216 #define TS_DRAW 2
217 #define BP_CHECKBOX 3
218
219 typedef HANDLE(WINAPI* OpenThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList);
220 typedef HRESULT(WINAPI* CloseThemeDataPtr)(HANDLE hTheme);
221 typedef HRESULT(WINAPI* GetThemePartSizePtr)(HANDLE hTheme, HDC hdc,
222 int iPartId, int iStateId,
223 RECT* prc, int ts, SIZE* psz);
224 typedef HRESULT(WINAPI* GetThemeContentRectPtr)(HANDLE hTheme, HDC hdc,
225 int iPartId, int iStateId,
226 const RECT* pRect,
227 RECT* pContentRect);
228
GetThemeSizes(HWND hwnd)229 static void GetThemeSizes(HWND hwnd) {
230 HMODULE themeDLL = LoadLibrary(L"uxtheme.dll");
231
232 if (!themeDLL) return;
233
234 OpenThemeDataPtr openTheme =
235 (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData");
236 CloseThemeDataPtr closeTheme =
237 (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData");
238 GetThemePartSizePtr getThemePartSize =
239 (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize");
240
241 if (!openTheme || !closeTheme || !getThemePartSize) {
242 FreeLibrary(themeDLL);
243 return;
244 }
245
246 HANDLE buttonTheme = openTheme(hwnd, L"Button");
247 if (!buttonTheme) {
248 FreeLibrary(themeDLL);
249 return;
250 }
251 HDC hdc = GetDC(hwnd);
252 SIZE s;
253 getThemePartSize(buttonTheme, hdc, BP_CHECKBOX, 0, nullptr, TS_DRAW, &s);
254 gCheckboxPadding = s.cx;
255 closeTheme(buttonTheme);
256 FreeLibrary(themeDLL);
257 }
258
259 // Gets the position of a window relative to another window's client area
GetRelativeRect(HWND hwnd,HWND hwndParent,RECT * r)260 static void GetRelativeRect(HWND hwnd, HWND hwndParent, RECT* r) {
261 GetWindowRect(hwnd, r);
262 MapWindowPoints(nullptr, hwndParent, (POINT*)r, 2);
263 }
264
SetDlgItemVisible(HWND hwndDlg,UINT item,bool visible)265 static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible) {
266 HWND hwnd = GetDlgItem(hwndDlg, item);
267
268 ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE);
269 }
270
271 /* === Crash Reporting Dialog === */
272
StretchDialog(HWND hwndDlg,int ydiff)273 static void StretchDialog(HWND hwndDlg, int ydiff) {
274 RECT r;
275 GetWindowRect(hwndDlg, &r);
276 r.bottom += ydiff;
277 MoveWindow(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE);
278 }
279
ReflowDialog(HWND hwndDlg,int ydiff)280 static void ReflowDialog(HWND hwndDlg, int ydiff) {
281 // Move items attached to the bottom down/up by as much as
282 // the window resize
283 for (set<UINT>::const_iterator item = gAttachedBottom.begin();
284 item != gAttachedBottom.end(); item++) {
285 RECT r;
286 HWND hwnd = GetDlgItem(hwndDlg, *item);
287 GetRelativeRect(hwnd, hwndDlg, &r);
288 r.top += ydiff;
289 r.bottom += ydiff;
290 MoveWindow(hwnd, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE);
291 }
292 }
293
SendThreadProc(LPVOID param)294 static DWORD WINAPI SendThreadProc(LPVOID param) {
295 bool finishedOk;
296 SendThreadData* td = (SendThreadData*)param;
297
298 if (td->sendURL.empty()) {
299 finishedOk = false;
300 LogMessage("No server URL, not sending report");
301 } else {
302 Json::StreamWriterBuilder builder;
303 builder["indentation"] = "";
304 string parameters(Json::writeString(builder, td->queryParameters));
305 google_breakpad::CrashReportSender sender(L"");
306 finishedOk = (sender.SendCrashReport(td->sendURL, parameters, td->files,
307 &td->serverResponse) ==
308 google_breakpad::RESULT_SUCCEEDED);
309 if (finishedOk) {
310 LogMessage("Crash report submitted successfully");
311 } else {
312 // get an error string and print it to the log
313 // XXX: would be nice to get the HTTP status code here, filed:
314 // http://code.google.com/p/google-breakpad/issues/detail?id=220
315 LogMessage(FormatLastError());
316 }
317 }
318
319 if (gAutoSubmit) {
320 // Ordinarily this is done on the main thread in CrashReporterDialogProc,
321 // for auto submit we don't run that and it should be safe to finish up
322 // here as is done on other platforms.
323 SendCompleted(finishedOk, WideToUTF8(gSendData.serverResponse));
324 } else {
325 PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0);
326 }
327
328 return 0;
329 }
330
EndCrashReporterDialog(HWND hwndDlg,int code)331 static void EndCrashReporterDialog(HWND hwndDlg, int code) {
332 // Save the current values to the registry
333 SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE,
334 IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0);
335 SetBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE,
336 IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
337
338 EndDialog(hwndDlg, code);
339 }
340
MaybeResizeProgressText(HWND hwndDlg)341 static void MaybeResizeProgressText(HWND hwndDlg) {
342 HWND hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESSTEXT);
343 HDC hdc = GetDC(hwndProgress);
344 HFONT hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0);
345 if (hfont) SelectObject(hdc, hfont);
346 SIZE size;
347 RECT rect;
348 GetRelativeRect(hwndProgress, hwndDlg, &rect);
349
350 wchar_t text[1024];
351 GetWindowText(hwndProgress, text, 1024);
352
353 if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size)) return;
354
355 if (size.cx < (rect.right - rect.left)) return;
356
357 // Figure out how much we need to resize things vertically
358 // This is sort of a fudge, but it should be good enough.
359 int wantedHeight =
360 size.cy * (int)ceil((float)size.cx / (float)(rect.right - rect.left));
361 int diff = wantedHeight - (rect.bottom - rect.top);
362 if (diff <= 0) return;
363
364 MoveWindow(hwndProgress, rect.left, rect.top, rect.right - rect.left,
365 wantedHeight, TRUE);
366
367 gAttachedBottom.clear();
368 gAttachedBottom.insert(IDC_CLOSEBUTTON);
369 gAttachedBottom.insert(IDC_RESTARTBUTTON);
370
371 StretchDialog(hwndDlg, diff);
372
373 for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
374 gAttachedBottom.insert(kDefaultAttachedBottom[i]);
375 }
376 }
377
MaybeSendReport(HWND hwndDlg)378 static void MaybeSendReport(HWND hwndDlg) {
379 if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) {
380 EndCrashReporterDialog(hwndDlg, 0);
381 return;
382 }
383
384 // disable all the form controls
385 EnableWindow(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK), false);
386 EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), false);
387 EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), false);
388 EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), false);
389 EnableWindow(GetDlgItem(hwndDlg, IDC_CLOSEBUTTON), false);
390 EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON), false);
391
392 SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTDURINGSUBMIT).c_str());
393 MaybeResizeProgressText(hwndDlg);
394 // start throbber
395 // play entire AVI, and loop
396 Animate_Play(GetDlgItem(hwndDlg, IDC_THROBBER), 0, -1, -1);
397 SetDlgItemVisible(hwndDlg, IDC_THROBBER, true);
398 gThreadHandle = nullptr;
399 gSendData.hDlg = hwndDlg;
400 gSendData.queryParameters = gQueryParameters;
401
402 gThreadHandle =
403 CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0, nullptr);
404 }
405
RestartApplication()406 static void RestartApplication() {
407 wstring cmdLine;
408
409 for (unsigned int i = 0; i < gRestartArgs.size(); i++) {
410 cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" ";
411 }
412
413 STARTUPINFO si;
414 PROCESS_INFORMATION pi;
415
416 ZeroMemory(&si, sizeof(si));
417 si.cb = sizeof(si);
418 si.dwFlags = STARTF_USESHOWWINDOW;
419 si.wShowWindow = SW_SHOWNORMAL;
420 ZeroMemory(&pi, sizeof(pi));
421
422 if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
423 0, nullptr, nullptr, &si, &pi)) {
424 CloseHandle(pi.hProcess);
425 CloseHandle(pi.hThread);
426 }
427 }
428
ShowReportInfo(HWND hwndDlg)429 static void ShowReportInfo(HWND hwndDlg) {
430 wstring description;
431
432 for (Json::ValueConstIterator iter = gQueryParameters.begin();
433 iter != gQueryParameters.end(); ++iter) {
434 description += UTF8ToWide(iter.name());
435 description += L": ";
436 string value;
437 if (iter->isString()) {
438 value = iter->asString();
439 } else {
440 Json::StreamWriterBuilder builder;
441 builder["indentation"] = "";
442 value = Json::writeString(builder, *iter);
443 }
444 description += UTF8ToWide(value);
445 description += L"\n";
446 }
447
448 description += L"\n";
449 description += Str(ST_EXTRAREPORTINFO);
450
451 SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str());
452 }
453
UpdateURL(HWND hwndDlg)454 static void UpdateURL(HWND hwndDlg) {
455 if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) {
456 gQueryParameters["URL"] = gURLParameter;
457 } else {
458 gQueryParameters.removeMember("URL");
459 }
460 }
461
UpdateComment(HWND hwndDlg)462 static void UpdateComment(HWND hwndDlg) {
463 wchar_t comment[MAX_COMMENT_LENGTH + 1];
464 GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment,
465 sizeof(comment) / sizeof(comment[0]));
466 if (wcslen(comment) > 0)
467 gQueryParameters["Comments"] = WideToUTF8(comment);
468 else
469 gQueryParameters.removeMember("Comments");
470 }
471
472 /*
473 * Dialog procedure for the "view report" dialog.
474 */
ViewReportDialogProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam)475 static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message,
476 WPARAM wParam, LPARAM lParam) {
477 switch (message) {
478 case WM_INITDIALOG: {
479 SetWindowText(hwndDlg, Str(ST_VIEWREPORTTITLE).c_str());
480 SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str());
481 SendDlgItemMessage(hwndDlg, IDC_VIEWREPORTTEXT, EM_SETTARGETDEVICE,
482 (WPARAM) nullptr, 0);
483 ShowReportInfo(hwndDlg);
484 SetFocus(GetDlgItem(hwndDlg, IDOK));
485 return FALSE;
486 }
487
488 case WM_COMMAND: {
489 if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK)
490 EndDialog(hwndDlg, 0);
491 return FALSE;
492 }
493 }
494 return FALSE;
495 }
496
497 // Return the number of bytes this string will take encoded
498 // in UTF-8
BytesInUTF8(wchar_t * str)499 static inline int BytesInUTF8(wchar_t* str) {
500 // Just count size of buffer for UTF-8, minus one
501 // (we don't need to count the null terminator)
502 return WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, nullptr,
503 nullptr) -
504 1;
505 }
506
507 // Calculate the length of the text in this edit control (in bytes,
508 // in the UTF-8 encoding) after replacing the current selection
509 // with |insert|.
NewTextLength(HWND hwndEdit,wchar_t * insert)510 static int NewTextLength(HWND hwndEdit, wchar_t* insert) {
511 wchar_t current[MAX_COMMENT_LENGTH + 1];
512
513 GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1);
514 DWORD selStart, selEnd;
515 SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
516
517 int selectionLength = 0;
518 if (selEnd - selStart > 0) {
519 wchar_t selection[MAX_COMMENT_LENGTH + 1];
520 google_breakpad::WindowsStringUtils::safe_wcsncpy(
521 selection, MAX_COMMENT_LENGTH + 1, current + selStart,
522 selEnd - selStart);
523 selection[selEnd - selStart] = '\0';
524 selectionLength = BytesInUTF8(selection);
525 }
526
527 // current string length + replacement text length
528 // - replaced selection length
529 return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength;
530 }
531
532 // Window procedure for subclassing edit controls
EditSubclassProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)533 static LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
534 LPARAM lParam) {
535 static WNDPROC super = nullptr;
536
537 if (super == nullptr) super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
538
539 switch (uMsg) {
540 case WM_PAINT: {
541 HDC hdc;
542 PAINTSTRUCT ps;
543 RECT r;
544 wchar_t windowText[1024];
545
546 GetWindowText(hwnd, windowText, 1024);
547 // if the control contains text or is focused, draw it normally
548 if (GetFocus() == hwnd || windowText[0] != '\0')
549 return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
550
551 GetClientRect(hwnd, &r);
552 hdc = BeginPaint(hwnd, &ps);
553 FillRect(hdc, &r,
554 GetSysColorBrush(IsWindowEnabled(hwnd) ? COLOR_WINDOW
555 : COLOR_BTNFACE));
556 SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
557 SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
558 SetBkMode(hdc, TRANSPARENT);
559 wchar_t* txt = (wchar_t*)GetProp(hwnd, L"PROP_GRAYTEXT");
560 // Get the actual edit control rect
561 CallWindowProc(super, hwnd, EM_GETRECT, 0, (LPARAM)&r);
562 UINT format = DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL;
563 if (gRTLlayout) format |= DT_RIGHT;
564 if (txt) DrawText(hdc, txt, wcslen(txt), &r, format);
565 EndPaint(hwnd, &ps);
566 return 0;
567 }
568
569 // We handle WM_CHAR and WM_PASTE to limit the comment box to 500
570 // bytes in UTF-8.
571 case WM_CHAR: {
572 // Leave accelerator keys and non-printing chars (except LF) alone
573 if (wParam & (1 << 24) || wParam & (1 << 29) ||
574 (wParam < ' ' && wParam != '\n'))
575 break;
576
577 wchar_t ch[2] = {(wchar_t)wParam, 0};
578 if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH) return 0;
579
580 break;
581 }
582
583 case WM_PASTE: {
584 if (IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(hwnd)) {
585 HGLOBAL hg = GetClipboardData(CF_UNICODETEXT);
586 wchar_t* pastedText = (wchar_t*)GlobalLock(hg);
587 int newSize = 0;
588
589 if (pastedText) newSize = NewTextLength(hwnd, pastedText);
590
591 GlobalUnlock(hg);
592 CloseClipboard();
593
594 if (newSize > MAX_COMMENT_LENGTH) return 0;
595 }
596 break;
597 }
598
599 case WM_SETFOCUS:
600 case WM_KILLFOCUS: {
601 RECT r;
602 GetClientRect(hwnd, &r);
603 InvalidateRect(hwnd, &r, TRUE);
604 break;
605 }
606
607 case WM_DESTROY: {
608 // cleanup our property
609 HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT");
610 if (hData) GlobalFree(hData);
611 }
612 }
613
614 return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
615 }
616
617 // Resize a control to fit this text
ResizeControl(HWND hwndButton,RECT & rect,wstring text,bool shiftLeft,int userDefinedPadding)618 static int ResizeControl(HWND hwndButton, RECT& rect, wstring text,
619 bool shiftLeft, int userDefinedPadding) {
620 HDC hdc = GetDC(hwndButton);
621 HFONT hfont = (HFONT)SendMessage(hwndButton, WM_GETFONT, 0, 0);
622 if (hfont) SelectObject(hdc, hfont);
623 SIZE size, oldSize;
624 int sizeDiff = 0;
625
626 wchar_t oldText[1024];
627 GetWindowText(hwndButton, oldText, 1024);
628
629 if (GetTextExtentPoint32(hdc, text.c_str(), text.length(), &size)
630 // default text on the button
631 && GetTextExtentPoint32(hdc, oldText, wcslen(oldText), &oldSize)) {
632 /*
633 Expand control widths to accomidate wider text strings. For most
634 controls (including buttons) the text padding is defined by the
635 dialog's rc file. Some controls (such as checkboxes) have padding
636 that extends to the end of the dialog, in which case we ignore the
637 rc padding and rely on a user defined value passed in through
638 userDefinedPadding.
639 */
640 int textIncrease = size.cx - oldSize.cx;
641 if (textIncrease < 0) return 0;
642 int existingTextPadding;
643 if (userDefinedPadding == 0)
644 existingTextPadding = (rect.right - rect.left) - oldSize.cx;
645 else
646 existingTextPadding = userDefinedPadding;
647 sizeDiff = textIncrease + existingTextPadding;
648
649 if (shiftLeft) {
650 // shift left by the amount the button should grow
651 rect.left -= sizeDiff;
652 } else {
653 // grow right instead
654 rect.right += sizeDiff;
655 }
656 MoveWindow(hwndButton, rect.left, rect.top, rect.right - rect.left,
657 rect.bottom - rect.top, TRUE);
658 }
659 return sizeDiff;
660 }
661
662 // The window was resized horizontally, so widen some of our
663 // controls to make use of the space
StretchControlsToFit(HWND hwndDlg)664 static void StretchControlsToFit(HWND hwndDlg) {
665 int controls[] = {IDC_DESCRIPTIONTEXT, IDC_SUBMITREPORTCHECK, IDC_COMMENTTEXT,
666 IDC_INCLUDEURLCHECK, IDC_PROGRESSTEXT};
667
668 RECT dlgRect;
669 GetClientRect(hwndDlg, &dlgRect);
670
671 for (int i = 0; i < sizeof(controls) / sizeof(controls[0]); i++) {
672 RECT r;
673 HWND hwndControl = GetDlgItem(hwndDlg, controls[i]);
674 GetRelativeRect(hwndControl, hwndDlg, &r);
675 // 6 pixel spacing on the right
676 if (r.right + 6 != dlgRect.right) {
677 r.right = dlgRect.right - 6;
678 MoveWindow(hwndControl, r.left, r.top, r.right - r.left, r.bottom - r.top,
679 TRUE);
680 }
681 }
682 }
683
SubmitReportChecked(HWND hwndDlg)684 static void SubmitReportChecked(HWND hwndDlg) {
685 bool enabled = (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
686 EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled);
687 EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled);
688 EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled);
689 SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled);
690 }
691
DialogBoxParamMaybeRTL(UINT idd,HWND hwndParent,DLGPROC dlgProc,LPARAM param)692 static INT_PTR DialogBoxParamMaybeRTL(UINT idd, HWND hwndParent,
693 DLGPROC dlgProc, LPARAM param) {
694 INT_PTR rv = 0;
695 if (gRTLlayout) {
696 // We need to toggle the WS_EX_LAYOUTRTL style flag on the dialog
697 // template.
698 HRSRC hDialogRC = FindResource(nullptr, MAKEINTRESOURCE(idd), RT_DIALOG);
699 HGLOBAL hDlgTemplate = LoadResource(nullptr, hDialogRC);
700 DLGTEMPLATEEX* pDlgTemplate = (DLGTEMPLATEEX*)LockResource(hDlgTemplate);
701 unsigned long sizeDlg = SizeofResource(nullptr, hDialogRC);
702 HGLOBAL hMyDlgTemplate = GlobalAlloc(GPTR, sizeDlg);
703 DLGTEMPLATEEX* pMyDlgTemplate = (DLGTEMPLATEEX*)GlobalLock(hMyDlgTemplate);
704 memcpy(pMyDlgTemplate, pDlgTemplate, sizeDlg);
705
706 pMyDlgTemplate->exStyle |= WS_EX_LAYOUTRTL;
707
708 rv = DialogBoxIndirectParam(nullptr, (LPCDLGTEMPLATE)pMyDlgTemplate,
709 hwndParent, dlgProc, param);
710 GlobalUnlock(hMyDlgTemplate);
711 GlobalFree(hMyDlgTemplate);
712 } else {
713 rv = DialogBoxParam(nullptr, MAKEINTRESOURCE(idd), hwndParent, dlgProc,
714 param);
715 }
716
717 return rv;
718 }
719
CrashReporterDialogProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam)720 static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message,
721 WPARAM wParam, LPARAM lParam) {
722 static int sHeight = 0;
723
724 bool success;
725 bool enabled;
726
727 switch (message) {
728 case WM_INITDIALOG: {
729 GetThemeSizes(hwndDlg);
730 RECT r;
731 GetClientRect(hwndDlg, &r);
732 sHeight = r.bottom - r.top;
733
734 SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str());
735 HICON hIcon =
736 LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_MAINICON));
737 SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
738 SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
739
740 // resize the "View Report" button based on the string length
741 RECT rect;
742 HWND hwnd = GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON);
743 GetRelativeRect(hwnd, hwndDlg, &rect);
744 ResizeControl(hwnd, rect, Str(ST_VIEWREPORT), false, 0);
745 SetDlgItemText(hwndDlg, IDC_VIEWREPORTBUTTON, Str(ST_VIEWREPORT).c_str());
746
747 hwnd = GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK);
748 GetRelativeRect(hwnd, hwndDlg, &rect);
749 long maxdiff = ResizeControl(hwnd, rect, Str(ST_CHECKSUBMIT), false,
750 gCheckboxPadding);
751 SetDlgItemText(hwndDlg, IDC_SUBMITREPORTCHECK,
752 Str(ST_CHECKSUBMIT).c_str());
753
754 if (!CheckBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE,
755 &enabled))
756 enabled = true;
757
758 CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK,
759 enabled ? BST_CHECKED : BST_UNCHECKED);
760 SubmitReportChecked(hwndDlg);
761
762 HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT);
763 WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr(
764 hwndComment, GWLP_WNDPROC, (LONG_PTR)EditSubclassProc);
765
766 // Subclass comment edit control to get placeholder text
767 SetWindowLongPtr(hwndComment, GWLP_USERDATA, (LONG_PTR)OldWndProc);
768 wstring commentGrayText = Str(ST_COMMENTGRAYTEXT);
769 wchar_t* hMem = (wchar_t*)GlobalAlloc(
770 GPTR, (commentGrayText.length() + 1) * sizeof(wchar_t));
771 wcscpy(hMem, commentGrayText.c_str());
772 SetProp(hwndComment, L"PROP_GRAYTEXT", hMem);
773
774 hwnd = GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK);
775 GetRelativeRect(hwnd, hwndDlg, &rect);
776 long diff =
777 ResizeControl(hwnd, rect, Str(ST_CHECKURL), false, gCheckboxPadding);
778 maxdiff = std::max(diff, maxdiff);
779 SetDlgItemText(hwndDlg, IDC_INCLUDEURLCHECK, Str(ST_CHECKURL).c_str());
780
781 // want this on by default
782 if (CheckBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE,
783 &enabled) &&
784 !enabled) {
785 CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_UNCHECKED);
786 } else {
787 CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_CHECKED);
788 }
789
790 SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT,
791 Str(ST_REPORTPRESUBMIT).c_str());
792
793 RECT closeRect;
794 HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON);
795 GetRelativeRect(hwndClose, hwndDlg, &closeRect);
796
797 RECT restartRect;
798 HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON);
799 GetRelativeRect(hwndRestart, hwndDlg, &restartRect);
800
801 // set the close button text and shift the buttons around
802 // since the size may need to change
803 int sizeDiff = ResizeControl(hwndClose, closeRect, Str(ST_QUIT), true, 0);
804 restartRect.left -= sizeDiff;
805 restartRect.right -= sizeDiff;
806 SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str());
807
808 if (gRestartArgs.size() > 0) {
809 // Resize restart button to fit text
810 ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0);
811 SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str());
812 } else {
813 // No restart arguments, so just hide the restart button
814 SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false);
815 }
816 // See if we need to widen the window
817 // Leave 6 pixels on either side + 6 pixels between the buttons
818 int neededSize = closeRect.right - closeRect.left + restartRect.right -
819 restartRect.left + 6 * 3;
820 GetClientRect(hwndDlg, &r);
821 // We may already have resized one of the checkboxes above
822 maxdiff = std::max(maxdiff, neededSize - (r.right - r.left));
823
824 if (maxdiff > 0) {
825 // widen window
826 GetWindowRect(hwndDlg, &r);
827 r.right += maxdiff;
828 MoveWindow(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top,
829 TRUE);
830 // shift both buttons right
831 if (restartRect.left + maxdiff < 6) maxdiff += 6;
832 closeRect.left += maxdiff;
833 closeRect.right += maxdiff;
834 restartRect.left += maxdiff;
835 restartRect.right += maxdiff;
836 MoveWindow(hwndClose, closeRect.left, closeRect.top,
837 closeRect.right - closeRect.left,
838 closeRect.bottom - closeRect.top, TRUE);
839 StretchControlsToFit(hwndDlg);
840 }
841 // need to move the restart button regardless
842 MoveWindow(hwndRestart, restartRect.left, restartRect.top,
843 restartRect.right - restartRect.left,
844 restartRect.bottom - restartRect.top, TRUE);
845
846 // Resize the description text last, in case the window was resized
847 // before this.
848 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETEVENTMASK,
849 (WPARAM) nullptr, ENM_REQUESTRESIZE);
850
851 wstring description = Str(ST_CRASHREPORTERHEADER);
852 description += L"\n\n";
853 description += Str(ST_CRASHREPORTERDESCRIPTION);
854 SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str());
855
856 // Make the title bold.
857 CHARFORMAT fmt = {
858 0,
859 };
860 fmt.cbSize = sizeof(fmt);
861 fmt.dwMask = CFM_BOLD;
862 fmt.dwEffects = CFE_BOLD;
863 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0,
864 Str(ST_CRASHREPORTERHEADER).length());
865 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETCHARFORMAT,
866 SCF_SELECTION, (LPARAM)&fmt);
867 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0);
868 // Force redraw.
869 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETTARGETDEVICE,
870 (WPARAM) nullptr, 0);
871 // Force resize.
872 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_REQUESTRESIZE, 0, 0);
873
874 // if no URL was given, hide the URL checkbox
875 if (!gQueryParameters.isMember("URL")) {
876 RECT urlCheckRect;
877 GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect);
878
879 SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false);
880
881 gAttachedBottom.erase(IDC_VIEWREPORTBUTTON);
882 gAttachedBottom.erase(IDC_SUBMITREPORTCHECK);
883 gAttachedBottom.erase(IDC_COMMENTTEXT);
884
885 StretchDialog(hwndDlg, urlCheckRect.top - urlCheckRect.bottom);
886
887 gAttachedBottom.insert(IDC_VIEWREPORTBUTTON);
888 gAttachedBottom.insert(IDC_SUBMITREPORTCHECK);
889 gAttachedBottom.insert(IDC_COMMENTTEXT);
890 }
891
892 MaybeResizeProgressText(hwndDlg);
893
894 // Open the AVI resource for the throbber
895 Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER),
896 MAKEINTRESOURCE(IDR_THROBBER));
897
898 UpdateURL(hwndDlg);
899
900 SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK));
901 return FALSE;
902 }
903 case WM_SIZE: {
904 ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight);
905 sHeight = HIWORD(lParam);
906 InvalidateRect(hwndDlg, nullptr, TRUE);
907 return FALSE;
908 }
909 case WM_NOTIFY: {
910 NMHDR* notification = reinterpret_cast<NMHDR*>(lParam);
911 if (notification->code == EN_REQUESTRESIZE) {
912 // Resizing the rich edit control to fit the description text.
913 REQRESIZE* reqresize = reinterpret_cast<REQRESIZE*>(lParam);
914 RECT newSize = reqresize->rc;
915 RECT oldSize;
916 GetRelativeRect(notification->hwndFrom, hwndDlg, &oldSize);
917
918 // resize the text box as requested
919 MoveWindow(notification->hwndFrom, newSize.left, newSize.top,
920 newSize.right - newSize.left, newSize.bottom - newSize.top,
921 TRUE);
922
923 // Resize the dialog to fit (the WM_SIZE handler will move the controls)
924 StretchDialog(hwndDlg, newSize.bottom - oldSize.bottom);
925 }
926 return FALSE;
927 }
928 case WM_COMMAND: {
929 if (HIWORD(wParam) == BN_CLICKED) {
930 switch (LOWORD(wParam)) {
931 case IDC_VIEWREPORTBUTTON:
932 DialogBoxParamMaybeRTL(IDD_VIEWREPORTDIALOG, hwndDlg,
933 (DLGPROC)ViewReportDialogProc, 0);
934 break;
935 case IDC_SUBMITREPORTCHECK:
936 SubmitReportChecked(hwndDlg);
937 break;
938 case IDC_INCLUDEURLCHECK:
939 UpdateURL(hwndDlg);
940 break;
941 case IDC_CLOSEBUTTON:
942 MaybeSendReport(hwndDlg);
943 break;
944 case IDC_RESTARTBUTTON:
945 RestartApplication();
946 MaybeSendReport(hwndDlg);
947 break;
948 }
949 } else if (HIWORD(wParam) == EN_CHANGE) {
950 switch (LOWORD(wParam)) {
951 case IDC_COMMENTTEXT:
952 UpdateComment(hwndDlg);
953 }
954 }
955
956 return FALSE;
957 }
958 case WM_UPLOADCOMPLETE: {
959 WaitForSingleObject(gThreadHandle, INFINITE);
960 success = (wParam == 1);
961 SendCompleted(success, WideToUTF8(gSendData.serverResponse));
962 // hide throbber
963 Animate_Stop(GetDlgItem(hwndDlg, IDC_THROBBER));
964 SetDlgItemVisible(hwndDlg, IDC_THROBBER, false);
965
966 SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT,
967 success ? Str(ST_REPORTSUBMITSUCCESS).c_str()
968 : Str(ST_SUBMITFAILED).c_str());
969 MaybeResizeProgressText(hwndDlg);
970 // close dialog after 5 seconds
971 SetTimer(hwndDlg, 0, 5000, nullptr);
972 //
973 return TRUE;
974 }
975
976 case WM_TIMER: {
977 // The "1" gets used down in UIShowCrashUI to indicate that we at least
978 // tried to send the report.
979 EndCrashReporterDialog(hwndDlg, 1);
980 return FALSE;
981 }
982
983 case WM_CLOSE: {
984 EndCrashReporterDialog(hwndDlg, 0);
985 return FALSE;
986 }
987 }
988 return FALSE;
989 }
990
UTF8ToWide(const string & utf8,bool * success)991 static wstring UTF8ToWide(const string& utf8, bool* success) {
992 wchar_t* buffer = nullptr;
993 int buffer_size =
994 MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
995 if (buffer_size == 0) {
996 if (success) *success = false;
997 return L"";
998 }
999
1000 buffer = new wchar_t[buffer_size];
1001 if (buffer == nullptr) {
1002 if (success) *success = false;
1003 return L"";
1004 }
1005
1006 MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, buffer, buffer_size);
1007 wstring str = buffer;
1008 delete[] buffer;
1009
1010 if (success) *success = true;
1011
1012 return str;
1013 }
1014
WideToMBCP(const wstring & wide,unsigned int cp,bool * success=nullptr)1015 static string WideToMBCP(const wstring& wide, unsigned int cp,
1016 bool* success = nullptr) {
1017 char* buffer = nullptr;
1018 int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), -1, nullptr, 0,
1019 nullptr, nullptr);
1020 if (buffer_size == 0) {
1021 if (success) *success = false;
1022 return "";
1023 }
1024
1025 buffer = new char[buffer_size];
1026 if (buffer == nullptr) {
1027 if (success) *success = false;
1028 return "";
1029 }
1030
1031 WideCharToMultiByte(cp, 0, wide.c_str(), -1, buffer, buffer_size, nullptr,
1032 nullptr);
1033 string mb = buffer;
1034 delete[] buffer;
1035
1036 if (success) *success = true;
1037
1038 return mb;
1039 }
1040
WideToUTF8(const wstring & wide,bool * success)1041 string WideToUTF8(const wstring& wide, bool* success) {
1042 return WideToMBCP(wide, CP_UTF8, success);
1043 }
1044
1045 /* === Crashreporter UI Functions === */
1046
UIInit()1047 bool UIInit() {
1048 for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
1049 gAttachedBottom.insert(kDefaultAttachedBottom[i]);
1050 }
1051
1052 DoInitCommonControls();
1053
1054 return true;
1055 }
1056
UIShutdown()1057 void UIShutdown() {}
1058
UIShowDefaultUI()1059 void UIShowDefaultUI() {
1060 MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(), L"Crash Reporter",
1061 MB_OK | MB_ICONSTOP);
1062 }
1063
CanUseMainCrashReportServer()1064 static bool CanUseMainCrashReportServer() {
1065 // Any NT from 6.0 and above is fine.
1066 if (IsWindowsVersionOrGreater(6, 0, 0)) {
1067 return true;
1068 }
1069
1070 // On NT 5 servers, we need Server 2003 SP2.
1071 if (IsWindowsServer()) {
1072 return IsWindowsVersionOrGreater(5, 2, 2);
1073 }
1074
1075 // Otherwise we have an NT 5 client.
1076 // We need exactly XP SP3 (version 5.1 SP3 but not version 5.2).
1077 return (IsWindowsVersionOrGreater(5, 1, 3) &&
1078 !IsWindowsVersionOrGreater(5, 2, 0));
1079 }
1080
UIShowCrashUI(const StringTable & files,const Json::Value & queryParameters,const string & sendURL,const vector<string> & restartArgs)1081 bool UIShowCrashUI(const StringTable& files, const Json::Value& queryParameters,
1082 const string& sendURL, const vector<string>& restartArgs) {
1083 gSendData.hDlg = nullptr;
1084 gSendData.sendURL = UTF8ToWide(sendURL);
1085
1086 // Older Windows don't support the crash report server's crypto.
1087 // This is a hack to use an alternate server.
1088 if (!CanUseMainCrashReportServer() &&
1089 gSendData.sendURL.find(SENDURL_ORIGINAL) == 0) {
1090 gSendData.sendURL.replace(0, ARRAYSIZE(SENDURL_ORIGINAL) - 1,
1091 SENDURL_XPSP2);
1092 }
1093
1094 for (StringTable::const_iterator i = files.begin(); i != files.end(); i++) {
1095 gSendData.files[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
1096 }
1097
1098 gQueryParameters = queryParameters;
1099
1100 if (gQueryParameters.isMember("Vendor")) {
1101 gCrashReporterKey = L"Software\\";
1102 string vendor = gQueryParameters["Vendor"].asString();
1103 if (!vendor.empty()) {
1104 gCrashReporterKey += UTF8ToWide(vendor) + L"\\";
1105 }
1106 string productName = gQueryParameters["ProductName"].asString();
1107 gCrashReporterKey += UTF8ToWide(productName) + L"\\Crash Reporter";
1108 }
1109
1110 if (gQueryParameters.isMember("URL")) {
1111 gURLParameter = gQueryParameters["URL"].asString();
1112 }
1113
1114 gRestartArgs = restartArgs;
1115
1116 if (gStrings.find("isRTL") != gStrings.end() && gStrings["isRTL"] == "yes")
1117 gRTLlayout = true;
1118
1119 if (gAutoSubmit) {
1120 gSendData.queryParameters = gQueryParameters;
1121
1122 gThreadHandle =
1123 CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0, nullptr);
1124 WaitForSingleObject(gThreadHandle, INFINITE);
1125 // SendCompleted was called from SendThreadProc
1126 return true;
1127 }
1128
1129 return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr,
1130 (DLGPROC)CrashReporterDialogProc, 0);
1131 }
1132
UIError_impl(const string & message)1133 void UIError_impl(const string& message) {
1134 wstring title = Str(ST_CRASHREPORTERTITLE);
1135 if (title.empty()) title = L"Crash Reporter Error";
1136
1137 MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(),
1138 MB_OK | MB_ICONSTOP);
1139 }
1140
UIGetIniPath(string & path)1141 bool UIGetIniPath(string& path) {
1142 wchar_t fileName[MAX_PATH];
1143 if (GetModuleFileName(nullptr, fileName, MAX_PATH)) {
1144 // get crashreporter ini
1145 wchar_t* s = wcsrchr(fileName, '.');
1146 if (s) {
1147 wcscpy(s, L".ini");
1148 path = WideToUTF8(fileName);
1149 return true;
1150 }
1151 }
1152
1153 return false;
1154 }
1155
UIGetSettingsPath(const string & vendor,const string & product,string & settings_path)1156 bool UIGetSettingsPath(const string& vendor, const string& product,
1157 string& settings_path) {
1158 wchar_t path[MAX_PATH] = {};
1159 HRESULT hRes = SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, 0, path);
1160 if (FAILED(hRes)) {
1161 // This provides a fallback for getting the path to APPDATA by querying the
1162 // registry when the call to SHGetFolderPath is unable to provide this path
1163 // (Bug 513958).
1164 HKEY key;
1165 DWORD type, dwRes;
1166 DWORD size = sizeof(path) - 1;
1167 dwRes = ::RegOpenKeyExW(HKEY_CURRENT_USER,
1168 L"Software\\Microsoft\\Windows\\CurrentVersion\\Exp"
1169 L"lorer\\Shell Folders",
1170 0, KEY_READ, &key);
1171 if (dwRes != ERROR_SUCCESS) return false;
1172
1173 dwRes =
1174 RegQueryValueExW(key, L"AppData", nullptr, &type, (LPBYTE)&path, &size);
1175 ::RegCloseKey(key);
1176 // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the
1177 // buffer size must not equal 0, and the buffer size be a multiple of 2.
1178 if (dwRes != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0)
1179 return false;
1180 }
1181
1182 if (!vendor.empty()) {
1183 PathAppend(path, UTF8ToWide(vendor).c_str());
1184 }
1185 PathAppend(path, UTF8ToWide(product).c_str());
1186 PathAppend(path, L"Crash Reports");
1187 settings_path = WideToUTF8(path);
1188 return true;
1189 }
1190
UIEnsurePathExists(const string & path)1191 bool UIEnsurePathExists(const string& path) {
1192 if (CreateDirectory(UTF8ToWide(path).c_str(), nullptr) == 0) {
1193 if (GetLastError() != ERROR_ALREADY_EXISTS) return false;
1194 }
1195
1196 return true;
1197 }
1198
UIFileExists(const string & path)1199 bool UIFileExists(const string& path) {
1200 DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str());
1201 return (attrs != INVALID_FILE_ATTRIBUTES);
1202 }
1203
UIMoveFile(const string & oldfile,const string & newfile)1204 bool UIMoveFile(const string& oldfile, const string& newfile) {
1205 if (oldfile == newfile) return true;
1206
1207 return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str()) ==
1208 TRUE;
1209 }
1210
UIDeleteFile(const string & oldfile)1211 bool UIDeleteFile(const string& oldfile) {
1212 return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE;
1213 }
1214
UIOpenRead(const string & filename,ios_base::openmode mode)1215 ifstream* UIOpenRead(const string& filename, ios_base::openmode mode) {
1216 #if defined(_MSC_VER)
1217 ifstream* file = new ifstream();
1218 file->open(UTF8ToWide(filename).c_str(), mode);
1219 #else // GCC
1220 ifstream* file =
1221 new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), mode);
1222 #endif // _MSC_VER
1223
1224 return file;
1225 }
1226
UIOpenWrite(const string & filename,ios_base::openmode mode)1227 ofstream* UIOpenWrite(const string& filename, ios_base::openmode mode) {
1228 #if defined(_MSC_VER)
1229 ofstream* file = new ofstream();
1230 file->open(UTF8ToWide(filename).c_str(), mode);
1231 #else // GCC
1232 ofstream* file =
1233 new ofstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), mode);
1234 #endif // _MSC_VER
1235
1236 return file;
1237 }
1238
1239 struct FileData {
1240 FILETIME timestamp;
1241 wstring path;
1242 };
1243
CompareFDTime(const FileData & fd1,const FileData & fd2)1244 static bool CompareFDTime(const FileData& fd1, const FileData& fd2) {
1245 return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0;
1246 }
1247
UIPruneSavedDumps(const std::string & directory)1248 void UIPruneSavedDumps(const std::string& directory) {
1249 wstring wdirectory = UTF8ToWide(directory);
1250
1251 WIN32_FIND_DATA fdata;
1252 wstring findpath = wdirectory + L"\\*.dmp";
1253 HANDLE dirlist = FindFirstFile(findpath.c_str(), &fdata);
1254 if (dirlist == INVALID_HANDLE_VALUE) return;
1255
1256 vector<FileData> dumpfiles;
1257
1258 for (BOOL ok = true; ok; ok = FindNextFile(dirlist, &fdata)) {
1259 FileData fd = {fdata.ftLastWriteTime, wdirectory + L"\\" + fdata.cFileName};
1260 dumpfiles.push_back(fd);
1261 }
1262
1263 sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime);
1264
1265 while (dumpfiles.size() > kSaveCount) {
1266 // get the path of the oldest file
1267 wstring path = (--dumpfiles.end())->path;
1268 DeleteFile(path.c_str());
1269
1270 // s/.dmp/.extra/
1271 path.replace(path.size() - 4, 4, L".extra");
1272 DeleteFile(path.c_str());
1273
1274 dumpfiles.pop_back();
1275 }
1276 FindClose(dirlist);
1277 }
1278
UIRunProgram(const string & exename,const std::vector<std::string> & args,bool wait)1279 bool UIRunProgram(const string& exename, const std::vector<std::string>& args,
1280 bool wait) {
1281 wstring cmdLine = L"\"" + UTF8ToWide(exename) + L"\" ";
1282
1283 for (auto arg : args) {
1284 cmdLine += L"\"" + UTF8ToWide(arg) + L"\" ";
1285 }
1286
1287 STARTUPINFO si = {};
1288 si.cb = sizeof(si);
1289 PROCESS_INFORMATION pi = {};
1290
1291 if (!CreateProcess(/* lpApplicationName */ nullptr, (LPWSTR)cmdLine.c_str(),
1292 /* lpProcessAttributes */ nullptr,
1293 /* lpThreadAttributes */ nullptr,
1294 /* bInheritHandles */ false,
1295 NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
1296 /* lpEnvironment */ nullptr,
1297 /* lpCurrentDirectory */ nullptr, &si, &pi)) {
1298 return false;
1299 }
1300
1301 if (wait) {
1302 WaitForSingleObject(pi.hProcess, INFINITE);
1303 }
1304
1305 CloseHandle(pi.hProcess);
1306 CloseHandle(pi.hThread);
1307 return true;
1308 }
1309
UIGetEnv(const string & name)1310 string UIGetEnv(const string& name) {
1311 const wchar_t* var = _wgetenv(UTF8ToWide(name).c_str());
1312 if (var && *var) {
1313 return WideToUTF8(var);
1314 }
1315
1316 return "";
1317 }
1318