// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/web_applications/os_integration/web_app_shortcut_win.h"

#include <algorithm>
#include <utility>
#include <vector>

#include "base/base_paths_win.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_path_override.h"
#include "base/test/test_future.h"
#include "base/win/shortcut.h"
#include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/installer/util/shell_util.h"
#include "chrome/installer/util/util_constants.h"
#include "components/services/app_service/public/cpp/icon_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_family.h"
#include "ui/gfx/image/image_skia.h"

namespace web_app::internals {
namespace {

constexpr char kWebAppId[] = "random_id1";
constexpr char kWebAppId2[] = "random_id2";

bool CreateTestAppShortcut(const base::FilePath& shortcut_path,
                           const base::FilePath::StringType& profile_name,
                           webapps::AppId app_id = kWebAppId) {
  base::CommandLine args_cl(base::CommandLine::NO_PROGRAM);
  args_cl.AppendSwitchNative(switches::kProfileDirectory, profile_name);
  args_cl.AppendSwitchASCII(switches::kAppId, app_id);

  base::win::ShortcutProperties shortcut_properties;
  shortcut_properties.set_arguments(args_cl.GetArgumentsString());
  shortcut_properties.set_target(base::FilePath(FILE_PATH_LITERAL("target")));
  return base::win::CreateOrUpdateShortcutLink(
      shortcut_path, shortcut_properties,
      base::win::ShortcutOperation::kCreateAlways);
}

base::FilePath GetShortcutPath(
    const base::FilePath& shortcut_dir,
    const base::FilePath::StringType& shortcut_name) {
  return shortcut_dir.Append(shortcut_name).AddExtension(installer::kLnkExt);
}

void CreateAndVerifyTestAppShortcut(
    const base::FilePath::StringType& shortcut_name,
    const base::FilePath& shortcut_dir,
    const base::FilePath& profile_path) {
  const base::FilePath shortcut_path =
      GetShortcutPath(shortcut_dir, shortcut_name);
  EXPECT_TRUE(
      CreateTestAppShortcut(shortcut_path, profile_path.BaseName().value()));
  const std::vector<base::FilePath> result =
      FindAppShortcutsByProfileAppIdAndTitle(shortcut_dir, profile_path,
                                             base::WideToUTF16(shortcut_name),
                                             kWebAppId);
  EXPECT_EQ(1u, result.size());
}

// Synchronous wrapper for UpdatePlatformShortcuts for ease of testing.
Result UpdatePlatformShortcuts(
    const base::FilePath& web_app_path,
    const std::u16string& old_app_title,
    std::optional<ShortcutLocations> user_specified_locations,
    const ShortcutInfo& shortcut_info) {
  base::test::TestFuture<Result> result;
  web_app::internals::UpdatePlatformShortcuts(
      web_app_path, old_app_title, std::move(user_specified_locations),
      result.GetCallback(), shortcut_info);
  return result.Get();
}

}  // namespace

class WebAppShortcutWinTest : public WebAppTest {
 protected:
  base::ScopedPathOverride override_taskbar_pin{base::DIR_TASKBAR_PINS};
  base::ScopedPathOverride override_implicit_apps{
      base::DIR_IMPLICIT_APP_SHORTCUTS};
};

TEST_F(WebAppShortcutWinTest, GetSanitizedFileName) {
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("__")), GetSanitizedFileName(u""));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("clean filename")),
            GetSanitizedFileName(u"clean filename"));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("illegal chars")),
            GetSanitizedFileName(u"illegal*chars..."));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("path separator")),
            GetSanitizedFileName(u"path/separator"));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_   _")),
            GetSanitizedFileName(u"***"));
  // Test reserved names on Windows.
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_COM1")),
            GetSanitizedFileName(u"COM1"));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_PRN")),
            GetSanitizedFileName(u"PRN"));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_CON")),
            GetSanitizedFileName(u"CON"));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_LPT9")),
            GetSanitizedFileName(u"LPT9"));
  // Test reserved names with extension (only base filename is checked).
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_COM1.lnk")),
            GetSanitizedFileName(u"COM1.lnk"));
  // Test sanitization of '%' character.
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("percent 1")),
            GetSanitizedFileName(u"percent%1"));

  // Test sanitization of exploit strings from b/513893425.
  // Case 1: %1 and NUL byte.
  std::u16string input1 = u"Updated PoC %1";
  input1.push_back(u'\0');
  input1.append(
      u" --single-argument --headless --utility-cmd-prefix=calc.exe inert");
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL(
                "Updated PoC  1  --single-argument --headless "
                "--utility-cmd-prefix=calc.exe inert")),
            GetSanitizedFileName(input1));

  // Case 2: %1 and no NUL byte, but multiple %1.
  EXPECT_EQ(
      base::FilePath(FILE_PATH_LITERAL(
          "Victim  1 --headless --utility-cmd-prefix=calc.exe  1 inert")),
      GetSanitizedFileName(
          u"Victim %1 --headless --utility-cmd-prefix=calc.exe %1 inert"));
}

