diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 9ff8a9bcb..cc1a4b8a4 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -128,11 +128,19 @@ void dConfig::SetDatabaseSync(std::function sync) { void dConfig::ProcessLine(const std::string& line, const std::string& file, const std::string& description) { auto splitLoc = line.find('='); if (splitLoc == std::string::npos) return; - auto key = line.substr(0, splitLoc); - auto value = line.substr(splitLoc + 1); - - //Make sure that on Linux, we remove special characters: - if (!value.empty() && value.at(value.size() - 1) == '\r') value.erase(value.size() - 1); + // Stray spaces around keys and values (for example "client_location = ../client ") are a + // common setup mistake that otherwise surfaces as an unrelated error much later, so drop them. + // This also removes the \r left at the end of lines of a file saved with Windows line endings. + const auto trim = [](std::string_view str) { + constexpr std::string_view whitespace = " \t\r\n"; + const auto start = str.find_first_not_of(whitespace); + if (start == std::string_view::npos) return std::string{}; + const auto end = str.find_last_not_of(whitespace); + return std::string{ str.substr(start, end - start + 1) }; + }; + const auto key = trim(std::string_view(line).substr(0, splitLoc)); + const auto value = trim(std::string_view(line).substr(splitLoc + 1)); + if (key.empty()) return; if (this->m_ConfigValues.find(key) != this->m_ConfigValues.end()) return; diff --git a/tests/dCommonTests/ConfigLayerTests.cpp b/tests/dCommonTests/ConfigLayerTests.cpp index dd8ebffb3..5610d44fc 100644 --- a/tests/dCommonTests/ConfigLayerTests.cpp +++ b/tests/dCommonTests/ConfigLayerTests.cpp @@ -4,6 +4,10 @@ #include "dConfig.h" #include "ConfigSync.h" +#include "BinaryPathFinder.h" + +#include +#include namespace { void SetEnv(const char* name, const char* value) { @@ -111,3 +115,24 @@ TEST(ConfigLayerTests, OwnedValuesComeFromTheOwner) { EXPECT_EQ(config.GetValue("permission_accounts_ban"), "6"); EXPECT_EQ(config.GetValue("permission_players_view"), "2"); } + +TEST(ConfigLayerTests, WhitespaceAroundKeysAndValuesIsIgnored) { + const auto path = BinaryPathFinder::GetBinaryDir() / "whitespace_test_config.ini"; + { + std::ofstream out(path, std::ios::binary); + out << "zz_plain=value\n"; + out << "zz_spaced = spaced value \r\n"; + out << "\tzz_tabbed\t=\tx\t\n"; + out << " = no key\n"; + out << "zz_empty= \n"; + } + + dConfig config("whitespace_test_config.ini"); + EXPECT_EQ(config.GetValue("zz_plain"), "value"); + EXPECT_EQ(config.GetValue("zz_spaced"), "spaced value"); // inner spaces are kept + EXPECT_EQ(config.GetValue("zz_tabbed"), "x"); + EXPECT_EQ(config.GetValue("zz_empty"), ""); + EXPECT_EQ(config.GetValue(""), ""); + + std::filesystem::remove(path); +}