// Copyright (c) 2009 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 <shlobj.h>

#include "chrome_frame/html_utils.h"
#include "chrome_frame/utils.h"

#include "base/file_util.h"
#include "base/file_version_info.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/registry.h"
#include "base/scoped_comptr_win.h"
#include "base/string_util.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/installer/util/google_update_constants.h"
#include "googleurl/src/gurl.h"
#include "grit/chrome_frame_resources.h"
#include "chrome_frame/resource.h"

// Note that these values are all lower case and are compared to
// lower-case-transformed values.
const wchar_t kMetaTag[] = L"meta";
const wchar_t kHttpEquivAttribName[] = L"http-equiv";
const wchar_t kContentAttribName[] = L"content";
const wchar_t kXUACompatValue[] = L"x-ua-compatible";
const wchar_t kBodyTag[] = L"body";
const wchar_t kChromeContentPrefix[] = L"chrome=";
const wchar_t kChromeProtocolPrefix[] = L"cf:";

static const wchar_t kChromeFrameConfigKey[] =
    L"Software\\Google\\ChromeFrame";
static const wchar_t kChromeFrameOptinUrlsKey[] = L"OptinUrls";

// Used to isolate chrome frame builds from google chrome release channels.
const wchar_t kChromeFrameOmahaSuffix[] = L"-cf";
const wchar_t kDevChannelName[] = L"-dev";

const wchar_t kChromeAttachExternalTabPrefix[] = L"attach_external_tab";

HRESULT UtilRegisterTypeLib(HINSTANCE tlb_instance,
                            LPCOLESTR index,
                            bool for_current_user_only) {
  CComBSTR path;
  CComPtr<ITypeLib> type_lib;
  HRESULT hr = AtlLoadTypeLib(tlb_instance, index, &path, &type_lib);
  if (SUCCEEDED(hr)) {
    hr = UtilRegisterTypeLib(type_lib, path, NULL, for_current_user_only);
  }
  return hr;
}

HRESULT UtilUnRegisterTypeLib(HINSTANCE tlb_instance,
                              LPCOLESTR index,
                              bool for_current_user_only) {
  CComBSTR path;
  CComPtr<ITypeLib> type_lib;
  HRESULT hr = AtlLoadTypeLib(tlb_instance, index, &path, &type_lib);
  if (SUCCEEDED(hr)) {
    hr = UtilUnRegisterTypeLib(type_lib, for_current_user_only);
  }
  return hr;
}

HRESULT UtilRegisterTypeLib(LPCWSTR typelib_path,
                            bool for_current_user_only) {
  if (NULL == typelib_path) {
    return E_INVALIDARG;
  }
  CComBSTR path;
  CComPtr<ITypeLib> type_lib;
  HRESULT hr = ::LoadTypeLib(typelib_path, &type_lib);
  if (SUCCEEDED(hr)) {
    hr = UtilRegisterTypeLib(type_lib,
                             typelib_path,
                             NULL,
                             for_current_user_only);
  }
  return hr;
}

HRESULT UtilUnRegisterTypeLib(LPCWSTR typelib_path,
                              bool for_current_user_only) {
  CComPtr<ITypeLib> type_lib;
  HRESULT hr = ::LoadTypeLib(typelib_path, &type_lib);
  if (SUCCEEDED(hr)) {
    hr = UtilUnRegisterTypeLib(type_lib, for_current_user_only);
  }
  return hr;
}

HRESULT UtilRegisterTypeLib(ITypeLib* typelib,
                            LPCWSTR typelib_path,
                            LPCWSTR help_dir,
                            bool for_current_user_only) {
  typedef HRESULT(WINAPI *RegisterTypeLibPrototype)(ITypeLib FAR* type_lib,
                                                    OLECHAR FAR* full_path,
                                                    OLECHAR FAR* help_dir);
  LPCSTR function_name =
    for_current_user_only ? "RegisterTypeLibForUser" : "RegisterTypeLib";
  RegisterTypeLibPrototype reg_tlb =
      reinterpret_cast<RegisterTypeLibPrototype>(
          GetProcAddress(GetModuleHandle(_T("oleaut32.dll")),
                                         function_name));
  if (NULL == reg_tlb) {
    return E_FAIL;
  }
  return reg_tlb(typelib,
                 const_cast<OLECHAR*>(typelib_path),
                 const_cast<OLECHAR*>(help_dir));
}

