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