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