From f527c9cb662cef4ed147ef2ebd1bcdc6ccf0d562 Mon Sep 17 00:00:00 2001 From: alexandre-charlot_qonto Date: Sun, 24 Aug 2025 22:45:53 +0200 Subject: [PATCH] clangd(PathMapping): normalize mixed WSL/Windows URIs --- clang-tools-extra/clangd/PathMapping.cpp | 75 +++++++++++++++++-- .../clangd/unittests/PathMappingTests.cpp | 22 ++++++ 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp index 4b93ff2c60c5c..68cdf0e3f037e 100644 --- a/clang-tools-extra/clangd/PathMapping.cpp +++ b/clang-tools-extra/clangd/PathMapping.cpp @@ -12,6 +12,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include +#include #include #include @@ -28,6 +29,30 @@ std::optional doPathMapping(llvm::StringRef S, llvm::consumeError(Uri.takeError()); return std::nullopt; } + + std::string BodyStr = (*Uri).body().str(); + std::replace(BodyStr.begin(), BodyStr.end(), '\\', '/'); + + bool DidPreferEmbeddedDrive = false; + if (BodyStr.rfind("/mnt/", 0) == 0 && BodyStr.size() > 6) { + for (size_t i = 0; i + 2 < BodyStr.size(); ++i) { + if (std::isalpha((unsigned char)BodyStr[i]) && BodyStr[i + 1] == ':' && + BodyStr[i + 2] == '/') { + BodyStr = std::string("/") + BodyStr.substr(i); + DidPreferEmbeddedDrive = true; + break; + } + } + } + + if (Dir == PathMapping::Direction::ClientToServer && DidPreferEmbeddedDrive) { + if (BodyStr.size() >= 3 && BodyStr[0] == '/' && + std::isalpha((unsigned char)BodyStr[1]) && BodyStr[2] == ':') { + return URI((*Uri).scheme(), (*Uri).authority(), BodyStr).toString(); + } + } + + llvm::StringRef BodyRef(BodyStr); for (const auto &Mapping : Mappings) { const std::string &From = Dir == PathMapping::Direction::ClientToServer ? Mapping.ClientPath @@ -35,13 +60,53 @@ std::optional doPathMapping(llvm::StringRef S, const std::string &To = Dir == PathMapping::Direction::ClientToServer ? Mapping.ServerPath : Mapping.ClientPath; - llvm::StringRef Body = Uri->body(); - if (Body.consume_front(From) && (Body.empty() || Body.front() == '/')) { - std::string MappedBody = (To + Body).str(); - return URI(Uri->scheme(), Uri->authority(), MappedBody) - .toString(); + llvm::StringRef Working = BodyRef; + if (Working.consume_front(From)) { + if (From.empty() || From.back() == '/' || Working.empty() || + Working.front() == '/') { + llvm::StringRef Adjusted = Working; + + char MappingDrive = 0; + if (To.size() >= 3 && To[0] == '/' && + std::isalpha((unsigned char)To[1]) && To[2] == ':') + MappingDrive = To[1]; + if (MappingDrive) { + for (size_t i = 0; i + 2 < (size_t)Working.size(); ++i) { + char c = Working[i]; + if (std::isalpha((unsigned char)c) && Working[i + 1] == ':' && + Working[i + 2] == '/') { + if (std::tolower((unsigned char)c) == + std::tolower((unsigned char)MappingDrive)) { + Adjusted = Working.substr(i + 3); + break; + } + } + } + } + std::string MappedBody = (To + Adjusted).str(); + return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString(); + } + } + } + + if (Dir == PathMapping::Direction::ServerToClient) { + for (const auto &Mapping : Mappings) { + const std::string &From = Mapping.ServerPath; + const std::string &To = Mapping.ClientPath; + if (From.empty()) + continue; + llvm::StringRef W = BodyRef; + if (W.starts_with(From)) { + llvm::StringRef Rest = W.substr(From.size()); + if (Rest.empty() || Rest.front() == '/') { + std::string MappedBody = (To + Rest).str(); + return URI((*Uri).scheme(), (*Uri).authority(), MappedBody) + .toString(); + } + } } } + return std::nullopt; } diff --git a/clang-tools-extra/clangd/unittests/PathMappingTests.cpp b/clang-tools-extra/clangd/unittests/PathMappingTests.cpp index 2e26148495a01..a5bc54e7c1d72 100644 --- a/clang-tools-extra/clangd/unittests/PathMappingTests.cpp +++ b/clang-tools-extra/clangd/unittests/PathMappingTests.cpp @@ -212,6 +212,28 @@ TEST(ApplyPathMappingTests, MapsKeys) { EXPECT_EQ(*Params, *ExpectedParams); } + +TEST(DoPathMappingTests, HandlesEncodedWindowsFragmentsInbound) { + // Simulate a client sending a WSL-style path that embeds an encoded Windows + // fragment (percent-encoded backslashes and drive letter). The server should + // normalize and map it to a Windows-style path. + PathMappings Mappings{{"/mnt/c/", "/C:/"}}; + const char *Orig = "file:///mnt/c/projects/cod2-asi/C:%5cUsers%5caukx%5cprojects%5ccod2-asi%5csrc%5cedge_detection.h"; + std::optional Mapped = doPathMapping(Orig, PathMapping::Direction::ClientToServer, Mappings); + ASSERT_TRUE(bool(Mapped)); + EXPECT_EQ(*Mapped, std::string("file:///C:/Users/aukx/projects/cod2-asi/src/edge_detection.h")); +} + +TEST(DoPathMappingTests, HandlesWindowsToWslOutbound) { + // Server returns Windows-style URIs; they should be mapped back to the + // client's WSL-style paths before sending out. + PathMappings Mappings{{"/mnt/c/", "/C:/"}}; + const char *Orig = "file:///C:/projects/cod2-asi/src/edge_detection.h"; + std::optional Mapped = doPathMapping(Orig, PathMapping::Direction::ServerToClient, Mappings); + ASSERT_TRUE(bool(Mapped)); + EXPECT_EQ(*Mapped, std::string("file:///mnt/c/projects/cod2-asi/src/edge_detection.h")); +} + } // namespace } // namespace clangd } // namespace clang