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