diff --git a/common/platform_util_win.cc b/common/platform_util_win.cc new file mode 100644 index 000000000000..ffdd340c3c96 --- /dev/null +++ b/common/platform_util_win.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/platform_util.h" + +#include +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/win/registry.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" +#include "googleurl/src/gurl.h" +#include "ui/base/win/shell.h" + +namespace { + +// Old ShellExecute crashes the process when the command for a given scheme +// is empty. This function tells if it is. +bool ValidateShellCommandForScheme(const std::string& scheme) { + base::win::RegKey key; + std::wstring registry_path = ASCIIToWide(scheme) + + L"\\shell\\open\\command"; + key.Open(HKEY_CLASSES_ROOT, registry_path.c_str(), KEY_READ); + if (!key.Valid()) + return false; + DWORD size = 0; + key.ReadValue(NULL, NULL, &size, NULL); + if (size <= 2) + return false; + return true; +} + +} // namespace + +namespace platform_util { + +void ShowItemInFolder(const base::FilePath& full_path) { + base::FilePath dir = full_path.DirName().AsEndingWithSeparator(); + // ParseDisplayName will fail if the directory is "C:", it must be "C:\\". + if (dir.empty()) + return; + + typedef HRESULT (WINAPI *SHOpenFolderAndSelectItemsFuncPtr)( + PCIDLIST_ABSOLUTE pidl_Folder, + UINT cidl, + PCUITEMID_CHILD_ARRAY pidls, + DWORD flags); + + static SHOpenFolderAndSelectItemsFuncPtr open_folder_and_select_itemsPtr = + NULL; + static bool initialize_open_folder_proc = true; + if (initialize_open_folder_proc) { + initialize_open_folder_proc = false; + // The SHOpenFolderAndSelectItems API is exposed by shell32 version 6 + // and does not exist in Win2K. We attempt to retrieve this function export + // from shell32 and if it does not exist, we just invoke ShellExecute to + // open the folder thus losing the functionality to select the item in + // the process. + HMODULE shell32_base = GetModuleHandle(L"shell32.dll"); + if (!shell32_base) { + NOTREACHED() << " " << __FUNCTION__ << "(): Can't open shell32.dll"; + return; + } + open_folder_and_select_itemsPtr = + reinterpret_cast + (GetProcAddress(shell32_base, "SHOpenFolderAndSelectItems")); + } + if (!open_folder_and_select_itemsPtr) { + ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW); + return; + } + + base::win::ScopedComPtr desktop; + HRESULT hr = SHGetDesktopFolder(desktop.Receive()); + if (FAILED(hr)) + return; + + base::win::ScopedCoMem dir_item; + hr = desktop->ParseDisplayName(NULL, NULL, + const_cast(dir.value().c_str()), + NULL, &dir_item, NULL); + if (FAILED(hr)) + return; + + base::win::ScopedCoMem file_item; + hr = desktop->ParseDisplayName(NULL, NULL, + const_cast(full_path.value().c_str()), + NULL, &file_item, NULL); + if (FAILED(hr)) + return; + + const ITEMIDLIST* highlight[] = { file_item }; + + hr = (*open_folder_and_select_itemsPtr)(dir_item, arraysize(highlight), + highlight, NULL); + + if (FAILED(hr)) { + // On some systems, the above call mysteriously fails with "file not + // found" even though the file is there. In these cases, ShellExecute() + // seems to work as a fallback (although it won't select the file). + if (hr == ERROR_FILE_NOT_FOUND) { + ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW); + } else { + LPTSTR message = NULL; + DWORD message_length = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + 0, hr, 0, reinterpret_cast(&message), 0, NULL); + LOG(WARNING) << " " << __FUNCTION__ + << "(): Can't open full_path = \"" + << full_path.value() << "\"" + << " hr = " << hr + << " " << reinterpret_cast(&message); + if (message) + LocalFree(message); + } + } +} + +void OpenItem(const base::FilePath& full_path) { + ui::win::OpenItemViaShell(full_path); +} + +void OpenExternal(const GURL& url) { + // Quote the input scheme to be sure that the command does not have + // parameters unexpected by the external program. This url should already + // have been escaped. + std::string escaped_url = url.spec(); + escaped_url.insert(0, "\""); + escaped_url += "\""; + + // According to Mozilla in uriloader/exthandler/win/nsOSHelperAppService.cpp: + // "Some versions of windows (Win2k before SP3, Win XP before SP1) crash in + // ShellExecute on long URLs (bug 161357 on bugzilla.mozilla.org). IE 5 and 6 + // support URLS of 2083 chars in length, 2K is safe." + const size_t kMaxUrlLength = 2048; + if (escaped_url.length() > kMaxUrlLength) { + NOTREACHED(); + return; + } + + if (base::win::GetVersion() < base::win::VERSION_WIN7) { + if (!ValidateShellCommandForScheme(url.scheme())) + return; + } + + if (reinterpret_cast(ShellExecuteA(NULL, "open", + escaped_url.c_str(), NULL, NULL, + SW_SHOWNORMAL)) <= 32) { + // We fail to execute the call. We could display a message to the user. + // TODO(nsylvain): we should also add a dialog to warn on errors. See + // bug 1136923. + return; + } +} + +} // namespace platform_util