HRESULT UtilUnRegisterTypeLib(ITypeLib* typelib,
                              bool for_current_user_only) {
  if (NULL == typelib) {
    return E_INVALIDARG;
  }
  typedef HRESULT(WINAPI *UnRegisterTypeLibPrototype)(
      REFGUID libID,
      unsigned short wVerMajor,  // NOLINT
      unsigned short wVerMinor,  // NOLINT
      LCID lcid,
      SYSKIND syskind);
  LPCSTR function_name =
    for_current_user_only ? "UnRegisterTypeLibForUser" : "UnRegisterTypeLib";

  UnRegisterTypeLibPrototype unreg_tlb =
      reinterpret_cast<UnRegisterTypeLibPrototype>(
          GetProcAddress(GetModuleHandle(_T("oleaut32.dll")),
                                         function_name));
  if (NULL == unreg_tlb) {
    return E_FAIL;
  }
  TLIBATTR* tla = NULL;
  HRESULT hr = typelib->GetLibAttr(&tla);
  if (SUCCEEDED(hr)) {
    hr = unreg_tlb(tla->guid,
                   tla->wMajorVerNum,
                   tla->wMinorVerNum,
                   tla->lcid,
                   tla->syskind);
    typelib->ReleaseTLibAttr(tla);
  }
  return hr;
}

HRESULT UtilGetXUACompatContentValue(const std::wstring& html_string,
                                     std::wstring* content_value) {
  if (!content_value) {
    return E_POINTER;
  }

  // Fail fast if the string X-UA-Compatible isn't in html_string
  if (StringToLowerASCII(html_string).find(kXUACompatValue) ==
      std::wstring::npos) {
    return E_FAIL;
  }

  HTMLScanner scanner(html_string.c_str());

  // Build the list of meta tags that occur before the body tag is hit.
  HTMLScanner::StringRangeList tag_list;
  scanner.GetTagsByName(kMetaTag, &tag_list, kBodyTag);

  // Search the list of meta tags for one with an http-equiv="X-UA-Compatible"
  // attribute.
  HTMLScanner::StringRange attribute;
  std::string search_attribute_ascii(WideToASCII(kXUACompatValue));
  HTMLScanner::StringRangeList::const_iterator tag_list_iter(tag_list.begin());
  for (; tag_list_iter != tag_list.end(); tag_list_iter++) {
    if (!tag_list_iter->GetTagAttribute(kHttpEquivAttribName, &attribute)) {
      continue;
    }

    // We found an http-equiv meta tag, check its value using the ascii
    // case-insensitive comparison method.
    if (!attribute.LowerCaseEqualsASCII(search_attribute_ascii.c_str())) {
      continue;
    }

    // We found our X-UA-Compatible meta tag so look for and extract
    // the value of the content attribute.
    if (!tag_list_iter->GetTagAttribute(kContentAttribName, &attribute)) {
      continue;
    }

    // Found the content string, copy and return.
    content_value->assign(attribute.Copy());
    return S_OK;
  }

  return E_FAIL;
}

bool AppendSuffixToChannelName(std::wstring* string,
                               const std::wstring& channel_name,
                               const std::wstring& suffix) {
  size_t pos = string->find(channel_name);
  // Append the suffix only if we find the channel name.
  if (pos != std::wstring::npos) {
    pos += channel_name.size();
    // Append the suffix only to the channel name only if the name is not
    // already followed by suffix.
    if (string->find(suffix, pos) != pos) {
      string->insert(pos, suffix);
      return true;
    }
  }
  return false;
}

bool RemoveSuffixFromChannelName(std::wstring* string,
                                 const std::wstring& channel_name,
                                 const std::wstring& suffix) {
  std::wstring decorated_channel(channel_name + suffix);
  size_t pos = string->find(decorated_channel);
  // TODO(robertshield): Remove the suffix iff the suffix is the last thing in
  // the string or is followed by another suffix that starts with '-'.
  if (pos != std::wstring::npos) {
    pos += channel_name.size();
    string->erase(pos, suffix.size());
    return true;
  }
  return false;
}

