| // 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 <objbase.h> | 
 | #include <shellapi.h> | 
 | #include <shlobj.h> | 
 | #include <propkey.h> | 
 | #include <wrl/client.h> | 
 |  | 
 | #include "base/files/file_util.h" | 
 | #include "base/threading/thread_restrictions.h" | 
 | #include "base/win/scoped_propvariant.h" | 
 | #include "base/win/win_util.h" | 
 | #include "base/win/windows_version.h" | 
 |  | 
 | namespace base { | 
 | namespace win { | 
 |  | 
 | namespace { | 
 |  | 
 | using Microsoft::WRL::ComPtr; | 
 |  | 
 | // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they | 
 | // are already initialized). | 
 | // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|. | 
 | // If any of the above steps fail, both |i_shell_link| and |i_persist_file| will | 
 | // be released. | 
 | void InitializeShortcutInterfaces(const wchar_t* shortcut, | 
 |                                   ComPtr<IShellLink>* i_shell_link, | 
 |                                   ComPtr<IPersistFile>* i_persist_file) { | 
 |   i_shell_link->Reset(); | 
 |   i_persist_file->Reset(); | 
 |   if (FAILED(::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, | 
 |                                 IID_PPV_ARGS(i_shell_link->GetAddressOf()))) || | 
 |       FAILED(i_shell_link->CopyTo(i_persist_file->GetAddressOf())) || | 
 |       (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) { | 
 |     i_shell_link->Reset(); | 
 |     i_persist_file->Reset(); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | ShortcutProperties::ShortcutProperties() | 
 |     : icon_index(-1), dual_mode(false), options(0U) { | 
 | } | 
 |  | 
 | ShortcutProperties::ShortcutProperties(const ShortcutProperties& other) = | 
 |     default; | 
 |  | 
 | ShortcutProperties::~ShortcutProperties() { | 
 | } | 
 |  | 
 | bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path, | 
 |                                 const ShortcutProperties& properties, | 
 |                                 ShortcutOperation operation) { | 
 |   AssertBlockingAllowed(); | 
 |  | 
 |   // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING. | 
 |   if (operation != SHORTCUT_UPDATE_EXISTING && | 
 |       !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) { | 
 |     NOTREACHED(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   bool shortcut_existed = PathExists(shortcut_path); | 
 |  | 
 |   // Interfaces to the old shortcut when replacing an existing shortcut. | 
 |   ComPtr<IShellLink> old_i_shell_link; | 
 |   ComPtr<IPersistFile> old_i_persist_file; | 
 |  | 
 |   // Interfaces to the shortcut being created/updated. | 
 |   ComPtr<IShellLink> i_shell_link; | 
 |   ComPtr<IPersistFile> i_persist_file; | 
 |   switch (operation) { | 
 |     case SHORTCUT_CREATE_ALWAYS: | 
 |       InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); | 
 |       break; | 
 |     case SHORTCUT_UPDATE_EXISTING: | 
 |       InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link, | 
 |                                    &i_persist_file); | 
 |       break; | 
 |     case SHORTCUT_REPLACE_EXISTING: | 
 |       InitializeShortcutInterfaces(shortcut_path.value().c_str(), | 
 |                                    &old_i_shell_link, &old_i_persist_file); | 
 |       // Confirm |shortcut_path| exists and is a shortcut by verifying | 
 |       // |old_i_persist_file| was successfully initialized in the call above. If | 
 |       // so, initialize the interfaces to begin writing a new shortcut (to | 
 |       // overwrite the current one if successful). | 
 |       if (old_i_persist_file.Get()) | 
 |         InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); | 
 |       break; | 
 |     default: | 
 |       NOTREACHED(); | 
 |   } | 
 |  | 
 |   // Return false immediately upon failure to initialize shortcut interfaces. | 
 |   if (!i_persist_file.Get()) | 
 |     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) { | 
 |     if (FAILED(i_shell_link->SetArguments(properties.arguments.c_str()))) | 
 |       return false; | 
 |   } else if (old_i_persist_file.Get()) { | 
 |     wchar_t current_arguments[MAX_PATH] = {0}; | 
 |     if (SUCCEEDED(old_i_shell_link->GetArguments(current_arguments, | 
 |                                                  MAX_PATH))) { | 
 |       i_shell_link->SetArguments(current_arguments); | 
 |     } | 
 |   } | 
 |  | 
 |   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; | 
 |   bool has_toast_activator_clsid = | 
 |       (properties.options & | 
 |        ShortcutProperties::PROPERTIES_TOAST_ACTIVATOR_CLSID) != 0; | 
 |   if (has_app_id || has_dual_mode || has_toast_activator_clsid) { | 
 |     ComPtr<IPropertyStore> property_store; | 
 |     if (FAILED(i_shell_link.CopyTo(property_store.GetAddressOf())) || | 
 |         !property_store.Get()) | 
 |       return false; | 
 |  | 
 |     if (has_app_id && | 
 |         !SetAppIdForPropertyStore(property_store.Get(), | 
 |                                   properties.app_id.c_str())) { | 
 |       return false; | 
 |     } | 
 |     if (has_dual_mode && | 
 |         !SetBooleanValueForPropertyStore(property_store.Get(), | 
 |                                          PKEY_AppUserModel_IsDualMode, | 
 |                                          properties.dual_mode)) { | 
 |       return false; | 
 |     } | 
 |     if (has_toast_activator_clsid && | 
 |         !SetClsidForPropertyStore(property_store.Get(), | 
 |                                   PKEY_AppUserModel_ToastActivatorCLSID, | 
 |                                   properties.toast_activator_clsid)) { | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   // Release the interfaces to the old shortcut to make sure it doesn't prevent | 
 |   // overwriting it if needed. | 
 |   old_i_persist_file.Reset(); | 
 |   old_i_shell_link.Reset(); | 
 |  | 
 |   HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE); | 
 |  | 
 |   // Release the interfaces in case the SHChangeNotify call below depends on | 
 |   // the operations above being fully completed. | 
 |   i_persist_file.Reset(); | 
 |   i_shell_link.Reset(); | 
 |  | 
 |   // If we successfully created/updated the icon, notify the shell that we have | 
 |   // done so. | 
 |   const bool succeeded = SUCCEEDED(result); | 
 |   if (succeeded) { | 
 |     if (shortcut_existed) { | 
 |       // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing | 
 |       // required. | 
 |       SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); | 
 |     } else { | 
 |       SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcut_path.value().c_str(), | 
 |                      NULL); | 
 |     } | 
 |   } | 
 |  | 
 |   return succeeded; | 
 | } | 
 |  | 
 | bool ResolveShortcutProperties(const FilePath& shortcut_path, | 
 |                                uint32_t options, | 
 |                                ShortcutProperties* properties) { | 
 |   DCHECK(options && properties); | 
 |   AssertBlockingAllowed(); | 
 |  | 
 |   if (options & ~ShortcutProperties::PROPERTIES_ALL) | 
 |     NOTREACHED() << "Unhandled property is used."; | 
 |  | 
 |   ComPtr<IShellLink> i_shell_link; | 
 |  | 
 |   // Get pointer to the IShellLink interface. | 
 |   if (FAILED(::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, | 
 |                                 IID_PPV_ARGS(&i_shell_link)))) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   ComPtr<IPersistFile> persist; | 
 |   // Query IShellLink for the IPersistFile interface. | 
 |   if (FAILED(i_shell_link.CopyTo(persist.GetAddressOf()))) | 
 |     return false; | 
 |  | 
 |   // Load the shell link. | 
 |   if (FAILED(persist->Load(shortcut_path.value().c_str(), STGM_READ))) | 
 |     return false; | 
 |  | 
 |   // Reset |properties|. | 
 |   properties->options = 0; | 
 |  | 
 |   wchar_t temp[MAX_PATH]; | 
 |   if (options & ShortcutProperties::PROPERTIES_TARGET) { | 
 |     if (FAILED(i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY))) | 
 |       return false; | 
 |     properties->set_target(FilePath(temp)); | 
 |   } | 
 |  | 
 |   if (options & ShortcutProperties::PROPERTIES_WORKING_DIR) { | 
 |     if (FAILED(i_shell_link->GetWorkingDirectory(temp, MAX_PATH))) | 
 |       return false; | 
 |     properties->set_working_dir(FilePath(temp)); | 
 |   } | 
 |  | 
 |   if (options & ShortcutProperties::PROPERTIES_ARGUMENTS) { | 
 |     if (FAILED(i_shell_link->GetArguments(temp, MAX_PATH))) | 
 |       return false; | 
 |     properties->set_arguments(temp); | 
 |   } | 
 |  | 
 |   if (options & ShortcutProperties::PROPERTIES_DESCRIPTION) { | 
 |     // Note: description length constrained by MAX_PATH. | 
 |     if (FAILED(i_shell_link->GetDescription(temp, MAX_PATH))) | 
 |       return false; | 
 |     properties->set_description(temp); | 
 |   } | 
 |  | 
 |   if (options & ShortcutProperties::PROPERTIES_ICON) { | 
 |     int temp_index; | 
 |     if (FAILED(i_shell_link->GetIconLocation(temp, MAX_PATH, &temp_index))) | 
 |       return false; | 
 |     properties->set_icon(FilePath(temp), temp_index); | 
 |   } | 
 |  | 
 |   if (options & (ShortcutProperties::PROPERTIES_APP_ID | | 
 |                  ShortcutProperties::PROPERTIES_DUAL_MODE | | 
 |                  ShortcutProperties::PROPERTIES_TOAST_ACTIVATOR_CLSID)) { | 
 |     ComPtr<IPropertyStore> property_store; | 
 |     if (FAILED(i_shell_link.CopyTo(property_store.GetAddressOf()))) | 
 |       return false; | 
 |  | 
 |     if (options & ShortcutProperties::PROPERTIES_APP_ID) { | 
 |       ScopedPropVariant pv_app_id; | 
 |       if (property_store->GetValue(PKEY_AppUserModel_ID, | 
 |                                    pv_app_id.Receive()) != S_OK) { | 
 |         return false; | 
 |       } | 
 |       switch (pv_app_id.get().vt) { | 
 |         case VT_EMPTY: | 
 |           properties->set_app_id(L""); | 
 |           break; | 
 |         case VT_LPWSTR: | 
 |           properties->set_app_id(pv_app_id.get().pwszVal); | 
 |           break; | 
 |         default: | 
 |           NOTREACHED() << "Unexpected variant type: " << pv_app_id.get().vt; | 
 |           return false; | 
 |       } | 
 |     } | 
 |  | 
 |     if (options & ShortcutProperties::PROPERTIES_DUAL_MODE) { | 
 |       ScopedPropVariant pv_dual_mode; | 
 |       if (property_store->GetValue(PKEY_AppUserModel_IsDualMode, | 
 |                                    pv_dual_mode.Receive()) != S_OK) { | 
 |         return false; | 
 |       } | 
 |       switch (pv_dual_mode.get().vt) { | 
 |         case VT_EMPTY: | 
 |           properties->set_dual_mode(false); | 
 |           break; | 
 |         case VT_BOOL: | 
 |           properties->set_dual_mode(pv_dual_mode.get().boolVal == VARIANT_TRUE); | 
 |           break; | 
 |         default: | 
 |           NOTREACHED() << "Unexpected variant type: " << pv_dual_mode.get().vt; | 
 |           return false; | 
 |       } | 
 |     } | 
 |  | 
 |     if (options & ShortcutProperties::PROPERTIES_TOAST_ACTIVATOR_CLSID) { | 
 |       ScopedPropVariant pv_toast_activator_clsid; | 
 |       if (property_store->GetValue(PKEY_AppUserModel_ToastActivatorCLSID, | 
 |                                    pv_toast_activator_clsid.Receive()) != | 
 |           S_OK) { | 
 |         return false; | 
 |       } | 
 |       switch (pv_toast_activator_clsid.get().vt) { | 
 |         case VT_EMPTY: | 
 |           properties->set_toast_activator_clsid(CLSID_NULL); | 
 |           break; | 
 |         case VT_CLSID: | 
 |           properties->set_toast_activator_clsid( | 
 |               *(pv_toast_activator_clsid.get().puuid)); | 
 |           break; | 
 |         default: | 
 |           NOTREACHED() << "Unexpected variant type: " | 
 |                        << pv_toast_activator_clsid.get().vt; | 
 |           return false; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | bool ResolveShortcut(const FilePath& shortcut_path, | 
 |                      FilePath* target_path, | 
 |                      string16* args) { | 
 |   uint32_t options = 0; | 
 |   if (target_path) | 
 |     options |= ShortcutProperties::PROPERTIES_TARGET; | 
 |   if (args) | 
 |     options |= ShortcutProperties::PROPERTIES_ARGUMENTS; | 
 |   DCHECK(options); | 
 |  | 
 |   ShortcutProperties properties; | 
 |   if (!ResolveShortcutProperties(shortcut_path, options, &properties)) | 
 |     return false; | 
 |  | 
 |   if (target_path) | 
 |     *target_path = properties.target; | 
 |   if (args) | 
 |     *args = properties.arguments; | 
 |   return true; | 
 | } | 
 |  | 
 | bool CanPinShortcutToTaskbar() { | 
 |   // "Pin to taskbar" stopped being supported in Windows 10. | 
 |   return GetVersion() < VERSION_WIN10; | 
 | } | 
 |  | 
 | bool PinShortcutToTaskbar(const FilePath& shortcut) { | 
 |   AssertBlockingAllowed(); | 
 |   DCHECK(CanPinShortcutToTaskbar()); | 
 |  | 
 |   intptr_t result = reinterpret_cast<intptr_t>(ShellExecute( | 
 |       NULL, L"taskbarpin", shortcut.value().c_str(), NULL, NULL, 0)); | 
 |   return result > 32; | 
 | } | 
 |  | 
 | bool UnpinShortcutFromTaskbar(const FilePath& shortcut) { | 
 |   AssertBlockingAllowed(); | 
 |  | 
 |   intptr_t result = reinterpret_cast<intptr_t>(ShellExecute( | 
 |       NULL, L"taskbarunpin", shortcut.value().c_str(), NULL, NULL, 0)); | 
 |   return result > 32; | 
 | } | 
 |  | 
 | }  // namespace win | 
 | }  // namespace base |