Don't allow to install third-party themes that override the default one (fix #4226) (#4335)

This 1) hides user themes whose name is the same as the default,
and are present in the user folders (i.e.  'extensions' and
'data/themes' folders), and 2) doesn't allow to install themes with
the same content/ID of the default aseprite-theme (fix #4226)
This commit is contained in:
Gaspar Capello 2024-07-29 16:02:05 -03:00 committed by GitHub
parent b65fb3a14e
commit 8f3af748b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 73 additions and 5 deletions

View File

@ -90,6 +90,7 @@ job_working = {0}<<Working...||&Cancel
nothing_to_report = Crash Report<<Nothing to report||&OK nothing_to_report = Crash Report<<Nothing to report||&OK
install_extension = Warning\n<<Do you really want to install the given extension?\n<<"{0}"\n||&Install||&Cancel install_extension = Warning\n<<Do you really want to install the given extension?\n<<"{0}"\n||&Install||&Cancel
uninstall_extension_warning = Warning\n<<Do you really want to uninstall "{0}" extension?\n||&Yes||&No uninstall_extension_warning = Warning\n<<Do you really want to uninstall "{0}" extension?\n||&Yes||&No
cannot_install_default_extension = Error<<This extension cannot be installed because tries to replace the default theme.<<Contact the developer so they fix the theme ID or theme name string<<from \"default\" to something else.||&OK
unknown_output_file_format_error = Aseprite\n<<Unknown file format "{0}" in output filename\n||&OK unknown_output_file_format_error = Aseprite\n<<Unknown file format "{0}" in output filename\n||&OK
update_screen_ui_scaling_with_theme_values = Update Screen/UI Scaling\n<<The new theme "{0}" wants to adjust some values for you:\n<< Screen Scaling: {1}% -> {2}%\n<< UI Scaling: {3}% -> {4}%\n<<Allow these changes?\n||&Adjust Scaling||&Don't Adjust Scaling update_screen_ui_scaling_with_theme_values = Update Screen/UI Scaling\n<<The new theme "{0}" wants to adjust some values for you:\n<< Screen Scaling: {1}% -> {2}%\n<< UI Scaling: {3}% -> {4}%\n<<Allow these changes?\n||&Adjust Scaling||&Don't Adjust Scaling
update_extension = Update Extension\n<<The extension "{0}" already exists.\n<<Do you want to {1} from v{2} to v{3}?\n||&Yes||&No update_extension = Update Extension\n<<The extension "{0}" already exists.\n<<Do you want to {1} from v{2} to v{3}?\n||&Yes||&No

View File

@ -926,6 +926,17 @@ public:
} }
} }
// Get the extension information from the compressed
// package.json file.
const ExtensionInfo info =
App::instance()->extensions().getCompressedExtensionInfo(filename);
// Check if the filename corresponds to aseprite-default theme
if (base::string_to_lower(info.name) ==
Extension::kAsepriteDefaultThemeExtensionName) {
ui::Alert::show(Strings::alerts_cannot_install_default_extension());
return false;
}
// Install? // Install?
if (ui::Alert::show(Strings::alerts_install_extension(filename)) != 1) if (ui::Alert::show(Strings::alerts_install_extension(filename)) != 1)
return false; return false;
@ -1378,7 +1389,8 @@ private:
if (!ext->isEnabled()) if (!ext->isEnabled())
continue; continue;
if (ext->themes().empty()) if (ext->themes().empty() ||
isExtensionADuplicatedDefaultTheme(ext))
continue; continue;
if (first) { if (first) {
@ -1410,6 +1422,9 @@ private:
extensionsList()->addChild(sep); extensionsList()->addChild(sep);
for (auto e : App::instance()->extensions()) { for (auto e : App::instance()->extensions()) {
if (e->category() == category) { if (e->category() == category) {
if (category == Extension::Category::Themes &&
isExtensionADuplicatedDefaultTheme(e))
continue;
ExtensionItem* item = new ExtensionItem(e); ExtensionItem* item = new ExtensionItem(e);
extensionsList()->addChild(item); extensionsList()->addChild(item);
hasItems = true; hasItems = true;
@ -1571,6 +1586,10 @@ private:
// package.json file. // package.json file.
ExtensionInfo info = exts.getCompressedExtensionInfo(filename); ExtensionInfo info = exts.getCompressedExtensionInfo(filename);
if (info.defaultTheme) {
ui::Alert::show(Strings::alerts_cannot_install_default_extension());
return;
}
// Check if the extension already exist // Check if the extension already exist
for (auto ext : exts) { for (auto ext : exts) {
if (base::string_to_lower(ext->name()) != if (base::string_to_lower(ext->name()) !=
@ -1749,6 +1768,17 @@ private:
return paths; return paths;
} }
static base::paths getUserDirPaths(const base::paths& dirNames) {
ResourceFinder rf;
for (auto& fn : dirNames)
rf.includeUserDir(fn.c_str());
base::paths paths;
while (rf.next())
paths.push_back(base::normalize_path(rf.filename()));
return paths;
}
void updateCategoryVisibility() { void updateCategoryVisibility() {
bool visibleCategories[int(Extension::Category::Max)]; bool visibleCategories[int(Extension::Category::Max)];
for (auto& v : visibleCategories) for (auto& v : visibleCategories)
@ -1764,6 +1794,20 @@ private:
} }
} }
// Function to determine if the input extension is the default theme
static bool isExtensionADuplicatedDefaultTheme(const Extension* e) {
if (!e->isDefaultTheme())
return false;
auto userThemePaths =
getUserDirPaths({"extensions", skin::SkinTheme::kThemesFolderName});
for (auto& p : userThemePaths) {
// Has the user path (p) the same path of the extension (e->path())?
if (std::strncmp(e->path().c_str(), p.c_str(), p.size()) == 0)
return true;
}
return false;
}
#ifdef LAF_WINDOWS #ifdef LAF_WINDOWS
void onTabletAPIChange() { void onTabletAPIChange() {
const bool pointerApi = tabletApiWindowsPointer()->isSelected(); const bool pointerApi = tabletApiWindowsPointer()->isSelected();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020-2023 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello // Copyright (C) 2017-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -51,12 +51,14 @@
namespace app { namespace app {
const char* Extension::kAsepriteDefaultThemeExtensionName = "aseprite-theme";
const char* Extension::kAsepriteDefaultThemeId = "default";
namespace { namespace {
const char* kPackageJson = "package.json"; const char* kPackageJson = "package.json";
const char* kInfoJson = "__info.json"; const char* kInfoJson = "__info.json";
const char* kPrefLua = "__pref.lua"; const char* kPrefLua = "__pref.lua";
const char* kAsepriteDefaultThemeExtensionName = "aseprite-theme";
class ReadArchive { class ReadArchive {
public: public:
@ -283,6 +285,8 @@ void Extension::addTheme(const std::string& id,
const std::string& path, const std::string& path,
const std::string& variant) const std::string& variant)
{ {
if (id == kAsepriteDefaultThemeId && !isDefaultTheme())
return;
m_themes[id] = ThemeInfo(path, variant); m_themes[id] = ThemeInfo(path, variant);
updateCategory(Category::Themes); updateCategory(Category::Themes);
} }
@ -1003,6 +1007,21 @@ ExtensionInfo Extensions::getCompressedExtensionInfo(const std::string& zipFn)
std::string err; std::string err;
auto json = json11::Json::parse(out.str(), err); auto json = json11::Json::parse(out.str(), err);
if (err.empty()) { if (err.empty()) {
if (json["contributes"].is_object()) {
auto themes = json["contributes"]["themes"];
if (json["name"].string_value() == Extension::kAsepriteDefaultThemeExtensionName)
info.defaultTheme = true;
else {
if (themes.is_array()) {
for (int i = 0; i < themes.array_items().size(); i++) {
if (themes[i]["id"].string_value() == Extension::kAsepriteDefaultThemeId) {
info.defaultTheme = true;
break;
}
}
}
}
}
info.name = json["name"].string_value(); info.name = json["name"].string_value();
info.version = json["version"].string_value(); info.version = json["version"].string_value();
info.dstPath = base::join_path(m_userExtensionsPath, info.name); info.dstPath = base::join_path(m_userExtensionsPath, info.name);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020-2023 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello // Copyright (C) 2017-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -34,6 +34,7 @@ namespace app {
std::string version; std::string version;
std::string dstPath; std::string dstPath;
std::string commonPath; std::string commonPath;
bool defaultTheme = false;
}; };
enum DeletePluginPref { kNo, kYes }; enum DeletePluginPref { kNo, kYes };
@ -41,6 +42,8 @@ namespace app {
class Extension { class Extension {
friend class Extensions; friend class Extensions;
public: public:
static const char* kAsepriteDefaultThemeExtensionName;
static const char* kAsepriteDefaultThemeId;
enum class Category { enum class Category {
None, None,
@ -54,6 +57,8 @@ namespace app {
Max Max
}; };
bool isDefaultTheme() const;
class DitheringMatrixInfo { class DitheringMatrixInfo {
public: public:
DitheringMatrixInfo(); DitheringMatrixInfo();
@ -150,7 +155,6 @@ namespace app {
void uninstall(const DeletePluginPref delPref); void uninstall(const DeletePluginPref delPref);
void uninstallFiles(const std::string& path, void uninstallFiles(const std::string& path,
const DeletePluginPref delPref); const DeletePluginPref delPref);
bool isDefaultTheme() const;
void updateCategory(const Category newCategory); void updateCategory(const Category newCategory);
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
void initScripts(); void initScripts();