HRESULT UtilUpdateOmahaConfig(bool add_cf_suffix) {
  HKEY reg_root = HKEY_LOCAL_MACHINE;

  RegKey key;
  std::wstring ap_key_value;
  std::wstring reg_key(google_update::kRegPathClientState);
  reg_key.append(L"\\");
  reg_key.append(google_update::kChromeGuid);
  if (!key.Open(reg_root, reg_key.c_str(), KEY_READ | KEY_WRITE) ||
      !key.ReadValue(google_update::kRegApField, &ap_key_value)) {
    // Can't read the Omaha config.
    return REGDB_E_READREGDB;
  }

  HRESULT result = S_OK;
  // We've read the key in, try and modify it then write it back.
  if (add_cf_suffix && AppendSuffixToChannelName(&ap_key_value,
                                                 kDevChannelName,
                                                 kChromeFrameOmahaSuffix)) {
    if (!key.WriteValue(google_update::kRegApField, ap_key_value.c_str())) {
      DLOG(ERROR) << "Failed to add suffix to omaha ap key value.";
      result = REGDB_E_WRITEREGDB;
    }
  } else if (!add_cf_suffix &&
             RemoveSuffixFromChannelName(&ap_key_value,
                                         kDevChannelName,
                                         kChromeFrameOmahaSuffix)) {
    if (!key.WriteValue(google_update::kRegApField, ap_key_value.c_str())) {
      DLOG(ERROR) << "Failed to remove suffix from omaha ap key value.";
      result = REGDB_E_WRITEREGDB;
    }
  } else {
    // Getting here means that no modifications needed to be made.
    result = S_FALSE;
  }

  return result;
}

std::wstring GetResourceString(int resource_id) {
  std::wstring resource_string;
  HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase);
  const ATLSTRINGRESOURCEIMAGE* image = AtlGetStringResourceImage(
      this_module, resource_id);
  if (image) {
    resource_string.assign(image->achString, image->nLength);
  } else {
    NOTREACHED() << "Unable to find resource id " << resource_id;
  }
  return resource_string;
}

void DisplayVersionMismatchWarning(HWND parent,
                                   const std::string& server_version) {
  // Obtain the current module version.
  FileVersionInfo* file_version_info =
      FileVersionInfo::CreateFileVersionInfoForCurrentModule();
  DCHECK(file_version_info);
  std::wstring version_string(file_version_info->file_version());
  std::wstring wide_server_version;
  if (server_version.empty()) {
    wide_server_version = GetResourceString(IDS_VERSIONUNKNOWN);
  } else {
    wide_server_version = ASCIIToWide(server_version);
  }
  std::wstring title = GetResourceString(IDS_VERSIONMISMATCH_HEADER);
  std::wstring message;
  SStringPrintf(&message, GetResourceString(IDS_VERSIONMISMATCH).c_str(),
                wide_server_version.c_str(), version_string.c_str());

  ::MessageBox(parent, message.c_str(), title.c_str(), MB_OK);
}

std::string CreateJavascript(const std::string& function_name,
                             const std::string args) {
  std::string script_string = "javascript:";
  script_string += function_name + "(";
  if (!args.empty()) {
    script_string += "'";
    script_string += args;
    script_string += "'";
  }
  script_string += ")";
  return script_string;
}

AddRefModule::AddRefModule() {
  // TODO(tommi): Override the module's Lock/Unlock methods to call
  //  npapi::SetValue(NPPVpluginKeepLibraryInMemory) and keep the dll loaded
  //  while the module's refcount is > 0.  Only do this when we're being
  //  used as an NPAPI module.
  _pAtlModule->Lock();
}


AddRefModule::~AddRefModule() {
  _pAtlModule->Unlock();
}

namespace {
const char kIEImageName[] = "iexplore.exe";
const char kFirefoxImageName[] = "firefox.exe";
const char kOperaImageName[] = "opera.exe";
}  // namespace

std::wstring GetHostProcessName(bool include_extension) {
  FilePath exe;
  if (PathService::Get(base::FILE_EXE, &exe))
    exe = exe.BaseName();
  if (!include_extension) {
    exe = exe.RemoveExtension();
  }
  return exe.ToWStringHack();
}

BrowserType GetBrowserType() {
  static BrowserType browser_type = BROWSER_INVALID;

  if (browser_type == BROWSER_INVALID) {
    std::wstring exe(GetHostProcessName(true));
    if (!exe.empty()) {
      std::wstring::const_iterator begin = exe.begin();
      std::wstring::const_iterator end = exe.end();
      if (LowerCaseEqualsASCII(begin, end, kIEImageName)) {
        browser_type = BROWSER_IE;
      } else if (LowerCaseEqualsASCII(begin, end, kFirefoxImageName)) {
        browser_type = BROWSER_FIREFOX;
      } else if (LowerCaseEqualsASCII(begin, end, kOperaImageName)) {
        browser_type = BROWSER_OPERA;
      } else {
        browser_type = BROWSER_UNKNOWN;
      }
    } else {
      NOTREACHED();
    }
  }

  return browser_type;
}

