Skip to content

Commit 4742eeb

Browse files
committed
fix(launcher): avoid appimage origin from path shadowing
#3975
1 parent 279d8af commit 4742eeb

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

src/system/desktop_entry.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ namespace {
2626

2727
constexpr Logger kLog("desktop_entry");
2828

29+
bool isUserDesktopEntry(const fs::path& filepath) {
30+
fs::path dataHome;
31+
if (const char* configured = std::getenv("XDG_DATA_HOME"); configured != nullptr && configured[0] != '\0') {
32+
dataHome = configured;
33+
} else if (const char* home = std::getenv("HOME"); home != nullptr && home[0] != '\0') {
34+
dataHome = fs::path(home) / ".local/share";
35+
} else {
36+
return false;
37+
}
38+
39+
const fs::path applications = (dataHome / "applications").lexically_normal();
40+
const fs::path normalized = filepath.lexically_normal();
41+
const auto mismatch = std::ranges::mismatch(applications, normalized);
42+
return mismatch.in1 == applications.end();
43+
}
44+
2945
fs::path resolveExecutable(std::string_view executable) {
3046
const fs::path path(executable);
3147
if (path.has_parent_path()) {
@@ -56,7 +72,7 @@ namespace {
5672
return {};
5773
}
5874

59-
bool executableIsAppImage(std::string_view exec) {
75+
bool executableIsAppImage(std::string_view exec, bool resolveFromPath) {
6076
const auto start = exec.find_first_not_of(' ');
6177
if (start == std::string_view::npos) {
6278
return false;
@@ -74,7 +90,10 @@ namespace {
7490
return true;
7591
}
7692

77-
std::ifstream file(resolveExecutable(executable.string()), std::ios::binary);
93+
const fs::path resolved = executable.has_parent_path()
94+
? executable
95+
: (resolveFromPath ? resolveExecutable(executable.string()) : fs::path{});
96+
std::ifstream file(resolved, std::ios::binary);
7897
std::array<char, 10> header{};
7998
if (!file.read(header.data(), static_cast<std::streamsize>(header.size()))) {
8099
return false;
@@ -419,7 +438,8 @@ namespace {
419438
entry.startupWmClassLower = StringUtils::toLower(entry.startupWmClass);
420439
entry.idLower = StringUtils::toLower(entry.id);
421440
entry.execLower = StringUtils::toLower(entry.exec);
422-
entry.origin = detectOrigin(filepath, hasAppImageMetadata || executableIsAppImage(entry.exec));
441+
entry.origin =
442+
detectOrigin(filepath, hasAppImageMetadata || executableIsAppImage(entry.exec, isUserDesktopEntry(filepath)));
423443

424444
// Build actions in the declared order.
425445
for (const auto& id : actionOrder) {

tests/app_identity_test.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ namespace {
9696

9797
const fs::path root = fs::temp_directory_path() / ("noctalia-appimage-origin-" + std::to_string(getpid()));
9898
const fs::path applications = root / "data/applications";
99+
const fs::path systemApplications = root / "system/applications";
99100
const fs::path executable = root / "PortableApp";
100101
const fs::path bin = root / "bin";
101102
const fs::path workingDirectory = root / "cwd";
102103
fs::create_directories(applications);
104+
fs::create_directories(systemApplications);
103105
fs::create_directories(bin);
104106
fs::create_directories(workingDirectory);
105107
{
@@ -131,6 +133,7 @@ namespace {
131133
binary << "#!/bin/sh\nexit 0\n";
132134
chmod((bin / "native-app").c_str(), 0700);
133135
fs::create_symlink(executable, bin / "path-appimage");
136+
fs::create_symlink(executable, bin / "shadowed-native");
134137
fs::create_symlink(executable, workingDirectory / "native-app");
135138
}
136139
{
@@ -141,6 +144,10 @@ namespace {
141144
std::ofstream entry(applications / "path.desktop");
142145
entry << "[Desktop Entry]\nType=Application\nName=PATH AppImage\nExec=path-appimage\n";
143146
}
147+
{
148+
std::ofstream entry(systemApplications / "shadowed-native.desktop");
149+
entry << "[Desktop Entry]\nType=Application\nName=Shadowed Native App\nExec=shadowed-native\n";
150+
}
144151

145152
const char* oldDataHome = std::getenv("XDG_DATA_HOME");
146153
const char* oldDataDirs = std::getenv("XDG_DATA_DIRS");
@@ -153,7 +160,7 @@ namespace {
153160
oldPath == nullptr ? std::nullopt : std::optional<std::string>(oldPath);
154161
const fs::path savedWorkingDirectory = fs::current_path();
155162
setenv("XDG_DATA_HOME", (root / "data").c_str(), 1);
156-
setenv("XDG_DATA_DIRS", (root / "empty").c_str(), 1);
163+
setenv("XDG_DATA_DIRS", (root / "system").c_str(), 1);
157164
setenv("PATH", bin.c_str(), 1);
158165
fs::current_path(workingDirectory);
159166

@@ -167,6 +174,7 @@ namespace {
167174
TEST_CHECK(findOrigin("suffix") == DesktopEntryOrigin::AppImage);
168175
TEST_CHECK(findOrigin("native") == DesktopEntryOrigin::System);
169176
TEST_CHECK(findOrigin("path") == DesktopEntryOrigin::AppImage);
177+
TEST_CHECK(findOrigin("shadowed-native") == DesktopEntryOrigin::System);
170178

171179
fs::current_path(savedWorkingDirectory);
172180

0 commit comments

Comments
 (0)