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 { 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 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: 83 PathMappingMessageHandler(MessageHandler &Handler, 84 const PathMappings &Mappings) 85 : WrappedHandler(Handler), Mappings(Mappings) {} 86 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 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 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: 115 PathMappingTransport(std::unique_ptr<Transport> Transp, PathMappings Mappings) 116 : WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {} 117 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 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 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 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" 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 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> 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> 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