IEVersion GetIEVersion() {
  static IEVersion ie_version = IE_INVALID;

  if (ie_version == IE_INVALID) {
    wchar_t exe_path[MAX_PATH];
    HMODULE mod = GetModuleHandle(NULL);
    GetModuleFileName(mod, exe_path, arraysize(exe_path) - 1);
    std::wstring exe_name(file_util::GetFilenameFromPath(exe_path));
    if (!LowerCaseEqualsASCII(exe_name, kIEImageName)) {
      ie_version = NON_IE;
    } else {
      uint32 high = 0;
      uint32 low  = 0;
      if (GetModuleVersion(mod, &high, &low)) {
        switch (HIWORD(high)) {
          case 6:
            ie_version = IE_6;
            break;
          case 7:
            ie_version = IE_7;
            break;
          default:
            ie_version = HIWORD(high) >= 8 ? IE_8 : IE_UNSUPPORTED;
            break;
        }
      } else {
        NOTREACHED() << "Can't get IE version";
      }
    }
  }

  return ie_version;
}

bool IsIEInPrivate() {
  typedef BOOL (WINAPI* IEIsInPrivateBrowsingPtr)();
  bool incognito_mode = false;
  HMODULE h = GetModuleHandle(L"ieframe.dll");
  if (h) {
    IEIsInPrivateBrowsingPtr IsInPrivate =
        reinterpret_cast<IEIsInPrivateBrowsingPtr>(GetProcAddress(h,
        "IEIsInPrivateBrowsing"));
    if (IsInPrivate) {
      incognito_mode = !!IsInPrivate();
    }
  }

  return incognito_mode;
}

bool GetModuleVersion(HMODULE module, uint32* high, uint32* low) {
  DCHECK(module != NULL)
      << "Please use GetModuleHandle(NULL) to get the process name";
  DCHECK(high);

  bool ok = false;

  HRSRC res = FindResource(module,
      reinterpret_cast<const wchar_t*>(VS_VERSION_INFO), RT_VERSION);
  if (res) {
    HGLOBAL res_data = LoadResource(module, res);
    DWORD version_resource_size = SizeofResource(module, res);
    const void* readonly_resource_data = LockResource(res_data);
    if (readonly_resource_data && version_resource_size) {
      // Copy data as VerQueryValue tries to modify the data. This causes
      // exceptions and heap corruption errors if debugger is attached.
      scoped_ptr<char> data(new char[version_resource_size]);
      memcpy(data.get(), readonly_resource_data, version_resource_size);
      if (data.get()) {
        VS_FIXEDFILEINFO* ver_info = NULL;
        UINT info_size = 0;
        if (VerQueryValue(data.get(), L"\\",
                          reinterpret_cast<void**>(&ver_info), &info_size)) {
          *high = ver_info->dwFileVersionMS;
          if (low != NULL)
            *low = ver_info->dwFileVersionLS;
          ok = true;
        }

        UnlockResource(res_data);
      }
      FreeResource(res_data);
    }
  }

  return ok;
}

namespace {

const int kMaxSubmenuDepth = 10;

// Copies original_menu and returns the copy. The caller is responsible for
// closing the returned HMENU. This does not currently copy over bitmaps
// (e.g. hbmpChecked, hbmpUnchecked or hbmpItem), so checkmarks, radio buttons,
// and custom icons won't work.
// It also copies over submenus up to a maximum depth of kMaxSubMenuDepth.
//
// TODO(robertshield): Add support for the bitmap fields if need be.
HMENU UtilCloneContextMenuImpl(HMENU original_menu, int depth) {
  DCHECK(IsMenu(original_menu));

  if (depth >= kMaxSubmenuDepth)
    return NULL;

  HMENU new_menu = CreatePopupMenu();
  int item_count = GetMenuItemCount(original_menu);
  if (item_count <= 0) {
    NOTREACHED();
  } else {
    for (int i = 0; i < item_count; i++) {
      MENUITEMINFO item_info = { 0 };
      item_info.cbSize = sizeof(MENUITEMINFO);
      item_info.fMask = MIIM_ID | MIIM_STRING | MIIM_FTYPE |
                        MIIM_STATE | MIIM_DATA | MIIM_SUBMENU |
                        MIIM_CHECKMARKS | MIIM_BITMAP;

      // Call GetMenuItemInfo a first time to obtain the buffer size for
      // the label.
      if (GetMenuItemInfo(original_menu, i, TRUE, &item_info)) {
        item_info.cch++;  // Increment this as per MSDN
        std::vector<wchar_t> buffer(item_info.cch, 0);
        item_info.dwTypeData = &buffer[0];

        // Call GetMenuItemInfo a second time with dwTypeData set to a buffer
        // of a correct size to get the label.
        GetMenuItemInfo(original_menu, i, TRUE, &item_info);

        // Clone any submenus. Within reason.
        if (item_info.hSubMenu) {
          HMENU new_submenu = UtilCloneContextMenuImpl(item_info.hSubMenu,
                                                       depth + 1);
          item_info.hSubMenu = new_submenu;
        }

        // Now insert the item into the new menu.
        InsertMenuItem(new_menu, i, TRUE, &item_info);
      }
    }
  }
  return new_menu;
}

}  // namespace

