mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-01 21:01:12 +00:00
Open Project Attachments Through One Guarded Helper
The Edit Project Info view launched attachments directly, without the checks the project page has. Both now call desktop_open_project_attachment, which checks that the file is inside the auxiliary directory, asks for confirmation where needed and then opens it. The auxiliary root was built through encode_path, which returns code page bytes on Windows, while boost::filesystem reads a narrow string as UTF-8. With a non-ASCII temporary directory the root never matched and no attachment opened. It is now built from the UTF-8 path directly. The list of program extensions could not be kept complete and let unknown types open without a prompt. It is replaced by is_safe_to_open_file_name, a list of plain document, image, model and video types that open directly. Everything else asks first.
This commit is contained in:
@@ -266,8 +266,9 @@ 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.
|
// True if a file with this name is of a type that the desktop opens as plain content, so it cannot run code.
|
||||||
extern bool is_executable_file_name(const std::string &file_name);
|
// Anything unknown is not safe.
|
||||||
|
extern bool is_safe_to_open_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"); }
|
||||||
|
|||||||
+12
-18
@@ -1134,26 +1134,20 @@ 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)
|
bool is_safe_to_open_file_name(const std::string &file_name)
|
||||||
{
|
{
|
||||||
static const std::vector<std::string> executable_extensions = {
|
// Formats that cannot carry macros or scripts. Legacy and OpenDocument office files, HTML and SVG are left out on purpose.
|
||||||
// Windows
|
static const std::vector<std::string> safe_extensions = {
|
||||||
"exe", "com", "bat", "cmd", "scr", "pif", "msi", "msp", "msc", "cpl", "lnk", "url", "hta", "js", "jse", "vbs",
|
"jpg", "jpeg", "jfif", "pjpeg", "pjp", "png", "gif", "bmp", "webp", "tif", "tiff",
|
||||||
"vbe", "wsf", "wsh", "ps1", "psm1", "reg", "jar", "appref-ms", "scf", "inf", "py", "pyw",
|
"pdf", "txt", "md", "csv", "docx", "xlsx", "pptx",
|
||||||
// macOS
|
"stl", "obj", "3mf", "amf", "ply", "step", "stp", "iges", "igs", "dxf",
|
||||||
"app", "command", "pkg", "terminal", "workflow",
|
"mp4", "mov", "webm"};
|
||||||
// Linux
|
// The name must end in the extension itself: Windows drops trailing dots and spaces and reads ':' as a stream separator.
|
||||||
"sh", "bash", "run", "desktop", "appimage"};
|
const size_t dot = file_name.find_last_of('.');
|
||||||
// Windows ignores trailing dots and spaces, so "setup.exe." still runs as an .exe.
|
if (dot == std::string::npos || file_name.find_first_of("/\\:") != std::string::npos)
|
||||||
const size_t end = file_name.find_last_not_of(". ");
|
|
||||||
if (end == std::string::npos)
|
|
||||||
return false;
|
return false;
|
||||||
const std::string name = file_name.substr(0, end + 1);
|
const std::string extension = boost::algorithm::to_lower_copy(file_name.substr(dot + 1));
|
||||||
const size_t dot = name.find_last_of('.');
|
return std::find(safe_extensions.begin(), safe_extensions.end(), extension) != safe_extensions.end();
|
||||||
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)
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
|
|||||||
if (m_type == AddFileButton)
|
if (m_type == AddFileButton)
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
wxLaunchDefaultApplication(m_file_path.wstring(), 0);
|
desktop_open_project_attachment(this, m_file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
|
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
|
||||||
|
|||||||
@@ -30,11 +30,13 @@
|
|||||||
|
|
||||||
#include "AboutDialog.hpp"
|
#include "AboutDialog.hpp"
|
||||||
#include "MsgDialog.hpp"
|
#include "MsgDialog.hpp"
|
||||||
|
#include "Plater.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
|
|
||||||
#include "WebUserLoginDialog.hpp"
|
#include "WebUserLoginDialog.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@@ -643,4 +645,26 @@ void desktop_open_any_folder( const std::string& path )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
|
||||||
|
{
|
||||||
|
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
|
||||||
|
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
|
||||||
|
boost::system::error_code ec;
|
||||||
|
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
|
||||||
|
if (!is_safe_to_open_file_name(path.filename().string())) {
|
||||||
|
MessageDialog dlg(parent,
|
||||||
|
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
|
||||||
|
"program or script 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);
|
||||||
|
if (dlg.ShowModal() != wxID_YES)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return wxLaunchDefaultApplication(from_path(path), 0);
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ extern void about();
|
|||||||
extern void desktop_open_datadir_folder();
|
extern void desktop_open_datadir_folder();
|
||||||
// Ask the destop to open one folder
|
// Ask the destop to open one folder
|
||||||
extern void desktop_open_any_folder(const std::string& path);
|
extern void desktop_open_any_folder(const std::string& path);
|
||||||
|
// Ask the desktop to open a file from the project's auxiliary directory, after a confirmation
|
||||||
|
// unless its type is known to be plain content. Returns false if the file was not opened.
|
||||||
|
extern bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path);
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
#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 {
|
||||||
@@ -294,25 +293,8 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
|
|||||||
if (!accessory_path.empty()) {
|
if (!accessory_path.empty()) {
|
||||||
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
|
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
|
||||||
fs::path path(decode_path);
|
fs::path path(decode_path);
|
||||||
// Only open the project's own extracted auxiliary files, with the root built as in Reload().
|
if (!desktop_open_project_attachment(this, path))
|
||||||
fs::path aux_root(encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str()));
|
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: not opening " << decode_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);
|
|
||||||
} else {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: ignoring path outside the project auxiliary directory: " << decode_path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strCmd == "request_3mf_info") {
|
else if (strCmd == "request_3mf_info") {
|
||||||
|
|||||||
@@ -368,19 +368,22 @@ 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]") {
|
TEST_CASE("is_safe_to_open_file_name accepts plain documents, images and models", "[utils]") {
|
||||||
const std::string executable = GENERATE(as<std::string>{},
|
const std::string safe = GENERATE(as<std::string>{},
|
||||||
"setup.exe", "SETUP.EXE", "Manual.pdf.exe", "run.bat", "start.cmd", "shortcut.lnk", "site.url", "script.ps1",
|
"Manual.pdf", "BOM.xlsx", "BOM.csv", "guide.docx", "notes.txt", "README.md", "photo.JPG", "render.png",
|
||||||
"macro.vbs", "tool.jar", "script.py", "Install.command", "Tool.app", "installer.pkg", "install.sh",
|
"assembly.step", "part.stl", "project.3mf", "drawing.dxf", "build.mp4", "setup.exe.pdf", ".pdf");
|
||||||
"launcher.desktop", "Printer.AppImage", "setup.exe.", "setup.exe ", "setup.exe. .", ".exe");
|
INFO(safe);
|
||||||
INFO(executable);
|
CHECK(is_safe_to_open_file_name(safe));
|
||||||
CHECK(is_executable_file_name(executable));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("is_executable_file_name leaves documents and models alone", "[utils]") {
|
TEST_CASE("is_safe_to_open_file_name rejects programs and anything it does not know", "[utils]") {
|
||||||
const std::string document = GENERATE(as<std::string>{},
|
const std::string unsafe = GENERATE(as<std::string>{},
|
||||||
"Manual.pdf", "BOM.xlsx", "notes.txt", "photo.JPG", "assembly.step", "part.stl", "project.3mf", "readme",
|
"setup.exe", "SETUP.EXE", "Manual.pdf.exe", "run.bat", "shortcut.lnk", "site.url", "script.ps1", "help.chm",
|
||||||
"exe", "setup.exe.pdf", "", "...", "dir.exe/readme");
|
"tool.jar", "script.py", "Install.command", "install.sh", "launcher.desktop", "Printer.AppImage",
|
||||||
INFO(document);
|
// Documents that can carry macros or scripts.
|
||||||
CHECK_FALSE(is_executable_file_name(document));
|
"BOM.xls", "BOM.xlsm", "guide.doc", "guide.docm", "sheet.ods", "page.html", "logo.svg", "bundle.zip",
|
||||||
|
// No extension, an unknown one, or a name the desktop would read differently.
|
||||||
|
"readme", "pdf", "data.xyz", "", "...", "Manual.pdf.", "Manual.pdf ", "setup.exe:note.txt", "dir.pdf/readme");
|
||||||
|
INFO(unsafe);
|
||||||
|
CHECK_FALSE(is_safe_to_open_file_name(unsafe));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user