TEST_F(WebAppShortcutWinTest, GetShortcutPaths) {
  ShortcutLocations shortcut_locations;
  EXPECT_TRUE(GetShortcutPaths(shortcut_locations).empty());

  // The location APP_MENU_LOCATION_HIDDEN is not used on Windows.
  shortcut_locations.applications_menu_location = APP_MENU_LOCATION_HIDDEN;
  EXPECT_TRUE(GetShortcutPaths(shortcut_locations).empty());

  shortcut_locations.on_desktop = true;
  shortcut_locations.applications_menu_location =
      APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
  shortcut_locations.in_quick_launch_bar = true;
  shortcut_locations.in_startup = true;
  const std::vector<base::FilePath> result =
      GetShortcutPaths(shortcut_locations);
  std::vector<ShellUtil::ShortcutLocation> expected_locations{
      ShellUtil::SHORTCUT_LOCATION_DESKTOP,
      ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR,
      ShellUtil::SHORTCUT_LOCATION_STARTUP};

  base::FilePath expected_result;
  for (const auto& location : expected_locations) {
    ASSERT_TRUE(ShellUtil::GetShortcutPath(location, ShellUtil::CURRENT_USER,
                                           &expected_result));
    EXPECT_TRUE(std::ranges::contains(result, expected_result));
  }
}

TEST_F(WebAppShortcutWinTest, CheckAndSaveIcon) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath icon_file =
      temp_dir.GetPath().Append(FILE_PATH_LITERAL("test.ico"));

  gfx::ImageFamily image_family;
  image_family.Add(gfx::Image(CreateDefaultApplicationIcon(5)));
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));
  // CheckAndSaveIcon() should succeed even if called multiple times.
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));
  image_family.clear();
  image_family.Add(gfx::Image(CreateDefaultApplicationIcon(6)));
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));
}

