// Copyright (c) 2012 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 "win8/delegate_execute/chrome_util.h"

#include <windows.h>
#include <atlbase.h>
#include <shlobj.h>
#include <stddef.h>

#include <algorithm>
#include <limits>
#include <string>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/md5.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/util_constants.h"

#if defined(GOOGLE_CHROME_BUILD)
#include "google_update/google_update_idl.h"  // nogncheck
#endif

namespace {

#if defined(GOOGLE_CHROME_BUILD)

// TODO(grt): These constants live in installer_util.  Consider moving them
// into common_constants to allow for reuse.
const base::FilePath::CharType kNewChromeExe[] =
    FILE_PATH_LITERAL("new_chrome.exe");
const wchar_t kRenameCommandValue[] = L"cmd";
const wchar_t kRegPathChromeClientBase[] =
    L"Software\\Google\\Update\\Clients\\";

// Returns the name of the global event used to detect if |chrome_exe| is in
// use by a browser process.
// TODO(grt): Move this somewhere central so it can be used by both this
// IsBrowserRunning (below) and IsBrowserAlreadyRunning (browser_util_win.cc).
base::string16 GetEventName(const base::FilePath& chrome_exe) {
  static wchar_t const kEventPrefix[] = L"Global\\";
  const size_t prefix_len = arraysize(kEventPrefix) - 1;
  base::string16 name;
  name.reserve(prefix_len + chrome_exe.value().size());
  name.assign(kEventPrefix, prefix_len);
  name.append(chrome_exe.value());
  std::replace(name.begin() + prefix_len, name.end(), '\\', '!');
  std::transform(name.begin() + prefix_len, name.end(),
                 name.begin() + prefix_len, tolower);
  return name;
}

// Returns true if |chrome_exe| is in use by a browser process. In this case,
// "in use" means past ChromeBrowserMainParts::PreMainMessageLoopRunImpl.
bool IsBrowserRunning(const base::FilePath& chrome_exe) {
  base::win::ScopedHandle handle(::OpenEvent(
      SYNCHRONIZE, FALSE, GetEventName(chrome_exe).c_str()));
  if (handle.IsValid())
    return true;
  DWORD last_error = ::GetLastError();
  if (last_error != ERROR_FILE_NOT_FOUND) {
    AtlTrace("%hs. Failed to open browser event; error %u.\n", __FUNCTION__,
             last_error);
  }
  return false;
}

// Returns true if the file new_chrome.exe exists in the same directory as
// |chrome_exe|.
bool NewChromeExeExists(const base::FilePath& chrome_exe) {
  base::FilePath new_chrome_exe(chrome_exe.DirName().Append(kNewChromeExe));
  return base::PathExists(new_chrome_exe);
}

bool GetUpdateCommand(bool is_per_user, base::string16* update_command) {
  const HKEY root = is_per_user ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
  base::string16 reg_path_chrome_client = kRegPathChromeClientBase;
  reg_path_chrome_client.append(dist->GetAppGuid());
  base::win::RegKey key(root, reg_path_chrome_client.c_str(), KEY_QUERY_VALUE);

  return key.ReadValue(kRenameCommandValue, update_command) == ERROR_SUCCESS;
}

#endif  // GOOGLE_CHROME_BUILD

}  // namespace

namespace delegate_execute {

void UpdateChromeIfNeeded(const base::FilePath& chrome_exe) {
#if defined(GOOGLE_CHROME_BUILD)
  // Nothing to do if a browser is already running or if there's no
  // new_chrome.exe.
  if (IsBrowserRunning(chrome_exe) || !NewChromeExeExists(chrome_exe))
    return;

  base::Process process;

  if (InstallUtil::IsPerUserInstall(chrome_exe)) {
    // Read the update command from the registry.
    base::string16 update_command;
    if (!GetUpdateCommand(true, &update_command)) {
      AtlTrace("%hs. Failed to read update command from registry.\n",
               __FUNCTION__);
    } else {
      // Run the update command.
      base::LaunchOptions launch_options;
      launch_options.start_hidden = true;
      process = base::LaunchProcess(update_command, launch_options);
      if (!process.IsValid()) {
        AtlTrace("%hs. Failed to launch command to finalize update; "
                 "error %u.\n", __FUNCTION__, ::GetLastError());
      }
    }
  } else {
    // Run the update command via Google Update.
    HRESULT hr = S_OK;
    base::win::ScopedComPtr<IProcessLauncher> process_launcher;
    hr = process_launcher.CreateInstance(__uuidof(ProcessLauncherClass));
    if (FAILED(hr)) {
      AtlTrace("%hs. Failed to Create ProcessLauncher; hr=0x%X.\n",
               __FUNCTION__, hr);
    } else {
      ULONG_PTR handle = 0;
      BrowserDistribution* dist = BrowserDistribution::GetDistribution();
      hr = process_launcher->LaunchCmdElevated(
          dist->GetAppGuid().c_str(), kRenameCommandValue,
          GetCurrentProcessId(), &handle);
      if (FAILED(hr)) {
        AtlTrace("%hs. Failed to launch command to finalize update; "
                 "hr=0x%X.\n", __FUNCTION__, hr);
      } else {
        process = base::Process(reinterpret_cast<base::ProcessHandle>(handle));
      }
    }
  }

  // Wait for the update to complete and report the results.
  if (process.IsValid()) {
    int exit_code = 0;
    if (!process.WaitForExitWithTimeout(
             base::TimeDelta::FromMilliseconds(INFINITE), &exit_code)) {
      AtlTrace("%hs. Failed to get result when finalizing update.\n",
               __FUNCTION__);
    } else if (exit_code != installer::RENAME_SUCCESSFUL) {
      AtlTrace("%hs. Failed to finalize update with exit code %d.\n",
               __FUNCTION__, exit_code);
    } else {
      AtlTrace("%hs. Finalized pending update.\n", __FUNCTION__);
    }
  }
#endif
}

}  // delegate_execute
