1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/platform_util.h"
6
7#include <commdlg.h>
8#include <dwmapi.h>
9#include <shellapi.h>
10#include <shlobj.h>
11
12#include "base/bind.h"
13#include "base/bind_helpers.h"
14#include "base/files/file_path.h"
15#include "base/logging.h"
16#include "base/strings/string_util.h"
17#include "base/strings/utf_string_conversions.h"
18#include "base/win/registry.h"
19#include "base/win/scoped_co_mem.h"
20#include "base/win/scoped_comptr.h"
21#include "base/win/windows_version.h"
22#include "chrome/browser/lifetime/application_lifetime.h"
23#include "chrome/browser/ui/host_desktop.h"
24#include "content/public/browser/browser_thread.h"
25#include "ui/base/win/shell.h"
26#include "ui/gfx/native_widget_types.h"
27#include "url/gurl.h"
28
29using content::BrowserThread;
30
31namespace {
32
33void ShowItemInFolderOnFileThread(const base::FilePath& full_path) {
34  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
35  base::FilePath dir = full_path.DirName().AsEndingWithSeparator();
36  // ParseDisplayName will fail if the directory is "C:", it must be "C:\\".
37  if (dir.empty())
38    return;
39
40  typedef HRESULT (WINAPI *SHOpenFolderAndSelectItemsFuncPtr)(
41      PCIDLIST_ABSOLUTE pidl_Folder,
42      UINT cidl,
43      PCUITEMID_CHILD_ARRAY pidls,
44      DWORD flags);
45
46  static SHOpenFolderAndSelectItemsFuncPtr open_folder_and_select_itemsPtr =
47    NULL;
48  static bool initialize_open_folder_proc = true;
49  if (initialize_open_folder_proc) {
50    initialize_open_folder_proc = false;
51    // The SHOpenFolderAndSelectItems API is exposed by shell32 version 6
52    // and does not exist in Win2K. We attempt to retrieve this function export
53    // from shell32 and if it does not exist, we just invoke ShellExecute to
54    // open the folder thus losing the functionality to select the item in
55    // the process.
56    HMODULE shell32_base = GetModuleHandle(L"shell32.dll");
57    if (!shell32_base) {
58      NOTREACHED() << " " << __FUNCTION__ << "(): Can't open shell32.dll";
59      return;
60    }
61    open_folder_and_select_itemsPtr =
62        reinterpret_cast<SHOpenFolderAndSelectItemsFuncPtr>
63            (GetProcAddress(shell32_base, "SHOpenFolderAndSelectItems"));
64  }
65  if (!open_folder_and_select_itemsPtr) {
66    ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW);
67    return;
68  }
69
70  base::win::ScopedComPtr<IShellFolder> desktop;
71  HRESULT hr = SHGetDesktopFolder(desktop.Receive());
72  if (FAILED(hr))
73    return;
74
75  base::win::ScopedCoMem<ITEMIDLIST> dir_item;
76  hr = desktop->ParseDisplayName(NULL, NULL,
77                                 const_cast<wchar_t *>(dir.value().c_str()),
78                                 NULL, &dir_item, NULL);
79  if (FAILED(hr))
80    return;
81
82  base::win::ScopedCoMem<ITEMIDLIST> file_item;
83  hr = desktop->ParseDisplayName(NULL, NULL,
84      const_cast<wchar_t *>(full_path.value().c_str()),
85      NULL, &file_item, NULL);
86  if (FAILED(hr))
87    return;
88
89  const ITEMIDLIST* highlight[] = { file_item };
90
91  hr = (*open_folder_and_select_itemsPtr)(dir_item, arraysize(highlight),
92                                          highlight, NULL);
93
94  if (FAILED(hr)) {
95    // On some systems, the above call mysteriously fails with "file not
96    // found" even though the file is there.  In these cases, ShellExecute()
97    // seems to work as a fallback (although it won't select the file).
98    if (hr == ERROR_FILE_NOT_FOUND) {
99      ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW);
100    } else {
101      LPTSTR message = NULL;
102      DWORD message_length = FormatMessage(
103          FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
104          0, hr, 0, reinterpret_cast<LPTSTR>(&message), 0, NULL);
105      LOG(WARNING) << " " << __FUNCTION__
106                   << "(): Can't open full_path = \""
107                   << full_path.value() << "\""
108                   << " hr = " << hr
109                   << " " << reinterpret_cast<LPTSTR>(&message);
110      if (message)
111        LocalFree(message);
112    }
113  }
114}
115
116// Old ShellExecute crashes the process when the command for a given scheme
117// is empty. This function tells if it is.
118bool ValidateShellCommandForScheme(const std::string& scheme) {
119  base::win::RegKey key;
120  std::wstring registry_path = ASCIIToWide(scheme) +
121                               L"\\shell\\open\\command";
122  key.Open(HKEY_CLASSES_ROOT, registry_path.c_str(), KEY_READ);
123  if (!key.Valid())
124    return false;
125  DWORD size = 0;
126  key.ReadValue(NULL, NULL, &size, NULL);
127  if (size <= 2)
128    return false;
129  return true;
130}
131
132void OpenExternalOnFileThread(const GURL& url) {
133  // Quote the input scheme to be sure that the command does not have
134  // parameters unexpected by the external program. This url should already
135  // have been escaped.
136  std::string escaped_url = url.spec();
137  escaped_url.insert(0, "\"");
138  escaped_url += "\"";
139
140  // According to Mozilla in uriloader/exthandler/win/nsOSHelperAppService.cpp:
141  // "Some versions of windows (Win2k before SP3, Win XP before SP1) crash in
142  // ShellExecute on long URLs (bug 161357 on bugzilla.mozilla.org). IE 5 and 6
143  // support URLS of 2083 chars in length, 2K is safe."
144  const size_t kMaxUrlLength = 2048;
145  if (escaped_url.length() > kMaxUrlLength) {
146    NOTREACHED();
147    return;
148  }
149
150  if (base::win::GetVersion() < base::win::VERSION_WIN7) {
151    if (!ValidateShellCommandForScheme(url.scheme()))
152      return;
153  }
154
155  if (reinterpret_cast<ULONG_PTR>(ShellExecuteA(NULL, "open",
156                                                escaped_url.c_str(), NULL, NULL,
157                                                SW_SHOWNORMAL)) <= 32) {
158    // We fail to execute the call. We could display a message to the user.
159    // TODO(nsylvain): we should also add a dialog to warn on errors. See
160    // bug 1136923.
161    return;
162  }
163}
164
165}  // namespace
166
167namespace platform_util {
168
169void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
170  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171
172  if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
173    chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING);
174
175  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
176      base::Bind(&ShowItemInFolderOnFileThread, full_path));
177}
178
179void OpenItem(Profile* profile, const base::FilePath& full_path) {
180  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
181
182  if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
183    chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING);
184
185  BrowserThread::PostTask(
186      BrowserThread::FILE, FROM_HERE,
187      base::Bind(base::IgnoreResult(&ui::win::OpenItemViaShell), full_path));
188}
189
190void OpenExternal(Profile* profile, const GURL& url) {
191  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192
193  if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH &&
194      !url.SchemeIsHTTPOrHTTPS())
195    chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING);
196
197  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
198                          base::Bind(&OpenExternalOnFileThread, url));
199}
200
201#if !defined(USE_AURA)
202gfx::NativeWindow GetTopLevel(gfx::NativeView view) {
203  return ::GetAncestor(view, GA_ROOT);
204}
205
206gfx::NativeView GetParent(gfx::NativeView view) {
207  return ::GetParent(view);
208}
209
210bool IsWindowActive(gfx::NativeWindow window) {
211  return ::GetForegroundWindow() == window;
212}
213
214void ActivateWindow(gfx::NativeWindow window) {
215  ::SetForegroundWindow(window);
216}
217
218bool IsVisible(gfx::NativeView view) {
219  // MSVC complains if we don't include != 0.
220  return ::IsWindowVisible(view) != 0;
221}
222#endif
223
224}  // namespace platform_util
225