1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "sandbox/win/sandbox_poc/main_ui_window.h"
6 
7 #include <windows.h>
8 #include <CommCtrl.h>
9 #include <commdlg.h>
10 #include <stdarg.h>
11 #include <stddef.h>
12 #include <time.h>
13 #include <windowsx.h>
14 
15 #include <algorithm>
16 #include <sstream>
17 
18 #include "base/logging.h"
19 #include "base/win/atl.h"
20 #include "sandbox/win/sandbox_poc/resource.h"
21 #include "sandbox/win/src/acl.h"
22 #include "sandbox/win/src/sandbox.h"
23 #include "sandbox/win/src/win_utils.h"
24 
25 HWND MainUIWindow::list_view_ = NULL;
26 
27 const wchar_t MainUIWindow::kDefaultDll_[]        = L"\\POCDLL.dll";
28 const wchar_t MainUIWindow::kDefaultEntryPoint_[] = L"Run";
29 const wchar_t MainUIWindow::kDefaultLogFile_[]    = L"";
30 
MainUIWindow()31 MainUIWindow::MainUIWindow()
32     : broker_(NULL),
33       spawn_target_(L""),
34       instance_handle_(NULL),
35       dll_path_(L""),
36       entry_point_(L"") {
37 }
38 
~MainUIWindow()39 MainUIWindow::~MainUIWindow() {
40 }
41 
CreateMainWindowAndLoop(HINSTANCE instance,wchar_t * command_line,int show_command,sandbox::BrokerServices * broker)42 unsigned int MainUIWindow::CreateMainWindowAndLoop(
43     HINSTANCE instance,
44     wchar_t* command_line,
45     int show_command,
46     sandbox::BrokerServices* broker) {
47   DCHECK(instance);
48   DCHECK(command_line);
49   DCHECK(broker);
50 
51   instance_handle_ = instance;
52   spawn_target_ = command_line;
53   broker_ = broker;
54 
55   // We'll use spawn_target_ later for creating a child process, but
56   // CreateProcess doesn't like double quotes, so we remove them along with
57   // tabs and spaces from the start and end of the string
58   const wchar_t *trim_removal = L" \r\t\"";
59   spawn_target_.erase(0, spawn_target_.find_first_not_of(trim_removal));
60   spawn_target_.erase(spawn_target_.find_last_not_of(trim_removal) + 1);
61 
62   WNDCLASSEX window_class = {0};
63   window_class.cbSize        = sizeof(WNDCLASSEX);
64   window_class.style         = CS_HREDRAW | CS_VREDRAW;
65   window_class.lpfnWndProc   = MainUIWindow::WndProc;
66   window_class.cbClsExtra    = 0;
67   window_class.cbWndExtra    = 0;
68   window_class.hInstance     = instance;
69   window_class.hIcon         =
70       ::LoadIcon(instance, MAKEINTRESOURCE(IDI_SANDBOX));
71   window_class.hCursor       = ::LoadCursor(NULL, IDC_ARROW);
72   window_class.hbrBackground = GetStockBrush(WHITE_BRUSH);
73   window_class.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU_MAIN_UI);
74   window_class.lpszClassName = L"sandbox_ui_1";
75   window_class.hIconSm       = NULL;
76 
77   INITCOMMONCONTROLSEX controls = {
78     sizeof(INITCOMMONCONTROLSEX),
79     ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES
80   };
81   ::InitCommonControlsEx(&controls);
82 
83   if (!::RegisterClassEx(&window_class))
84     return ::GetLastError();
85 
86   // Create a main window of size 600x400
87   HWND window = ::CreateWindowW(window_class.lpszClassName,
88                                 L"",            // window name
89                                 WS_OVERLAPPEDWINDOW,
90                                 CW_USEDEFAULT,  // x
91                                 CW_USEDEFAULT,  // y
92                                 600,            // width
93                                 400,            // height
94                                 NULL,           // parent
95                                 NULL,           // NULL = use class menu
96                                 instance,
97                                 0);             // lpParam
98 
99   if (NULL == window)
100     return ::GetLastError();
101 
102   ::SetWindowLongPtr(window,
103                      GWLP_USERDATA,
104                      reinterpret_cast<LONG_PTR>(this));
105 
106   ::SetWindowText(window, L"Sandbox Proof of Concept");
107 
108   ::ShowWindow(window, show_command);
109 
110   MSG message;
111   // Now lets start the message pump retrieving messages for any window that
112   // belongs to the current thread
113   while (::GetMessage(&message, NULL, 0, 0)) {
114     ::TranslateMessage(&message);
115     ::DispatchMessage(&message);
116   }
117 
118   return 0;
119 }
120 
WndProc(HWND window,UINT message_id,WPARAM wparam,LPARAM lparam)121 LRESULT CALLBACK MainUIWindow::WndProc(HWND window,
122                                        UINT message_id,
123                                        WPARAM wparam,
124                                        LPARAM lparam) {
125   MainUIWindow* host = FromWindow(window);
126 
127   #define HANDLE_MSG(hwnd, message, fn)    \
128     case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
129 
130   switch (message_id) {
131     case WM_CREATE:
132       // 'host' is not yet available when we get the WM_CREATE message
133       return HANDLE_WM_CREATE(window, wparam, lparam, OnCreate);
134     case WM_DESTROY:
135       return HANDLE_WM_DESTROY(window, wparam, lparam, host->OnDestroy);
136     case WM_SIZE:
137       return HANDLE_WM_SIZE(window, wparam, lparam, host->OnSize);
138     case WM_COMMAND: {
139       // Look at which menu item was clicked on (or which accelerator)
140       int id = LOWORD(wparam);
141       switch (id) {
142         case ID_FILE_EXIT:
143           host->OnFileExit();
144           break;
145         case ID_COMMANDS_SPAWNTARGET:
146           host->OnCommandsLaunch(window);
147           break;
148         default:
149           // Some other menu item or accelerator
150           break;
151       }
152 
153       return ERROR_SUCCESS;
154     }
155 
156     default:
157       // Some other WM_message, let it pass to DefWndProc
158       break;
159   }
160 
161   return DefWindowProc(window, message_id, wparam, lparam);
162 }
163 
SpawnTargetWndProc(HWND dialog,UINT message_id,WPARAM wparam,LPARAM lparam)164 INT_PTR CALLBACK MainUIWindow::SpawnTargetWndProc(HWND dialog,
165                                                   UINT message_id,
166                                                   WPARAM wparam,
167                                                   LPARAM lparam) {
168   // Grab a reference to the main UI window (from the window handle)
169   MainUIWindow* host = FromWindow(GetParent(dialog));
170   DCHECK(host);
171 
172   switch (message_id) {
173     case WM_INITDIALOG: {
174       // Initialize the window text for DLL name edit box
175       HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
176       wchar_t current_dir[MAX_PATH];
177       if (GetCurrentDirectory(MAX_PATH, current_dir)) {
178         std::wstring dll_path =
179             std::wstring(current_dir) + std::wstring(kDefaultDll_);
180         ::SetWindowText(edit_box_dll_name, dll_path.c_str());
181       }
182 
183       // Initialize the window text for Entry Point edit box
184       HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
185       ::SetWindowText(edit_box_entry_point, kDefaultEntryPoint_);
186 
187       // Initialize the window text for Log File edit box
188       HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
189       ::SetWindowText(edit_box_log_file, kDefaultLogFile_);
190 
191       return static_cast<INT_PTR>(TRUE);
192     }
193     case WM_COMMAND:
194       // If the user presses the OK button (Launch)
195       if (LOWORD(wparam) == IDOK) {
196         if (host->OnLaunchDll(dialog)) {
197           if (host->SpawnTarget()) {
198             ::EndDialog(dialog, LOWORD(wparam));
199           }
200         }
201         return static_cast<INT_PTR>(TRUE);
202       } else if (LOWORD(wparam) == IDCANCEL) {
203         // If the user presses the Cancel button
204         ::EndDialog(dialog, LOWORD(wparam));
205         return static_cast<INT_PTR>(TRUE);
206       } else if (LOWORD(wparam) == IDC_BROWSE_DLL) {
207         // If the user presses the Browse button to look for a DLL
208         std::wstring dll_path = host->OnShowBrowseForDllDlg(dialog);
209         if (dll_path.length() > 0) {
210           // Initialize the window text for Log File edit box
211           HWND edit_box_dll_path = ::GetDlgItem(dialog, IDC_DLL_NAME);
212           ::SetWindowText(edit_box_dll_path, dll_path.c_str());
213         }
214         return static_cast<INT_PTR>(TRUE);
215       } else if (LOWORD(wparam) == IDC_BROWSE_LOG) {
216         // If the user presses the Browse button to look for a log file
217         std::wstring log_path = host->OnShowBrowseForLogFileDlg(dialog);
218         if (log_path.length() > 0) {
219           // Initialize the window text for Log File edit box
220           HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
221           ::SetWindowText(edit_box_log_file, log_path.c_str());
222         }
223         return static_cast<INT_PTR>(TRUE);
224       }
225 
226       break;
227   }
228 
229   return static_cast<INT_PTR>(FALSE);
230 }
231 
FromWindow(HWND main_window)232 MainUIWindow* MainUIWindow::FromWindow(HWND main_window) {
233   // We store a 'this' pointer using SetWindowLong in CreateMainWindowAndLoop
234   // so that we can retrieve it with this function later. This prevents us
235   // from having to define all the message handling functions (that we refer to
236   // in the window proc) as static
237   ::GetWindowLongPtr(main_window, GWLP_USERDATA);
238   return reinterpret_cast<MainUIWindow*>(
239       ::GetWindowLongPtr(main_window, GWLP_USERDATA));
240 }
241 
OnCreate(HWND parent_window,LPCREATESTRUCT)242 BOOL MainUIWindow::OnCreate(HWND parent_window, LPCREATESTRUCT) {
243   // Create the listview that will the main app UI
244   list_view_ = ::CreateWindow(WC_LISTVIEW,    // Class name
245                               L"",            // Window name
246                               WS_CHILD | WS_VISIBLE | LVS_REPORT |
247                               LVS_NOCOLUMNHEADER | WS_BORDER,
248                               0,              // x
249                               0,              // y
250                               0,              // width
251                               0,              // height
252                               parent_window,  // parent
253                               NULL,           // menu
254                               ::GetModuleHandle(NULL),
255                               0);             // lpParam
256 
257   DCHECK(list_view_);
258   if (!list_view_)
259     return FALSE;
260 
261   LVCOLUMN list_view_column = {0};
262   list_view_column.mask = LVCF_FMT | LVCF_WIDTH;
263   list_view_column.fmt = LVCFMT_LEFT;
264   list_view_column.cx = 10000;  // Maximum size of an entry in the list view.
265   ListView_InsertColumn(list_view_, 0, &list_view_column);
266 
267   // Set list view to show green font on black background
268   ListView_SetBkColor(list_view_, CLR_NONE);
269   ListView_SetTextColor(list_view_, RGB(0x0, 0x0, 0x0));
270   ListView_SetTextBkColor(list_view_, CLR_NONE);
271 
272   return TRUE;
273 }
274 
OnDestroy(HWND window)275 void MainUIWindow::OnDestroy(HWND window) {
276   // Post a quit message because our application is over when the
277   // user closes this window.
278   ::PostQuitMessage(0);
279 }
280 
OnSize(HWND window,UINT state,int cx,int cy)281 void MainUIWindow::OnSize(HWND window, UINT state, int cx, int cy) {
282   // If we have a valid inner child, resize it to cover the entire
283   // client area of the main UI window.
284   if (list_view_) {
285     ::MoveWindow(list_view_,
286                  0,      // x
287                  0,      // y
288                  cx,     // width
289                  cy,     // height
290                  TRUE);  // repaint
291   }
292 }
293 
OnPaint(HWND window)294 void MainUIWindow::OnPaint(HWND window) {
295   PAINTSTRUCT paintstruct;
296   ::BeginPaint(window, &paintstruct);
297   // add painting code here if required
298   ::EndPaint(window, &paintstruct);
299 }
300 
OnFileExit()301 void MainUIWindow::OnFileExit() {
302   ::PostQuitMessage(0);
303 }
304 
OnCommandsLaunch(HWND window)305 void MainUIWindow::OnCommandsLaunch(HWND window) {
306   // User wants to see the Select DLL dialog box
307   ::DialogBox(instance_handle_,
308               MAKEINTRESOURCE(IDD_LAUNCH_DLL),
309               window,
310               SpawnTargetWndProc);
311 }
312 
OnLaunchDll(HWND dialog)313 bool MainUIWindow::OnLaunchDll(HWND dialog) {
314   HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
315   HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
316   HWND edit_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
317 
318   wchar_t dll_path[MAX_PATH];
319   wchar_t entry_point[MAX_PATH];
320   wchar_t log_file[MAX_PATH];
321 
322   int dll_name_len    = ::GetWindowText(edit_box_dll_name, dll_path, MAX_PATH);
323   int entry_point_len = ::GetWindowText(edit_box_entry_point,
324                                         entry_point, MAX_PATH);
325   // Log file is optional (can be blank)
326   ::GetWindowText(edit_log_file, log_file, MAX_PATH);
327 
328   if (0 >= dll_name_len) {
329     ::MessageBox(dialog,
330                  L"Please specify a DLL for the target to load",
331                  L"No DLL specified",
332                  MB_ICONERROR);
333     return false;
334   }
335 
336   if (GetFileAttributes(dll_path) == INVALID_FILE_ATTRIBUTES) {
337     ::MessageBox(dialog,
338                  L"DLL specified was not found",
339                  L"DLL not found",
340                  MB_ICONERROR);
341     return false;
342   }
343 
344   if (0 >= entry_point_len) {
345     ::MessageBox(dialog,
346                  L"Please specify an entry point for the DLL",
347                  L"No entry point specified",
348                  MB_ICONERROR);
349     return false;
350   }
351 
352   // store these values in the member variables for use in SpawnTarget
353   log_file_ = std::wstring(L"\"") + log_file + std::wstring(L"\"");
354   dll_path_ = dll_path;
355   entry_point_ = entry_point;
356 
357   return true;
358 }
359 
ListenPipeThunk(void * param)360 DWORD WINAPI MainUIWindow::ListenPipeThunk(void *param) {
361   return reinterpret_cast<MainUIWindow*>(param)->ListenPipe();
362 }
363 
WaitForTargetThunk(void * param)364 DWORD WINAPI MainUIWindow::WaitForTargetThunk(void *param) {
365   return reinterpret_cast<MainUIWindow*>(param)->WaitForTarget();
366 }
367 
368 // Thread waiting for the target application to die. It displays
369 // a message in the list view when it happens.
WaitForTarget()370 DWORD MainUIWindow::WaitForTarget() {
371   WaitForSingleObject(target_.hProcess, INFINITE);
372 
373   DWORD exit_code = 0;
374   if (!GetExitCodeProcess(target_.hProcess, &exit_code)) {
375     exit_code = 0xFFFF;  // Default exit code
376   }
377 
378   ::CloseHandle(target_.hProcess);
379   ::CloseHandle(target_.hThread);
380 
381   AddDebugMessage(L"Targed exited with return code %d", exit_code);
382   return 0;
383 }
384 
385 // Thread waiting for messages on the log pipe. It displays the messages
386 // in the listview.
ListenPipe()387 DWORD MainUIWindow::ListenPipe() {
388   HANDLE logfile_handle = NULL;
389   ATL::CString file_to_open = log_file_.c_str();
390   file_to_open.Remove(L'\"');
391   if (file_to_open.GetLength()) {
392     logfile_handle = ::CreateFile(file_to_open.GetBuffer(),
393                                   GENERIC_WRITE,
394                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
395                                   NULL,  // Default security attributes
396                                   CREATE_ALWAYS,
397                                   FILE_ATTRIBUTE_NORMAL,
398                                   NULL);  // No template
399     if (INVALID_HANDLE_VALUE == logfile_handle) {
400       AddDebugMessage(L"Failed to open \"%ls\" for logging. Error %d",
401                       file_to_open.GetBuffer(), ::GetLastError());
402       logfile_handle = NULL;
403     }
404   }
405 
406   const int kSizeBuffer = 1024;
407   BYTE read_buffer[kSizeBuffer] = {0};
408   ATL::CStringA read_buffer_global;
409   ATL::CStringA string_to_print;
410 
411   DWORD last_error = 0;
412   while (last_error == ERROR_SUCCESS || last_error == ERROR_PIPE_LISTENING ||
413          last_error == ERROR_NO_DATA) {
414     DWORD read_data_length;
415     if (::ReadFile(pipe_handle_,
416                   read_buffer,
417                   kSizeBuffer - 1,  // Max read size
418                   &read_data_length,
419                   NULL)) {  // Not overlapped
420       if (logfile_handle) {
421         DWORD write_data_length;
422         ::WriteFile(logfile_handle,
423                     read_buffer,
424                     read_data_length,
425                     &write_data_length,
426                     FALSE);  // Not overlapped
427       }
428 
429       // Append the new buffer to the current buffer
430       read_buffer[read_data_length] = NULL;
431       read_buffer_global += reinterpret_cast<char *>(read_buffer);
432       read_buffer_global.Remove(10);  // Remove the CRs
433 
434       // If we completed a new line, output it
435       int endline = read_buffer_global.Find(13);  // search for LF
436       while (-1 != endline) {
437         string_to_print = read_buffer_global;
438         string_to_print.Delete(endline, string_to_print.GetLength());
439         read_buffer_global.Delete(0, endline);
440 
441         //  print the line (with the ending LF)
442         OutputDebugStringA(string_to_print.GetBuffer());
443 
444         // Remove the ending LF
445         read_buffer_global.Delete(0, 1);
446 
447         // Add the line to the log
448         AddDebugMessage(L"%S", string_to_print.GetBuffer());
449 
450         endline = read_buffer_global.Find(13);
451       }
452       last_error = ERROR_SUCCESS;
453     } else {
454       last_error = GetLastError();
455       Sleep(100);
456     }
457   }
458 
459   if (read_buffer_global.GetLength()) {
460     AddDebugMessage(L"%S", read_buffer_global.GetBuffer());
461   }
462 
463   CloseHandle(pipe_handle_);
464 
465   if (logfile_handle) {
466     CloseHandle(logfile_handle);
467   }
468 
469   return 0;
470 }
471 
SpawnTarget()472 bool MainUIWindow::SpawnTarget() {
473   // Generate the pipe name
474   GUID random_id;
475   CoCreateGuid(&random_id);
476 
477   wchar_t log_pipe[MAX_PATH] = {0};
478   wnsprintf(log_pipe, MAX_PATH - 1,
479             L"\\\\.\\pipe\\sbox_pipe_log_%lu_%lu_%lu_%lu",
480             random_id.Data1,
481             random_id.Data2,
482             random_id.Data3,
483             random_id.Data4);
484 
485   // We concatenate the four strings, add three spaces and a zero termination
486   // We use the resulting string as a param to CreateProcess (in SpawnTarget)
487   // Documented maximum for command line in CreateProcess is 32K (msdn)
488   size_t size_call = spawn_target_.length() + entry_point_.length() +
489                   dll_path_.length() + wcslen(log_pipe) + 6;
490   if (32 * 1024 < (size_call * sizeof(wchar_t))) {
491     AddDebugMessage(L"The length of the arguments exceeded 32K. "
492                     L"Aborting operation.");
493     return false;
494   }
495 
496   wchar_t * arguments = new wchar_t[size_call];
497   wnsprintf(arguments, static_cast<int>(size_call), L"%ls %ls \"%ls\" %ls",
498             spawn_target_.c_str(), entry_point_.c_str(),
499             dll_path_.c_str(), log_pipe);
500 
501   arguments[size_call - 1] = L'\0';
502 
503   scoped_refptr<sandbox::TargetPolicy> policy = broker_->CreatePolicy();
504   policy->SetJobLevel(sandbox::JOB_LOCKDOWN, 0);
505   policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
506                         sandbox::USER_LOCKDOWN);
507   policy->SetAlternateDesktop(true);
508   policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
509 
510   // Set the rule to allow the POC dll to be loaded by the target. Note that
511   // the rule allows 'all access' to the DLL, which could mean that the target
512   // could modify the DLL on disk.
513   policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
514                   sandbox::TargetPolicy::FILES_ALLOW_ANY, dll_path_.c_str());
515 
516   sandbox::ResultCode warning_result = sandbox::SBOX_ALL_OK;
517   DWORD last_error = ERROR_SUCCESS;
518   sandbox::ResultCode result =
519       broker_->SpawnTarget(spawn_target_.c_str(), arguments, policy,
520                            &warning_result, &last_error, &target_);
521 
522   policy.reset();
523 
524   bool return_value = false;
525   if (sandbox::SBOX_ALL_OK != result) {
526     AddDebugMessage(
527         L"Failed to spawn target %ls w/args (%ls), sandbox error code: %d",
528         spawn_target_.c_str(), arguments, result);
529     return_value = false;
530   } else {
531     DWORD thread_id;
532     ::CreateThread(NULL,  // Default security attributes
533                    NULL,  // Default stack size
534                    &MainUIWindow::WaitForTargetThunk,
535                    this,
536                    0,  // No flags
537                    &thread_id);
538 
539     pipe_handle_ = ::CreateNamedPipe(log_pipe,
540                                      PIPE_ACCESS_INBOUND | WRITE_DAC,
541                                      PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
542                                      1,  // Number of instances.
543                                      512,  // Out buffer size.
544                                      512,  // In buffer size.
545                                      NMPWAIT_USE_DEFAULT_WAIT,
546                                      NULL);  // Default security descriptor
547 
548     if (INVALID_HANDLE_VALUE == pipe_handle_)
549       AddDebugMessage(L"Failed to create pipe. Error %d", ::GetLastError());
550 
551     if (!sandbox::AddKnownSidToObject(pipe_handle_, SE_KERNEL_OBJECT,
552                                       WinWorldSid, GRANT_ACCESS,
553                                       FILE_ALL_ACCESS))
554       AddDebugMessage(L"Failed to set security on pipe. Error %d",
555                       ::GetLastError());
556 
557     ::CreateThread(NULL,  // Default security attributes
558                    NULL,  // Default stack size
559                    &MainUIWindow::ListenPipeThunk,
560                    this,
561                    0,  // No flags
562                    &thread_id);
563 
564     ::ResumeThread(target_.hThread);
565 
566     AddDebugMessage(L"Successfully spawned target w/args (%ls)", arguments);
567     return_value = true;
568   }
569 
570   delete[] arguments;
571   return return_value;
572 }
573 
OnShowBrowseForDllDlg(HWND owner)574 std::wstring MainUIWindow::OnShowBrowseForDllDlg(HWND owner) {
575   wchar_t filename[MAX_PATH];
576   wcscpy_s(filename, MAX_PATH, L"");
577 
578   OPENFILENAMEW file_info = {0};
579   file_info.lStructSize = sizeof(file_info);
580   file_info.hwndOwner = owner;
581   file_info.lpstrFile = filename;
582   file_info.nMaxFile = MAX_PATH;
583   file_info.lpstrFilter = L"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
584 
585   file_info.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
586 
587   if (GetOpenFileName(&file_info)) {
588     return file_info.lpstrFile;
589   }
590 
591   return L"";
592 }
593 
OnShowBrowseForLogFileDlg(HWND owner)594 std::wstring MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner) {
595   wchar_t filename[MAX_PATH];
596   wcscpy_s(filename, MAX_PATH, L"");
597 
598   OPENFILENAMEW file_info = {0};
599   file_info.lStructSize = sizeof(file_info);
600   file_info.hwndOwner = owner;
601   file_info.lpstrFile = filename;
602   file_info.nMaxFile = MAX_PATH;
603   file_info.lpstrFilter = L"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
604 
605   file_info.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
606 
607   if (GetSaveFileName(&file_info)) {
608     return file_info.lpstrFile;
609   }
610 
611   return L"";
612 }
613 
AddDebugMessage(const wchar_t * format,...)614 void MainUIWindow::AddDebugMessage(const wchar_t* format, ...) {
615   DCHECK(format);
616   if (!format)
617     return;
618 
619   const int kMaxDebugBuffSize = 1024;
620 
621   va_list arg_list;
622   va_start(arg_list, format);
623 
624   wchar_t text[kMaxDebugBuffSize + 1];
625   vswprintf_s(text, kMaxDebugBuffSize, format, arg_list);
626   text[kMaxDebugBuffSize] = L'\0';
627 
628   InsertLineInListView(text);
629   va_end(arg_list);
630 }
631 
632 
InsertLineInListView(wchar_t * debug_message)633 void MainUIWindow::InsertLineInListView(wchar_t* debug_message) {
634   DCHECK(debug_message);
635   if (!debug_message)
636     return;
637 
638   // Prepend the time to the message
639   const int kSizeTime = 100;
640   size_t size_message_with_time = wcslen(debug_message) + kSizeTime;
641   wchar_t * message_time = new wchar_t[size_message_with_time];
642 
643   time_t time_temp;
644   time_temp = time(NULL);
645 
646   struct tm time = {0};
647   localtime_s(&time, &time_temp);
648 
649   size_t return_code;
650   return_code = wcsftime(message_time, kSizeTime, L"[%H:%M:%S] ", &time);
651 
652   wcscat_s(message_time, size_message_with_time, debug_message);
653 
654   // We add the debug message to the top of the listview
655   LVITEM item;
656   item.iItem = ListView_GetItemCount(list_view_);
657   item.iSubItem = 0;
658   item.mask = LVIF_TEXT | LVIF_PARAM;
659   item.pszText = message_time;
660   item.lParam = 0;
661 
662   ListView_InsertItem(list_view_, &item);
663 
664   delete[] message_time;
665 }
666