TEST_F(WebAppShortcutWinTest, FindAppShortcutsByProfileAppIdAndTitle) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath::StringType shortcut_name =
      FILE_PATH_LITERAL("test shortcut");
  const base::FilePath shortcut_path =
      GetShortcutPath(shortcut_dir, shortcut_name);
  const base::FilePath duplicate_shortcut_path =
      GetShortcutPath(shortcut_dir, FILE_PATH_LITERAL("test shortcut (5)"));
  const base::FilePath other_shortcut_path =
      GetShortcutPath(shortcut_dir, FILE_PATH_LITERAL("another test shortcut"));
  const base::FilePath other_profile_shortcut_path = GetShortcutPath(
      shortcut_dir, FILE_PATH_LITERAL("shortcut for another profile"));

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/path"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  ASSERT_TRUE(CreateTestAppShortcut(shortcut_path, profile_name));
  ASSERT_TRUE(CreateTestAppShortcut(duplicate_shortcut_path, profile_name));
  ASSERT_TRUE(CreateTestAppShortcut(other_shortcut_path, profile_name));
  ASSERT_TRUE(CreateTestAppShortcut(other_profile_shortcut_path,
                                    FILE_PATH_LITERAL("other profile")));

  // Find |shortcut_name| by name. The specified shortcut and its duplicate
  // should be found.
  std::vector<base::FilePath> result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, base::WideToUTF16(shortcut_name), kWebAppId);
  EXPECT_EQ(2u, result.size());
  EXPECT_TRUE(std::ranges::contains(result, shortcut_path));
  EXPECT_TRUE(std::ranges::contains(result, duplicate_shortcut_path));
  EXPECT_FALSE(std::ranges::contains(result, other_shortcut_path));
  EXPECT_FALSE(std::ranges::contains(result, other_profile_shortcut_path));

  // Find all shortcuts for |profile_name|. The shortcuts matching that profile
  // should be found.
  result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, /*shortcut_name=*/std::nullopt,
      /*app_id=*/std::nullopt);
  EXPECT_EQ(3u, result.size());
  EXPECT_TRUE(std::ranges::contains(result, shortcut_path));
  EXPECT_TRUE(std::ranges::contains(result, duplicate_shortcut_path));
  EXPECT_TRUE(std::ranges::contains(result, other_shortcut_path));
  EXPECT_FALSE(std::ranges::contains(result, other_profile_shortcut_path));
}

// Tests for crbug.com/481747254.
TEST_F(WebAppShortcutWinTest, FindAppShortcutsSameNameMultipleApps) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath::StringType shortcut_name =
      FILE_PATH_LITERAL("test shortcut");
  const base::FilePath::StringType shortcut_name_dupe =
      FILE_PATH_LITERAL("test shortcut (2)");
  const base::FilePath::StringType shortcut_name_five =
      FILE_PATH_LITERAL("test shortcut (5)");
  const base::FilePath shortcut_path =
      GetShortcutPath(shortcut_dir, shortcut_name);
  const base::FilePath shortcut_path_dupe =
      GetShortcutPath(shortcut_dir, shortcut_name_dupe);
  const base::FilePath shortcut_path_five =
      GetShortcutPath(shortcut_dir, shortcut_name_five);

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/path"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  // Create shortcuts for the same name but for 2 different app_ids.
  // Note: This does end up overwriting the old created shortcut if it has the
  // same name, so it can't really test the "core" issue of crbug.com/481747254.
  // We can still test some extra use-cases that is not in the test:
  // `FindAppShortcutsByProfileAppIdAndTitle`, so still nice to keep this
  // around.
  ASSERT_TRUE(CreateTestAppShortcut(shortcut_path, profile_name, kWebAppId));
  ASSERT_TRUE(CreateTestAppShortcut(shortcut_path, profile_name, kWebAppId2));
  ASSERT_TRUE(
      CreateTestAppShortcut(shortcut_path_dupe, profile_name, kWebAppId));
  ASSERT_TRUE(
      CreateTestAppShortcut(shortcut_path_dupe, profile_name, kWebAppId2));

  // Create one extra shortcut just for kWebAppId for testing.
  ASSERT_TRUE(
      CreateTestAppShortcut(shortcut_path_five, profile_name, kWebAppId));

  // Try to find the shortcut for the base name, but with no app_id specified.
  // It should return all 3 shortcuts created.
  std::vector<base::FilePath> result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, base::WideToUTF16(shortcut_name),
      /*app_id=*/std::nullopt);
  EXPECT_EQ(3u, result.size());
  EXPECT_TRUE(std::ranges::contains(result, shortcut_path));
  EXPECT_TRUE(std::ranges::contains(result, shortcut_path_dupe));
  EXPECT_TRUE(std::ranges::contains(result, shortcut_path_five));

  // Try to find the duplicated shortcut for the same name, but for the 2nd app.
  // It should return just one value that is not a shortcut for `kWebAppId`.
  result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, base::WideToUTF16(shortcut_name_dupe),
      kWebAppId2);
  EXPECT_EQ(1u, result.size());
  EXPECT_TRUE(IsAppShortcutForProfile(result[0], profile_path, kWebAppId2));
  EXPECT_FALSE(IsAppShortcutForProfile(result[0], profile_path, kWebAppId));

  // Try to find the shortcut for `kWebAppId`. There should only be one
  // remaining.
  result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, base::WideToUTF16(shortcut_name), kWebAppId);
  EXPECT_EQ(1u, result.size());
  EXPECT_EQ(shortcut_path_five, result[0]);
  EXPECT_TRUE(IsAppShortcutForProfile(result[0], profile_path, kWebAppId));
  EXPECT_FALSE(IsAppShortcutForProfile(result[0], profile_path, kWebAppId2));
}

