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)); +}