Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/linux/init/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 "=");
Expand All @@ -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);
}
}

Expand Down
101 changes: 101 additions & 0 deletions src/shared/inc/stringshared.h
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,107 @@ inline std::wstring FormatBytes(uint64_t bytes)
}
}

template <typename TChar>
inline std::basic_string<TChar> Trim(const std::basic_string<TChar>& 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<TChar>::npos)
{
return {};
}

const auto last = input.find_last_not_of(whitespace);
return input.substr(first, last - first + 1);
}

template <typename TChar>
inline std::basic_string<TChar> UnescapeShell(const std::basic_string<TChar>& input)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On bad input the original string is returned, I guess that's intentional?

{
enum class Quote
{
None,
Single,
Double
};

Quote quote = Quote::None;
std::basic_string<TChar> 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.
Comment thread
chemwolf6922 marked this conversation as resolved.
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 <>
Expand Down
35 changes: 35 additions & 0 deletions test/windows/SimpleTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<wchar_t>(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<std::pair<std::string, std::string>> 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)
Expand Down
4 changes: 2 additions & 2 deletions test/windows/UnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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).
{
Expand All @@ -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");
}
Expand Down