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.
This commit is contained in:
Hanif Koh
2026-09-29 23:50:10 +08:00
parent 88f5905c29
commit 40028f7b61
5 changed files with 62 additions and 2 deletions
+7 -1
View File
@@ -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. // Embedded YouTube players become a plain link to the video.
function GetYouTubeEmbedUrl( pNode ) function GetYouTubeEmbedUrl( pNode )
{ {
@@ -546,7 +552,7 @@ function CopyDescNodes( pSrc, pDst )
pElem.setAttribute('href',pNode.getAttribute('href')); pElem.setAttribute('href',pNode.getAttribute('href'));
else if( sTag=='IMG' ) else if( sTag=='IMG' )
{ {
if( !IsHttpUrl(pNode.getAttribute('src')) ) if( !IsHttpsUrl(pNode.getAttribute('src')) )
continue; continue;
pElem.setAttribute('src',pNode.getAttribute('src')); pElem.setAttribute('src',pNode.getAttribute('src'));
} }
+2
View File
@@ -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, // 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. // 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); 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 // Orca: custom protocal support utils
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); } inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
+23
View File
@@ -70,6 +70,7 @@
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
@@ -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); 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<std::string> 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) bool is_img_file(const std::string &path)
{ {
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg"); return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
+12
View File
@@ -27,6 +27,7 @@
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "GUI_ObjectList.hpp" #include "GUI_ObjectList.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "MsgDialog.hpp"
#include <slic3r/GUI/Widgets/WebView.hpp> #include <slic3r/GUI/Widgets/WebView.hpp>
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
@@ -297,6 +298,17 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
fs::path aux_root(encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str())); 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)) { if (is_absolute_path_within_root(path, aux_root) && fs::is_regular_file(path)) {
// 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); wxLaunchDefaultApplication(path.wstring(), 0);
} else { } else {
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: ignoring path outside the project auxiliary directory: " << decode_path; BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: ignoring path outside the project auxiliary directory: " << decode_path;
+17
View File
@@ -367,3 +367,20 @@ TEST_CASE("is_absolute_path_within_root accepts only entries inside the root", "
} }
#endif #endif
} }
TEST_CASE("is_executable_file_name flags attachments that would run as programs", "[utils]") {
const std::string executable = GENERATE(as<std::string>{},
"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<std::string>{},
"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));
}