1 //===--- PathMapping.cpp - apply path mappings to LSP messages -===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 #include "PathMapping.h"
9 #include "Transport.h"
10 #include "URI.h"
11 #include "llvm/ADT/None.h"
12 #include "llvm/ADT/STLExtras.h"
13 #include "llvm/Support/Errno.h"
14 #include "llvm/Support/Error.h"
15 #include "llvm/Support/Path.h"
16 #include <algorithm>
17 #include <tuple>
18 
19 namespace clang {
20 namespace clangd {
doPathMapping(llvm::StringRef S,PathMapping::Direction Dir,const PathMappings & Mappings)21 llvm::Optional<std::string> doPathMapping(llvm::StringRef S,
22                                           PathMapping::Direction Dir,
23                                           const PathMappings &Mappings) {
24   // Return early to optimize for the common case, wherein S is not a file URI
25   if (!S.startswith("file://"))
26     return llvm::None;
27   auto Uri = URI::parse(S);
28   if (!Uri) {
29     llvm::consumeError(Uri.takeError());
30     return llvm::None;
31   }
32   for (const auto &Mapping : Mappings) {
33     const std::string &From = Dir == PathMapping::Direction::ClientToServer
34                                   ? Mapping.ClientPath
35                                   : Mapping.ServerPath;
36     const std::string &To = Dir == PathMapping::Direction::ClientToServer
37                                 ? Mapping.ServerPath
38                                 : Mapping.ClientPath;
39     llvm::StringRef Body = Uri->body();
40     if (Body.consume_front(From) && (Body.empty() || Body.front() == '/')) {
41       std::string MappedBody = (To + Body).str();
42       return URI(Uri->scheme(), Uri->authority(), MappedBody.c_str())
43           .toString();
44     }
45   }
46   return llvm::None;
47 }
48 
applyPathMappings(llvm::json::Value & V,PathMapping::Direction Dir,const PathMappings & Mappings)49 void applyPathMappings(llvm::json::Value &V, PathMapping::Direction Dir,
50                        const PathMappings &Mappings) {
51   using Kind = llvm::json::Value::Kind;
52   Kind K = V.kind();
53   if (K == Kind::Object) {
54     llvm::json::Object *Obj = V.getAsObject();
55     llvm::json::Object MappedObj;
56     // 1. Map all the Keys
57     for (auto &KV : *Obj) {
58       if (llvm::Optional<std::string> MappedKey =
59               doPathMapping(KV.first.str(), Dir, Mappings)) {
60         MappedObj.try_emplace(std::move(*MappedKey), std::move(KV.second));
61       } else {
62         MappedObj.try_emplace(std::move(KV.first), std::move(KV.second));
63       }
64     }
65     *Obj = std::move(MappedObj);
66     // 2. Map all the values
67     for (auto &KV : *Obj)
68       applyPathMappings(KV.second, Dir, Mappings);
69   } else if (K == Kind::Array) {
70     for (llvm::json::Value &Val : *V.getAsArray())
71       applyPathMappings(Val, Dir, Mappings);
72   } else if (K == Kind::String) {
73     if (llvm::Optional<std::string> Mapped =
74             doPathMapping(*V.getAsString(), Dir, Mappings))
75       V = std::move(*Mapped);
76   }
77 }
78 
79 namespace {
80 
81 class PathMappingMessageHandler : public Transport::MessageHandler {
82 public:
PathMappingMessageHandler(MessageHandler & Handler,const PathMappings & Mappings)83   PathMappingMessageHandler(MessageHandler &Handler,
84                             const PathMappings &Mappings)
85       : WrappedHandler(Handler), Mappings(Mappings) {}
86 
onNotify(llvm::StringRef Method,llvm::json::Value Params)87   bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
88     applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
89     return WrappedHandler.onNotify(Method, std::move(Params));
90   }
91 
onCall(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)92   bool onCall(llvm::StringRef Method, llvm::json::Value Params,
93               llvm::json::Value ID) override {
94     applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
95     return WrappedHandler.onCall(Method, std::move(Params), std::move(ID));
96   }
97 
onReply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> Result)98   bool onReply(llvm::json::Value ID,
99                llvm::Expected<llvm::json::Value> Result) override {
100     if (Result)
101       applyPathMappings(*Result, PathMapping::Direction::ClientToServer,
102                         Mappings);
103     return WrappedHandler.onReply(std::move(ID), std::move(Result));
104   }
105 
106 private:
107   Transport::MessageHandler &WrappedHandler;
108   const PathMappings &Mappings;
109 };
110 
111 // Apply path mappings to all LSP messages by intercepting all params/results
112 // and then delegating to the normal transport
113 class PathMappingTransport : public Transport {
114 public:
PathMappingTransport(std::unique_ptr<Transport> Transp,PathMappings Mappings)115   PathMappingTransport(std::unique_ptr<Transport> Transp, PathMappings Mappings)
116       : WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {}
117 
notify(llvm::StringRef Method,llvm::json::Value Params)118   void notify(llvm::StringRef Method, llvm::json::Value Params) override {
119     applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
120     WrappedTransport->notify(Method, std::move(Params));
121   }
122 
call(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)123   void call(llvm::StringRef Method, llvm::json::Value Params,
124             llvm::json::Value ID) override {
125     applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
126     WrappedTransport->call(Method, std::move(Params), std::move(ID));
127   }
128 
reply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> Result)129   void reply(llvm::json::Value ID,
130              llvm::Expected<llvm::json::Value> Result) override {
131     if (Result)
132       applyPathMappings(*Result, PathMapping::Direction::ServerToClient,
133                         Mappings);
134     WrappedTransport->reply(std::move(ID), std::move(Result));
135   }
136 
loop(MessageHandler & Handler)137   llvm::Error loop(MessageHandler &Handler) override {
138     PathMappingMessageHandler WrappedHandler(Handler, Mappings);
139     return WrappedTransport->loop(WrappedHandler);
140   }
141 
142 private:
143   std::unique_ptr<Transport> WrappedTransport;
144   PathMappings Mappings;
145 };
146 
147 // Converts a unix/windows path to the path portion of a file URI
148 // e.g. "C:\foo" -> "/C:/foo"
parsePath(llvm::StringRef Path)149 llvm::Expected<std::string> parsePath(llvm::StringRef Path) {
150   namespace path = llvm::sys::path;
151   if (path::is_absolute(Path, path::Style::posix)) {
152     return std::string(Path);
153   } else if (path::is_absolute(Path, path::Style::windows)) {
154     std::string Converted = path::convert_to_slash(Path, path::Style::windows);
155     if (Converted.front() != '/')
156       Converted = "/" + Converted;
157     return Converted;
158   }
159   return llvm::createStringError(llvm::inconvertibleErrorCode(),
160                                  "Path not absolute: " + Path);
161 }
162 
163 } // namespace
164 
operator <<(llvm::raw_ostream & OS,const PathMapping & M)165 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const PathMapping &M) {
166   return OS << M.ClientPath << "=" << M.ServerPath;
167 }
168 
169 llvm::Expected<PathMappings>
parsePathMappings(llvm::StringRef RawPathMappings)170 parsePathMappings(llvm::StringRef RawPathMappings) {
171   llvm::StringRef ClientPath, ServerPath, PathPair, Rest = RawPathMappings;
172   PathMappings ParsedMappings;
173   while (!Rest.empty()) {
174     std::tie(PathPair, Rest) = Rest.split(",");
175     std::tie(ClientPath, ServerPath) = PathPair.split("=");
176     if (ClientPath.empty() || ServerPath.empty())
177       return llvm::createStringError(llvm::inconvertibleErrorCode(),
178                                      "Not a valid path mapping pair: " +
179                                          PathPair);
180     llvm::Expected<std::string> ParsedClientPath = parsePath(ClientPath);
181     if (!ParsedClientPath)
182       return ParsedClientPath.takeError();
183     llvm::Expected<std::string> ParsedServerPath = parsePath(ServerPath);
184     if (!ParsedServerPath)
185       return ParsedServerPath.takeError();
186     ParsedMappings.push_back(
187         {std::move(*ParsedClientPath), std::move(*ParsedServerPath)});
188   }
189   return ParsedMappings;
190 }
191 
192 std::unique_ptr<Transport>
createPathMappingTransport(std::unique_ptr<Transport> Transp,PathMappings Mappings)193 createPathMappingTransport(std::unique_ptr<Transport> Transp,
194                            PathMappings Mappings) {
195   return std::make_unique<PathMappingTransport>(std::move(Transp), Mappings);
196 }
197 
198 } // namespace clangd
199 } // namespace clang
200