// Copyright 2013 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/app/chrome_breakpad_client.h"

#include "base/atomicops.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/strings/safe_sprintf.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_result_codes.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/crash_keys.h"
#include "chrome/common/env_vars.h"
#include "chrome/installer/util/google_update_settings.h"

#if defined(OS_WIN)
#include <windows.h>

#include "base/file_version_info.h"
#include "base/win/registry.h"
#include "chrome/installer/util/google_chrome_sxs_distribution.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/util_constants.h"
#include "policy/policy_constants.h"
#endif

#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS)
#include "chrome/browser/crash_upload_list.h"
#include "chrome/common/chrome_version_info_posix.h"
#endif

#if defined(OS_POSIX)
#include "base/debug/dump_without_crashing.h"
#endif

#if defined(OS_ANDROID)
#include "chrome/common/descriptors_android.h"
#endif

#if defined(OS_CHROMEOS)
#include "chrome/common/chrome_version_info.h"
#include "chromeos/chromeos_switches.h"
#endif

namespace chrome {

namespace {

#if defined(OS_WIN)
// This is the minimum version of google update that is required for deferred
// crash uploads to work.
const char kMinUpdateVersion[] = "1.3.21.115";

// The value name prefix will be of the form {chrome-version}-{pid}-{timestamp}
// (i.e., "#####.#####.#####.#####-########-########") which easily fits into a
// 63 character buffer.
const char kBrowserCrashDumpPrefixTemplate[] = "%s-%08x-%08x";
const size_t kBrowserCrashDumpPrefixLength = 63;
char g_browser_crash_dump_prefix[kBrowserCrashDumpPrefixLength + 1] = {};

// These registry key to which we'll write a value for each crash dump attempt.
HKEY g_browser_crash_dump_regkey = NULL;

// A atomic counter to make each crash dump value name unique.
base::subtle::Atomic32 g_browser_crash_dump_count = 0;
#endif

}  // namespace

ChromeBreakpadClient::ChromeBreakpadClient() {}

ChromeBreakpadClient::~ChromeBreakpadClient() {}

void ChromeBreakpadClient::SetClientID(const std::string& client_id) {
  crash_keys::SetClientID(client_id);
}

#if defined(OS_WIN)
bool ChromeBreakpadClient::GetAlternativeCrashDumpLocation(
    base::FilePath* crash_dir) {
  // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate
  // location to write breakpad crash dumps can be set.
  scoped_ptr<base::Environment> env(base::Environment::Create());
  std::string alternate_crash_dump_location;
  if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) {
    *crash_dir = base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location);
    return true;
  }

  return false;
}

void ChromeBreakpadClient::GetProductNameAndVersion(
    const base::FilePath& exe_path,
    base::string16* product_name,
    base::string16* version,
    base::string16* special_build,
    base::string16* channel_name) {
  DCHECK(product_name);
  DCHECK(version);
  DCHECK(special_build);
  DCHECK(channel_name);

  scoped_ptr<FileVersionInfo> version_info(
      FileVersionInfo::CreateFileVersionInfo(exe_path));

  if (version_info.get()) {
    // Get the information from the file.
    *version = version_info->product_version();
    if (!version_info->is_official_build())
      version->append(base::ASCIIToUTF16("-devel"));

    *product_name = version_info->product_short_name();
    *special_build = version_info->special_build();
  } else {
    // No version info found. Make up the values.
    *product_name = base::ASCIIToUTF16("Chrome");
    *version = base::ASCIIToUTF16("0.0.0.0-devel");
  }

  GoogleUpdateSettings::GetChromeChannelAndModifiers(
      !GetIsPerUserInstall(exe_path), channel_name);
}

bool ChromeBreakpadClient::ShouldShowRestartDialog(base::string16* title,
                                                   base::string16* message,
                                                   bool* is_rtl_locale) {
  scoped_ptr<base::Environment> env(base::Environment::Create());
  if (!env->HasVar(env_vars::kShowRestart) ||
      !env->HasVar(env_vars::kRestartInfo) ||
      env->HasVar(env_vars::kMetroConnected)) {
    return false;
  }

  std::string restart_info;
  env->GetVar(env_vars::kRestartInfo, &restart_info);

  // The CHROME_RESTART var contains the dialog strings separated by '|'.
  // See ChromeBrowserMainPartsWin::PrepareRestartOnCrashEnviroment()
  // for details.
  std::vector<std::string> dlg_strings;
  base::SplitString(restart_info, '|', &dlg_strings);

  if (dlg_strings.size() < 3)
    return false;

  *title = base::UTF8ToUTF16(dlg_strings[0]);
  *message = base::UTF8ToUTF16(dlg_strings[1]);
  *is_rtl_locale = dlg_strings[2] == env_vars::kRtlLocale;
  return true;
}

