Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions libs/linglong/src/linglong/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@
#include "linglong/utils/file.h"
#include "linglong/utils/finally/finally.h"
#include "linglong/utils/gettext.h"
#include "linglong/utils/namespace.h"

Check warning on line 43 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/utils/namespace.h" not found.
#include "linglong/utils/runtime_config.h"

Check warning on line 44 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/utils/runtime_config.h" not found.
#include "linglong/utils/xdg/directory.h"

Check warning on line 45 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/utils/xdg/directory.h" not found.
#include "linglong/utils/xdp.h"

Check warning on line 46 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "ocppi/runtime/ExecOption.hpp" not found.
#include "ocppi/runtime/ExecOption.hpp"

Check warning on line 47 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "ocppi/runtime/RunOption.hpp" not found.
#include "ocppi/runtime/RunOption.hpp"

Check warning on line 48 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "ocppi/runtime/Signal.hpp" not found.
#include "ocppi/runtime/Signal.hpp"

Check warning on line 49 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "ocppi/types/ContainerListItem.hpp" not found.
#include "ocppi/types/ContainerListItem.hpp"

#include <fmt/ranges.h>
Expand Down Expand Up @@ -867,6 +868,14 @@
return -1;
}

const auto &appid = runContext.getTargetID();
if (!options.disableXdp.has_value() && !utils::isValidXdgDesktopPortalId(appid)) {
LogW("appid '{}' doesn't conform to XDP ID specification, disabling XDP integration. "
"Use --enable-xdp to override.",
appid);
runOptions.disableXdp = true;
}

