From 40028f7b61b6fe5650a64d7191a77581f5e0791e Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Mon, 28 Sep 2026 17:05:29 +0800 Subject: [PATCH] Confirm Before Opening Program Attachments and Load Only HTTPS Images Opening a project attachment whose type runs as a program or script (executables, installers, shortcuts, shell and PowerShell scripts, macOS command files and apps, Linux desktop entries) now asks for confirmation first. The check lives in libslic3r as is_executable_file_name and ignores the trailing dots and spaces Windows strips from file names. Images in project descriptions are kept only when they load over https, so opening the Project tab no longer issues plain-http requests. --- resources/web/model/model.js | 8 +++++++- src/libslic3r/Utils.hpp | 2 ++ src/libslic3r/utils.cpp | 23 +++++++++++++++++++++++ src/slic3r/GUI/Project.cpp | 14 +++++++++++++- tests/libslic3r/test_utils.cpp | 17 +++++++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/resources/web/model/model.js b/resources/web/model/model.js index 0773f27204..3d7c2fc8ad 100644 --- a/resources/web/model/model.js +++ b/resources/web/model/model.js @@ -498,6 +498,12 @@ function IsHttpUrl( strUrl ) } } +// Images load as soon as the page opens, so only https sources are kept: no plain-http requests to the local network. +function IsHttpsUrl( strUrl ) +{ + return IsHttpUrl(strUrl) && new URL(strUrl).protocol=='https:'; +} + // Embedded YouTube players become a plain link to the video. function GetYouTubeEmbedUrl( pNode ) { @@ -546,7 +552,7 @@ function CopyDescNodes( pSrc, pDst ) pElem.setAttribute('href',pNode.getAttribute('href')); else if( sTag=='IMG' ) { - if( !IsHttpUrl(pNode.getAttribute('src')) ) + if( !IsHttpsUrl(pNode.getAttribute('src')) ) continue; pElem.setAttribute('src',pNode.getAttribute('src')); } diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index d4062e07b1..59f27b6ba6 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -266,6 +266,8 @@ extern bool is_symlink_target_within_root(const std::string &link_rel_path, cons // True if path names an entry strictly inside root: it must be spelled with root as its prefix, // and must still resolve inside root once symlinks are followed. extern bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root); +// True if opening a file with this name through the desktop would run it as a program or script. +extern bool is_executable_file_name(const std::string &file_name); // Orca: custom protocal support utils inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); } diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 5e795438f1..f4211347d4 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -70,6 +70,7 @@ #include #include +#include #include #include #include @@ -1133,6 +1134,28 @@ bool is_absolute_path_within_root(const boost::filesystem::path &path, const boo return !rel.empty() && rel != "." && is_path_within_root(rel.string(), root); } +bool is_executable_file_name(const std::string &file_name) +{ + static const std::vector executable_extensions = { + // Windows + "exe", "com", "bat", "cmd", "scr", "pif", "msi", "msp", "msc", "cpl", "lnk", "url", "hta", "js", "jse", "vbs", + "vbe", "wsf", "wsh", "ps1", "psm1", "reg", "jar", "appref-ms", "scf", "inf", "py", "pyw", + // macOS + "app", "command", "pkg", "terminal", "workflow", + // Linux + "sh", "bash", "run", "desktop", "appimage"}; + // Windows ignores trailing dots and spaces, so "setup.exe." still runs as an .exe. + const size_t end = file_name.find_last_not_of(". "); + if (end == std::string::npos) + return false; + const std::string name = file_name.substr(0, end + 1); + const size_t dot = name.find_last_of('.'); + if (dot == std::string::npos || name.find_first_of("/\\", dot) != std::string::npos) + return false; + const std::string extension = boost::algorithm::to_lower_copy(name.substr(dot + 1)); + return std::find(executable_extensions.begin(), executable_extensions.end(), extension) != executable_extensions.end(); +} + bool is_img_file(const std::string &path) { return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg"); diff --git a/src/slic3r/GUI/Project.cpp b/src/slic3r/GUI/Project.cpp index 1ccb269f51..8d45c94e7f 100644 --- a/src/slic3r/GUI/Project.cpp +++ b/src/slic3r/GUI/Project.cpp @@ -27,6 +27,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "MainFrame.hpp" +#include "MsgDialog.hpp" #include namespace Slic3r { namespace GUI { @@ -297,7 +298,18 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt) fs::path aux_root(encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str())); if (is_absolute_path_within_root(path, aux_root) && fs::is_regular_file(path)) { - wxLaunchDefaultApplication(path.wstring(), 0); + // Attachments come with the project, so ask before running one that is a program or script. + bool open = true; + if (is_executable_file_name(path.filename().string())) { + MessageDialog dlg(this, + wxString::Format(_L("\"%s\" is a program or script. Opening it will run it on this computer.\n\n" + "Only open attachments from projects you trust. Open it anyway?"), + from_path(path.filename())), + _L("Open attachment"), wxICON_WARNING | wxYES_NO); + open = dlg.ShowModal() == wxID_YES; + } + if (open) + wxLaunchDefaultApplication(path.wstring(), 0); } else { BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: ignoring path outside the project auxiliary directory: " << decode_path; } diff --git a/tests/libslic3r/test_utils.cpp b/tests/libslic3r/test_utils.cpp index ca5d9e07fc..7ff0f419fa 100644 --- a/tests/libslic3r/test_utils.cpp +++ b/tests/libslic3r/test_utils.cpp @@ -367,3 +367,20 @@ TEST_CASE("is_absolute_path_within_root accepts only entries inside the root", " } #endif } + +TEST_CASE("is_executable_file_name flags attachments that would run as programs", "[utils]") { + const std::string executable = GENERATE(as{}, + "setup.exe", "SETUP.EXE", "Manual.pdf.exe", "run.bat", "start.cmd", "shortcut.lnk", "site.url", "script.ps1", + "macro.vbs", "tool.jar", "script.py", "Install.command", "Tool.app", "installer.pkg", "install.sh", + "launcher.desktop", "Printer.AppImage", "setup.exe.", "setup.exe ", "setup.exe. .", ".exe"); + INFO(executable); + CHECK(is_executable_file_name(executable)); +} + +TEST_CASE("is_executable_file_name leaves documents and models alone", "[utils]") { + const std::string document = GENERATE(as{}, + "Manual.pdf", "BOM.xlsx", "notes.txt", "photo.JPG", "assembly.step", "part.stl", "project.3mf", "readme", + "exe", "setup.exe.pdf", "", "...", "dir.exe/readme"); + INFO(document); + CHECK_FALSE(is_executable_file_name(document)); +}