shortcut.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. // Copyright (c) 2012 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. #include "base/win/shortcut.h"
  5. #include <objbase.h>
  6. #include <propkey.h>
  7. #include <shlobj.h>
  8. #include <wrl/client.h>
  9. #include "base/files/file_util.h"
  10. #include "base/logging.h"
  11. #include "base/strings/string_util.h"
  12. #include "base/strings/utf_string_conversions.h"
  13. #include "base/threading/scoped_blocking_call.h"
  14. #include "base/win/scoped_propvariant.h"
  15. #include "base/win/win_util.h"
  16. #include "base/win/windows_version.h"
  17. namespace base {
  18. namespace win {
  19. namespace {
  20. using Microsoft::WRL::ComPtr;
  21. // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they
  22. // are already initialized).
  23. // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|.
  24. // If any of the above steps fail, both |i_shell_link| and |i_persist_file| will
  25. // be released.
  26. void InitializeShortcutInterfaces(const wchar_t* shortcut,
  27. ComPtr<IShellLink>* i_shell_link,
  28. ComPtr<IPersistFile>* i_persist_file) {
  29. // Reset in the inverse order of acquisition.
  30. i_persist_file->Reset();
  31. i_shell_link->Reset();
  32. ComPtr<IShellLink> shell_link;
  33. if (FAILED(::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
  34. IID_PPV_ARGS(&shell_link)))) {
  35. return;
  36. }
  37. ComPtr<IPersistFile> persist_file;
  38. if (FAILED(shell_link.As(&persist_file)))
  39. return;
  40. if (shortcut && FAILED(persist_file->Load(shortcut, STGM_READWRITE)))
  41. return;
  42. i_shell_link->Swap(shell_link);
  43. i_persist_file->Swap(persist_file);
  44. }
  45. } // namespace
  46. ShortcutProperties::ShortcutProperties() = default;
  47. ShortcutProperties::ShortcutProperties(const ShortcutProperties& other) =
  48. default;
  49. ShortcutProperties::~ShortcutProperties() = default;
  50. void ShortcutProperties::set_description(const std::wstring& description_in) {
  51. // Size restriction as per MSDN at http://goo.gl/OdNQq.
  52. DCHECK_LE(description_in.size(), static_cast<size_t>(INFOTIPSIZE));
  53. description = description_in;
  54. options |= PROPERTIES_DESCRIPTION;
  55. }
  56. bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path,
  57. const ShortcutProperties& properties,
  58. ShortcutOperation operation) {
  59. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  60. // Make sure the parent directories exist when creating the shortcut.
  61. if (operation == ShortcutOperation::kCreateAlways &&
  62. !base::CreateDirectory(shortcut_path.DirName())) {
  63. DLOG(ERROR) << "CreateDirectory " << shortcut_path.DirName() << " failed";
  64. return false;
  65. }
  66. // A target is required unless |operation| is kUpdateExisting.
  67. if (operation != ShortcutOperation::kUpdateExisting &&
  68. !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) {
  69. NOTREACHED();
  70. return false;
  71. }
  72. bool shortcut_existed = PathExists(shortcut_path);
  73. // Interfaces to the old shortcut when replacing an existing shortcut.
  74. ComPtr<IShellLink> old_i_shell_link;
  75. ComPtr<IPersistFile> old_i_persist_file;
  76. // Interfaces to the shortcut being created/updated.
  77. ComPtr<IShellLink> i_shell_link;
  78. ComPtr<IPersistFile> i_persist_file;
  79. switch (operation) {
  80. case ShortcutOperation::kCreateAlways:
  81. InitializeShortcutInterfaces(nullptr, &i_shell_link, &i_persist_file);
  82. break;
  83. case ShortcutOperation::kUpdateExisting:
  84. InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link,
  85. &i_persist_file);
  86. break;
  87. case ShortcutOperation::kReplaceExisting:
  88. InitializeShortcutInterfaces(shortcut_path.value().c_str(),
  89. &old_i_shell_link, &old_i_persist_file);
  90. // Confirm |shortcut_path| exists and is a shortcut by verifying
  91. // |old_i_persist_file| was successfully initialized in the call above. If
  92. // so, initialize the interfaces to begin writing a new shortcut (to
  93. // overwrite the current one if successful).
  94. if (old_i_persist_file.Get())
  95. InitializeShortcutInterfaces(nullptr, &i_shell_link, &i_persist_file);
  96. break;
  97. default:
  98. NOTREACHED();
  99. }
  100. // Return false immediately upon failure to initialize shortcut interfaces.
  101. if (!i_persist_file.Get())
  102. return false;
  103. if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) &&
  104. FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) {
  105. return false;
  106. }
  107. if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) &&
  108. FAILED(i_shell_link->SetWorkingDirectory(
  109. properties.working_dir.value().c_str()))) {
  110. return false;
  111. }
  112. if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) {
  113. if (FAILED(i_shell_link->SetArguments(properties.arguments.c_str())))
  114. return false;
  115. } else if (old_i_persist_file.Get()) {
  116. wchar_t current_arguments[MAX_PATH] = {0};
  117. if (SUCCEEDED(
  118. old_i_shell_link->GetArguments(current_arguments, MAX_PATH))) {
  119. i_shell_link->SetArguments(current_arguments);
  120. }
  121. }
  122. if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) &&
  123. FAILED(i_shell_link->SetDescription(properties.description.c_str()))) {
  124. return false;
  125. }
  126. if ((properties.options & ShortcutProperties::PROPERTIES_ICON) &&
  127. FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(),
  128. properties.icon_index))) {
  129. return false;
  130. }
  131. bool has_app_id =
  132. (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0;
  133. bool has_dual_mode =
  134. (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0;
  135. bool has_toast_activator_clsid =
  136. (properties.options &
  137. ShortcutProperties::PROPERTIES_TOAST_ACTIVATOR_CLSID) != 0;
  138. if (has_app_id || has_dual_mode || has_toast_activator_clsid) {
  139. ComPtr<IPropertyStore> property_store;
  140. if (FAILED(i_shell_link.As(&property_store)) || !property_store.Get())
  141. return false;
  142. if (has_app_id && !SetAppIdForPropertyStore(property_store.Get(),
  143. properties.app_id.c_str())) {
  144. return false;
  145. }
  146. if (has_dual_mode && !SetBooleanValueForPropertyStore(
  147. property_store.Get(), PKEY_AppUserModel_IsDualMode,
  148. properties.dual_mode)) {
  149. return false;
  150. }
  151. if (has_toast_activator_clsid &&
  152. !SetClsidForPropertyStore(property_store.Get(),
  153. PKEY_AppUserModel_ToastActivatorCLSID,
  154. properties.toast_activator_clsid)) {
  155. return false;
  156. }
  157. }
  158. // Release the interfaces to the old shortcut to make sure it doesn't prevent
  159. // overwriting it if needed.
  160. old_i_persist_file.Reset();
  161. old_i_shell_link.Reset();
  162. HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE);
  163. // Release the interfaces in case the SHChangeNotify call below depends on
  164. // the operations above being fully completed.
  165. i_persist_file.Reset();
  166. i_shell_link.Reset();
  167. // If we successfully created/updated the icon, notify the shell that we have
  168. // done so.
  169. if (!SUCCEEDED(result))
  170. return false;
  171. SHChangeNotify(shortcut_existed ? SHCNE_UPDATEITEM : SHCNE_CREATE,
  172. SHCNF_PATH | SHCNF_FLUSH, shortcut_path.value().c_str(),
  173. nullptr);
  174. return true;
  175. }
  176. bool ResolveShortcutProperties(const FilePath& shortcut_path,
  177. uint32_t options,
  178. ShortcutProperties* properties) {
  179. DCHECK(options && properties);
  180. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  181. if (options & ~ShortcutProperties::PROPERTIES_ALL)
  182. NOTREACHED() << "Unhandled property is used.";
  183. ComPtr<IShellLink> i_shell_link;
  184. // Get pointer to the IShellLink interface.
  185. if (FAILED(::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
  186. IID_PPV_ARGS(&i_shell_link)))) {
  187. return false;
  188. }
  189. ComPtr<IPersistFile> persist;
  190. // Query IShellLink for the IPersistFile interface.
  191. if (FAILED(i_shell_link.As(&persist)))
  192. return false;
  193. // Load the shell link.
  194. if (FAILED(persist->Load(shortcut_path.value().c_str(), STGM_READ)))
  195. return false;
  196. // Reset |properties|.
  197. properties->options = 0;
  198. wchar_t temp[MAX_PATH];
  199. if (options & ShortcutProperties::PROPERTIES_TARGET) {
  200. if (FAILED(
  201. i_shell_link->GetPath(temp, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) {
  202. return false;
  203. }
  204. properties->set_target(FilePath(temp));
  205. }
  206. if (options & ShortcutProperties::PROPERTIES_WORKING_DIR) {
  207. if (FAILED(i_shell_link->GetWorkingDirectory(temp, MAX_PATH)))
  208. return false;
  209. properties->set_working_dir(FilePath(temp));
  210. }
  211. if (options & ShortcutProperties::PROPERTIES_ARGUMENTS) {
  212. if (FAILED(i_shell_link->GetArguments(temp, MAX_PATH)))
  213. return false;
  214. properties->set_arguments(temp);
  215. }
  216. if (options & ShortcutProperties::PROPERTIES_DESCRIPTION) {
  217. // Note: description length constrained by MAX_PATH.
  218. if (FAILED(i_shell_link->GetDescription(temp, MAX_PATH)))
  219. return false;
  220. properties->set_description(temp);
  221. }
  222. if (options & ShortcutProperties::PROPERTIES_ICON) {
  223. int temp_index;
  224. if (FAILED(i_shell_link->GetIconLocation(temp, MAX_PATH, &temp_index))) {
  225. return false;
  226. }
  227. properties->set_icon(FilePath(temp), temp_index);
  228. }
  229. if (options & (ShortcutProperties::PROPERTIES_APP_ID |
  230. ShortcutProperties::PROPERTIES_DUAL_MODE |
  231. ShortcutProperties::PROPERTIES_TOAST_ACTIVATOR_CLSID)) {
  232. ComPtr<IPropertyStore> property_store;
  233. if (FAILED(i_shell_link.As(&property_store)))
  234. return false;
  235. if (options & ShortcutProperties::PROPERTIES_APP_ID) {
  236. ScopedPropVariant pv_app_id;
  237. if (property_store->GetValue(PKEY_AppUserModel_ID, pv_app_id.Receive()) !=
  238. S_OK) {
  239. return false;
  240. }
  241. switch (pv_app_id.get().vt) {
  242. case VT_EMPTY:
  243. properties->set_app_id(std::wstring());
  244. break;
  245. case VT_LPWSTR:
  246. properties->set_app_id(pv_app_id.get().pwszVal);
  247. break;
  248. default:
  249. NOTREACHED() << "Unexpected variant type: " << pv_app_id.get().vt;
  250. return false;
  251. }
  252. }
  253. if (options & ShortcutProperties::PROPERTIES_DUAL_MODE) {
  254. ScopedPropVariant pv_dual_mode;
  255. if (property_store->GetValue(PKEY_AppUserModel_IsDualMode,
  256. pv_dual_mode.Receive()) != S_OK) {
  257. return false;
  258. }
  259. switch (pv_dual_mode.get().vt) {
  260. case VT_EMPTY:
  261. properties->set_dual_mode(false);
  262. break;
  263. case VT_BOOL:
  264. properties->set_dual_mode(pv_dual_mode.get().boolVal == VARIANT_TRUE);
  265. break;
  266. default:
  267. NOTREACHED() << "Unexpected variant type: " << pv_dual_mode.get().vt;
  268. return false;
  269. }
  270. }
  271. if (options & ShortcutProperties::PROPERTIES_TOAST_ACTIVATOR_CLSID) {
  272. ScopedPropVariant pv_toast_activator_clsid;
  273. if (property_store->GetValue(PKEY_AppUserModel_ToastActivatorCLSID,
  274. pv_toast_activator_clsid.Receive()) !=
  275. S_OK) {
  276. return false;
  277. }
  278. switch (pv_toast_activator_clsid.get().vt) {
  279. case VT_EMPTY:
  280. properties->set_toast_activator_clsid(CLSID_NULL);
  281. break;
  282. case VT_CLSID:
  283. properties->set_toast_activator_clsid(
  284. *(pv_toast_activator_clsid.get().puuid));
  285. break;
  286. default:
  287. NOTREACHED() << "Unexpected variant type: "
  288. << pv_toast_activator_clsid.get().vt;
  289. return false;
  290. }
  291. }
  292. }
  293. return true;
  294. }
  295. bool ResolveShortcut(const FilePath& shortcut_path,
  296. FilePath* target_path,
  297. std::wstring* args) {
  298. uint32_t options = 0;
  299. if (target_path)
  300. options |= ShortcutProperties::PROPERTIES_TARGET;
  301. if (args)
  302. options |= ShortcutProperties::PROPERTIES_ARGUMENTS;
  303. DCHECK(options);
  304. ShortcutProperties properties;
  305. if (!ResolveShortcutProperties(shortcut_path, options, &properties))
  306. return false;
  307. if (target_path)
  308. *target_path = properties.target;
  309. if (args)
  310. *args = properties.arguments;
  311. return true;
  312. }
  313. } // namespace win
  314. } // namespace base