TEST_F(WebAppShortcutWinTest,
       FindAppShortcutsByProfileAppIdAndTitleIllegalCharacters) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath::StringType shortcut_name =
      FILE_PATH_LITERAL("shortcut*with*illegal*characters...");
  const base::FilePath sanitized_shortcut_path = GetShortcutPath(
      shortcut_dir, FILE_PATH_LITERAL("shortcut with illegal characters"));

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/path"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  ASSERT_TRUE(CreateTestAppShortcut(sanitized_shortcut_path, profile_name));

  // Find shortcuts matching `shortcut_name`. A shortcut with the sanitized name
  // should be found.
  std::vector<base::FilePath> result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, base::WideToUTF16(shortcut_name), kWebAppId);
  EXPECT_EQ(1u, result.size());
  EXPECT_TRUE(std::ranges::contains(result, sanitized_shortcut_path));
}

TEST_F(WebAppShortcutWinTest, UpdatePlatformShortcuts) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath::StringType shortcut_name =
      FILE_PATH_LITERAL("test shortcut");
  base::FilePath shortcut_path = GetShortcutPath(shortcut_dir, shortcut_name);

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/web_app"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  ASSERT_NO_FATAL_FAILURE(CreateAndVerifyTestAppShortcut(
      shortcut_name, shortcut_dir, profile_path));

  // Create shortcut in overridden taskbar dir.
  base::FilePath taskbar_dir;
  ASSERT_TRUE(base::PathService::Get(base::DIR_TASKBAR_PINS, &taskbar_dir));
  ASSERT_NO_FATAL_FAILURE(
      CreateAndVerifyTestAppShortcut(shortcut_name, taskbar_dir, profile_path));

  // Create shortcut in overridden implicit apps dir.
  base::FilePath implicit_apps_dir;
  ASSERT_TRUE(base::PathService::Get(base::DIR_IMPLICIT_APP_SHORTCUTS,
                                     &implicit_apps_dir));
  base::FilePath implicit_apps_sub_dir = implicit_apps_dir.AppendASCII("abcde");
  ASSERT_TRUE(base::CreateDirectory(implicit_apps_sub_dir));
  ASSERT_NO_FATAL_FAILURE(CreateAndVerifyTestAppShortcut(
      shortcut_name, implicit_apps_sub_dir, profile_path));

  // Create an icon file for the web app.
  const base::FilePath icon_file =
      GetIconFilePath(shortcut_dir, u"test shortcut");
  gfx::ImageFamily image_family;
  image_family.Add(gfx::Image(CreateDefaultApplicationIcon(5)));
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));

  // Update the web app with a new title.
  ShortcutInfo new_shortcut_info;
  new_shortcut_info.title = u"new title";
  new_shortcut_info.profile_path = profile_path;
  new_shortcut_info.profile_name = base::WideToUTF8(profile_name);
  new_shortcut_info.app_id = kWebAppId;

  // Set the favicon to be the same as the original icon.
  new_shortcut_info.favicon = std::move(image_family);

  UpdatePlatformShortcuts(shortcut_dir, base::WideToUTF16(shortcut_name),
                          /*user_specified_locations=*/std::nullopt,
                          new_shortcut_info);
  // The shortcut with the old title should be deleted from the shortcut
  // dir, the taskbar dir, and the implicit apps subdir.
  std::vector<base::FilePath> result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, base::WideToUTF16(shortcut_name), kWebAppId);
  EXPECT_EQ(0u, result.size());
  result = FindAppShortcutsByProfileAppIdAndTitle(
      taskbar_dir, profile_path, base::WideToUTF16(shortcut_name), kWebAppId);
  EXPECT_EQ(0u, result.size());
  result = FindAppShortcutsByProfileAppIdAndTitle(
      implicit_apps_sub_dir, profile_path, base::WideToUTF16(shortcut_name),
      kWebAppId);
  EXPECT_EQ(0u, result.size());
  // The shortcut with the new title should be found in the shortcut dir, the
  // taskbar dir, and the implicit_apps subdir.
  result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, new_shortcut_info.title, kWebAppId);
  EXPECT_EQ(1u, result.size());
  result = FindAppShortcutsByProfileAppIdAndTitle(
      taskbar_dir, profile_path, new_shortcut_info.title, kWebAppId);
  EXPECT_EQ(1u, result.size());
  result = FindAppShortcutsByProfileAppIdAndTitle(
      implicit_apps_sub_dir, profile_path, new_shortcut_info.title, kWebAppId);
  EXPECT_EQ(1u, result.size());
}

