// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome_frame/crash_server_init.h"

#include <sddl.h>
#include <Shlobj.h>
#include <stdlib.h>
#include "version.h"  // NOLINT

const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\";
const wchar_t kSystemPrincipalSid[] = L"S-1-5-18";

const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>(
    MiniDumpWithProcessThreadData |  // Get PEB and TEB.
    MiniDumpWithUnloadedModules |  // Get unloaded modules when available.
    MiniDumpWithIndirectlyReferencedMemory);  // Get memory referenced by stack.

extern "C" IMAGE_DOS_HEADER __ImageBase;

// Builds a string representation of the user's SID and places it in user_sid.
bool GetUserSidString(std::wstring* user_sid) {
  bool success = false;
  if (user_sid) {
    struct {
      TOKEN_USER token_user;
      BYTE buffer[SECURITY_MAX_SID_SIZE];
    } token_info_buffer;

    HANDLE token = NULL;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
      DWORD out_size;
      if (GetTokenInformation(token, TokenUser, &token_info_buffer.token_user,
                              sizeof(token_info_buffer), &out_size)) {
        wchar_t* user_sid_value = NULL;
        if (token_info_buffer.token_user.User.Sid &&
            ConvertSidToStringSid(token_info_buffer.token_user.User.Sid,
                                  &user_sid_value)) {
          *user_sid = user_sid_value;
          LocalFree(user_sid_value);
          user_sid_value = NULL;
          success = true;
        }
      }
      CloseHandle(token);
    }
  }

  return success;
}

bool IsRunningSystemInstall() {
  wchar_t exe_path[MAX_PATH * 2] = {0};
  GetModuleFileName(reinterpret_cast<HMODULE>(&__ImageBase),
                    exe_path,
                    _countof(exe_path));

  bool is_system = false;

  wchar_t program_files_path[MAX_PATH] = {0};
  if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL,
                                SHGFP_TYPE_CURRENT, program_files_path))) {
    if (wcsstr(exe_path, program_files_path) == exe_path) {
      is_system = true;
    }
  }

  return is_system;
}

google_breakpad::CustomClientInfo* GetCustomInfo() {
  static google_breakpad::CustomInfoEntry ver_entry(
      L"ver", TEXT(CHROME_VERSION_STRING));
  static google_breakpad::CustomInfoEntry prod_entry(L"prod", L"ChromeFrame");
  static google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32");
  static google_breakpad::CustomInfoEntry type_entry(L"ptype", L"chrome_frame");
  static google_breakpad::CustomInfoEntry entries[] = {
      ver_entry, prod_entry, plat_entry, type_entry };
  static google_breakpad::CustomClientInfo custom_info = {
      entries, ARRAYSIZE(entries) };
  return &custom_info;
}

google_breakpad::ExceptionHandler* InitializeCrashReporting(
    CrashReportingMode mode) {
  wchar_t temp_path[MAX_PATH + 1] = {0};
  DWORD path_len = ::GetTempPath(MAX_PATH, temp_path);

  std::wstring pipe_name;
  if (mode == HEADLESS) {
    // This flag is used for testing, connect to the test crash service.
    pipe_name = kChromePipeName;
  } else {
    // Otherwise, build a pipe name corresponding to either user or
    // system-level Omaha.
    pipe_name = kGoogleUpdatePipeName;
    if (IsRunningSystemInstall()) {
      pipe_name += kSystemPrincipalSid;
    } else {
      std::wstring user_sid;
      if (GetUserSidString(&user_sid)) {
        pipe_name += user_sid;
      } else {
        // We don't think we're a system install, but we couldn't get the
        // user SID. Try connecting to the system-level crash service as a
        // last ditch effort.
        pipe_name += kSystemPrincipalSid;
      }
    }
  }

  google_breakpad::ExceptionHandler* breakpad =
      new google_breakpad::ExceptionHandler(
          temp_path, NULL, NULL, NULL,
          google_breakpad::ExceptionHandler::HANDLER_ALL, kLargerDumpType,
          pipe_name.c_str(), GetCustomInfo());

  return breakpad;
}