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 { 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) 44 .toString(); 45 } 46 } 47 return llvm::None; 48 } 49 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: 84 PathMappingMessageHandler(MessageHandler &Handler, 85 const PathMappings &Mappings) 86 : WrappedHandler(Handler), Mappings(Mappings) {} 87 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 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 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: 116 PathMappingTransport(std::unique_ptr<Transport> Transp, PathMappings Mappings) 117 : WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {} 118 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 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 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 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" 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 } 155 if (path::is_absolute(Path, path::Style::windows)) { 156 std::string Converted = path::convert_to_slash(Path, path::Style::windows); 157 if (Converted.front() != '/') 158 Converted = "/" + Converted; 159 return Converted; 160 } 161 return error("Path not absolute: {0}", Path); 162 } 163 164 } // namespace 165 166 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const PathMapping &M) { 167 return OS << M.ClientPath << "=" << M.ServerPath; 168 } 169 170 llvm::Expected<PathMappings> 171 parsePathMappings(llvm::StringRef RawPathMappings) { 172 llvm::StringRef ClientPath, ServerPath, PathPair, Rest = RawPathMappings; 173 PathMappings ParsedMappings; 174 while (!Rest.empty()) { 175 std::tie(PathPair, Rest) = Rest.split(","); 176 std::tie(ClientPath, ServerPath) = PathPair.split("="); 177 if (ClientPath.empty() || ServerPath.empty()) 178 return error("Not a valid path mapping pair: {0}", PathPair); 179 llvm::Expected<std::string> ParsedClientPath = parsePath(ClientPath); 180 if (!ParsedClientPath) 181 return ParsedClientPath.takeError(); 182 llvm::Expected<std::string> ParsedServerPath = parsePath(ServerPath); 183 if (!ParsedServerPath) 184 return ParsedServerPath.takeError(); 185 ParsedMappings.push_back( 186 {std::move(*ParsedClientPath), std::move(*ParsedServerPath)}); 187 } 188 return ParsedMappings; 189 } 190 191 std::unique_ptr<Transport> 192 createPathMappingTransport(std::unique_ptr<Transport> Transp, 193 PathMappings Mappings) { 194 return std::make_unique<PathMappingTransport>(std::move(Transp), Mappings); 195 } 196 197 } // namespace clangd 198 } // namespace clang 199