bool ChromeBreakpadClient::AboutToRestart() {
  scoped_ptr<base::Environment> env(base::Environment::Create());
  if (!env->HasVar(env_vars::kRestartInfo))
    return false;

  env->SetVar(env_vars::kShowRestart, "1");
  return true;
}

bool ChromeBreakpadClient::GetDeferredUploadsSupported(
    bool is_per_user_install) {
  Version update_version = GoogleUpdateSettings::GetGoogleUpdateVersion(
      !is_per_user_install);
  if (!update_version.IsValid() ||
      update_version.IsOlderThan(std::string(kMinUpdateVersion)))
    return false;

  return true;
}

bool ChromeBreakpadClient::GetIsPerUserInstall(const base::FilePath& exe_path) {
  return InstallUtil::IsPerUserInstall(exe_path.value().c_str());
}

bool ChromeBreakpadClient::GetShouldDumpLargerDumps(bool is_per_user_install) {
  base::string16 channel_name =
      GoogleUpdateSettings::GetChromeChannel(!is_per_user_install);

  // Capture more detail in crash dumps for beta and dev channel builds.
  return (channel_name == installer::kChromeChannelDev ||
          channel_name == installer::kChromeChannelBeta ||
          channel_name == GoogleChromeSxSDistribution::ChannelName());
}

int ChromeBreakpadClient::GetResultCodeRespawnFailed() {
  return chrome::RESULT_CODE_RESPAWN_FAILED;
}

void ChromeBreakpadClient::InitBrowserCrashDumpsRegKey() {
  DCHECK(g_browser_crash_dump_regkey == NULL);

  base::win::RegKey regkey;
  if (regkey.Create(HKEY_CURRENT_USER,
                    chrome::kBrowserCrashDumpAttemptsRegistryPath,
                    KEY_ALL_ACCESS) != ERROR_SUCCESS) {
    return;
  }

  // We use the current process id and the current tick count as a (hopefully)
  // unique combination for the crash dump value. There's a small chance that
  // across a reboot we might have a crash dump signal written, and the next
  // browser process might have the same process id and tick count, but crash
  // before consuming the signal (overwriting the signal with an identical one).
  // For now, we're willing to live with that risk.
  int length = base::strings::SafeSPrintf(g_browser_crash_dump_prefix,
                                          kBrowserCrashDumpPrefixTemplate,
                                          chrome::kChromeVersion,
                                          ::GetCurrentProcessId(),
                                          ::GetTickCount());
  if (length <= 0) {
    NOTREACHED();
    g_browser_crash_dump_prefix[0] = '\0';
    return;
  }

  // Hold the registry key in a global for update on crash dump.
  g_browser_crash_dump_regkey = regkey.Take();
}

void ChromeBreakpadClient::RecordCrashDumpAttempt(bool is_real_crash) {
  // If we're not a browser (or the registry is unavailable to us for some
  // reason) then there's nothing to do.
  if (g_browser_crash_dump_regkey == NULL)
    return;

  // Generate the final value name we'll use (appends the crash number to the
  // base value name).
  const size_t kMaxValueSize = 2 * kBrowserCrashDumpPrefixLength;
  char value_name[kMaxValueSize + 1] = {};
  int length = base::strings::SafeSPrintf(
      value_name,
      "%s-%x",
      g_browser_crash_dump_prefix,
      base::subtle::NoBarrier_AtomicIncrement(&g_browser_crash_dump_count, 1));

  if (length > 0) {
    DWORD value_dword = is_real_crash ? 1 : 0;
    ::RegSetValueExA(g_browser_crash_dump_regkey, value_name, 0, REG_DWORD,
                     reinterpret_cast<BYTE*>(&value_dword),
                     sizeof(value_dword));
  }
}

