diff options
author | gab@chromium.org <gab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-12 07:14:55 +0000 |
---|---|---|
committer | gab@chromium.org <gab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-12 07:14:55 +0000 |
commit | f1024e222989f0ad1895d46bf407bac95c110b6c (patch) | |
tree | a8154c03b7ac6e04a5e2239698e5d8629f5d725a /base | |
parent | 69ebc68fc96a84197c8c93d2adb65e680cb2c988 (diff) | |
download | chromium_src-f1024e222989f0ad1895d46bf407bac95c110b6c.zip chromium_src-f1024e222989f0ad1895d46bf407bac95c110b6c.tar.gz chromium_src-f1024e222989f0ad1895d46bf407bac95c110b6c.tar.bz2 |
Fix and re-commit http://codereview.chromium.org/10914109/ (after revert in http://crrev.com/155918) -- Refactoring and tests for the highly undertested file_util::CreateOrUpdateShortcutLink() method.
Simplify file_util::CreateOrUpdateShortcutLink()'s interface (use a struct to set parameters passed which allows callers to specify exactly what they want without having to pass in a bunch of NULLs for the unused parameters).
The same concept will be used for ShellUtil's shortcut functions in an upcoming CL.
Moved ShellUtil::VerifyChromeShortcut() to file_util::VerifyShortcut() and augmented it for every shortcut properties. This will also allow other shortcut creators (web apps, profiles, etc.) to have a broader test coverage on the shortcut they create (i.e. more testable properties available).
I will leave it up to the owners of these various projects to augment their tests, this CL keeps the previously tested behavior, not more, not less.
This is the 1st CL of a massive refactoring effort for shortcuts (http://goo.gl/Az889) in which ShellUtil's shortcut methods have to be refactored (http://codereview.chromium.org/10836247/ : soon to incorporate interface changes from this CL) which led me even lower to first refactor file_util's shortcut methods.
TBR=robertshield@chromium.org, sky@chromium.org, agl@chromium.org, dgrogan@chromium.org
BUG=132825, 148539
TEST=base_unittests --gtest_filter=FileUtilShortcutTest*
installer_util_unitests --gtest_filter=ShellUtilTestWithDirAndDist*
unit_tests --gtest_filter=ProfileShortcutManagerTest*
(run tests on XP as well)
Review URL: https://chromiumcodereview.appspot.com/10909171
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@156250 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gyp | 14 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/file_util.h | 51 | ||||
-rw-r--r-- | base/file_util_unittest.cc | 69 | ||||
-rw-r--r-- | base/file_util_win.cc | 155 | ||||
-rw-r--r-- | base/test/test_shortcut_win.cc | 142 | ||||
-rw-r--r-- | base/test/test_shortcut_win.h | 38 | ||||
-rw-r--r-- | base/win/shortcut.cc | 180 | ||||
-rw-r--r-- | base/win/shortcut.h | 142 | ||||
-rw-r--r-- | base/win/shortcut_unittest.cc | 221 | ||||
-rw-r--r-- | base/win/win_util.cc | 10 | ||||
-rw-r--r-- | base/win/win_util.h | 7 |
12 files changed, 750 insertions, 281 deletions
diff --git a/base/base.gyp b/base/base.gyp index 6384ac9..1aa6269 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -523,6 +523,7 @@ 'win/scoped_bstr_unittest.cc', 'win/scoped_comptr_unittest.cc', 'win/scoped_process_information_unittest.cc', + 'win/shortcut_unittest.cc', 'win/startup_information_unittest.cc', 'win/scoped_variant_unittest.cc', 'win/win_util_unittest.cc', @@ -688,6 +689,17 @@ 'test/test_file_util_linux.cc', ], }], + ['OS=="win"', { + 'direct_dependent_settings': { + 'msvs_settings': { + 'VCLinkerTool': { + 'DelayLoadDLLs': [ + 'propsys.dll', + ], + }, + }, + }, + }], ], 'sources': [ 'perftimer.cc', @@ -720,6 +732,8 @@ 'test/test_listener_ios.mm', 'test/test_reg_util_win.cc', 'test/test_reg_util_win.h', + 'test/test_shortcut_win.cc', + 'test/test_shortcut_win.h', 'test/test_suite.cc', 'test/test_suite.h', 'test/test_support_android.cc', diff --git a/base/base.gypi b/base/base.gypi index 84ba038..0652249 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -505,6 +505,8 @@ 'win/scoped_process_information.cc', 'win/scoped_process_information.h', 'win/scoped_select_object.h', + 'win/shortcut.cc', + 'win/shortcut.h', 'win/startup_information.cc', 'win/startup_information.h', 'win/scoped_variant.cc', diff --git a/base/file_util.h b/base/file_util.h index 8dec13c..ef67b4d 100644 --- a/base/file_util.h +++ b/base/file_util.h @@ -226,57 +226,6 @@ BASE_EXPORT bool SetPosixFilePermissions(const FilePath& path, #endif // defined(OS_POSIX) #if defined(OS_WIN) -enum ShortcutOptions { - SHORTCUT_NO_OPTIONS = 0, - // Set DualMode property for Windows 8 Metro-enabled shortcuts. - SHORTCUT_DUAL_MODE = 1 << 0, - // Create a new shortcut (overwriting if necessary). - SHORTCUT_CREATE_ALWAYS = 1 << 1, -}; - -// Resolve Windows shortcut (.LNK file) -// This methods tries to resolve a shortcut .LNK file. The path of the shortcut -// to resolve is in |shortcut_path|. If |target_path| is not NULL, the target -// will be resolved and placed in |target_path|. If |args| is not NULL, the -// arguments will be retrieved and placed in |args|. The function returns true -// if all requested fields are are found successfully. -// Callers can safely use the same variable for both |shortcut_path| and -// |target_path|. -BASE_EXPORT bool ResolveShortcut(const FilePath& shortcut_path, - FilePath* target_path, - string16* args); - -// Creates (or updates) a Windows shortcut (.LNK file) -// This method creates (or updates) a shortcut link using the information given. -// Ensure you have initialized COM before calling into this function. -// |destination| is required. |source| is required when SHORTCUT_CREATE_ALWAYS -// is specified in |options|. All other parameters are optional and may be NULL. -// |source| is the existing file, |destination| is the new link file to be -// created; for best results pass the filename with the .lnk extension. -// The |icon| can specify a dll or exe in which case the icon index is the -// resource id. |app_id| is the app model id for the shortcut on Win7. -// |options|: bitfield for which the options come from ShortcutOptions. -// If SHORTCUT_CREATE_ALWAYS is not set in |options|, only specified (non-null) -// properties on an existing shortcut will be modified. If the shortcut does not -// exist, this method is a no-op and returns false. -BASE_EXPORT bool CreateOrUpdateShortcutLink(const wchar_t *source, - const wchar_t *destination, - const wchar_t *working_dir, - const wchar_t *arguments, - const wchar_t *description, - const wchar_t *icon, - int icon_index, - const wchar_t* app_id, - uint32 options); - -// Pins a shortcut to the Windows 7 taskbar. The shortcut file must already -// exist and be a shortcut that points to an executable. -BASE_EXPORT bool TaskbarPinShortcutLink(const wchar_t* shortcut); - -// Unpins a shortcut from the Windows 7 taskbar. The shortcut must exist and -// already be pinned to the taskbar. -BASE_EXPORT bool TaskbarUnpinShortcutLink(const wchar_t* shortcut); - // Copy from_path to to_path recursively and then delete from_path recursively. // Returns true if all operations succeed. // This function simulates Move(), but unlike Move() it works across volumes. diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc index 5a99983..04de367 100644 --- a/base/file_util_unittest.cc +++ b/base/file_util_unittest.cc @@ -1592,75 +1592,6 @@ TEST_F(ReadOnlyFileUtilTest, TextContentsEqual) { // We don't need equivalent functionality outside of Windows. #if defined(OS_WIN) -TEST_F(FileUtilTest, ResolveShortcutTest) { - FilePath target_file = temp_dir_.path().Append(L"Target.txt"); - CreateTextFile(target_file, L"This is the target."); - - FilePath link_file = temp_dir_.path().Append(L"Link.lnk"); - - HRESULT result; - IShellLink* shell = NULL; - IPersistFile* persist = NULL; - - CoInitialize(NULL); - // Temporarily create a shortcut for test - result = CoCreateInstance(CLSID_ShellLink, NULL, - CLSCTX_INPROC_SERVER, IID_IShellLink, - reinterpret_cast<LPVOID*>(&shell)); - EXPECT_TRUE(SUCCEEDED(result)); - result = shell->QueryInterface(IID_IPersistFile, - reinterpret_cast<LPVOID*>(&persist)); - EXPECT_TRUE(SUCCEEDED(result)); - result = shell->SetPath(target_file.value().c_str()); - EXPECT_TRUE(SUCCEEDED(result)); - result = shell->SetDescription(L"ResolveShortcutTest"); - EXPECT_TRUE(SUCCEEDED(result)); - result = shell->SetArguments(L"--args"); - EXPECT_TRUE(SUCCEEDED(result)); - result = persist->Save(link_file.value().c_str(), TRUE); - EXPECT_TRUE(SUCCEEDED(result)); - if (persist) - persist->Release(); - if (shell) - shell->Release(); - - bool is_solved; - std::wstring args; - is_solved = file_util::ResolveShortcut(link_file, &link_file, &args); - EXPECT_TRUE(is_solved); - EXPECT_EQ(L"--args", args); - std::wstring contents; - contents = ReadTextFile(link_file); - EXPECT_EQ(L"This is the target.", contents); - - // Cleaning - DeleteFile(target_file.value().c_str()); - DeleteFile(link_file.value().c_str()); - CoUninitialize(); -} - -TEST_F(FileUtilTest, CreateShortcutTest) { - const wchar_t* file_contents = L"This is another target."; - FilePath target_file = temp_dir_.path().Append(L"Target1.txt"); - CreateTextFile(target_file, file_contents); - - FilePath link_file = temp_dir_.path().Append(L"Link1.lnk"); - - CoInitialize(NULL); - EXPECT_TRUE(file_util::CreateOrUpdateShortcutLink( - target_file.value().c_str(), link_file.value().c_str(), NULL, - NULL, NULL, NULL, 0, NULL, - file_util::SHORTCUT_CREATE_ALWAYS)); - FilePath resolved_name; - EXPECT_TRUE(file_util::ResolveShortcut(link_file, &resolved_name, NULL)); - std::wstring read_contents = ReadTextFile(resolved_name); - EXPECT_EQ(file_contents, read_contents); - - DeleteFile(target_file.value().c_str()); - DeleteFile(link_file.value().c_str()); - CoUninitialize(); -} - TEST_F(FileUtilTest, CopyAndDeleteDirectoryTest) { // Create a directory FilePath dir_name_from = diff --git a/base/file_util_win.cc b/base/file_util_win.cc index dfc319c..332414a 100644 --- a/base/file_util_win.cc +++ b/base/file_util_win.cc @@ -5,7 +5,6 @@ #include "base/file_util.h" #include <windows.h> -#include <propvarutil.h> #include <psapi.h> #include <shellapi.h> #include <shlobj.h> @@ -23,10 +22,7 @@ #include "base/threading/thread_restrictions.h" #include "base/time.h" #include "base/utf_string_conversions.h" -#include "base/win/pe_image.h" -#include "base/win/scoped_comptr.h" #include "base/win/scoped_handle.h" -#include "base/win/win_util.h" #include "base/win/windows_version.h" namespace file_util { @@ -332,157 +328,6 @@ bool GetFileCreationLocalTime(const std::wstring& filename, return GetFileCreationLocalTimeFromHandle(file_handle.Get(), creation_time); } -bool ResolveShortcut(const FilePath& shortcut_path, - FilePath* target_path, - string16* args) { - base::ThreadRestrictions::AssertIOAllowed(); - - HRESULT result; - base::win::ScopedComPtr<IShellLink> i_shell_link; - - // Get pointer to the IShellLink interface. - result = i_shell_link.CreateInstance(CLSID_ShellLink, NULL, - CLSCTX_INPROC_SERVER); - if (FAILED(result)) - return false; - - base::win::ScopedComPtr<IPersistFile> persist; - // Query IShellLink for the IPersistFile interface. - result = persist.QueryFrom(i_shell_link); - if (FAILED(result)) - return false; - - // Load the shell link. - result = persist->Load(shortcut_path.value().c_str(), STGM_READ); - if (FAILED(result)) - return false; - - WCHAR temp[MAX_PATH]; - if (target_path) { - // Try to find the target of a shortcut. - result = i_shell_link->Resolve(0, SLR_NO_UI); - if (FAILED(result)) - return false; - - result = i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY); - if (FAILED(result)) - return false; - - *target_path = FilePath(temp); - } - - if (args) { - result = i_shell_link->GetArguments(temp, MAX_PATH); - if (FAILED(result)) - return false; - - *args = string16(temp); - } - return true; -} - -bool CreateOrUpdateShortcutLink(const wchar_t *source, - const wchar_t *destination, - const wchar_t *working_dir, - const wchar_t *arguments, - const wchar_t *description, - const wchar_t *icon, - int icon_index, - const wchar_t* app_id, - uint32 options) { - base::ThreadRestrictions::AssertIOAllowed(); - - bool create = (options & SHORTCUT_CREATE_ALWAYS) != 0; - - // |source| is required when SHORTCUT_CREATE_ALWAYS is specified. - DCHECK(source || !create); - - // Length of arguments and description must be less than MAX_PATH. - DCHECK(lstrlen(arguments) < MAX_PATH); - DCHECK(lstrlen(description) < MAX_PATH); - - base::win::ScopedComPtr<IShellLink> i_shell_link; - base::win::ScopedComPtr<IPersistFile> i_persist_file; - - // Get pointer to the IShellLink interface. - if (FAILED(i_shell_link.CreateInstance(CLSID_ShellLink, NULL, - CLSCTX_INPROC_SERVER)) || - FAILED(i_persist_file.QueryFrom(i_shell_link))) { - return false; - } - - if (!create && FAILED(i_persist_file->Load(destination, STGM_READWRITE))) - return false; - - if ((source || create) && FAILED(i_shell_link->SetPath(source))) - return false; - - if (working_dir && FAILED(i_shell_link->SetWorkingDirectory(working_dir))) - return false; - - if (arguments && FAILED(i_shell_link->SetArguments(arguments))) - return false; - - if (description && FAILED(i_shell_link->SetDescription(description))) - return false; - - if (icon && FAILED(i_shell_link->SetIconLocation(icon, icon_index))) - return false; - - bool is_dual_mode = (options & SHORTCUT_DUAL_MODE) != 0; - if ((app_id || is_dual_mode) && - base::win::GetVersion() >= base::win::VERSION_WIN7) { - base::win::ScopedComPtr<IPropertyStore> property_store; - if (FAILED(property_store.QueryFrom(i_shell_link)) || !property_store.get()) - return false; - - if (app_id && !base::win::SetAppIdForPropertyStore(property_store, app_id)) - return false; - if (is_dual_mode && - !base::win::SetDualModeForPropertyStore(property_store)) { - return false; - } - } - - HRESULT result = i_persist_file->Save(destination, TRUE); - - // If we successfully updated the icon, notify the shell that we have done so. - if (!create && SUCCEEDED(result)) { - // Release the interfaces in case the SHChangeNotify call below depends on - // the operations above being fully completed. - i_persist_file.Release(); - i_shell_link.Release(); - - SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); - } - - return SUCCEEDED(result); -} - -bool TaskbarPinShortcutLink(const wchar_t* shortcut) { - base::ThreadRestrictions::AssertIOAllowed(); - - // "Pin to taskbar" is only supported after Win7. - if (base::win::GetVersion() < base::win::VERSION_WIN7) - return false; - - int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarpin", shortcut, - NULL, NULL, 0)); - return result > 32; -} - -bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { - base::ThreadRestrictions::AssertIOAllowed(); - - // "Unpin from taskbar" is only supported after Win7. - if (base::win::GetVersion() < base::win::VERSION_WIN7) - return false; - - int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin", - shortcut, NULL, NULL, 0)); - return result > 32; -} - bool GetTempDir(FilePath* path) { base::ThreadRestrictions::AssertIOAllowed(); diff --git a/base/test/test_shortcut_win.cc b/base/test/test_shortcut_win.cc new file mode 100644 index 0000000..b919e69 --- /dev/null +++ b/base/test/test_shortcut_win.cc @@ -0,0 +1,142 @@ +// 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 "base/test/test_shortcut_win.h" + +#include <windows.h> +#include <shlobj.h> +#include <propkey.h> +#include <propvarutil.h> + +#include "base/file_path.h" +#include "base/string16.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" + +// propsys.lib is required for PropvariantTo*(). +#pragma comment(lib, "propsys.lib") + +namespace base { +namespace win { + +namespace { + +// Returns true if |actual_path|'s LongPathName case-insensitively matches +// |expected_path|'s LongPathName. +bool PathsAreEqual(const FilePath& expected_path, const FilePath& actual_path) { + wchar_t long_expected_path_chars[MAX_PATH] = {0}; + wchar_t long_actual_path_chars[MAX_PATH] = {0}; + + if (::GetLongPathName( + expected_path.value().c_str(), long_expected_path_chars, + MAX_PATH) == 0 || + ::GetLongPathName( + actual_path.value().c_str(), long_actual_path_chars, + MAX_PATH) == 0) { + return false; + } + + FilePath long_expected_path(long_expected_path_chars); + FilePath long_actual_path(long_actual_path_chars); + if(long_expected_path.empty() || long_actual_path.empty()) + return false; + + return long_expected_path == long_actual_path; +} + +} // namespace + +VerifyShortcutStatus VerifyShortcut(const FilePath& shortcut_path, + const ShortcutProperties& properties) { + ScopedComPtr<IShellLink> i_shell_link; + ScopedComPtr<IPersistFile> i_persist_file; + + wchar_t read_target[MAX_PATH] = {0}; + wchar_t read_working_dir[MAX_PATH] = {0}; + wchar_t read_arguments[MAX_PATH] = {0}; + wchar_t read_description[MAX_PATH] = {0}; + wchar_t read_icon[MAX_PATH] = {0}; + int read_icon_index = 0; + + // Initialize the shell interfaces. + if (FAILED(i_shell_link.CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER)) || + FAILED(i_persist_file.QueryFrom(i_shell_link))) { + return VERIFY_SHORTCUT_FAILURE_UNEXPECTED; + } + + // Load the shortcut. + if (FAILED(i_persist_file->Load(shortcut_path.value().c_str(), 0))) + return VERIFY_SHORTCUT_FAILURE_FILE_NOT_FOUND; + + if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) && + (FAILED(i_shell_link->GetPath( + read_target, MAX_PATH, NULL, SLGP_SHORTPATH)) || + !PathsAreEqual(properties.target, FilePath(read_target)))) { + return VERIFY_SHORTCUT_FAILURE_TARGET; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) && + (FAILED(i_shell_link->GetWorkingDirectory(read_working_dir, MAX_PATH)) || + FilePath(read_working_dir) != properties.working_dir)) { + return VERIFY_SHORTCUT_FAILURE_WORKING_DIR; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) && + (FAILED(i_shell_link->GetArguments(read_arguments, MAX_PATH)) || + string16(read_arguments) != properties.arguments)) { + return VERIFY_SHORTCUT_FAILURE_ARGUMENTS; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) && + (FAILED(i_shell_link->GetDescription(read_description, MAX_PATH)) || + string16(read_description) != properties.description)) { + return VERIFY_SHORTCUT_FAILURE_DESCRIPTION; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_ICON) && + (FAILED(i_shell_link->GetIconLocation(read_icon, MAX_PATH, + &read_icon_index)) || + read_icon_index != properties.icon_index || + !PathsAreEqual(properties.icon, FilePath(read_icon)))) { + return VERIFY_SHORTCUT_FAILURE_ICON; + } + + if(GetVersion() >= VERSION_WIN7) { + ScopedComPtr<IPropertyStore> property_store; + // Note that, as mentioned on MSDN at http://goo.gl/M8h9g, if a property is + // not set, GetValue will return S_OK and the PROPVARIANT will be set to + // VT_EMPTY. + PROPVARIANT pv_app_id, pv_dual_mode; + if (FAILED(property_store.QueryFrom(i_shell_link)) || + property_store->GetValue(PKEY_AppUserModel_ID, &pv_app_id) != S_OK || + property_store->GetValue(PKEY_AppUserModel_IsDualMode, + &pv_dual_mode) != S_OK) { + return VERIFY_SHORTCUT_FAILURE_UNEXPECTED; + } + + // Note, as mentioned on MSDN at http://goo.gl/hZ3sO, if |pv_app_id| is a + // VT_EMPTY it is successfully converted to the empty string. + wchar_t read_app_id[MAX_PATH] = {0}; + PropVariantToString(pv_app_id, read_app_id, MAX_PATH); + if((properties.options & ShortcutProperties::PROPERTIES_APP_ID) && + string16(read_app_id) != properties.app_id) { + return VERIFY_SHORTCUT_FAILURE_APP_ID; + } + + // Note, as mentioned on MSDN at http://goo.gl/9mBHB, if |pv_dual_mode| is a + // VT_EMPTY it is successfully converted to false. + BOOL read_dual_mode; + PropVariantToBoolean(pv_dual_mode, &read_dual_mode); + if((properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) && + static_cast<bool>(read_dual_mode) != properties.dual_mode) { + return VERIFY_SHORTCUT_FAILURE_DUAL_MODE; + } + } + + return VERIFY_SHORTCUT_SUCCESS; +} + +} // namespace win +} // namespace base diff --git a/base/test/test_shortcut_win.h b/base/test/test_shortcut_win.h new file mode 100644 index 0000000..cb5b2c3 --- /dev/null +++ b/base/test/test_shortcut_win.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef BASE_TEST_TEST_SHORTCUT_WIN_H_ +#define BASE_TEST_TEST_SHORTCUT_WIN_H_ + +#include "base/file_path.h" +#include "base/win/shortcut.h" + +// Windows shortcut functions used only by tests. + +namespace base { +namespace win { + +enum VerifyShortcutStatus { + VERIFY_SHORTCUT_SUCCESS = 0, + VERIFY_SHORTCUT_FAILURE_UNEXPECTED, + VERIFY_SHORTCUT_FAILURE_FILE_NOT_FOUND, + VERIFY_SHORTCUT_FAILURE_TARGET, + VERIFY_SHORTCUT_FAILURE_WORKING_DIR, + VERIFY_SHORTCUT_FAILURE_ARGUMENTS, + VERIFY_SHORTCUT_FAILURE_DESCRIPTION, + VERIFY_SHORTCUT_FAILURE_ICON, + VERIFY_SHORTCUT_FAILURE_APP_ID, + VERIFY_SHORTCUT_FAILURE_DUAL_MODE, +}; + +// Verify that a shortcut exists at |shortcut_path| with the expected +// |properties|. +VerifyShortcutStatus VerifyShortcut(const FilePath& shortcut_path, + const ShortcutProperties& properties); + + +} // namespace win +} // namespace base + +#endif // BASE_TEST_TEST_SHORTCUT_WIN_H_ diff --git a/base/win/shortcut.cc b/base/win/shortcut.cc new file mode 100644 index 0000000..570a5bd --- /dev/null +++ b/base/win/shortcut.cc @@ -0,0 +1,180 @@ +// 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 "base/win/shortcut.h" + +#include <shellapi.h> +#include <shlobj.h> + +#include "base/threading/thread_restrictions.h" +#include "base/win/scoped_comptr.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { + +bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path, + const ShortcutProperties& properties, + ShortcutOperation operation) { + base::ThreadRestrictions::AssertIOAllowed(); + + // A target is required when |operation| is SHORTCUT_CREATE_ALWAYS. + if (operation == SHORTCUT_CREATE_ALWAYS && + !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) { + NOTREACHED(); + return false; + } + + ScopedComPtr<IShellLink> i_shell_link; + ScopedComPtr<IPersistFile> i_persist_file; + if (FAILED(i_shell_link.CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER)) || + FAILED(i_persist_file.QueryFrom(i_shell_link))) { + return false; + } + + if (operation == SHORTCUT_UPDATE_EXISTING && + FAILED(i_persist_file->Load(shortcut_path.value().c_str(), + STGM_READWRITE))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) && + FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) && + FAILED(i_shell_link->SetWorkingDirectory( + properties.working_dir.value().c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) && + FAILED(i_shell_link->SetArguments(properties.arguments.c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) && + FAILED(i_shell_link->SetDescription(properties.description.c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_ICON) && + FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(), + properties.icon_index))) { + return false; + } + + bool has_app_id = + (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0; + bool has_dual_mode = + (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0; + if ((has_app_id || has_dual_mode) && + GetVersion() >= VERSION_WIN7) { + ScopedComPtr<IPropertyStore> property_store; + if (FAILED(property_store.QueryFrom(i_shell_link)) || !property_store.get()) + return false; + + if (has_app_id && + !SetAppIdForPropertyStore(property_store, properties.app_id.c_str())) { + return false; + } + if (has_dual_mode && + !SetDualModeForPropertyStore(property_store, properties.dual_mode)) { + return false; + } + } + + HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE); + + // If we successfully updated the icon, notify the shell that we have done so. + if (operation == SHORTCUT_UPDATE_EXISTING && SUCCEEDED(result)) { + // Release the interfaces in case the SHChangeNotify call below depends on + // the operations above being fully completed. + i_persist_file.Release(); + i_shell_link.Release(); + + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + } + + return SUCCEEDED(result); +} + +bool ResolveShortcut(const FilePath& shortcut_path, + FilePath* target_path, + string16* args) { + base::ThreadRestrictions::AssertIOAllowed(); + + HRESULT result; + ScopedComPtr<IShellLink> i_shell_link; + + // Get pointer to the IShellLink interface. + result = i_shell_link.CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) + return false; + + ScopedComPtr<IPersistFile> persist; + // Query IShellLink for the IPersistFile interface. + result = persist.QueryFrom(i_shell_link); + if (FAILED(result)) + return false; + + // Load the shell link. + result = persist->Load(shortcut_path.value().c_str(), STGM_READ); + if (FAILED(result)) + return false; + + WCHAR temp[MAX_PATH]; + if (target_path) { + // Try to find the target of a shortcut. + result = i_shell_link->Resolve(0, SLR_NO_UI); + if (FAILED(result)) + return false; + + result = i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY); + if (FAILED(result)) + return false; + + *target_path = FilePath(temp); + } + + if (args) { + result = i_shell_link->GetArguments(temp, MAX_PATH); + if (FAILED(result)) + return false; + + *args = string16(temp); + } + return true; +} + +bool TaskbarPinShortcutLink(const wchar_t* shortcut) { + base::ThreadRestrictions::AssertIOAllowed(); + + // "Pin to taskbar" is only supported after Win7. + if (GetVersion() < VERSION_WIN7) + return false; + + int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarpin", shortcut, + NULL, NULL, 0)); + return result > 32; +} + +bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { + base::ThreadRestrictions::AssertIOAllowed(); + + // "Unpin from taskbar" is only supported after Win7. + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return false; + + int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin", + shortcut, NULL, NULL, 0)); + return result > 32; +} + +} // namespace win +} // namespace base diff --git a/base/win/shortcut.h b/base/win/shortcut.h new file mode 100644 index 0000000..7491069 --- /dev/null +++ b/base/win/shortcut.h @@ -0,0 +1,142 @@ +// 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. + +#ifndef BASE_WIN_SHORTCUT_H_ +#define BASE_WIN_SHORTCUT_H_ + +#include <windows.h> + +#include "base/logging.h" +#include "base/file_path.h" +#include "base/string16.h" + +namespace base { +namespace win { + +enum ShortcutOperation { + // Create a new shortcut (overwriting if necessary). + SHORTCUT_CREATE_ALWAYS = 0, + // Update specified (non-null) properties only on an existing shortcut. + SHORTCUT_UPDATE_EXISTING, +}; + +// Properties for shortcuts. Properties set will be applied to the shortcut on +// creation/update, others will be ignored. +// Callers are encouraged to use the setters provided which take care of +// setting |options| as desired. +struct ShortcutProperties { + enum IndividualProperties { + PROPERTIES_TARGET = 1 << 0, + PROPERTIES_WORKING_DIR = 1 << 1, + PROPERTIES_ARGUMENTS = 1 << 2, + PROPERTIES_DESCRIPTION = 1 << 3, + PROPERTIES_ICON = 1 << 4, + PROPERTIES_APP_ID = 1 << 5, + PROPERTIES_DUAL_MODE = 1 << 6, + }; + + ShortcutProperties() : options(0U) {} + + void set_target(const FilePath& target_in) { + target = target_in; + options |= PROPERTIES_TARGET; + } + + void set_working_dir(const FilePath& working_dir_in) { + working_dir = working_dir_in; + options |= PROPERTIES_WORKING_DIR; + } + + void set_arguments(const string16& arguments_in) { + // Size restriction as per MSDN at http://goo.gl/TJ7q5. + DCHECK(arguments_in.size() < MAX_PATH); + arguments = arguments_in; + options |= PROPERTIES_ARGUMENTS; + } + + void set_description(const string16& description_in) { + // Size restriction as per MSDN at http://goo.gl/OdNQq. + DCHECK(description_in.size() < MAX_PATH); + description = description_in; + options |= PROPERTIES_DESCRIPTION; + } + + void set_icon(const FilePath& icon_in, int icon_index_in) { + icon = icon_in; + icon_index = icon_index_in; + options |= PROPERTIES_ICON; + } + + void set_app_id(const string16& app_id_in) { + app_id = app_id_in; + options |= PROPERTIES_APP_ID; + } + + void set_dual_mode(bool dual_mode_in) { + dual_mode = dual_mode_in; + options |= PROPERTIES_DUAL_MODE; + } + + // The target to launch from this shortcut. This is mandatory when creating + // a shortcut. + FilePath target; + // The name of the working directory when launching the shortcut. + FilePath working_dir; + // The arguments to be applied to |target| when launching from this shortcut. + // The length of this string must be less than MAX_PATH. + string16 arguments; + // The localized description of the shortcut. + // The length of this string must be less than MAX_PATH. + string16 description; + // The path to the icon (can be a dll or exe, in which case |icon_index| is + // the resource id). + FilePath icon; + int icon_index; + // The app model id for the shortcut (Win7+). + string16 app_id; + // Whether this is a dual mode shortcut (Win8+). + // For now this property can only be set to true (i.e. once set it cannot be + // unset). + // TODO (gab): Make it possible to set this property to false. + bool dual_mode; + // Bitfield made of IndividualProperties. Properties set in |options| will be + // set on the shortcut, others will be ignored. + uint32 options; +}; + +// This method creates (or updates) a shortcut link at |shortcut_path| using the +// information given through |properties|. +// Ensure you have initialized COM before calling into this function. +// |operation|: a choice from the ShortcutOperation enum. +// If |operation| is SHORTCUT_UPDATE_EXISTING and |shortcut_path| does not +// exist, this method is a no-op and returns false. +BASE_EXPORT bool CreateOrUpdateShortcutLink( + const FilePath& shortcut_path, + const ShortcutProperties& properties, + ShortcutOperation operation); + +// Resolve Windows shortcut (.LNK file) +// This methods tries to resolve a shortcut .LNK file. The path of the shortcut +// to resolve is in |shortcut_path|. If |target_path| is not NULL, the target +// will be resolved and placed in |target_path|. If |args| is not NULL, the +// arguments will be retrieved and placed in |args|. The function returns true +// if all requested fields are found successfully. +// Callers can safely use the same variable for both |shortcut_path| and +// |target_path|. +BASE_EXPORT bool ResolveShortcut(const FilePath& shortcut_path, + FilePath* target_path, + string16* args); + +// Pins a shortcut to the Windows 7 taskbar. The shortcut file must already +// exist and be a shortcut that points to an executable. +BASE_EXPORT bool TaskbarPinShortcutLink(const wchar_t* shortcut); + +// Unpins a shortcut from the Windows 7 taskbar. The shortcut must exist and +// already be pinned to the taskbar. +BASE_EXPORT bool TaskbarUnpinShortcutLink(const wchar_t* shortcut); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SHORTCUT_H_ diff --git a/base/win/shortcut_unittest.cc b/base/win/shortcut_unittest.cc new file mode 100644 index 0000000..68d832f --- /dev/null +++ b/base/win/shortcut_unittest.cc @@ -0,0 +1,221 @@ +// 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 "base/win/shortcut.h" + +#include <windows.h> +#include <objbase.h> + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/scoped_temp_dir.h" +#include "base/test/test_file_util.h" +#include "base/test/test_shortcut_win.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +static const char kFileContents[] = "This is a target."; +static const char kFileContents2[] = "This is another target."; + +class ShortcutTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(temp_dir_2_.CreateUniqueTempDir()); + + EXPECT_EQ(S_OK, CoInitialize(NULL)); + + link_file_ = temp_dir_.path().Append(L"My Link.lnk"); + + // Shortcut 1's properties + { + const FilePath target_file(temp_dir_.path().Append(L"Target 1.txt")); + file_util::WriteFile(target_file, kFileContents, + arraysize(kFileContents)); + + link_properties_.set_target(target_file); + link_properties_.set_working_dir(temp_dir_.path()); + link_properties_.set_arguments(L"--magic --awesome"); + link_properties_.set_description(L"Chrome is awesome."); + link_properties_.set_icon(link_properties_.target, 4); + link_properties_.set_app_id(L"Chrome"); + link_properties_.set_dual_mode(false); + } + + // Shortcut 2's properties (all different from properties of shortcut 1). + { + const FilePath target_file_2(temp_dir_.path().Append(L"Target 2.txt")); + file_util::WriteFile(target_file_2, kFileContents2, + arraysize(kFileContents2)); + + FilePath icon_path_2; + file_util::CreateTemporaryFileInDir(temp_dir_.path(), &icon_path_2); + + link_properties_2_.set_target(target_file_2); + link_properties_2_.set_working_dir(temp_dir_2_.path()); + link_properties_2_.set_arguments(L"--super --crazy"); + link_properties_2_.set_description(L"The best in the west."); + link_properties_2_.set_icon(icon_path_2, 0); + link_properties_2_.set_app_id(L"Chrome.UserLevelCrazySuffix"); + link_properties_2_.set_dual_mode(true); + } + } + + virtual void TearDown() OVERRIDE { + CoUninitialize(); + } + + ScopedTempDir temp_dir_; + ScopedTempDir temp_dir_2_; + + // The link file to be created/updated in the shortcut tests below. + FilePath link_file_; + + // Properties for the created shortcut. + base::win::ShortcutProperties link_properties_; + + // Properties for the updated shortcut. + base::win::ShortcutProperties link_properties_2_; +}; + +} // namespace + +TEST_F(ShortcutTest, CreateAndResolveShortcut) { + base::win::ShortcutProperties only_target_properties; + only_target_properties.set_target(link_properties_.target); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, only_target_properties, base::win::SHORTCUT_CREATE_ALWAYS)); + + FilePath resolved_name; + EXPECT_TRUE(base::win::ResolveShortcut(link_file_, &resolved_name, NULL)); + + char read_contents[arraysize(kFileContents)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents, read_contents); +} + +TEST_F(ShortcutTest, ResolveShortcutWithArgs) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + FilePath resolved_name; + string16 args; + EXPECT_TRUE(base::win::ResolveShortcut(link_file_, &resolved_name, &args)); + + char read_contents[arraysize(kFileContents)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents, read_contents); + EXPECT_EQ(link_properties_.arguments, args); +} + +TEST_F(ShortcutTest, CreateShortcutWithOnlySomeProperties) { + base::win::ShortcutProperties target_and_args_properties; + target_and_args_properties.set_target(link_properties_.target); + target_and_args_properties.set_arguments(link_properties_.arguments); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, target_and_args_properties, + base::win::SHORTCUT_CREATE_ALWAYS)); + + ASSERT_EQ(base::win::VERIFY_SHORTCUT_SUCCESS, + base::win::VerifyShortcut(link_file_, target_and_args_properties)); +} + +TEST_F(ShortcutTest, CreateShortcutVerifyProperties) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + ASSERT_EQ(base::win::VERIFY_SHORTCUT_SUCCESS, + base::win::VerifyShortcut(link_file_, link_properties_)); +} + +TEST_F(ShortcutTest, UpdateShortcutVerifyProperties) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, base::win::SHORTCUT_UPDATE_EXISTING)); + + ASSERT_EQ(base::win::VERIFY_SHORTCUT_SUCCESS, + base::win::VerifyShortcut(link_file_, link_properties_2_)); +} + +TEST_F(ShortcutTest, UpdateShortcutUpdateOnlyTargetAndResolve) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties update_only_target_properties; + update_only_target_properties.set_target(link_properties_2_.target); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, update_only_target_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_; + expected_properties.set_target(link_properties_2_.target); + ASSERT_EQ(base::win::VERIFY_SHORTCUT_SUCCESS, + base::win::VerifyShortcut(link_file_, expected_properties)); + + FilePath resolved_name; + EXPECT_TRUE(base::win::ResolveShortcut(link_file_, &resolved_name, NULL)); + + char read_contents[arraysize(kFileContents2)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents2, read_contents); +} + +TEST_F(ShortcutTest, UpdateShortcutMakeDualMode) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties make_dual_mode_properties; + make_dual_mode_properties.set_dual_mode(true); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, make_dual_mode_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_; + expected_properties.set_dual_mode(true); + ASSERT_EQ(base::win::VERIFY_SHORTCUT_SUCCESS, + base::win::VerifyShortcut(link_file_, expected_properties)); +} + +TEST_F(ShortcutTest, UpdateShortcutRemoveDualMode) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties remove_dual_mode_properties; + remove_dual_mode_properties.set_dual_mode(false); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, remove_dual_mode_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_2_; + expected_properties.set_dual_mode(false); + ASSERT_EQ(base::win::VERIFY_SHORTCUT_SUCCESS, + base::win::VerifyShortcut(link_file_, expected_properties)); +} + +TEST_F(ShortcutTest, UpdateShortcutClearArguments) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties clear_arguments_properties; + clear_arguments_properties.set_arguments(string16()); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, clear_arguments_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_; + expected_properties.set_arguments(string16()); + ASSERT_EQ(base::win::VERIFY_SHORTCUT_SUCCESS, + base::win::VerifyShortcut(link_file_, expected_properties)); +} diff --git a/base/win/win_util.cc b/base/win/win_util.cc index 55a4c3c..37aae27 100644 --- a/base/win/win_util.cc +++ b/base/win/win_util.cc @@ -175,13 +175,17 @@ bool SetAppIdForPropertyStore(IPropertyStore* property_store, app_id); } -bool SetDualModeForPropertyStore(IPropertyStore* property_store) { +bool SetDualModeForPropertyStore(IPropertyStore* property_store, + bool is_dual_mode) { return SetBooleanValueForPropertyStore(property_store, PKEY_AppUserModel_DualMode, - true) && + is_dual_mode) && + // TODO (gab): This property no longer exists in the final Win8 release + // and should be deleted from all shortcuts as it could interfere with + // a future (Win9+) property. SetUInt32ValueForPropertyStore(property_store, PKEY_AppUserModel_DualMode_UK, - 1U); + is_dual_mode ? 1U : 0U); } static const char16 kAutoRunKeyPath[] = diff --git a/base/win/win_util.h b/base/win/win_util.h index 09c66cd..51dfbd6 100644 --- a/base/win/win_util.h +++ b/base/win/win_util.h @@ -78,9 +78,10 @@ BASE_EXPORT bool SetStringValueForPropertyStore( BASE_EXPORT bool SetAppIdForPropertyStore(IPropertyStore* property_store, const wchar_t* app_id); -// Sets the DualModeApp property to true in |property_store|. The function is -// intended for tagging dual mode applications in Win8. -BASE_EXPORT bool SetDualModeForPropertyStore(IPropertyStore* property_store); +// Sets the DualModeApp property to |is_dual_mode| in |property_store|. This +// method is intended for tagging dual mode applications in Win8+. +BASE_EXPORT bool SetDualModeForPropertyStore(IPropertyStore* property_store, + bool is_dual_mode); // Adds the specified |command| using the specified |name| to the AutoRun key. // |root_key| could be HKCU or HKLM or the root of any user hive. |