From 8852fa934738476ca6ec812d544e74cee0a17599 Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Fri, 17 Jul 2026 13:04:04 +0800 Subject: [PATCH] unescape the value before setting LANG --- src/linux/init/config.cpp | 8 +-- src/shared/inc/stringshared.h | 101 ++++++++++++++++++++++++++++++++++ test/windows/SimpleTests.cpp | 35 ++++++++++++ test/windows/UnitTests.cpp | 4 +- 4 files changed, 142 insertions(+), 6 deletions(-) diff --git a/src/linux/init/config.cpp b/src/linux/init/config.cpp index 19aa9ca23d..d72ac36ad8 100644 --- a/src/linux/init/config.cpp +++ b/src/linux/init/config.cpp @@ -2597,9 +2597,8 @@ try // If the current line contains the "LANG=" string, update the // environment block with the remainder of the line. // - // N.B. No validation is done on the contents of the string. If the - // file contains multiple lines containing "LANG=" the last will - // be used. + // N.B. If the file contains multiple lines containing "LANG=" the last + // will be used. // auto Content = strstr(Line, LANG_ENV "="); @@ -2617,7 +2616,8 @@ try *SpecialCharacter = '\0'; } - Environment.AddVariable(LANG_ENV, Content); + const auto Value = wsl::shared::string::UnescapeShell(wsl::shared::string::Trim(std::string{Content})); + Environment.AddVariable(LANG_ENV, Value); } } diff --git a/src/shared/inc/stringshared.h b/src/shared/inc/stringshared.h index 978bd9cbd7..660ed8bc98 100644 --- a/src/shared/inc/stringshared.h +++ b/src/shared/inc/stringshared.h @@ -849,6 +849,107 @@ inline std::wstring FormatBytes(uint64_t bytes) } } +template +inline std::basic_string Trim(const std::basic_string& input) +{ + constexpr TChar whitespace[] = {TChar(' '), TChar('\t'), TChar('\n'), TChar('\r'), TChar('\f'), TChar('\v'), TChar('\0')}; + const auto first = input.find_first_not_of(whitespace); + if (first == std::basic_string::npos) + { + return {}; + } + + const auto last = input.find_last_not_of(whitespace); + return input.substr(first, last - first + 1); +} + +template +inline std::basic_string UnescapeShell(const std::basic_string& input) +{ + enum class Quote + { + None, + Single, + Double + }; + + Quote quote = Quote::None; + std::basic_string output; + output.reserve(input.size()); + + for (size_t index = 0; index < input.size(); index += 1) + { + const auto current = input[index]; + if (quote == Quote::Single) + { + if (current == TChar('\'')) + { + quote = Quote::None; + } + else + { + output.push_back(current); + } + + continue; + } + + if (current == TChar('\'')) + { + if (quote == Quote::Double) + { + output.push_back(current); + } + else + { + quote = Quote::Single; + } + + continue; + } + + if (current == TChar('"')) + { + quote = (quote == Quote::Double) ? Quote::None : Quote::Double; + continue; + } + + if (current != TChar('\\')) + { + output.push_back(current); + continue; + } + + if (++index == input.size()) + { + // return the original string if the escape is invalid. + return input; + } + + const auto escaped = input[index]; + if (quote == Quote::None) + { + // "\\\n" out of escape means continue the line. + if (escaped != TChar('\n')) + { + output.push_back(escaped); + } + } + else if (escaped == TChar('"') || escaped == TChar('\\') || escaped == TChar('$') || escaped == TChar('`')) + { + output.push_back(escaped); + } + else if (escaped != TChar('\n')) + { + output.push_back(current); + output.push_back(escaped); + } + } + + // return the original string if the escape is invalid. + return (quote == Quote::None) ? output : input; +} + } // namespace wsl::shared::string template <> diff --git a/test/windows/SimpleTests.cpp b/test/windows/SimpleTests.cpp index 5a4e77b9e4..03901416aa 100644 --- a/test/windows/SimpleTests.cpp +++ b/test/windows/SimpleTests.cpp @@ -259,6 +259,41 @@ class SimpleTests auto upperCaseGuidStringWide = wideGuidStringNoBraces; std::transform(upperCaseGuidStringWide.begin(), upperCaseGuidStringWide.end(), upperCaseGuidStringWide.begin(), toupper); VERIFY_ARE_EQUAL(upperCaseGuidStringWide, wsl::shared::string::GuidToString(guid, wsl::shared::string::GuidToStringFlags::Uppercase)); + + VERIFY_ARE_EQUAL(wsl::shared::string::Trim(std::string{" \tvalue\r\n"}), std::string{"value"}); + VERIFY_ARE_EQUAL(wsl::shared::string::Trim(std::string{" \t\r\n"}), std::string{}); + VERIFY_ARE_EQUAL(wsl::shared::string::Trim(std::wstring{L" \tvalue\r\n"}), std::wstring{L"value"}); + + const std::vector> shellStrings{ + {"", ""}, + {"plain", "plain"}, + {"\"\"", ""}, + {"''", ""}, + {"\"double quoted\"", "double quoted"}, + {"'single quoted'", "single quoted"}, + {"one' two'\" three\"", "one two three"}, + {"escaped\\ value", "escaped value"}, + {"escaped\\q", "escapedq"}, + {"escaped\\'quote", "escaped'quote"}, + {"abc\\\nedf", "abcedf"}, + {"\"abc\\\nedf\"", "abcedf"}, + {"'abc\\\nedf'", "abc\\\nedf"}, + {"\"escaped \\\"quote\\\" and \\\\ slash\"", "escaped \"quote\" and \\ slash"}, + {"\"escaped \\$dollar and \\`backtick\"", "escaped $dollar and `backtick"}, + {"\"literal \\q\"", "literal \\q"}, + {"'literal \\ value'", "literal \\ value"}, + {"unterminated'", "unterminated'"}, + {"\"unterminated", "\"unterminated"}, + {"trailing\\", "trailing\\"}, + {"\"trailing\\", "\"trailing\\"}}; + + for (const auto& [input, expected] : shellStrings) + { + VERIFY_ARE_EQUAL(wsl::shared::string::UnescapeShell(input), expected); + VERIFY_ARE_EQUAL( + wsl::shared::string::UnescapeShell(wsl::shared::string::MultiByteToWide(input)), + wsl::shared::string::MultiByteToWide(expected)); + } } TEST_METHOD(WindowsPathWithSpaces) diff --git a/test/windows/UnitTests.cpp b/test/windows/UnitTests.cpp index 43d26041fc..9d09e62b95 100644 --- a/test/windows/UnitTests.cpp +++ b/test/windows/UnitTests.cpp @@ -525,7 +525,7 @@ class UnitTests DistroFileChange defaultLocale(L"/etc/default/locale", LxsstuLaunchWsl(L"test -f /etc/default/locale") == 0); DistroFileChange localeConf(L"/etc/locale.conf", LxsstuLaunchWsl(L"test -f /etc/locale.conf") == 0); - const auto readLang = []() { return LxsstuLaunchWslAndCaptureOutput(L"echo $LANG").first; }; + const auto readLang = []() { return LxsstuLaunchWslAndCaptureOutput(L"printenv LANG").first; }; // Only /etc/default/locale is present (Debian/Ubuntu). { @@ -540,7 +540,7 @@ class UnitTests { defaultLocale.Delete(); localeConf.Delete(); - localeConf.SetContent(L"LANG=fr_FR.UTF-8\n"); + localeConf.SetContent(L"LANG=\"fr_FR.UTF-8\"\n"); TerminateDistribution(); VERIFY_ARE_EQUAL(readLang(), L"fr_FR.UTF-8\n"); }