HMENU UtilCloneContextMenu(HMENU original_menu) {
  return UtilCloneContextMenuImpl(original_menu, 0);
}

std::string ResolveURL(const std::string& document,
                       const std::string& relative) {
  if (document.empty()) {
    return GURL(relative).spec();
  } else {
    return GURL(document).Resolve(relative).spec();
  }
}

bool HaveSameOrigin(const std::string& url1, const std::string& url2) {
  GURL a(url1), b(url2);
  bool ret;
  if (a.is_valid() != b.is_valid()) {
    // Either (but not both) url is invalid, so they can't match.
    ret = false;
  } else if (!a.is_valid()) {
    // Both URLs are invalid (see first check).  Just check if the opaque
    // strings match exactly.
    ret = url1.compare(url2) == 0;
  } else if (a.GetOrigin() != b.GetOrigin()) {
    // The origins don't match.
    ret = false;
  } else {
    // we have a match.
    ret = true;
  }

  return ret;
}

int GetConfigInt(int default_value, const wchar_t* value_name) {
  int ret = default_value;
  RegKey config_key;
  if (config_key.Open(HKEY_CURRENT_USER, kChromeFrameConfigKey,
                      KEY_QUERY_VALUE)) {
    int value = FALSE;
    if (config_key.ReadValueDW(value_name, reinterpret_cast<DWORD*>(&value))) {
      ret = value;
    }
  }

  return ret;
}

bool GetConfigBool(bool default_value, const wchar_t* value_name) {
  DWORD value = GetConfigInt(default_value, value_name);
  return (value != FALSE);
}

bool IsOptInUrl(const wchar_t* url) {
  RegKey config_key;
  if (!config_key.Open(HKEY_CURRENT_USER, kChromeFrameConfigKey, KEY_READ))
    return false;

  RegistryValueIterator optin_urls_list(config_key.Handle(),
                                        kChromeFrameOptinUrlsKey);
  while (optin_urls_list.Valid()) {
    if (MatchPattern(url, optin_urls_list.Name()))
      return true;
    ++optin_urls_list;
  }

  return false;
}

HRESULT GetUrlFromMoniker(IMoniker* moniker, IBindCtx* bind_context,
                          std::wstring* url) {
  if (!moniker || !url) {
    NOTREACHED();
    return E_INVALIDARG;
  }

  ScopedComPtr<IBindCtx> temp_bind_context;
  if (!bind_context) {
    CreateBindCtx(0, temp_bind_context.Receive());
    bind_context = temp_bind_context;
  }

  CComHeapPtr<WCHAR> display_name;
  HRESULT hr = moniker->GetDisplayName(bind_context, NULL, &display_name);
  if (display_name)
    *url = display_name;

  return hr;
}

bool IsValidUrlScheme(const std::wstring& url, bool is_privileged) {
  if (url.empty())
    return false;

  GURL crack_url(url);

  if (crack_url.SchemeIs("http") || crack_url.SchemeIs("https") ||
      crack_url.SchemeIs("about") || crack_url.SchemeIs("view-source"))
    return true;

  if (is_privileged && crack_url.SchemeIs("chrome-extension"))
    return true;

  if (StartsWith(url, kChromeAttachExternalTabPrefix, false))
    return true;

  return false;
}

// TODO(robertshield): Register and use Chrome's PathProviders.
// - Note that this function is used by unit tests as well to override
//   PathService paths, so please test when addressing todo.
bool GetUserProfileBaseDirectory(std::wstring* path) {
  DCHECK(path);
  wchar_t path_buffer[MAX_PATH * 4];
  path_buffer[0] = 0;
  // TODO(robertshield): Ideally we should use SHGetFolderLocation and then
  // get a path via PIDL.
  HRESULT hr = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL,
                               SHGFP_TYPE_CURRENT, path_buffer);

  if (SUCCEEDED(hr)) {
    *path = path_buffer;
#if defined(GOOGLE_CHROME_BUILD)
    file_util::AppendToPath(path, FILE_PATH_LITERAL("Google"));
#endif
    file_util::AppendToPath(path, chrome::kBrowserAppName);
    file_util::AppendToPath(path, chrome::kUserDataDirname);
    return true;
  }

  return false;
}