auto container = this->containerBuilder.createRunContainer(runContext, runOptions);
if (!container) {
this->printer.printErr(container.error());
Expand Down
1 change: 1 addition & 0 deletions libs/linglong/tests/ll-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pfl_add_executable(
src/linglong/utils/sha256_test.cpp
src/linglong/utils/transaction_test.cpp
src/linglong/utils/xdg/directory_test.cpp
src/linglong/utils/xdp_test.cpp
src/main.cpp


Expand Down
101 changes: 101 additions & 0 deletions libs/linglong/tests/ll-tests/src/linglong/utils/xdp_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/

#include <gtest/gtest.h>

#include "linglong/utils/xdp.h"

using namespace linglong::utils;

TEST(IsValidXdgDesktopPortalIdTest, ValidStandardId)
{
EXPECT_TRUE(isValidXdgDesktopPortalId("org.example.app"));
EXPECT_TRUE(isValidXdgDesktopPortalId("com.deepin.calculator"));
EXPECT_TRUE(isValidXdgDesktopPortalId("org.freedesktop.portal"));
}

TEST(IsValidXdgDesktopPortalIdTest, ValidWithHyphenInLastSegment)
{
EXPECT_TRUE(isValidXdgDesktopPortalId("org.example.my-app"));
EXPECT_TRUE(isValidXdgDesktopPortalId("com.deepin.app-launcher"));
}

TEST(IsValidXdgDesktopPortalIdTest, ValidWithUnderscore)
{
EXPECT_TRUE(isValidXdgDesktopPortalId("org.example_my.app"));
EXPECT_TRUE(isValidXdgDesktopPortalId("com.deepin.my_app"));
}

TEST(IsValidXdgDesktopPortalIdTest, ValidWithDigits)
{
EXPECT_TRUE(isValidXdgDesktopPortalId("org.example.app2"));
EXPECT_TRUE(isValidXdgDesktopPortalId("org.2example.app"));
}

TEST(IsValidXdgDesktopPortalIdTest, ValidMultiSegment)
{
EXPECT_TRUE(isValidXdgDesktopPortalId("org.deepin.linglong.app"));
EXPECT_TRUE(isValidXdgDesktopPortalId("a.b.c.d.e"));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidSingleSegment)
{
EXPECT_FALSE(isValidXdgDesktopPortalId("single"));
EXPECT_FALSE(isValidXdgDesktopPortalId("app"));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidEmptyString)
{
EXPECT_FALSE(isValidXdgDesktopPortalId(""));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidHyphenInNonLastSegment)
{
EXPECT_FALSE(isValidXdgDesktopPortalId("org.my-app.app"));
EXPECT_FALSE(isValidXdgDesktopPortalId("my-org.example.app"));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidEmptySegment)
{
EXPECT_FALSE(isValidXdgDesktopPortalId("org..app"));
EXPECT_FALSE(isValidXdgDesktopPortalId(".org.app"));
EXPECT_FALSE(isValidXdgDesktopPortalId("org.app."));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidSpecialChars)
{
EXPECT_FALSE(isValidXdgDesktopPortalId("org.example.app!"));
EXPECT_FALSE(isValidXdgDesktopPortalId("org.example.app@home"));
EXPECT_FALSE(isValidXdgDesktopPortalId("org/example/app"));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidSlashInId)
{
EXPECT_FALSE(isValidXdgDesktopPortalId("org.deepin.calculator/stable"));
EXPECT_FALSE(isValidXdgDesktopPortalId("org/deepin/app"));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidHyphenInMultipleNonLastSegments)
{
EXPECT_FALSE(isValidXdgDesktopPortalId("my-org.my-app.app"));
}

TEST(IsValidXdgDesktopPortalIdTest, ValidHyphenOnlyInLastSegment)
{
EXPECT_TRUE(isValidXdgDesktopPortalId("org.example.a-b"));
EXPECT_TRUE(isValidXdgDesktopPortalId("org.example.a-b-c"));
}

TEST(IsValidXdgDesktopPortalIdTest, InvalidLastSegmentEmpty)
{
EXPECT_FALSE(isValidXdgDesktopPortalId("org.example."));
}

TEST(IsValidXdgDesktopPortalIdTest, TwoSegmentsValid)
{
EXPECT_TRUE(isValidXdgDesktopPortalId("org.app"));
EXPECT_TRUE(isValidXdgDesktopPortalId("a.b"));
}
1 change: 1 addition & 0 deletions libs/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pfl_add_library(
src/linglong/utils/transaction.h
src/linglong/utils/xdg/directory.cpp
src/linglong/utils/xdg/directory.h
src/linglong/utils/xdp.h
COMPILE_FEATURES
PUBLIC
cxx_std_17
Expand Down
53 changes: 53 additions & 0 deletions libs/utils/src/linglong/utils/xdp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/

#pragma once

#include <algorithm>
#include <cctype>
#include <string_view>
#include <vector>
Comment on lines +9 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the optimized implementation of isValidXdgDesktopPortalId (suggested below) is adopted, the <algorithm>, <cctype>, and <vector> headers are no longer necessary, and only <string_view> is required.

Suggested change
#include <algorithm>
#include <cctype>
#include <string_view>
#include <vector>
#include <string_view>


namespace linglong::utils {

inline bool isValidXdgDesktopPortalId(std::string_view appid) noexcept
{
std::vector<std::string_view> segments;

size_t start = 0;
for (size_t i = 0; i < appid.size(); ++i) {
if (appid[i] == '.') {
segments.push_back(appid.substr(start, i - start));
start = i + 1;
}
}
segments.push_back(appid.substr(start));

if (segments.size() < 2) {
return false;
}

for (size_t i = 0; i + 1 < segments.size(); ++i) {
if (segments[i].empty()) {
return false;
}
if (!std::all_of(segments[i].begin(), segments[i].end(), [](char c) {
return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
})) {
return false;
}
}

const auto &last = segments.back();
if (last.empty()) {
return false;
}
return std::all_of(last.begin(), last.end(), [](char c) {
return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '-';
});
}
Comment on lines +16 to +51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of isValidXdgDesktopPortalId is inefficient as it allocates a std::vector and performs multiple passes over the string. It can be implemented in a single pass without heap allocations.

Additionally, std::isalnum is locale-dependent. For protocol-level validation like D-Bus/XDP IDs, which are strictly ASCII, it is safer and more idiomatic to use explicit range checks.

Note: According to the D-Bus and Desktop Entry specifications, segments of a well-known name must not begin with a digit. The current implementation allows this (e.g., org.2example.app). If strict conformance is required, you should add a check to ensure segments do not start with a digit (and update the corresponding test case).

inline bool isValidXdgDesktopPortalId(std::string_view appid) noexcept
{
    if (appid.empty()) {
        return false;
    }

    size_t segmentCount = 0;
    size_t start = 0;
    for (size_t i = 0; i <= appid.size(); ++i) {
        if (i == appid.size() || appid[i] == '.') {
            std::string_view segment = appid.substr(start, i - start);
            if (segment.empty()) {
                return false;
            }

            bool isLast = (i == appid.size());
            for (char c : segment) {
                bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_');
                if (isLast && c == '-') {
                    ok = true;
                }
                if (!ok) {
                    return false;
                }
            }

            segmentCount++;
            start = i + 1;
        }
    }

    return segmentCount >= 2;
}


} // namespace linglong::utils
Loading