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