TEST_F(WebAppShortcutWinTest, UpdatePlatformShortcutsAppIdentityChange) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath::StringType shortcut_name =
      FILE_PATH_LITERAL("test shortcut");

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/web_app"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  ASSERT_NO_FATAL_FAILURE(CreateAndVerifyTestAppShortcut(
      shortcut_name, shortcut_dir, profile_path));

  // Create a 16x16 icon file for the web app.
  const base::FilePath icon_file =
      GetIconFilePath(shortcut_dir, u"test shortcut");
  gfx::ImageFamily image_family;
  image_family.Add(gfx::Image(CreateDefaultApplicationIcon(16)));
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));

  // Update the web app with a new title and a new icon family.
  ShortcutInfo new_shortcut_info;
  new_shortcut_info.title = u"new title";
  new_shortcut_info.profile_path = profile_path;
  new_shortcut_info.profile_name = base::WideToUTF8(profile_name);
  new_shortcut_info.app_id = kWebAppId;
  gfx::ImageFamily new_image_family;
  new_image_family.Add(gfx::Image(CreateDefaultApplicationIcon(32)));
  new_shortcut_info.favicon = std::move(new_image_family);

  UpdatePlatformShortcuts(shortcut_dir, base::WideToUTF16(shortcut_name),
                          /*user_specified_locations=*/std::nullopt,
                          new_shortcut_info);

  // The shortcut with the old title should have been deleted.
  std::vector<base::FilePath> result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, base::WideToUTF16(shortcut_name), kWebAppId);
  EXPECT_EQ(0u, result.size());

  // When an app changes both title and icons, the icon file and shortcuts
  // should reflect that. For example, the old icon file and its checksum should
  // have been deleted:
  EXPECT_FALSE(base::PathExists(icon_file));
  EXPECT_FALSE(base::PathExists(base::FilePath(
      icon_file.ReplaceExtension(FILE_PATH_LITERAL(".ico.md5")))));

  // The shortcut with the new title should now be in the shortcut dir.
  result = FindAppShortcutsByProfileAppIdAndTitle(
      shortcut_dir, profile_path, new_shortcut_info.title, kWebAppId);
  EXPECT_EQ(1u, result.size());

  // A new icon file (and checksum) should have been created.
  const base::FilePath new_icon_file =
      GetIconFilePath(shortcut_dir, u"new title");
  EXPECT_TRUE(base::PathExists(base::FilePath(
      new_icon_file.ReplaceExtension(FILE_PATH_LITERAL(".ico.md5")))));

  // The shortcut should have been updated to use the new icon.
  using base::win::ShortcutProperties;
  ShortcutProperties shortcut_properties;
  base::FilePath shortcut_file =
      shortcut_dir.Append(FILE_PATH_LITERAL("new title.lnk"));
  EXPECT_TRUE(base::PathExists(shortcut_file));
  EXPECT_TRUE(base::win::ResolveShortcutProperties(
      shortcut_file, ShortcutProperties::PROPERTIES_ICON,
      &shortcut_properties));
  EXPECT_EQ(new_icon_file, shortcut_properties.icon);
}

