| // 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 <propkey.h> | 
 |  | 
 | #include "base/files/file_util.h" | 
 | #include "base/threading/thread_restrictions.h" | 
 | #include "base/win/scoped_comptr.h" | 
 | #include "base/win/scoped_propvariant.h" | 
 | #include "base/win/win_util.h" | 
 | #include "base/win/windows_version.h" | 
 |  | 
 | namespace base { | 
 | namespace win { | 
 |  | 
 | namespace { | 
 |  | 
 | // 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, | 
 |     ScopedComPtr<IShellLink>* i_shell_link, | 
 |     ScopedComPtr<IPersistFile>* i_persist_file) { | 
 |   i_shell_link->Release(); | 
 |   i_persist_file->Release(); | 
 |   if (FAILED(i_shell_link->CreateInstance(CLSID_ShellLink, NULL, | 
 |                                           CLSCTX_INPROC_SERVER)) || | 
 |       FAILED(i_persist_file->QueryFrom(i_shell_link->get())) || | 
 |       (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) { | 
 |     i_shell_link->Release(); | 
 |     i_persist_file->Release(); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path, | 
 |                                 const ShortcutProperties& properties, | 
 |                                 ShortcutOperation operation) { | 
 |   base::ThreadRestrictions::AssertIOAllowed(); | 
 |  | 
 |   // 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. | 
 |   ScopedComPtr<IShellLink> old_i_shell_link; | 
 |   ScopedComPtr<IPersistFile> old_i_persist_file; | 
 |  | 
 |   // Interfaces to the shortcut being created/updated. | 
 |   ScopedComPtr<IShellLink> i_shell_link; | 
 |   ScopedComPtr<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; | 
 |   if ((has_app_id || has_dual_mode) && | 
 |       GetVersion() >= VERSION_WIN7) { | 
 |     ScopedComPtr<IPropertyStore> property_store; | 
 |     if (FAILED(property_store.QueryFrom(i_shell_link.get())) || | 
 |         !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; | 
 |     } | 
 |   } | 
 |  | 
 |   // Release the interfaces to the old shortcut to make sure it doesn't prevent | 
 |   // overwriting it if needed. | 
 |   old_i_persist_file.Release(); | 
 |   old_i_shell_link.Release(); | 
 |  | 
 |   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.Release(); | 
 |   i_shell_link.Release(); | 
 |  | 
 |   // 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 options, | 
 |                                ShortcutProperties* properties) { | 
 |   DCHECK(options && properties); | 
 |   base::ThreadRestrictions::AssertIOAllowed(); | 
 |  | 
 |   if (options & ~ShortcutProperties::PROPERTIES_ALL) | 
 |     NOTREACHED() << "Unhandled property is used."; | 
 |  | 
 |   ScopedComPtr<IShellLink> i_shell_link; | 
 |  | 
 |   // Get pointer to the IShellLink interface. | 
 |   if (FAILED(i_shell_link.CreateInstance(CLSID_ShellLink, NULL, | 
 |                                          CLSCTX_INPROC_SERVER))) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   ScopedComPtr<IPersistFile> persist; | 
 |   // Query IShellLink for the IPersistFile interface. | 
 |   if (FAILED(persist.QueryFrom(i_shell_link.get()))) | 
 |     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); | 
 |   } | 
 |  | 
 |   // Windows 7+ options, avoiding unnecessary work. | 
 |   if ((options & ShortcutProperties::PROPERTIES_WIN7) && | 
 |       GetVersion() >= VERSION_WIN7) { | 
 |     ScopedComPtr<IPropertyStore> property_store; | 
 |     if (FAILED(property_store.QueryFrom(i_shell_link.get()))) | 
 |       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; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | bool ResolveShortcut(const FilePath& shortcut_path, | 
 |                      FilePath* target_path, | 
 |                      string16* args) { | 
 |   uint32 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 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 (GetVersion() < VERSION_WIN7) | 
 |     return false; | 
 |  | 
 |   int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin", | 
 |       shortcut, NULL, NULL, 0)); | 
 |   return result > 32; | 
 | } | 
 |  | 
 | }  // namespace win | 
 | }  // namespace base |