Use native candidate actions and harden frontend resource loading
This commit is contained in:
1 parent
99fe8f589f
commit
d8e780dfa1
18 files changed
+1083
-227
No files matched your search
@@ -0,0 +1,167 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "fcitx-utils/environ.h"
|
||||
#include "fcitx-utils/log.h"
|
||||
#include "fcitx-utils/standardpaths.h"
|
||||
#include "fcitx/icontheme.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using fcitx::IconTheme;
|
||||
using fcitx::setEnvironment;
|
||||
using fcitx::StandardPaths;
|
||||
using fcitx::StandardPathsOption;
|
||||
|
||||
void word(std::vector<uint8_t> &bytes, size_t offset, uint32_t value) {
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
bytes.at(offset + 3 - i) = (value >> (8 * i)) & 255;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> validCache() {
|
||||
std::vector<uint8_t> bytes(128);
|
||||
bytes[1] = 1; // version 1.0
|
||||
word(bytes, 4, 16); // hash table
|
||||
word(bytes, 8, 48); // directory table
|
||||
word(bytes, 16, 1); // buckets
|
||||
word(bytes, 20, 24); // first node
|
||||
word(bytes, 24, UINT32_MAX); // end of chain
|
||||
word(bytes, 28, 64); // icon name
|
||||
word(bytes, 32, 80); // image list
|
||||
word(bytes, 48, 1);
|
||||
word(bytes, 52, 96); // directory string
|
||||
const std::string name = "audit-missing";
|
||||
std::copy(name.begin(), name.end(), bytes.begin() + 64);
|
||||
word(bytes, 80, 1); // one image in directory index zero
|
||||
const std::string dir = "16x16/apps";
|
||||
std::copy(dir.begin(), dir.end(), bytes.begin() + 96);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void save(const fs::path &path, const std::vector<uint8_t> &bytes) {
|
||||
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
||||
out.write(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||
out.close();
|
||||
FCITX_ASSERT(out.good());
|
||||
fs::last_write_time(path, fs::file_time_type::clock::now() +
|
||||
std::chrono::seconds(10));
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto pattern = (fs::temp_directory_path() / "fcitx-icon-cache-test-XXXXXX").string();
|
||||
auto *temporary = mkdtemp(pattern.data());
|
||||
FCITX_ASSERT(temporary);
|
||||
const fs::path root(temporary);
|
||||
// The fixture is private and all recursive cleanup is limited to this root.
|
||||
struct Cleanup {
|
||||
fs::path root;
|
||||
~Cleanup() { fs::remove_all(root); }
|
||||
} cleanup{root};
|
||||
setEnvironment("HOME", (root / "home").c_str());
|
||||
setEnvironment("XDG_DATA_HOME", (root / "data").c_str());
|
||||
setEnvironment("XDG_DATA_DIRS", (root / "empty").c_str());
|
||||
StandardPaths paths("fcitx5", {}, StandardPathsOption::SkipBuiltInPath);
|
||||
const auto themeDir = root / "data/icons/audit";
|
||||
fs::create_directories(themeDir / "16x16/apps");
|
||||
std::ofstream(themeDir / "index.theme")
|
||||
<< "[Icon Theme]\nName=Audit\nDirectories=16x16/apps\n"
|
||||
"[16x16/apps]\nSize=16\nType=Fixed\n";
|
||||
const auto icon = themeDir / "16x16/apps/audit-missing.png";
|
||||
std::ofstream(icon) << "lookup only";
|
||||
const auto cache = themeDir / "icon-theme.cache";
|
||||
auto check = [&](const std::vector<uint8_t> &bytes) {
|
||||
save(cache, bytes);
|
||||
IconTheme theme(std::string("audit"), paths);
|
||||
FCITX_ASSERT(theme.findIconPath("audit-missing", 16, 1, {".png"}) == icon);
|
||||
FCITX_ASSERT(theme.findIconPath("audit-missing", 16, 1, {".png"}) == icon);
|
||||
};
|
||||
check(validCache());
|
||||
auto bytes = validCache();
|
||||
// Distinguish cache-assisted lookup from a parser that always ignores it.
|
||||
word(bytes, 80, 0);
|
||||
save(cache, bytes);
|
||||
{
|
||||
IconTheme theme(std::string("audit"), paths);
|
||||
FCITX_ASSERT(theme.findIconPath("audit-missing", 16, 1, {".png"}).empty());
|
||||
}
|
||||
for (uint32_t offset : {UINT32_MAX, UINT32_MAX - 3, uint32_t(126)}) {
|
||||
bytes = validCache();
|
||||
word(bytes, 4, offset);
|
||||
check(bytes);
|
||||
}
|
||||
bytes = validCache();
|
||||
word(bytes, 16, UINT32_MAX); // overflowing hash table length
|
||||
check(bytes);
|
||||
bytes = validCache();
|
||||
word(bytes, 8, UINT32_MAX - 3); // overflowing directory table offset
|
||||
check(bytes);
|
||||
bytes = validCache();
|
||||
word(bytes, 24, 24); // self cycle on a nonmatching name
|
||||
bytes[64] = 'x';
|
||||
check(bytes);
|
||||
bytes = validCache();
|
||||
word(bytes, 24, 36); // two-node cycle
|
||||
word(bytes, 36, 24);
|
||||
word(bytes, 40, 64);
|
||||
word(bytes, 44, 80);
|
||||
bytes[64] = 'x';
|
||||
check(bytes);
|
||||
bytes = validCache();
|
||||
word(bytes, 80, UINT32_MAX); // overflowing image count
|
||||
check(bytes);
|
||||
bytes = validCache();
|
||||
bytes[85] = 2; // invalid directory index
|
||||
check(bytes);
|
||||
bytes = validCache();
|
||||
bytes.resize(1200, 'x');
|
||||
word(bytes, 28, 128); // no terminating NUL in 1024 bytes
|
||||
check(bytes);
|
||||
for (size_t length = 0; length < 128; ++length) {
|
||||
bytes = validCache();
|
||||
bytes.resize(length);
|
||||
check(bytes);
|
||||
}
|
||||
// Deterministic mutation smoke test: no assumptions about whether each
|
||||
// mutated cache is valid, but neither lookup may hang or access bad memory.
|
||||
std::mt19937 random(0x1c0);
|
||||
for (int i = 0; i < 512; ++i) {
|
||||
bytes = validCache();
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
bytes[random() % bytes.size()] = random() & 255;
|
||||
}
|
||||
save(cache, bytes);
|
||||
IconTheme theme(std::string("audit"), paths);
|
||||
theme.findIconPath("audit-missing", 16, 1, {".png"});
|
||||
theme.findIconPath("audit-missing", 32, 2, {".png"});
|
||||
}
|
||||
save(cache, validCache());
|
||||
{
|
||||
IconTheme theme(std::string("audit"), paths);
|
||||
std::ofstream(cache, std::ios::trunc).close();
|
||||
FCITX_ASSERT(theme.findIconPath("audit-missing", 16, 1, {".png"}) == icon);
|
||||
}
|
||||
fs::remove(cache);
|
||||
FCITX_ASSERT(mkfifo(cache.c_str(), 0600) == 0);
|
||||
{
|
||||
IconTheme theme(std::string("audit"), paths);
|
||||
FCITX_ASSERT(theme.findIconPath("audit-missing", 16, 1, {".png"}) == icon);
|
||||
}
|
||||
fs::remove(cache);
|
||||
// Oversized caches are ignored without loading their contents.
|
||||
save(cache, validCache());
|
||||
fs::resize_file(cache, 16 * 1024 * 1024 + 1);
|
||||
{
|
||||
IconTheme theme(std::string("audit"), paths);
|
||||
FCITX_ASSERT(theme.findIconPath("audit-missing", 16, 1, {".png"}) == icon);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user