TEST_F(WebAppShortcutWinTest, GetIconFilePath) {
  const base::FilePath web_app_path(FILE_PATH_LITERAL("test\\web\\app\\dir"));
  EXPECT_EQ(GetIconFilePath(web_app_path, u"test app name"),
            base::FilePath(
                FILE_PATH_LITERAL("test\\web\\app\\dir\\test app name.ico")));
  EXPECT_EQ(
      GetIconFilePath(web_app_path, u"***"),
      base::FilePath(FILE_PATH_LITERAL("test\\web\\app\\dir\\_   _.ico")));
  // Test reserved names on Windows.
  EXPECT_EQ(
      GetIconFilePath(web_app_path, u"COM1"),
      base::FilePath(FILE_PATH_LITERAL("test\\web\\app\\dir\\_COM1.ico")));
}

TEST_F(WebAppShortcutWinTest, UpdatePlatformShortcuts_PreventHijacking) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/web_app"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  // Create a shortcut for App A (kWebAppId) with name "old title.lnk".
  const base::FilePath::StringType old_title = FILE_PATH_LITERAL("old title");
  const base::FilePath old_shortcut_path =
      GetShortcutPath(shortcut_dir, old_title);
  ASSERT_TRUE(
      CreateTestAppShortcut(old_shortcut_path, profile_name, kWebAppId));

  // Create a colliding shortcut for App B (kWebAppId2) with name "new
  // title.lnk".
  const base::FilePath::StringType new_title = FILE_PATH_LITERAL("new title");
  const base::FilePath colliding_shortcut_path =
      GetShortcutPath(shortcut_dir, new_title);
  ASSERT_TRUE(
      CreateTestAppShortcut(colliding_shortcut_path, profile_name, kWebAppId2));

  // Create an icon file for the web app (App A) with old title.
  const base::FilePath icon_file = GetIconFilePath(shortcut_dir, u"old title");
  gfx::ImageFamily image_family;
  image_family.Add(gfx::Image(CreateDefaultApplicationIcon(5)));
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));

  // Update App A (kWebAppId) with a new title "new title".
  ShortcutInfo shortcut_info;
  shortcut_info.title = u"new title";
  shortcut_info.profile_path = profile_path;
  shortcut_info.profile_name = base::WideToUTF8(profile_name);
  shortcut_info.app_id = kWebAppId;
  shortcut_info.favicon = std::move(image_family);

  UpdatePlatformShortcuts(shortcut_dir, u"old title",
                          /*user_specified_locations=*/std::nullopt,
                          shortcut_info);

  // Assert that App B's shortcut is not overwritten (still owned by
  // kWebAppId2).
  EXPECT_TRUE(base::PathExists(colliding_shortcut_path));
  EXPECT_TRUE(IsAppShortcutForProfile(colliding_shortcut_path, profile_path,
                                      kWebAppId2));

  // Assert that App A's shortcut is renamed to "new title (1).lnk" (owned by
  // kWebAppId).
  const base::FilePath unique_shortcut_path =
      GetShortcutPath(shortcut_dir, FILE_PATH_LITERAL("new title (1)"));
  EXPECT_TRUE(base::PathExists(unique_shortcut_path));
  EXPECT_TRUE(
      IsAppShortcutForProfile(unique_shortcut_path, profile_path, kWebAppId));

  // Assert that App A's old shortcut "old title.lnk" is deleted.
  EXPECT_FALSE(base::PathExists(old_shortcut_path));
}

