// 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 "chrome_frame/chrome_launcher.h"

#include <windows.h>
#include <shellapi.h>
#include <shlwapi.h>

#include "policy/policy_constants.h"

// Herein lies stuff selectively stolen from Chrome. We don't pull it in
// directly because all of it results in many things we don't want being
// included as well.
namespace {

// These are the switches we will allow (along with their values) in the
// safe-for-Low-Integrity version of the Chrome command line.
// Including the chrome switch files pulls in a bunch of dependencies sadly, so
// we redefine things here:
const wchar_t* kAllowedSwitches[] = {
  L"automation-channel",
  L"chrome-frame",
  L"chrome-version",
  L"disable-background-mode",
  L"disable-popup-blocking",
  L"disable-print-preview",
  L"disable-renderer-accessibility",
  L"enable-experimental-extension-apis",
  L"force-renderer-accessibility",
  L"full-memory-crash-report",
  L"lang",
  L"no-default-browser-check",
  L"no-first-run",
  L"noerrdialogs",
  L"user-data-dir",
};

const wchar_t kWhitespaceChars[] = {
  0x0009, /* <control-0009> to <control-000D> */
  0x000A,
  0x000B,
  0x000C,
  0x000D,
  0x0020, /* Space */
  0x0085, /* <control-0085> */
  0x00A0, /* No-Break Space */
  0x1680, /* Ogham Space Mark */
  0x180E, /* Mongolian Vowel Separator */
  0x2000, /* En Quad to Hair Space */
  0x2001,
  0x2002,
  0x2003,
  0x2004,
  0x2005,
  0x2006,
  0x2007,
  0x2008,
  0x2009,
  0x200A,
  0x200C, /* Zero Width Non-Joiner */
  0x2028, /* Line Separator */
  0x2029, /* Paragraph Separator */
  0x202F, /* Narrow No-Break Space */
  0x205F, /* Medium Mathematical Space */
  0x3000, /* Ideographic Space */
  0
};

const wchar_t kLauncherExeBaseName[] = L"chrome_launcher.exe";
const wchar_t kBrowserProcessExecutableName[] = L"chrome.exe";

}  // end namespace