bool ChromeBreakpadClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) {
// Determine whether configuration management allows loading the crash reporter.
// Since the configuration management infrastructure is not initialized at this
// point, we read the corresponding registry key directly. The return status
// indicates whether policy data was successfully read. If it is true,
// |breakpad_enabled| contains the value set by policy.
  base::string16 key_name =
      base::UTF8ToUTF16(policy::key::kMetricsReportingEnabled);
  DWORD value = 0;
  base::win::RegKey hklm_policy_key(HKEY_LOCAL_MACHINE,
                                    policy::kRegistryChromePolicyKey, KEY_READ);
  if (hklm_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) {
    *breakpad_enabled = value != 0;
    return true;
  }

  base::win::RegKey hkcu_policy_key(HKEY_CURRENT_USER,
                                    policy::kRegistryChromePolicyKey, KEY_READ);
  if (hkcu_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) {
    *breakpad_enabled = value != 0;
    return true;
  }

  return false;
}
#endif  // defined(OS_WIN)

#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS)
void ChromeBreakpadClient::GetProductNameAndVersion(std::string* product_name,
                                                    std::string* version) {
  DCHECK(product_name);
  DCHECK(version);
#if defined(OS_ANDROID)
  *product_name = "Chrome_Android";
#elif defined(OS_CHROMEOS)
  *product_name = "Chrome_ChromeOS";
#else  // OS_LINUX
#if !defined(ADDRESS_SANITIZER)
  *product_name = "Chrome_Linux";
#else
  *product_name = "Chrome_Linux_ASan";
#endif
#endif

  *version = PRODUCT_VERSION;
}

base::FilePath ChromeBreakpadClient::GetReporterLogFilename() {
  return base::FilePath(CrashUploadList::kReporterLogFilename);
}
#endif

bool ChromeBreakpadClient::GetCrashDumpLocation(base::FilePath* crash_dir) {
  // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate
  // location to write breakpad crash dumps can be set.
  scoped_ptr<base::Environment> env(base::Environment::Create());
  std::string alternate_crash_dump_location;
  if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) {
    base::FilePath crash_dumps_dir_path =
        base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location);
    PathService::Override(chrome::DIR_CRASH_DUMPS, crash_dumps_dir_path);
  }

  return PathService::Get(chrome::DIR_CRASH_DUMPS, crash_dir);
}

size_t ChromeBreakpadClient::RegisterCrashKeys() {
  // Note: This is not called on Windows because Breakpad is initialized in the
  // EXE module, but code that uses crash keys is in the DLL module.
  // RegisterChromeCrashKeys() will be called after the DLL is loaded.
  return crash_keys::RegisterChromeCrashKeys();
}

bool ChromeBreakpadClient::IsRunningUnattended() {
  scoped_ptr<base::Environment> env(base::Environment::Create());
  return env->HasVar(env_vars::kHeadless);
}

bool ChromeBreakpadClient::GetCollectStatsConsent() {
#if defined(GOOGLE_CHROME_BUILD)
  bool is_official_chrome_build = true;
#else
  bool is_official_chrome_build = false;
#endif

#if defined(OS_CHROMEOS)
  bool is_guest_session = CommandLine::ForCurrentProcess()->HasSwitch(
      chromeos::switches::kGuestSession);
  bool is_stable_channel =
      chrome::VersionInfo::GetChannel() == chrome::VersionInfo::CHANNEL_STABLE;

  if (is_guest_session && is_stable_channel)
    return false;
#endif  // defined(OS_CHROMEOS)

#if defined(OS_ANDROID)
  // TODO(jcivelli): we should not initialize the crash-reporter when it was not
  // enabled. Right now if it is disabled we still generate the minidumps but we
  // do not upload them.
  return is_official_chrome_build;
#else  // !defined(OS_ANDROID)
  return is_official_chrome_build &&
      GoogleUpdateSettings::GetCollectStatsConsent();
#endif  // defined(OS_ANDROID)
}

#if defined(OS_ANDROID)
int ChromeBreakpadClient::GetAndroidMinidumpDescriptor() {
  return kAndroidMinidumpDescriptor;
}
#endif

bool ChromeBreakpadClient::EnableBreakpadForProcess(
    const std::string& process_type) {
  return process_type == switches::kRendererProcess ||
         process_type == switches::kPluginProcess ||
         process_type == switches::kPpapiPluginProcess ||
         process_type == switches::kZygoteProcess ||
         process_type == switches::kGpuProcess;
}

}  // namespace chrome