TEST_F(WebAppShortcutWinTest, UpdatePlatformShortcuts_OverwriteOwnApp) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/web_app"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  // Create a shortcut for App A (kWebAppId) with name "old title.lnk".
  const base::FilePath::StringType old_title = FILE_PATH_LITERAL("old title");
  const base::FilePath old_shortcut_path =
      GetShortcutPath(shortcut_dir, old_title);
  ASSERT_TRUE(
      CreateTestAppShortcut(old_shortcut_path, profile_name, kWebAppId));

  // Create another shortcut for App A (kWebAppId) with name "new title.lnk".
  const base::FilePath::StringType new_title = FILE_PATH_LITERAL("new title");
  const base::FilePath colliding_shortcut_path =
      GetShortcutPath(shortcut_dir, new_title);
  ASSERT_TRUE(
      CreateTestAppShortcut(colliding_shortcut_path, profile_name, kWebAppId));

  // Create an icon file for the web app (App A) with old title.
  const base::FilePath icon_file = GetIconFilePath(shortcut_dir, u"old title");
  gfx::ImageFamily image_family;
  image_family.Add(gfx::Image(CreateDefaultApplicationIcon(5)));
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));

  // Update App A (kWebAppId) with a new title "new title".
  ShortcutInfo shortcut_info;
  shortcut_info.title = u"new title";
  shortcut_info.profile_path = profile_path;
  shortcut_info.profile_name = base::WideToUTF8(profile_name);
  shortcut_info.app_id = kWebAppId;
  shortcut_info.favicon = std::move(image_family);

  UpdatePlatformShortcuts(shortcut_dir, u"old title",
                          /*user_specified_locations=*/std::nullopt,
                          shortcut_info);

  // Assert that "new title.lnk" is overwritten and remains owned by kWebAppId.
  EXPECT_TRUE(base::PathExists(colliding_shortcut_path));
  EXPECT_TRUE(IsAppShortcutForProfile(colliding_shortcut_path, profile_path,
                                      kWebAppId));

  // Assert that no "new title (1).lnk" is created.
  const base::FilePath unique_shortcut_path =
      GetShortcutPath(shortcut_dir, FILE_PATH_LITERAL("new title (1)"));
  EXPECT_FALSE(base::PathExists(unique_shortcut_path));

  // Assert that App A's old shortcut "old title.lnk" is deleted.
  EXPECT_FALSE(base::PathExists(old_shortcut_path));
}

