From e1041ddc61a5c98d88287edb7ec7f9af36bd9f41 Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Sat, 27 Jul 2024 00:47:56 -0400 Subject: [PATCH] Add tests for MANIFEST.MF parser Signed-off-by: Evan Goode --- launcher/Manifest.cpp | 31 ++++++---- launcher/Manifest.h | 44 +++++++++++---- tests/CMakeLists.txt | 3 + tests/Manifest_test.cpp | 122 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 tests/Manifest_test.cpp diff --git a/launcher/Manifest.cpp b/launcher/Manifest.cpp index 7755d4b75..43eaf5658 100644 --- a/launcher/Manifest.cpp +++ b/launcher/Manifest.cpp @@ -60,6 +60,11 @@ Manifest::Manifest(std::istream& is) read(is, std::nullopt); } +Manifest::Manifest(std::istream& is, const std::string& jar_filename) +{ + read(is, jar_filename); +} + bool Manifest::isValidName(const std::string& name) { const auto len = name.length(); @@ -74,21 +79,22 @@ std::string Manifest::getErrorPosition(const std::optional& filenam return "manifest of " + *filename + ":" + std::to_string(line_number); } -int Manifest::readAttributes(section_t& section, std::istream& is, lbuf_t lbuf, const std::optional& filename, int line_number) +int Manifest::readAttributes(manifest_section_t& section, + std::istream& is, + lbuf_t lbuf, + const std::optional& filename, + int line_number) { std::optional name; std::string value; std::string full_line; - while (!is.eof() && is.getline(lbuf, MAX_LINE_LENGTH)) { + while (!is.eof() && is.getline(lbuf, MANIFEST_MAX_LINE_LENGTH)) { std::size_t len = strlen(lbuf); line_number += 1; - if (is.fail()) { - throw std::length_error("line too long (" + getErrorPosition(filename, line_number) + ")"); - } if (len > 0 && lbuf[len - 1] == '\r') { len -= 1; } @@ -130,6 +136,9 @@ int Manifest::readAttributes(section_t& section, std::istream& is, lbuf_t lbuf, } section[*name] = value; } + if (!is.eof() && is.fail()) { + throw std::length_error("line too long (" + getErrorPosition(filename, line_number) + ")"); + } return line_number; } @@ -144,7 +153,7 @@ std::optional Manifest::parseName(lbuf_t lbuf, std::size_t len) void Manifest::read(std::istream& is, const std::optional& jar_filename) { // Line buffer - char lbuf[MAX_LINE_LENGTH]; + char lbuf[MANIFEST_MAX_LINE_LENGTH]; // Read the main attributes for the manifest int line_number = readAttributes(m_main_section, is, lbuf, jar_filename, 0); @@ -153,14 +162,11 @@ void Manifest::read(std::istream& is, const std::optional& jar_file bool skip_empty_lines = true; std::optional lastline; - while (!is.eof() && is.getline(lbuf, MAX_LINE_LENGTH)) { + while (!is.eof() && is.getline(lbuf, MANIFEST_MAX_LINE_LENGTH)) { std::size_t len = strlen(lbuf); line_number += 1; - if (is.fail()) { - throw std::length_error("manifest line too long (" + getErrorPosition(jar_filename, line_number) + ")"); - } if (len > 0 && lbuf[len - 1] == '\r') { len -= 1; } @@ -192,10 +198,13 @@ void Manifest::read(std::istream& is, const std::optional& jar_file lastline = std::nullopt; } - section_t& attr = m_individual_sections[*name]; + manifest_section_t& attr = m_individual_sections[*name]; line_number = readAttributes(attr, is, lbuf, jar_filename, line_number); name = std::nullopt; skip_empty_lines = true; } + if (!is.eof() && is.fail()) { + throw std::length_error("manifest line too long (" + getErrorPosition(jar_filename, line_number) + ")"); + } } diff --git a/launcher/Manifest.h b/launcher/Manifest.h index 1b668d060..c61b8b760 100644 --- a/launcher/Manifest.h +++ b/launcher/Manifest.h @@ -20,26 +20,48 @@ #include #include -constexpr const int MAX_LINE_LENGTH = 512; -using lbuf_t = char[MAX_LINE_LENGTH]; +constexpr const int MANIFEST_MAX_LINE_LENGTH = 512; +using lbuf_t = char[MANIFEST_MAX_LINE_LENGTH]; -using section_t = std::map; -using sections_t = std::map; -using manifest_t = std::pair; +using manifest_section_t = std::map; +using manifest_sections_t = std::map; class Manifest { public: Manifest(std::istream& is); - section_t& getMainAttributes() { return m_main_section; } - sections_t& getEntries() { return m_individual_sections; } - section_t& getAttributes(std::string& name) { return m_individual_sections.at(name); } + Manifest(std::istream& is, const std::string& jar_filename); + Manifest(const Manifest& other) + { + m_main_section = other.m_main_section; + m_individual_sections = other.m_individual_sections; + } + manifest_section_t& getMainAttributes() { return m_main_section; } + manifest_sections_t& getEntries() { return m_individual_sections; } + manifest_section_t& getAttributes(const std::string& name) { return m_individual_sections.at(name); } + Manifest& operator=(const Manifest& other) + { + if (this == &other) { + return *this; + } + m_main_section = other.m_main_section; + m_individual_sections = other.m_individual_sections; + return *this; + } + bool operator==(const Manifest& other) const + { + return m_main_section == other.m_main_section && m_individual_sections == other.m_individual_sections; + } private: static std::string getErrorPosition(const std::optional& filename, int line_number); static std::optional parseName(lbuf_t lbuf, std::size_t len); static bool isValidName(const std::string& name); - int readAttributes(section_t& section, std::istream& is, lbuf_t lbuf, const std::optional& jar_filename, int line_number); + int readAttributes(manifest_section_t& section, + std::istream& is, + lbuf_t lbuf, + const std::optional& jar_filename, + int line_number); void read(std::istream& is, const std::optional& jar_filename); - section_t m_main_section; - sections_t m_individual_sections; + manifest_section_t m_main_section; + manifest_sections_t m_individual_sections; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59e0e3144..ccf340521 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,9 @@ ecm_add_test(ResourceModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME TexturePackParse) +ecm_add_test(Manifest_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME Manifest) + ecm_add_test(DataPackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME DataPackParse) diff --git a/tests/Manifest_test.cpp b/tests/Manifest_test.cpp new file mode 100644 index 000000000..6d2882d83 --- /dev/null +++ b/tests/Manifest_test.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include + +#include "Manifest.h" + +class ManifestTest : public QObject { + Q_OBJECT + + private slots: + void test_emptyManifest() + { + std::istringstream iss; + const auto& manifest = new Manifest(iss); + QVERIFY(manifest->getMainAttributes().empty()); + QVERIFY(manifest->getEntries().empty()); + } + void test_parseManifest() + { + std::string manifest_text{ R"(Manifest-Version: 1.0 +Created-By: 1.8.0 (Oracle Inc.) + +Name: common/class1.class +SHA-256-Digest: D7fzW7bq0W+7YRCfxfZ4LY5LlCy+PXisjRgMCIiebS4= + +Name: common/class2.class +SHA-256-Digest: TwUa/b/a2EQRsHWKupdFAR7S/BTeL52xTBvaB8C78Kc= +)" }; + + std::istringstream iss{ manifest_text }; + const auto& manifest = new Manifest(iss); + + QVERIFY(manifest->getEntries().size() == 2); + + auto& main_attributes = manifest->getMainAttributes(); + QVERIFY(main_attributes.size() == 2); + QVERIFY(main_attributes["Manifest-Version"] == "1.0"); + QVERIFY(main_attributes["Created-By"] == "1.8.0 (Oracle Inc.)"); + + auto& class1_attributes = manifest->getAttributes("common/class1.class"); + QVERIFY(class1_attributes.size() == 1); + QVERIFY(class1_attributes["SHA-256-Digest"] == "D7fzW7bq0W+7YRCfxfZ4LY5LlCy+PXisjRgMCIiebS4="); + + auto& class2_attributes = manifest->getAttributes("common/class2.class"); + QVERIFY(class2_attributes.size() == 1); + QVERIFY(class2_attributes["SHA-256-Digest"] == "TwUa/b/a2EQRsHWKupdFAR7S/BTeL52xTBvaB8C78Kc="); + + // Manifest should parse even without the trailing newline + std::string manifest_text_no_newline{ manifest_text }; + manifest_text_no_newline.pop_back(); + + std::istringstream iss_no_newline{ manifest_text_no_newline }; + const auto& manifest_no_newline = new Manifest(iss_no_newline); + + QVERIFY(*manifest_no_newline == *manifest); + } + void test_invalidName() + { + std::istringstream iss{ R"(Manifest-Version: 1.0 + +A-Name-That-Is-Way-Too-Loooooooooooooooooooooooooooooooooooooooooooooooonoooooooooong: 1 +)" }; + bool caught = false; + try { + new Manifest(iss); + } catch (const std::runtime_error&) { + caught = true; + } + QVERIFY(caught); + } + void test_lineTooLong() + { + std::string manifest_text{ "Manifest-Version: " }; + manifest_text.append(std::string(MANIFEST_MAX_LINE_LENGTH, '1')); + std::istringstream iss{ manifest_text }; + bool caught = false; + try { + new Manifest(iss); + } catch (const std::length_error&) { + caught = true; + } + QVERIFY(caught); + } + void test_misplacedContinuation() + { + std::istringstream iss{ " Manifest-Version: 1.0" }; + bool caught = false; + try { + new Manifest(iss); + } catch (const std::runtime_error&) { + caught = true; + } + QVERIFY(caught); + } + void test_misingColon() + { + std::istringstream iss{ "Manifest-Version 1.0" }; + bool caught = false; + try { + new Manifest(iss); + } catch (const std::runtime_error&) { + caught = true; + } + QVERIFY(caught); + } + void test_misingSpace() + { + std::istringstream iss{ "Manifest-Version:1.0" }; + bool caught = false; + try { + new Manifest(iss); + } catch (const std::runtime_error&) { + caught = true; + } + QVERIFY(caught); + } +}; + +QTEST_GUILESS_MAIN(ManifestTest) + +#include "Manifest_test.moc"