From d1a37ad8cad0702406ea59f4306d149a3149cf4a Mon Sep 17 00:00:00 2001 From: Leon Vinken Date: Sun, 4 Oct 2026 20:27:23 +0200 Subject: [PATCH] Fix #35444 - [TablEdit import] show more detailed error message when a TablEdit file cannot be opened. --- .../tabledit/internal/importtef.cpp | 105 ++++++++++--- .../tabledit/internal/importtef.h | 17 +- .../tabledit/internal/tableditreader.cpp | 21 ++- .../tabledit/tests/data/file_protected.tef | Bin 0 -> 871 bytes .../tabledit/tests/data/header_format_v10.tef | Bin 0 -> 871 bytes .../tests/data/header_incorrect_signature.tef | Bin 0 -> 871 bytes .../tests/data/header_invalid_format_v09.tef | Bin 0 -> 256 bytes .../tests/data/header_invalid_format_v11.tef | Bin 0 -> 256 bytes .../tests/data/header_invalid_too_small.tef | Bin 0 -> 255 bytes .../tests/data/header_signature_1.mscx | 148 ++++++++++++++++++ .../tests/data/header_signature_1.tef | Bin 0 -> 871 bytes .../tests/data/header_signature_2.mscx | 148 ++++++++++++++++++ .../tests/data/header_signature_2.tef | Bin 0 -> 871 bytes .../tabledit/tests/tabledit_tests.cpp | 63 +++++++- 14 files changed, 465 insertions(+), 37 deletions(-) create mode 100644 src/importexport/tabledit/tests/data/file_protected.tef create mode 100644 src/importexport/tabledit/tests/data/header_format_v10.tef create mode 100644 src/importexport/tabledit/tests/data/header_incorrect_signature.tef create mode 100644 src/importexport/tabledit/tests/data/header_invalid_format_v09.tef create mode 100644 src/importexport/tabledit/tests/data/header_invalid_format_v11.tef create mode 100644 src/importexport/tabledit/tests/data/header_invalid_too_small.tef create mode 100644 src/importexport/tabledit/tests/data/header_signature_1.mscx create mode 100644 src/importexport/tabledit/tests/data/header_signature_1.tef create mode 100644 src/importexport/tabledit/tests/data/header_signature_2.mscx create mode 100644 src/importexport/tabledit/tests/data/header_signature_2.tef diff --git a/src/importexport/tabledit/internal/importtef.cpp b/src/importexport/tabledit/internal/importtef.cpp index 50a62f9a2d4e..560cac5d2a71 100644 --- a/src/importexport/tabledit/internal/importtef.cpp +++ b/src/importexport/tabledit/internal/importtef.cpp @@ -49,6 +49,7 @@ #include "engraving/dom/text.h" #include "engraving/dom/timesig.h" #include "engraving/dom/volta.h" +#include "io/file.h" #include "log.h" using namespace mu::engraving; @@ -1445,8 +1446,26 @@ void TablEdit::readTefTexts() } } -void TablEdit::readTefHeader() +//--------------------------------------------------------- +// readTefHeader - read the header of a TablEdit format v3 file +// preconditions: +// - file has been opened and is readable +// - file size is at least the header size +// - file has correct v3 wFormat and signature +// first check if the file is protected (meaning import is not allowed) +// in that case, immediately return with FileBadFormat +//--------------------------------------------------------- + +mu::engraving::Err TablEdit::readTefHeader() { + _file->seek(OFFSET_SECURITYCODE); + tefHeader.securityCode = readUInt32(); + tefHeader.securityFlags = readUInt32(); + if (tefHeader.securityCode != 0) { + _errorMessage = muse::mtrc("iex_tabledit", "The file is protected."); + return Err::FileBadFormat; + } + _file->seek(0); readUInt16(); // skip private01 tefHeader.version = readUInt16(); tefHeader.subVersion = readUInt16(); @@ -1455,8 +1474,6 @@ void TablEdit::readTefHeader() tefHeader.reverb = readUInt16(); readUInt16(); // todo syncope (is signed) readUInt16(); // skip private02 - tefHeader.securityCode = readUInt32(); - tefHeader.securityFlags = readUInt32(); _file->seek(OFFSET_TBED); tefHeader.tbed = readUInt32(); readUInt32(); // skip contents @@ -1472,21 +1489,82 @@ void TablEdit::readTefHeader() _file->seek(OFFSET_OLDNUM); tefHeader.wOldNum = readUInt16(); tefHeader.wFormat = readUInt16(); + return Err::NoError; +} + +uint32_t TablEdit::readSignature() +{ + _file->seek(OFFSET_TBED); + return readUInt32(); +} + +uint16_t TablEdit::readWFormat() +{ + _file->seek(OFFSET_WFORMAT); + return readUInt16(); } //--------------------------------------------------------- // import -// -// see src/importexport/guitarpro/internal/importgtp.cpp createLinkedTabs() -// for linked staves //--------------------------------------------------------- -Err TablEdit::import() +Err TablEdit::import(const muse::io::path_t& path) { - readTefHeader(); + muse::io::File tefFile(path); + if (!tefFile.exists()) { + LOGN("file '%s' not found", path.toStdString().c_str()); + return Err::FileNotFound; + } + + if (!tefFile.open(muse::io::IODevice::ReadOnly)) { + LOGN("file '%s' not accessible", path.toStdString().c_str()); + return Err::FileOpenError; + } + + // do checks to verify this is a file type and version we can handle + const size_t fileSize { tefFile.size() }; + if (fileSize < HEADER_SIZE) { + // a TablEdit file cannot be smaller than HEADER_SIZE + _errorMessage = muse::mtrc("iex_tabledit", "The file size is too small (%1 bytes).").arg(fileSize); + return Err::FileBadFormat; + } + + _file = &tefFile; // all TablEdit's file handling functions depend on _file + + const int wFormatHiByte { readWFormat() >> 8 }; + if (wFormatHiByte < 10) { + _errorMessage = muse::mtrc("iex_tabledit", "The file is in an older TablEdit format (version %1)." + " The importer does not support this version.").arg(wFormatHiByte); + return Err::FileBadFormat; + } + if (wFormatHiByte > 10) { + _errorMessage = muse::mtrc("iex_tabledit", "The file is in a newer TablEdit format (version %1)." + " The importer does not support this version.").arg(wFormatHiByte); + return Err::FileBadFormat; + } + + // Format v10 files contain either "debt" or "tbed" in ASCII as signature, imported as a 32-bit integer + const int64_t actualSignature { readSignature() }; // String::arg() does not support uit32_t + const int signature1 { 0x64656274 }; + const int signature2 { 0x74626564 }; + if (actualSignature != signature1 && actualSignature != signature2) { + _errorMessage = muse::mtrc("iex_tabledit", "The file signature is incorrect (%1).").arg(actualSignature); + return Err::FileBadFormat; + } + + // do actual import + const Err err { readTefHeader() }; + if (err == Err::FileBadFormat) { + _errorMessage = muse::mtrc("iex_tabledit", "The file is protected."); + return err; + } else if (err != Err::NoError) { + _errorMessage = muse::mtrc("iex_tabledit", "Unexpected error reading header."); + return err; + } LOGN("version %d subversion %d", tefHeader.version, tefHeader.subVersion); LOGN("tempo %d chorus %d reverb %d", tefHeader.tempo, tefHeader.chorus, tefHeader.reverb); LOGN("securityCode %d securityFlags %d", tefHeader.securityCode, tefHeader.securityFlags); + LOGN("tbed %d", tefHeader.tbed); LOGN("title '%s'", tefHeader.title.c_str()); LOGN("subTitle '%s'", tefHeader.subTitle.c_str()); LOGN("comment '%s'", tefHeader.comment.c_str()); @@ -1494,17 +1572,6 @@ Err TablEdit::import() LOGN("internetLink '%s'", tefHeader.internetLink.c_str()); LOGN("copyright '%s'", tefHeader.copyright.c_str()); LOGN("tbed %d wOldNum %d wFormat %d", tefHeader.tbed, tefHeader.wOldNum, tefHeader.wFormat); - if ((tefHeader.wFormat >> 8) < 10) { - return Err::FileBadFormat; - //return Err::FileTooOld; // TODO: message is too specific for MuseScore format - } - if ((tefHeader.wFormat >> 8) > 10) { - return Err::FileBadFormat; - //return Err::FileTooNew; - } - if (tefHeader.securityCode != 0) { - return Err::FileBadFormat; // todo "file is protected" message ? - } readTefTexts(); for (const auto& text : tefTexts) { LOGN("text: '%s'", text.c_str()); diff --git a/src/importexport/tabledit/internal/importtef.h b/src/importexport/tabledit/internal/importtef.h index 897acc62eb85..687ddb68881a 100644 --- a/src/importexport/tabledit/internal/importtef.h +++ b/src/importexport/tabledit/internal/importtef.h @@ -35,7 +35,11 @@ namespace mu::iex::tabledit { class MeasureHandler; +// header size +static const size_t HEADER_SIZE = 256; + // offsets into the file header +static const uint8_t OFFSET_SECURITYCODE = 0x10; static const uint8_t OFFSET_TBED = 0x38; static const uint8_t OFFSET_CONTENTS = 0x3C; static const uint8_t OFFSET_TITLE = 0x40; @@ -49,6 +53,7 @@ static const uint8_t OFFSET_READINGLIST = 0x80; static const uint8_t OFFSET_INTERNETLINK = 0x84; static const uint8_t OFFSET_COPYRIGHT = 0x8C; static const uint8_t OFFSET_OLDNUM = 0xCA; +static const uint8_t OFFSET_WFORMAT = 0xCC; struct TefMeasure { int flag { 0 }; @@ -67,6 +72,7 @@ struct TefReadingListItem { class TablEdit { + muse::String _errorMessage; muse::io::IODevice* _file = nullptr; mu::engraving::MasterScore* score = nullptr; @@ -138,12 +144,14 @@ class TablEdit void initializeVoiceAllocators(std::vector& allocators); engraving::part_idx_t partIdx(size_t stringIdx, bool& ok) const; int stringNumberPreviousParts(engraving::part_idx_t partIdx) const; + uint32_t readSignature(); void readTefContents(); - void readTefHeader(); + mu::engraving::Err readTefHeader(); void readTefInstruments(); void readTefMeasures(); void readTefReadingList(); void readTefTexts(); + uint16_t readWFormat(); TefHeader tefHeader; std::vector tefTextMarkers; @@ -156,8 +164,9 @@ class TablEdit std::map effectMap; public: - TablEdit(muse::io::IODevice* f, mu::engraving::MasterScore* s) - : _file(f), score(s) {} - mu::engraving::Err import(); + TablEdit(mu::engraving::MasterScore* s) + : score(s) {} + mu::engraving::Err import(const muse::io::path_t& path); + muse::String errorMessage() { return _errorMessage; } }; } // namespace mu::iex::tabledit diff --git a/src/importexport/tabledit/internal/tableditreader.cpp b/src/importexport/tabledit/internal/tableditreader.cpp index 5dd47b105566..ddb38c299f89 100644 --- a/src/importexport/tabledit/internal/tableditreader.cpp +++ b/src/importexport/tabledit/internal/tableditreader.cpp @@ -22,7 +22,6 @@ #include "engraving/dom/score.h" #include "engraving/engravingerrors.h" -#include "io/file.h" #include "importtef.h" #include "tableditreader.h" @@ -32,22 +31,20 @@ using namespace mu::engraving; muse::Ret TablEditReader::read(MasterScore* score, const muse::io::path_t& path, const Options&) { - Err err = import(score, path); + TablEdit tablEdit{ score }; + Err err = tablEdit.import(path); + if (err == Err::FileBadFormat) { + // Give a specific reason instead of the generic "Bad format" text. + return make_ret(err, tablEdit.errorMessage()); + } + return make_ret(err, path); } Err TablEditReader::import(MasterScore* score, const muse::io::path_t& path, const Options&) { - if (!fileSystem()->exists(path)) { - return Err::FileNotFound; - } - - muse::io::File file(path); - if (!file.open(muse::io::IODevice::ReadOnly)) { - return Err::FileOpenError; - } - TablEdit tablEdit{ &file, score }; - Err err = tablEdit.import(); + TablEdit tablEdit{ score }; + Err err = tablEdit.import(path); return err; } diff --git a/src/importexport/tabledit/tests/data/file_protected.tef b/src/importexport/tabledit/tests/data/file_protected.tef new file mode 100644 index 0000000000000000000000000000000000000000..33cc2f914241b45e11edb439c2d312a1fafc5b4e GIT binary patch literal 871 zcmWe&U}4_IP{9BLj4%d}W@JXH{@^^jXl(K z76ukBT&6PNk^ng$i1-;o5|eUVQ!-0ZQxuXa6`;aAAQ}Q3SXdYsT!4HwsDK2NHehF9 zP*By>Gq!>VxR+*@Bo-+Imn7z;Bo?JWRiRQ37!3>zfyfX@7#P?Z8kk$zSy)=y*qOTl z`2hwdM!^R9`bHp0C@{2gFw_TX0O_-`x3coE0c(X5Q>K6vT1HEK$%^09ss3Pm7^`1X;=A0O9-x F0sy$LHP`?E literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/data/header_incorrect_signature.tef b/src/importexport/tabledit/tests/data/header_incorrect_signature.tef new file mode 100644 index 0000000000000000000000000000000000000000..a1b749eee39ba154c81dd32d28edb22b735a8a2f GIT binary patch literal 871 zcmWe&U}4_IP{Du=FfwBkg@~o5CY6MPl(K z76ukBT&6PNk^ng$i1-;o5|eUVQ!-0ZQxuXa6`;aAAQ}Q3SXdYsT!4HwsDK2NHehF9 zP*By>Gq!>VxR+*@Bo-+Imn7z;Bo?JWRiRQ37!3>zfyfX@7#P?Z8kk$zSy)=y*qOTl z`2hwdM!^R9`bHp0C@{2gFw_TX0O_-`x3coE0c(X5Q>K6vT1HEK$%^09ss3Pm7^`1X;=A0O9-x F0sy<-HQ4|F literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/data/header_invalid_format_v09.tef b/src/importexport/tabledit/tests/data/header_invalid_format_v09.tef new file mode 100644 index 0000000000000000000000000000000000000000..d8947751b46746bb6ad5941379d6c76ad788486d GIT binary patch literal 256 QcmZQz7(Rf7lXSlT00Wx<4gdfE literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/data/header_invalid_format_v11.tef b/src/importexport/tabledit/tests/data/header_invalid_format_v11.tef new file mode 100644 index 0000000000000000000000000000000000000000..ad8ecb5de99a4db55a6dec466c06ee2c2d18ea33 GIT binary patch literal 256 QcmZQz7(Rf7n{>Yb00aR55C8xG literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/data/header_invalid_too_small.tef b/src/importexport/tabledit/tests/data/header_invalid_too_small.tef new file mode 100644 index 0000000000000000000000000000000000000000..280a6c8296972f2ba63bc403ec1909d0abc99c40 GIT binary patch literal 255 LcmZQz7|{R#0RI31 literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/data/header_signature_1.mscx b/src/importexport/tabledit/tests/data/header_signature_1.mscx new file mode 100644 index 000000000000..4225d437bd89 --- /dev/null +++ b/src/importexport/tabledit/tests/data/header_signature_1.mscx @@ -0,0 +1,148 @@ + + + + B_B + 480 + + 1 + 1 + 1 + 0 + + TablEdited by + + + + + + tef + + + + + + C_C + + D_D + + stdNormal + + G8vb + + + E_E + D_D + + tab6StrSimple + 6 + 1.5 + 1 + 0 + 0 + MuseScore Tab Modern + 15 + 0 + 1 + tab_fret_number + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 1 + + + + + Guitar Standard + + + pluck.guitar + + 25 + 38 + 45 + 50 + 55 + 59 + 64 + + + + + + + + + 10 + F_F + + + G_G + + + H_H + 0 + + + I_I + 4 + 4 + + + 2 + 1 + J_J + metNoteQuarterUp = 120 + + + K_K + whole + + L_L + 48 + 14 + 3 + 4 + + + + M_M + + + + + + + + + N_N + H_H + 0 + + + O_O + K_K + whole + + P_P + L_L + 48 + 14 + 3 + 4 + + + + Q_Q + M_M + + + + + + diff --git a/src/importexport/tabledit/tests/data/header_signature_1.tef b/src/importexport/tabledit/tests/data/header_signature_1.tef new file mode 100644 index 0000000000000000000000000000000000000000..db228d45cb2edec85687ada50c02f3260e00cd17 GIT binary patch literal 871 zcmWe&U}4_IP{Du=FfwBkg@~0TrKW^~l(K z76ukBT&6PNk^ng$i1-;o5|eUVQ!-0ZQxuXa6`;aAAQ}Q3SXdYsT!4HwsDK2NHehF9 zP*By>Gq!>VxR+*@Bo-+Imn7z;Bo?JWRiRQ37!3>zfyfX@7#P?Z8kk$zSy)=y*qOTl z`2hwdM!^R9`bHp0C@{2gFw_TX0O_-`x3coE0c(X5Q>K6vT1HEK$%^09ss3Pm7^`1X;=A0O9-x F0sy$&HP`?E literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/data/header_signature_2.mscx b/src/importexport/tabledit/tests/data/header_signature_2.mscx new file mode 100644 index 000000000000..4225d437bd89 --- /dev/null +++ b/src/importexport/tabledit/tests/data/header_signature_2.mscx @@ -0,0 +1,148 @@ + + + + B_B + 480 + + 1 + 1 + 1 + 0 + + TablEdited by + + + + + + tef + + + + + + C_C + + D_D + + stdNormal + + G8vb + + + E_E + D_D + + tab6StrSimple + 6 + 1.5 + 1 + 0 + 0 + MuseScore Tab Modern + 15 + 0 + 1 + tab_fret_number + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 1 + + + + + Guitar Standard + + + pluck.guitar + + 25 + 38 + 45 + 50 + 55 + 59 + 64 + + + + + + + + + 10 + F_F + + + G_G + + + H_H + 0 + + + I_I + 4 + 4 + + + 2 + 1 + J_J + metNoteQuarterUp = 120 + + + K_K + whole + + L_L + 48 + 14 + 3 + 4 + + + + M_M + + + + + + + + + N_N + H_H + 0 + + + O_O + K_K + whole + + P_P + L_L + 48 + 14 + 3 + 4 + + + + Q_Q + M_M + + + + + + diff --git a/src/importexport/tabledit/tests/data/header_signature_2.tef b/src/importexport/tabledit/tests/data/header_signature_2.tef new file mode 100644 index 0000000000000000000000000000000000000000..2862d4f6178a5e7cfcfe67ae2e1d9f50d25ce8eb GIT binary patch literal 871 zcmWe&U}4_IP{Du=FfwBkg@~o3CY6MPl(K z76ukBT&6PNk^ng$i1-;o5|eUVQ!-0ZQxuXa6`;aAAQ}Q3SXdYsT!4HwsDK2NHehF9 zP*By>Gq!>VxR+*@Bo-+Imn7z;Bo?JWRiRQ37!3>zfyfX@7#P?Z8kk$zSy)=y*qOTl z`2hwdM!^R9`bHp0C@{2gFw_TX0O_-`x3coE0c(X5Q>K6vT1HEK$%^09ss3Pm7^`1X;=A0O9-x F0sy$LHP`?E literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/tabledit_tests.cpp b/src/importexport/tabledit/tests/tabledit_tests.cpp index 26fcf7f28b9e..696bc486b6a3 100644 --- a/src/importexport/tabledit/tests/tabledit_tests.cpp +++ b/src/importexport/tabledit/tests/tabledit_tests.cpp @@ -22,22 +22,27 @@ #include +#include "engraving/compat/scoreaccess.h" +#include "engraving/dom/stafftext.h" +#include "engraving/infrastructure/localfileinfoprovider.h" #include "engraving/tests/utils/scorecomp.h" #include "engraving/tests/utils/scorerw.h" -#include "engraving/dom/stafftext.h" - #include "importexport/tabledit/internal/tableditreader.h" using namespace mu::engraving; static const muse::String TABLEDIT_DIR("data/"); +//! NOTE Temporary for testing +static const muse::modularity::ContextPtr utestCtx = std::make_shared(1); + class TablEdit_Tests : public ::testing::Test { public: void tefReadTest(const char* file); MasterScore* tefRead(const char* file, Err& err); + void tefReadBadFormat(const char* file, const char* message); }; //--------------------------------------------------------- @@ -61,6 +66,26 @@ void TablEdit_Tests::tefReadTest(const char* file) delete score; } +//--------------------------------------------------------- +// tefReadBadFormat +// read a TablEdit file with an invalid header, +// verify Err::FileBadFormat and correct the error message are returned +//--------------------------------------------------------- + +void TablEdit_Tests::tefReadBadFormat(const char* name, const char* message) +{ + const muse::modularity::ContextPtr iocCtx = utestCtx; + muse::io::path_t path = ScoreRW::rootPath() + u"/" + TABLEDIT_DIR + name + u".tef"; + MasterScore* score = compat::ScoreAccess::createMasterScoreWithBaseStyle(iocCtx); + score->setFileInfoProvider(std::make_shared(path)); + + mu::iex::tabledit::TablEditReader tablEditReader; + muse::Ret ret { tablEditReader.read(score, path) }; + + EXPECT_TRUE(ret.code() == static_cast(Err::FileBadFormat)); + EXPECT_TRUE(ret.text() == message); +} + //--------------------------------------------------------- // tefRead // read a TablEdit file and report the importer's error code @@ -159,6 +184,14 @@ TEST_F(TablEdit_Tests, tef_guitar_new_standard_tuning) { tefReadTest("guitar_new_standard_tuning"); } +TEST_F(TablEdit_Tests, tef_header_signature_1) { + tefReadTest("header_signature_1"); +} + +TEST_F(TablEdit_Tests, tef_header_signature_2) { + tefReadTest("header_signature_2"); +} + TEST_F(TablEdit_Tests, tef_key_signatures) { tefReadTest("key_signatures"); } @@ -306,6 +339,32 @@ TEST_F(TablEdit_Tests, tef_voices_multi_part) { tefReadTest("voices_multi_part"); } +//--------------------------------------------------------- +// invalid header and protected file tests +//--------------------------------------------------------- + +TEST_F(TablEdit_Tests, tef_file_protected) { + tefReadBadFormat("file_protected", "The file is protected."); +} + +TEST_F(TablEdit_Tests, tef_header_incorrect_signature) { + tefReadBadFormat("header_incorrect_signature", "The file signature is incorrect (1952605541)."); +} + +TEST_F(TablEdit_Tests, tef_header_invalid_format_v09) { + tefReadBadFormat("header_invalid_format_v09", + "The file is in an older TablEdit format (version 9). The importer does not support this version."); +} + +TEST_F(TablEdit_Tests, tef_header_invalid_format_v11) { + tefReadBadFormat("header_invalid_format_v11", + "The file is in a newer TablEdit format (version 11). The importer does not support this version."); +} + +TEST_F(TablEdit_Tests, tef_header_invalid_too_small) { + tefReadBadFormat("header_invalid_too_small", "The file size is too small (255 bytes)."); +} + //--------------------------------------------------------- // malformed input fix tests //