TEST_F(WebAppShortcutWinTest, UpdatePlatformShortcuts_RepeatedUpdateAndRename) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath shortcut_dir = temp_dir.GetPath();

  const base::FilePath profile_path(FILE_PATH_LITERAL("test/profile/web_app"));
  const base::FilePath::StringType profile_name =
      profile_path.BaseName().value();

  // Create a shortcut for App A (kWebAppId) with name "old title.lnk".
  const base::FilePath::StringType old_title = FILE_PATH_LITERAL("old title");
  const base::FilePath old_shortcut_path =
      GetShortcutPath(shortcut_dir, old_title);
  ASSERT_TRUE(
      CreateTestAppShortcut(old_shortcut_path, profile_name, kWebAppId));

  // Create a colliding shortcut for App B (kWebAppId2) with name "new
  // title.lnk".
  const base::FilePath::StringType new_title = FILE_PATH_LITERAL("new title");
  const base::FilePath colliding_shortcut_path =
      GetShortcutPath(shortcut_dir, new_title);
  ASSERT_TRUE(
      CreateTestAppShortcut(colliding_shortcut_path, profile_name, kWebAppId2));

  // Create an icon file for the web app (App A) with old title.
  const base::FilePath icon_file = GetIconFilePath(shortcut_dir, u"old title");
  gfx::ImageFamily image_family;
  image_family.Add(gfx::Image(CreateDefaultApplicationIcon(5)));
  EXPECT_TRUE(CheckAndSaveIcon(icon_file, image_family,
                               /*refresh_shell_icon_cache=*/false));

  // Update App A (kWebAppId) with a new title "new title".
  ShortcutInfo shortcut_info;
  shortcut_info.title = u"new title";
  shortcut_info.profile_path = profile_path;
  shortcut_info.profile_name = base::WideToUTF8(profile_name);
  shortcut_info.app_id = kWebAppId;
  shortcut_info.favicon = std::move(image_family);

  UpdatePlatformShortcuts(shortcut_dir, u"old title",
                          /*user_specified_locations=*/std::nullopt,
                          shortcut_info);

  // Assert that App B's shortcut is not overwritten.
  EXPECT_TRUE(base::PathExists(colliding_shortcut_path));
  EXPECT_TRUE(IsAppShortcutForProfile(colliding_shortcut_path, profile_path,
                                      kWebAppId2));

  // Assert that App A's shortcut is renamed to "new title (1).lnk".
  const base::FilePath unique_shortcut_path =
      GetShortcutPath(shortcut_dir, FILE_PATH_LITERAL("new title (1)"));
  EXPECT_TRUE(base::PathExists(unique_shortcut_path));
  EXPECT_TRUE(
      IsAppShortcutForProfile(unique_shortcut_path, profile_path, kWebAppId));
  EXPECT_FALSE(base::PathExists(old_shortcut_path));

  // --- Repeated Update ---
  // Update App A (kWebAppId) with the same title "new title" again.
  // We need to re-create the icon family because std::move emptied it.
  gfx::ImageFamily image_family2;
  image_family2.Add(gfx::Image(CreateDefaultApplicationIcon(5)));
  shortcut_info.favicon = std::move(image_family2);

  // Note: old_app_title is now "new title" because that is the current title.
  UpdatePlatformShortcuts(shortcut_dir, u"new title",
                          /*user_specified_locations=*/std::nullopt,
                          shortcut_info);

  // Assert that we still use "new title (1).lnk" and NOT "new title (2).lnk".
  EXPECT_TRUE(base::PathExists(unique_shortcut_path));
  EXPECT_TRUE(
      IsAppShortcutForProfile(unique_shortcut_path, profile_path, kWebAppId));

  const base::FilePath unique_shortcut_path2 =
      GetShortcutPath(shortcut_dir, FILE_PATH_LITERAL("new title (2)"));
  EXPECT_FALSE(base::PathExists(unique_shortcut_path2));

  // --- Rename ---
  // Rename App A to "new new title".
  shortcut_info.title = u"new new title";
  gfx::ImageFamily image_family3;
  image_family3.Add(gfx::Image(CreateDefaultApplicationIcon(5)));
  shortcut_info.favicon = std::move(image_family3);

  UpdatePlatformShortcuts(shortcut_dir, u"new title",
                          /*user_specified_locations=*/std::nullopt,
                          shortcut_info);

  // Assert that "new title (1).lnk" is deleted.
  EXPECT_FALSE(base::PathExists(unique_shortcut_path));

  // Assert that "new new title.lnk" is created.
  const base::FilePath final_shortcut_path =
      GetShortcutPath(shortcut_dir, FILE_PATH_LITERAL("new new title"));
  EXPECT_TRUE(base::PathExists(final_shortcut_path));
  EXPECT_TRUE(
      IsAppShortcutForProfile(final_shortcut_path, profile_path, kWebAppId));
}

}  // namespace web_app::internals
