mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 19:31:22 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5023acce1 |
@@ -307,14 +307,17 @@ bool PrusaFileParser::check_3mf_from_prusa(const std::string filename)
|
||||
|
||||
mz_zip_archive_file_stat stat;
|
||||
if (!mz_zip_reader_file_stat(&archive, model_file_index, &stat)) goto EXIT;
|
||||
// expat sizes its buffer with an int, so a larger entry cannot be parsed in one piece.
|
||||
if (stat.m_uncomp_size > static_cast<mz_uint64>(std::numeric_limits<int>::max())) goto EXIT;
|
||||
|
||||
void *parser_buffer = XML_GetBuffer(m_parser, (int) stat.m_uncomp_size);
|
||||
const int xml_size = static_cast<int>(stat.m_uncomp_size);
|
||||
void *parser_buffer = XML_GetBuffer(m_parser, xml_size);
|
||||
if (parser_buffer == nullptr) goto EXIT;
|
||||
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t) stat.m_uncomp_size, 0);
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, static_cast<size_t>(xml_size), 0);
|
||||
if (res == 0) goto EXIT;
|
||||
|
||||
XML_ParseBuffer(m_parser, (int) stat.m_uncomp_size, 1);
|
||||
XML_ParseBuffer(m_parser, xml_size, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1346,19 +1349,26 @@ ModelVolumeType type_from_string(const std::string &s)
|
||||
XML_SetUserData(m_xml_parser, (void*)this);
|
||||
XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_config_xml_element, _3MF_Importer::_handle_end_config_xml_element);
|
||||
|
||||
void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size);
|
||||
// expat sizes its buffer with an int, so a larger entry cannot be parsed in one piece.
|
||||
if (stat.m_uncomp_size > static_cast<mz_uint64>(std::numeric_limits<int>::max())) {
|
||||
add_error("Found invalid size");
|
||||
return false;
|
||||
}
|
||||
const int xml_size = static_cast<int>(stat.m_uncomp_size);
|
||||
|
||||
void* parser_buffer = XML_GetBuffer(m_xml_parser, xml_size);
|
||||
if (parser_buffer == nullptr) {
|
||||
add_error("Unable to create buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0);
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, static_cast<size_t>(xml_size), 0);
|
||||
if (res == 0) {
|
||||
add_error("Error while reading config data to buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) {
|
||||
if (!XML_ParseBuffer(m_xml_parser, xml_size, 1)) {
|
||||
char error_buf[1024];
|
||||
::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser));
|
||||
add_error(error_buf);
|
||||
|
||||
@@ -2508,19 +2508,26 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
XML_SetEntityDeclHandler(m_xml_parser, nullptr);
|
||||
XML_SetExternalEntityRefHandler(m_xml_parser, nullptr);
|
||||
|
||||
void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size);
|
||||
// expat sizes its buffer with an int, so a larger entry cannot be parsed in one piece.
|
||||
if (stat.m_uncomp_size > static_cast<mz_uint64>(std::numeric_limits<int>::max())) {
|
||||
add_error("Found invalid size");
|
||||
return false;
|
||||
}
|
||||
const int xml_size = static_cast<int>(stat.m_uncomp_size);
|
||||
|
||||
void* parser_buffer = XML_GetBuffer(m_xml_parser, xml_size);
|
||||
if (parser_buffer == nullptr) {
|
||||
add_error("Unable to create buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0);
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, static_cast<size_t>(xml_size), 0);
|
||||
if (res == 0) {
|
||||
add_error("Error while reading config data to buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) {
|
||||
if (!XML_ParseBuffer(m_xml_parser, xml_size, 1)) {
|
||||
char error_buf[1024];
|
||||
::snprintf(error_buf, 1024, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser));
|
||||
add_error(error_buf);
|
||||
|
||||
@@ -285,21 +285,6 @@ inline std::string sanitize_filename(const std::string &filename){
|
||||
const std::regex special_chars("[/\\\\:*?\"<>|]");
|
||||
return std::regex_replace(filename, special_chars, "_");
|
||||
}
|
||||
// Reduce an untrusted, possibly path-qualified name to a single sanitized file name.
|
||||
// Returns an empty string when nothing usable remains.
|
||||
inline std::string sanitize_file_basename(const std::string &name){
|
||||
const size_t sep = name.find_last_of("/\\");
|
||||
const std::string base = sanitize_filename(sep == std::string::npos ? name : name.substr(sep + 1));
|
||||
// Names made only of dots and spaces refer to the folder or its parent, or are stripped to nothing on Windows.
|
||||
return base.find_first_not_of(". ") == std::string::npos ? std::string() : base;
|
||||
}
|
||||
// Marker file a download of this process writes to before it is renamed to filename.
|
||||
boost::filesystem::path download_marker_path(const boost::filesystem::path &dest_folder, const std::string &filename);
|
||||
// Finds a sanitized variant of filename, "name(N).ext" if needed, that neither an entry of dest_folder
|
||||
// nor the download marker of another download uses. The marker at ignored_marker does not count.
|
||||
// Returns true and the name in result, or false and the last name tried.
|
||||
bool find_unused_filename(const boost::filesystem::path &dest_folder, const std::string &filename,
|
||||
const boost::filesystem::path &ignored_marker, std::string &result);
|
||||
// File path / name / extension splitting utilities, working with UTF-8,
|
||||
// to be published to Perl.
|
||||
namespace PerlUtils {
|
||||
|
||||
@@ -1321,31 +1321,6 @@ unsigned get_current_pid()
|
||||
#endif
|
||||
}
|
||||
|
||||
boost::filesystem::path download_marker_path(const boost::filesystem::path &dest_folder, const std::string &filename)
|
||||
{
|
||||
return dest_folder / (filename + "." + std::to_string(get_current_pid()) + ".download");
|
||||
}
|
||||
|
||||
bool find_unused_filename(const boost::filesystem::path &dest_folder, const std::string &filename,
|
||||
const boost::filesystem::path &ignored_marker, std::string &result)
|
||||
{
|
||||
// Probe the name that will be written, so a name the sanitizing maps onto an existing file is versioned too.
|
||||
const std::string sanitized = sanitize_filename(filename);
|
||||
const std::string extension = boost::filesystem::path(sanitized).extension().string();
|
||||
const std::string stem = sanitized.substr(0, sanitized.size() - extension.size());
|
||||
auto is_used = [&](const std::string &name) {
|
||||
const boost::filesystem::path marker = download_marker_path(dest_folder, name);
|
||||
return boost::filesystem::exists(dest_folder / name) || (marker != ignored_marker && boost::filesystem::exists(marker));
|
||||
};
|
||||
result = sanitized;
|
||||
for (size_t version = 1; is_used(result); ++version) {
|
||||
if (version > 999)
|
||||
return false;
|
||||
result = stem + "(" + std::to_string(version) + ")" + extension;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string per_user_temp_id()
|
||||
{
|
||||
#ifdef WIN32
|
||||
|
||||
@@ -71,6 +71,17 @@ bool FileGet::is_subdomain(const std::string& url, const std::string& domain)
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
unsigned get_current_pid()
|
||||
{
|
||||
#ifdef WIN32
|
||||
return GetCurrentProcessId();
|
||||
#else
|
||||
return ::getpid();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// int = DOWNLOAD ID; string = file path
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = error msg
|
||||
@@ -133,10 +144,25 @@ void FileGet::priv::get_perform()
|
||||
std::string extension;
|
||||
if (m_written == 0)
|
||||
{
|
||||
std::string final_filename;
|
||||
bool found = false;
|
||||
boost::filesystem::path dest_path = m_dest_folder / m_filename;
|
||||
extension = dest_path.extension().string();
|
||||
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
// Find unsed filename
|
||||
try {
|
||||
found = find_unused_filename(m_dest_folder, m_filename, m_tmp_path, final_filename);
|
||||
size_t version = 0;
|
||||
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
|
||||
{
|
||||
++version;
|
||||
if (version > 999) {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / (final_filename + extension)).string()));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
final_filename = GUI::format("%1%(%2%)", just_filename, std::to_string(version));
|
||||
}
|
||||
} catch (const boost::filesystem::filesystem_error& e)
|
||||
{
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
@@ -145,18 +171,10 @@ void FileGet::priv::get_perform()
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
if (!found) {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / final_filename).string()));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
m_filename = final_filename;
|
||||
extension = boost::filesystem::path(m_filename).extension().string();
|
||||
m_filename = sanitize_filename(final_filename + extension);
|
||||
|
||||
m_tmp_path = download_marker_path(m_dest_folder, m_filename);
|
||||
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
|
||||
evt->SetString(boost::nowide::widen(m_filename));
|
||||
@@ -203,32 +221,7 @@ void FileGet::priv::get_perform()
|
||||
if(dest_path.empty()) {
|
||||
std::string filename = extract_remote_filename(header);
|
||||
if (!filename.empty()) {
|
||||
// The name comes from the server: keep it inside the destination folder and never
|
||||
// replace an existing file. Keep the current name if nothing usable remains.
|
||||
filename = sanitize_file_basename(filename);
|
||||
std::string unused;
|
||||
try {
|
||||
if (filename.empty() || !find_unused_filename(m_dest_folder, filename, m_tmp_path, unused))
|
||||
unused.clear();
|
||||
} catch (const boost::filesystem::filesystem_error&) {
|
||||
unused.clear();
|
||||
}
|
||||
const boost::filesystem::path tmp_path = unused.empty() ? m_tmp_path : download_marker_path(m_dest_folder, unused);
|
||||
if (tmp_path != m_tmp_path) {
|
||||
// Move the marker to the adopted name so that other downloads see the name as taken.
|
||||
// Only before anything is written, so that no downloaded data has to be carried over.
|
||||
FILE* tmp_file = m_written == 0 ? fopen(wxString(tmp_path.wstring()).c_str(), "wb") : nullptr;
|
||||
if (tmp_file != nullptr) {
|
||||
fclose(file);
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(m_tmp_path, ec);
|
||||
file = tmp_file;
|
||||
m_tmp_path = tmp_path;
|
||||
} else
|
||||
unused.clear();
|
||||
}
|
||||
if (!unused.empty())
|
||||
m_filename = unused;
|
||||
m_filename = filename;
|
||||
dest_path = m_dest_folder / m_filename;
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
|
||||
evt->SetString(boost::nowide::widen(m_filename));
|
||||
@@ -334,18 +327,6 @@ void FileGet::priv::get_perform()
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
fclose(file);
|
||||
// Another file may have taken the name while downloading.
|
||||
if (!dest_path.empty() && boost::filesystem::exists(dest_path)) {
|
||||
std::string unused;
|
||||
if (!find_unused_filename(m_dest_folder, m_filename, m_tmp_path, unused))
|
||||
throw std::runtime_error("No unused file name.");
|
||||
m_filename = unused;
|
||||
dest_path = m_dest_folder / m_filename;
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
|
||||
evt->SetString(boost::nowide::widen(m_filename));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
boost::filesystem::rename(m_tmp_path, dest_path);
|
||||
}
|
||||
catch (const std::exception& /*e*/)
|
||||
|
||||
+47
-38
@@ -15681,11 +15681,6 @@ void Plater::import_model_id(wxString download_info)
|
||||
//wxString sError = error.what();
|
||||
}
|
||||
|
||||
// The name comes from the link: reduce it to a plain file name inside the download folder.
|
||||
filename = from_u8(sanitize_file_basename(into_u8(filename)));
|
||||
if (filename.empty())
|
||||
filename = "untitled.3mf";
|
||||
|
||||
bool download_ok = false;
|
||||
int retry_count = 0;
|
||||
const int max_retries = 3;
|
||||
@@ -15727,28 +15722,51 @@ void Plater::import_model_id(wxString download_info)
|
||||
|
||||
msg = _L("Preparing 3MF file...");
|
||||
|
||||
//gets the number of files with the same name
|
||||
std::vector<wxString> vecFiles;
|
||||
bool is_already_exist = false;
|
||||
|
||||
|
||||
target_path = fs::path(wxGetApp().app_config->get("download_path"));
|
||||
|
||||
//check file suffix
|
||||
wxString extension = fs::path(filename.wx_str()).extension().c_str();
|
||||
if (!extension.Contains(".3mf")) {
|
||||
msg = _L("Download failed; unknown file format.");
|
||||
return;
|
||||
try
|
||||
{
|
||||
vecFiles.clear();
|
||||
wxString extension = fs::path(filename.wx_str()).extension().c_str();
|
||||
|
||||
|
||||
//check file suffix
|
||||
if (!extension.Contains(".3mf")) {
|
||||
msg = _L("Download failed; unknown file format.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = filename.substr(0, filename.length() - extension.length() - 1);
|
||||
|
||||
for (const auto& iter : boost::filesystem::directory_iterator(target_path))
|
||||
{
|
||||
if (boost::filesystem::is_directory(iter.path()))
|
||||
continue;
|
||||
|
||||
wxString sFile = iter.path().filename().string().c_str();
|
||||
if (strstr(sFile.c_str(), name.c_str()) != NULL) {
|
||||
vecFiles.push_back(sFile);
|
||||
}
|
||||
|
||||
if (sFile == filename) is_already_exist = true;
|
||||
}
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
//wxString sError = error.what();
|
||||
}
|
||||
|
||||
//never replace an existing file
|
||||
std::string unused_filename;
|
||||
try {
|
||||
if (!find_unused_filename(target_path, into_u8(filename), {}, unused_filename))
|
||||
unused_filename.clear();
|
||||
} catch (const std::exception&) {
|
||||
unused_filename.clear();
|
||||
//update filename
|
||||
if (is_already_exist && vecFiles.size() >= 1) {
|
||||
wxString extension = fs::path(filename.wx_str()).extension().c_str();
|
||||
wxString name = filename.substr(0, filename.length() - extension.length());
|
||||
filename = wxString::Format("%s(%d)%s", name, vecFiles.size() + 1, extension).ToStdString();
|
||||
}
|
||||
if (unused_filename.empty()) {
|
||||
msg = _L("Importing to Orca Slicer failed. Please download the file and manually import it.");
|
||||
return;
|
||||
}
|
||||
filename = from_u8(unused_filename);
|
||||
|
||||
|
||||
msg = _L("Downloading project...");
|
||||
@@ -15760,6 +15778,10 @@ void Plater::import_model_id(wxString download_info)
|
||||
boost::uuids::uuid uuid = boost::uuids::random_generator()();
|
||||
std::string unique = to_string(uuid).substr(0, 6);
|
||||
|
||||
if (filename.empty()) {
|
||||
filename = "untitled.3mf";
|
||||
}
|
||||
|
||||
//target_path /= (boost::format("%1%_%2%.3mf") % filename % unique).str();
|
||||
target_path /= fs::path(filename.wc_str());
|
||||
|
||||
@@ -15808,26 +15830,13 @@ void Plater::import_model_id(wxString download_info)
|
||||
cont = false;
|
||||
}
|
||||
})
|
||||
.on_complete([&cont, &download_ok, &msg, tmp_path, &target_path](std::string body, unsigned /* http_status */) {
|
||||
.on_complete([&cont, &download_ok, tmp_path, target_path](std::string body, unsigned /* http_status */) {
|
||||
fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
file.write(body.c_str(), body.size());
|
||||
file.close();
|
||||
fs::rename(tmp_path, target_path);
|
||||
cont = false;
|
||||
try {
|
||||
// Another file may have taken the name while downloading.
|
||||
std::string unused_filename;
|
||||
if (find_unused_filename(target_path.parent_path(), target_path.filename().string(), {}, unused_filename)) {
|
||||
target_path = target_path.parent_path() / unused_filename;
|
||||
fs::rename(tmp_path, target_path);
|
||||
download_ok = true;
|
||||
return;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "import_model_id: failed to move the download into place: " << e.what();
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
fs::remove(tmp_path, ec);
|
||||
msg = _L("Importing to Orca Slicer failed. Please download the file and manually import it.");
|
||||
download_ok = true;
|
||||
}).perform_sync();
|
||||
|
||||
// for break while
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -1461,3 +1462,79 @@ SCENARIO("bbs_3mf_is_published detects only genuinely published 3MFs", "[3mf]")
|
||||
}
|
||||
}
|
||||
|
||||
// Writes a single-entry zip whose central directory carries a zip64 record declaring an
|
||||
// uncompressed size beyond what the 32-bit expat buffer API can take, while the deflated
|
||||
// payload inflates to only ~64 KiB. Built by hand because miniz never writes a size that
|
||||
// disagrees with the data.
|
||||
static void write_zip_with_oversized_entry(const std::string& path, const std::string& entry)
|
||||
{
|
||||
const std::string xml = "<?xml version=\"1.0\"?><!--" + std::string(65536, 'A') + "--><a/>";
|
||||
size_t comp_len = 0;
|
||||
void* comp = tdefl_compress_mem_to_heap(xml.data(), xml.size(), &comp_len, TDEFL_DEFAULT_MAX_PROBES);
|
||||
REQUIRE(comp != nullptr);
|
||||
const std::string deflated(static_cast<const char*>(comp), comp_len);
|
||||
mz_free(comp);
|
||||
const uint32_t crc = static_cast<uint32_t>(mz_crc32(MZ_CRC32_INIT, reinterpret_cast<const unsigned char*>(xml.data()), xml.size()));
|
||||
const uint64_t claimed_size = (uint64_t(1) << 32) + 16;
|
||||
|
||||
std::string out;
|
||||
auto put = [&out](uint64_t v, int bytes) {
|
||||
for (int i = 0; i < bytes; ++i)
|
||||
out.push_back(static_cast<char>((v >> (8 * i)) & 0xFF));
|
||||
};
|
||||
// local file header, with the true sizes
|
||||
put(0x04034b50, 4); put(45, 2); put(0, 2); put(8, 2); put(0, 2); put(0, 2);
|
||||
put(crc, 4); put(deflated.size(), 4); put(xml.size(), 4); put(entry.size(), 2); put(0, 2);
|
||||
out += entry + deflated;
|
||||
// central directory header, sizes deferred to the zip64 extra field
|
||||
const size_t cd_offset = out.size();
|
||||
put(0x02014b50, 4); put(45, 2); put(45, 2); put(0, 2); put(8, 2); put(0, 2); put(0, 2);
|
||||
put(crc, 4); put(0xFFFFFFFF, 4); put(0xFFFFFFFF, 4); put(entry.size(), 2); put(20, 2);
|
||||
put(0, 2); put(0, 2); put(0, 2); put(0, 4); put(0, 4);
|
||||
out += entry;
|
||||
put(0x0001, 2); put(16, 2); put(claimed_size, 8); put(deflated.size(), 8);
|
||||
const size_t cd_size = out.size() - cd_offset;
|
||||
// end of central directory
|
||||
put(0x06054b50, 4); put(0, 2); put(0, 2); put(1, 2); put(1, 2);
|
||||
put(cd_size, 4); put(cd_offset, 4); put(0, 2);
|
||||
|
||||
boost::nowide::ofstream f(path, std::ios::binary);
|
||||
REQUIRE(f.good());
|
||||
f.write(out.data(), static_cast<std::streamsize>(out.size()));
|
||||
REQUIRE(f.good());
|
||||
}
|
||||
|
||||
TEST_CASE("3MF XML entries declaring more than an int can hold fail to load", "[3mf]") {
|
||||
ScopedTemporaryFile temp(".3mf");
|
||||
const std::string path = temp.string();
|
||||
|
||||
SECTION("BBS importer") {
|
||||
write_zip_with_oversized_entry(path, "_rels/.rels");
|
||||
Model model;
|
||||
DynamicPrintConfig config;
|
||||
ConfigSubstitutionContext ctxt{ForwardCompatibilitySubstitutionRule::Enable};
|
||||
PlateDataPtrs plates;
|
||||
std::vector<Preset*> project_presets;
|
||||
bool is_bbl_3mf = false, is_orca_3mf = false;
|
||||
Semver file_version;
|
||||
bool loaded = true;
|
||||
REQUIRE_NOTHROW(loaded = load_bbs_3mf(path.c_str(), &config, &ctxt, &model, &plates, &project_presets, &is_bbl_3mf,
|
||||
&is_orca_3mf, &file_version, nullptr, LoadStrategy::LoadModel | LoadStrategy::LoadConfig));
|
||||
CHECK_FALSE(loaded);
|
||||
release_PlateData_list(plates);
|
||||
}
|
||||
SECTION("PrusaSlicer importer") {
|
||||
write_zip_with_oversized_entry(path, "Metadata/Slic3r_PE_model.config");
|
||||
Model model;
|
||||
DynamicPrintConfig config;
|
||||
ConfigSubstitutionContext ctxt{ForwardCompatibilitySubstitutionRule::Disable};
|
||||
bool loaded = true;
|
||||
REQUIRE_NOTHROW(loaded = load_3mf(path.c_str(), config, ctxt, &model, false));
|
||||
CHECK_FALSE(loaded);
|
||||
}
|
||||
SECTION("PrusaSlicer fingerprint probe") {
|
||||
write_zip_with_oversized_entry(path, "3D/3dmodel.model");
|
||||
PrusaFileParser parser;
|
||||
CHECK_FALSE(parser.check_3mf_from_prusa(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,94 +152,3 @@ TEST_CASE("resolve_cli_input_path leaves inputs that must not be completed uncha
|
||||
REQUIRE(resolve_cli_input_path("").empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("sanitize_file_basename keeps only a plain file name from an untrusted name", "[Utils]") {
|
||||
const std::string unicode = "\xe6\xa8\xa1\xe5\x9e\x8b \xc3\xa9t\xc3\xa9.3mf"; // UTF-8 CJK and accented Latin
|
||||
const auto [input, expected] = GENERATE_COPY(table<std::string, std::string>({
|
||||
{"normal.3mf", "normal.3mf"},
|
||||
{"../../x.3mf", "x.3mf"},
|
||||
{"..\\..\\x.3mf", "x.3mf"},
|
||||
{"C:\\x.3mf", "x.3mf"},
|
||||
{"C:x.3mf", "C_x.3mf"},
|
||||
{"/etc/x", "x"},
|
||||
{"a/b\\c.gcode", "c.gcode"},
|
||||
{"x:stream", "x_stream"}, // no NTFS alternate data stream
|
||||
{"x.", "x."},
|
||||
{".3mf", ".3mf"},
|
||||
{unicode, unicode},
|
||||
}));
|
||||
CAPTURE(input);
|
||||
CHECK(sanitize_file_basename(input) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("sanitize_file_basename rejects names that do not name a file", "[Utils]") {
|
||||
const std::string input = GENERATE(as<std::string>{}, "", ".", "..", "../..", "dir/", "..\\", " ", ". .", "...");
|
||||
CAPTURE(input);
|
||||
CHECK(sanitize_file_basename(input).empty());
|
||||
}
|
||||
|
||||
namespace {
|
||||
void touch(const boost::filesystem::path &path) { std::ofstream(path.string()) << "existing"; }
|
||||
std::string file_contents(const boost::filesystem::path &path)
|
||||
{
|
||||
std::ifstream file(path.string());
|
||||
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("find_unused_filename keeps a name nothing uses", "[Utils]") {
|
||||
ScopedTemporaryDir dir;
|
||||
std::string name;
|
||||
REQUIRE(find_unused_filename(dir.path(), "model.3mf", {}, name));
|
||||
CHECK(name == "model.3mf");
|
||||
}
|
||||
|
||||
TEST_CASE("find_unused_filename versions a name an existing file uses", "[Utils]") {
|
||||
ScopedTemporaryDir dir;
|
||||
touch(dir.path() / "model.3mf");
|
||||
std::string name;
|
||||
REQUIRE(find_unused_filename(dir.path(), "model.3mf", {}, name));
|
||||
CHECK(name == "model(1).3mf");
|
||||
}
|
||||
|
||||
TEST_CASE("find_unused_filename versions a name that maps onto an existing file once sanitized", "[Utils]") {
|
||||
ScopedTemporaryDir dir;
|
||||
touch(dir.path() / "my_model.3mf");
|
||||
const std::string input = GENERATE(as<std::string>{}, "my?model.3mf", "my:model.3mf", "my*model.3mf");
|
||||
CAPTURE(input);
|
||||
std::string name;
|
||||
REQUIRE(find_unused_filename(dir.path(), input, {}, name));
|
||||
CHECK(name == "my_model(1).3mf");
|
||||
CHECK(file_contents(dir.path() / "my_model.3mf") == "existing");
|
||||
}
|
||||
|
||||
TEST_CASE("find_unused_filename treats the marker of another download as used", "[Utils]") {
|
||||
ScopedTemporaryDir dir;
|
||||
touch(download_marker_path(dir.path(), "model.3mf"));
|
||||
std::string name;
|
||||
REQUIRE(find_unused_filename(dir.path(), "model.3mf", {}, name));
|
||||
CHECK(name == "model(1).3mf");
|
||||
}
|
||||
|
||||
TEST_CASE("find_unused_filename ignores the marker of the download asking", "[Utils]") {
|
||||
ScopedTemporaryDir dir;
|
||||
const boost::filesystem::path own_marker = download_marker_path(dir.path(), "model.3mf");
|
||||
touch(own_marker);
|
||||
std::string name;
|
||||
REQUIRE(find_unused_filename(dir.path(), "model.3mf", own_marker, name));
|
||||
CHECK(name == "model.3mf");
|
||||
}
|
||||
|
||||
TEST_CASE("find_unused_filename gives up after 999 versions", "[Utils]") {
|
||||
ScopedTemporaryDir dir;
|
||||
touch(dir.path() / "model.3mf");
|
||||
for (int version = 1; version < 999; ++version)
|
||||
touch(dir.path() / ("model(" + std::to_string(version) + ").3mf"));
|
||||
std::string name;
|
||||
REQUIRE(find_unused_filename(dir.path(), "model.3mf", {}, name));
|
||||
CHECK(name == "model(999).3mf");
|
||||
|
||||
touch(dir.path() / name);
|
||||
REQUIRE_FALSE(find_unused_filename(dir.path(), "model.3mf", {}, name));
|
||||
CHECK(name == "model(999).3mf");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user