mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 19:31:14 +00:00
Add a ResourceTexture plugin capability for height-map texture packs
A fifth Python plugin capability type, ResourceTexture, contributes height-map textures to the Texture Displacement picker. A pack returns a directory of images; the host validates it once at load and lists the images under a section of their own. - New capability type ResourceTexture, type string "resource-texture", exposed to plugin authors as orca.resource_texture.ResourceTexturePluginCapabilityBase with one required method, get_texture_dir(). - The directory is resolved once on the load worker, under the GIL, and cached as a plain string: the picker runs inside the ImGui frame, where a trampoline call would block on the GIL behind a load/unload worker, or throw past EndPopup. - Validation resolves the directory and requires containment in the plugin root, re-checked per file so a symlink inside the pack cannot resolve outside it. Containment is the only security control here and it guards exfiltration: layer bytes are embedded in every saved .3mf, so a pack that could return ~/.ssh/id_rsa would leak it through any shared project. - New host registry PluginResourceTextures, filled from both the plugin and the capability lifecycle callbacks plus a startup sweep. It holds strings only and never a capability reference, so nothing outlives Py_Finalize. - The picker lists pack textures read-only, one collapsible section per pack, in a fixed-size scrolling popup whose height follows the window. A generation counter invalidates the cached listing, and each gizmo's cached thumbnails, when the set of packs changes. - Tests in tests/slic3rutils/test_plugin_resource_texture.cpp cover the validator (parent-directory and absolute escapes, an escaping symlink, the extension whitelist, the 64-file cap), the listing and its load/disable/re-enable/unload lifecycle, hostile get_texture_dir() overrides, decode parity with hand-picked files, and a .3mf round trip with the pack uninstalled. No persistence work is needed: a layer carries its own image bytes and 3MF writes them into the archive, so uninstalling a pack leaves existing projects intact. Behaviour change worth noting for review: the picker popup is now a fixed size that scrolls, rather than sizing itself to its content, and it gains one collapsible section per pack. That sizing applies to every user, not only those with packs installed.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <boost/filesystem/directory.hpp>
|
||||
#include <boost/filesystem/file_status.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include "PluginAuditManager.hpp"
|
||||
@@ -1255,4 +1256,76 @@ boost::filesystem::path find_installed_plugin_entry(const boost::filesystem::pat
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> collect_resource_texture_files(const std::string& dir,
|
||||
const std::string& plugin_root,
|
||||
std::string& error)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Picker sanity bound, not a security control: the popup draws every entry in its grid.
|
||||
constexpr size_t kMaxResourceTextureFiles = 64;
|
||||
|
||||
std::vector<std::string> files;
|
||||
error.clear();
|
||||
|
||||
if (dir.empty()) {
|
||||
error = _u8L("Resource texture directory is not set");
|
||||
return files;
|
||||
}
|
||||
if (plugin_root.empty()) {
|
||||
error = _u8L("Plugin folder could not be determined.");
|
||||
return files;
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
const fs::path resolved_dir = fs::weakly_canonical(fs::path(dir), ec);
|
||||
if (ec) {
|
||||
error = _u8L("Resource texture directory could not be resolved");
|
||||
return files;
|
||||
}
|
||||
|
||||
if (!fs::is_directory(resolved_dir, ec) || ec) {
|
||||
error = _u8L("Resource texture path is not a directory");
|
||||
return files;
|
||||
}
|
||||
|
||||
const fs::path resolved_root = fs::weakly_canonical(fs::path(plugin_root), ec);
|
||||
if (ec) {
|
||||
error = _u8L("Plugin folder could not be resolved");
|
||||
return files;
|
||||
}
|
||||
|
||||
if (!is_inside_allowed_root(resolved_dir, resolved_root)) {
|
||||
error = _u8L("Resource texture directory is outside the plugin folder");
|
||||
return files;
|
||||
}
|
||||
|
||||
for (fs::directory_iterator it(resolved_dir, ec), end; it != end && !ec; it.increment(ec)) {
|
||||
const fs::path& path = it->path();
|
||||
|
||||
if (!fs::is_regular_file(path, ec) || ec)
|
||||
continue;
|
||||
|
||||
std::string ext = path.extension().string();
|
||||
boost::algorithm::to_lower(ext);
|
||||
if (ext != ".png" && ext != ".jpg" && ext != ".jpeg" && ext != ".bmp")
|
||||
continue;
|
||||
|
||||
// Re-check per file: a symlink inside the pack must not resolve outside it.
|
||||
if (!is_inside_allowed_root(path, resolved_root))
|
||||
continue;
|
||||
|
||||
files.push_back(path.string());
|
||||
if (files.size() >= kMaxResourceTextureFiles)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
error = _u8L("Resource texture directory could not be read");
|
||||
return {};
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
Reference in New Issue
Block a user