1// Copyright (c) 2006-2010 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
22HWND MainUIWindow::list_view_ = NULL;
23
24const wchar_t MainUIWindow::kDefaultDll_[]        = L"\\POCDLL.dll";
25const wchar_t MainUIWindow::kDefaultEntryPoint_[] = L"Run";
26const wchar_t MainUIWindow::kDefaultLogFile_[]    = L"";
27
28MainUIWindow::MainUIWindow()
29    : instance_handle_(NULL),
30      spawn_target_(L""),
31      dll_path_(L""),
32      entry_point_(L""),
33      broker_(NULL) {
34}
35
36MainUIWindow::~MainUIWindow() {
37}
38
39unsigned 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
118LRESULT 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
161INT_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        std::wstring dll_path = std::wstring(current_dir) +
178                                std::wstring(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        std::wstring 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        std::wstring 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
231MainUIWindow* 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
241BOOL 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
274void 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
282void 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
298void MainUIWindow::OnPaint(HWND window) {
299  PAINTSTRUCT paintstruct;
300  ::BeginPaint(window, &paintstruct);
301  // add painting code here if required
302  ::EndPaint(window, &paintstruct);
303}
304
305void MainUIWindow::OnFileExit() {
306  ::PostQuitMessage(0);
307}
308
309void 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
317bool 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_ = std::wstring(L"\"") + log_file + std::wstring(L"\"");
358  dll_path_ = dll_path;
359  entry_point_ = entry_point;
360
361  return true;
362}
363
364DWORD WINAPI MainUIWindow::ListenPipeThunk(void *param) {
365  return reinterpret_cast<MainUIWindow*>(param)->ListenPipe();
366}
367
368DWORD 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.
374DWORD 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.
391DWORD 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
477bool 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::AddKnownSidToKernelObject(pipe_handle_, WinWorldSid,
557                                            FILE_ALL_ACCESS))
558      AddDebugMessage(L"Failed to set security on pipe. Error %d",
559                      ::GetLastError());
560
561    ::CreateThread(NULL,  // Default security attributes
562                   NULL,  // Default stack size
563                   &MainUIWindow::ListenPipeThunk,
564                   this,
565                   0,  // No flags
566                   &thread_id);
567
568    ::ResumeThread(target_.hThread);
569
570    AddDebugMessage(L"Successfully spawned target w/args (%ls)", arguments);
571    return_value = true;
572  }
573
574  delete[] arguments;
575  return return_value;
576}
577
578std::wstring MainUIWindow::OnShowBrowseForDllDlg(HWND owner) {
579  wchar_t filename[MAX_PATH];
580  wcscpy_s(filename, MAX_PATH, L"");
581
582  OPENFILENAMEW file_info = {0};
583  file_info.lStructSize = sizeof(file_info);
584  file_info.hwndOwner = owner;
585  file_info.lpstrFile = filename;
586  file_info.nMaxFile = MAX_PATH;
587  file_info.lpstrFilter = L"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
588
589  file_info.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
590
591  if (GetOpenFileName(&file_info)) {
592    return file_info.lpstrFile;
593  }
594
595  return L"";
596}
597
598std::wstring MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner) {
599  wchar_t filename[MAX_PATH];
600  wcscpy_s(filename, MAX_PATH, L"");
601
602  OPENFILENAMEW file_info = {0};
603  file_info.lStructSize = sizeof(file_info);
604  file_info.hwndOwner = owner;
605  file_info.lpstrFile = filename;
606  file_info.nMaxFile = MAX_PATH;
607  file_info.lpstrFilter = L"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
608
609  file_info.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
610
611  if (GetSaveFileName(&file_info)) {
612    return file_info.lpstrFile;
613  }
614
615  return L"";
616}
617
618void MainUIWindow::AddDebugMessage(const wchar_t* format, ...) {
619  DCHECK(format);
620  if (!format)
621    return;
622
623  const int kMaxDebugBuffSize = 1024;
624
625  va_list arg_list;
626  _crt_va_start(arg_list, format);
627
628  wchar_t text[kMaxDebugBuffSize + 1];
629  vswprintf_s(text, kMaxDebugBuffSize, format, arg_list);
630  text[kMaxDebugBuffSize] = L'\0';
631
632  InsertLineInListView(text);
633}
634
635
636void MainUIWindow::InsertLineInListView(wchar_t* debug_message) {
637  DCHECK(debug_message);
638  if (!debug_message)
639    return;
640
641  // Prepend the time to the message
642  const int kSizeTime = 100;
643  size_t size_message_with_time = wcslen(debug_message) + kSizeTime;
644  wchar_t * message_time = new wchar_t[size_message_with_time];
645
646  time_t time_temp;
647  time_temp = time(NULL);
648
649  struct tm time = {0};
650  localtime_s(&time, &time_temp);
651
652  size_t return_code;
653  return_code = wcsftime(message_time, kSizeTime, L"[%H:%M:%S] ", &time);
654
655  wcscat_s(message_time, size_message_with_time, debug_message);
656
657  // We add the debug message to the top of the listview
658  LVITEM item;
659  item.iItem = ListView_GetItemCount(list_view_);
660  item.iSubItem = 0;
661  item.mask = LVIF_TEXT | LVIF_PARAM;
662  item.pszText = message_time;
663  item.lParam = 0;
664
665  ListView_InsertItem(list_view_, &item);
666
667  delete[] message_time;
668}
669