namespace chrome_launcher {

std::wstring TrimWhiteSpace(const wchar_t* input_str) {
  std::wstring output;
  if (input_str != NULL) {
    std::wstring str(input_str);

    const std::wstring::size_type first_good_char =
        str.find_first_not_of(kWhitespaceChars);
    const std::wstring::size_type last_good_char =
        str.find_last_not_of(kWhitespaceChars);

    if (first_good_char != std::wstring::npos &&
        last_good_char != std::wstring::npos &&
        last_good_char >= first_good_char) {
      // + 1 because find_last_not_of returns the index, and we want the count
      output = str.substr(first_good_char,
                          last_good_char - first_good_char + 1);
    }
  }

  return output;
}

bool IsValidArgument(const std::wstring& arg) {
  if (arg.length() < 2) {
    return false;
  }

  for (int i = 0; i < arraysize(kAllowedSwitches); ++i) {
    size_t arg_length = lstrlenW(kAllowedSwitches[i]);
    if (arg.find(kAllowedSwitches[i], 2) == 2) {
      // The argument starts off right, now it must either end here, or be
      // followed by an equals sign.
      if (arg.length() == (arg_length + 2) ||
          (arg.length() > (arg_length + 2) && arg[arg_length+2] == L'=')) {
        return true;
      }
    }
  }

  return false;
}

bool IsValidCommandLine(const wchar_t* command_line) {
  if (command_line == NULL) {
    return false;
  }

  int num_args = 0;
  wchar_t** args = NULL;
  args = CommandLineToArgvW(command_line, &num_args);

  bool success = true;
  // Note that we skip args[0] since that is just our executable name and
  // doesn't get passed through to Chrome.
  for (int i = 1; i < num_args; ++i) {
    std::wstring trimmed_arg = TrimWhiteSpace(args[i]);
    if (!IsValidArgument(trimmed_arg)) {
      success = false;
      break;
    }
  }

  return success;
}

// Looks up optionally configured launch parameters for Chrome that may have
// been set via group policy.
void AppendAdditionalLaunchParameters(std::wstring* command_line) {
  static const HKEY kRootKeys[] = {
    HKEY_LOCAL_MACHINE,
    HKEY_CURRENT_USER
  };

  std::wstring launch_params_value_name(
      &policy::key::kAdditionalLaunchParameters[0],
      &policy::key::kAdditionalLaunchParameters[
          lstrlenA(policy::key::kAdditionalLaunchParameters)]);

  // Used for basic checks since CreateProcess doesn't support command lines
  // longer than 0x8000 characters. If we surpass that length, we do not add the
  // additional parameters.  Because we need to add a space before the
  // extra parameters, we use 0x7fff and not 0x8000.
  const size_t kMaxChars = 0x7FFF - command_line->size();
  HKEY key;
  LONG result;
  bool found = false;
  for (int i = 0; !found && i < arraysize(kRootKeys); ++i) {
    result = ::RegOpenKeyExW(kRootKeys[i], policy::kRegistryChromePolicyKey, 0,
                             KEY_QUERY_VALUE, &key);
    if (result == ERROR_SUCCESS) {
      DWORD size = 0;
      DWORD type = 0;
      result = RegQueryValueExW(key, launch_params_value_name.c_str(),
                                0, &type, NULL, &size);
      if (result == ERROR_SUCCESS && type == REG_SZ && size > 0 &&
          (size / sizeof(wchar_t)) < kMaxChars) {
        // This size includes any terminating null character or characters
        // unless the data was stored without them, so for safety we allocate
        // one extra char and zero out the buffer.
        wchar_t* value = new wchar_t[(size / sizeof(wchar_t)) + 1];
        memset(value, 0, size + sizeof(wchar_t));
        result = RegQueryValueExW(key, launch_params_value_name.c_str(), 0,
                                  &type, reinterpret_cast<BYTE*>(&value[0]),
                                  &size);
        if (result == ERROR_SUCCESS) {
          *command_line += L' ';
          *command_line += value;
          found = true;
        }
        delete [] value;
      }
      ::RegCloseKey(key);
    }
  }
}

bool SanitizeAndLaunchChrome(const wchar_t* command_line) {
  bool success = false;
  if (IsValidCommandLine(command_line)) {
    std::wstring chrome_path;
    if (GetChromeExecutablePath(&chrome_path)) {
      const wchar_t* args = PathGetArgs(command_line);

      // Build the command line string with the quoted path to chrome.exe.
      std::wstring command_line;
      command_line.reserve(chrome_path.size() + 2);
      command_line.append(1, L'\"').append(chrome_path).append(1, L'\"');

      if (args != NULL) {
        command_line += L' ';
        command_line += args;
      }

      // Append parameters that might be set by group policy.
      AppendAdditionalLaunchParameters(&command_line);

      STARTUPINFO startup_info = {0};
      startup_info.cb = sizeof(startup_info);
      startup_info.dwFlags = STARTF_USESHOWWINDOW;
      startup_info.wShowWindow = SW_SHOW;
      PROCESS_INFORMATION process_info = {0};
      if (CreateProcess(&chrome_path[0], &command_line[0],
                        NULL, NULL, FALSE, 0, NULL, NULL,
                        &startup_info, &process_info)) {
        // Close handles.
        CloseHandle(process_info.hThread);
        CloseHandle(process_info.hProcess);
        success = true;
      } else {
        _ASSERT(FALSE);
      }
    }
  }

  return success;
}

bool GetChromeExecutablePath(std::wstring* chrome_path) {
  _ASSERT(chrome_path);

  wchar_t cur_path[MAX_PATH * 4] = {0};
  // Assume that we are always built into an exe.
  GetModuleFileName(NULL, cur_path, arraysize(cur_path) / 2);

  PathRemoveFileSpec(cur_path);

  bool success = false;
  if (PathAppend(cur_path, kBrowserProcessExecutableName)) {
    if (!PathFileExists(cur_path)) {
      // The installation model for Chrome places the DLLs in a versioned
      // sub-folder one down from the Chrome executable. If we fail to find
      // chrome.exe in the current path, try looking one up and launching that
      // instead. In practice, that means we back up two and append the
      // executable name again.
      PathRemoveFileSpec(cur_path);
      PathRemoveFileSpec(cur_path);
      PathAppend(cur_path, kBrowserProcessExecutableName);
    }

    if (PathFileExists(cur_path)) {
      *chrome_path = cur_path;
      success = true;
    }
  }

  return success;
}

}  // namespace chrome_launcher