nixd
Loading...
Searching...
No Matches
URI.cpp
Go to the documentation of this file.
1//===---- URI.h - File URIs with schemes -------------------------*- C++-*-===//
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
9#include "lspserver/URI.h"
10#include "lspserver/Logger.h"
11#include <llvm/ADT/StringExtras.h>
12#include <llvm/ADT/Twine.h>
13#include <llvm/Support/Error.h>
14#include <llvm/Support/Path.h>
15
16LLVM_INSTANTIATE_REGISTRY(lspserver::URISchemeRegistry)
17
18namespace lspserver {
19namespace {
20
21bool isWindowsPath(llvm::StringRef Path) {
22 return Path.size() > 1 && llvm::isAlpha(Path[0]) && Path[1] == ':';
23}
24
25bool isNetworkPath(llvm::StringRef Path) {
26 return Path.size() > 2 && Path[0] == Path[1] &&
27 llvm::sys::path::is_separator(Path[0]);
28}
29
30/// This manages file paths in the file system. All paths in the scheme
31/// are absolute (with leading '/').
32/// Note that this scheme is hardcoded into the library and not registered in
33/// registry.
34class FileSystemScheme : public URIScheme {
35public:
36 llvm::Expected<std::string>
37 getAbsolutePath(llvm::StringRef Authority, llvm::StringRef Body,
38 llvm::StringRef /*HintPath*/) const override {
39 if (!Body.startswith("/"))
40 return error("File scheme: expect body to be an absolute path starting "
41 "with '/': {0}",
42 Body);
43 llvm::SmallString<128> Path;
44 if (!Authority.empty()) {
45 // Windows UNC paths e.g. file://server/share => \\server\share
46 ("//" + Authority).toVector(Path);
47 } else if (isWindowsPath(Body.substr(1))) {
48 // Windows paths e.g. file:///X:/path => X:\path
49 Body.consume_front("/");
50 }
51 Path.append(Body);
52 llvm::sys::path::native(Path);
53 return std::string(Path);
54 }
55
56 llvm::Expected<URI>
57 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
58 std::string Body;
59 llvm::StringRef Authority;
60 llvm::StringRef Root = llvm::sys::path::root_name(AbsolutePath);
61 if (isNetworkPath(Root)) {
62 // Windows UNC paths e.g. \\server\share => file://server/share
63 Authority = Root.drop_front(2);
64 AbsolutePath.consume_front(Root);
65 } else if (isWindowsPath(Root)) {
66 // Windows paths e.g. X:\path => file:///X:/path
67 Body = "/";
68 }
69 Body += llvm::sys::path::convert_to_slash(AbsolutePath);
70 return URI("file", Authority, Body);
71 }
72};
73
74llvm::Expected<std::unique_ptr<URIScheme>>
75findSchemeByName(llvm::StringRef Scheme) {
76 if (Scheme == "file")
77 return std::make_unique<FileSystemScheme>();
78
79 for (const auto &URIScheme : URISchemeRegistry::entries()) {
80 if (URIScheme.getName() != Scheme)
81 continue;
82 return URIScheme.instantiate();
83 }
84 return error("Can't find scheme: {0}", Scheme);
85}
86
87bool shouldEscape(unsigned char C) {
88 // Unreserved characters.
89 if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||
90 (C >= '0' && C <= '9'))
91 return false;
92 switch (C) {
93 case '-':
94 case '_':
95 case '.':
96 case '~':
97 case '/': // '/' is only reserved when parsing.
98 // ':' is only reserved for relative URI paths, which clangd doesn't produce.
99 case ':':
100 return false;
101 }
102 return true;
103}
104
105/// Encodes a string according to percent-encoding.
106/// - Unreserved characters are not escaped.
107/// - Reserved characters always escaped with exceptions like '/'.
108/// - All other characters are escaped.
109void percentEncode(llvm::StringRef Content, std::string &Out) {
110 for (unsigned char C : Content)
111 if (shouldEscape(C)) {
112 Out.push_back('%');
113 Out.push_back(llvm::hexdigit(C / 16));
114 Out.push_back(llvm::hexdigit(C % 16));
115 } else {
116 Out.push_back(C);
117 }
118}
119
120/// Decodes a string according to percent-encoding.
121std::string percentDecode(llvm::StringRef Content) {
122 std::string Result;
123 for (auto I = Content.begin(), E = Content.end(); I != E; ++I) {
124 if (*I != '%') {
125 Result += *I;
126 continue;
127 }
128 if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) &&
129 llvm::isHexDigit(*(I + 2))) {
130 Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2)));
131 I += 2;
132 } else
133 Result.push_back(*I);
134 }
135 return Result;
136}
137
138bool isValidScheme(llvm::StringRef Scheme) {
139 if (Scheme.empty())
140 return false;
141 if (!llvm::isAlpha(Scheme[0]))
142 return false;
143 return llvm::all_of(llvm::drop_begin(Scheme), [](char C) {
144 return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-';
145 });
146}
147
148} // namespace
149
150URI::URI(llvm::StringRef Scheme, llvm::StringRef Authority,
151 llvm::StringRef Body)
152 : Scheme(Scheme), Authority(Authority), Body(Body) {
153 assert(!Scheme.empty());
154 assert((Authority.empty() || Body.startswith("/")) &&
155 "URI body must start with '/' when authority is present.");
156}
157
158std::string URI::toString() const {
159 std::string Result;
160 percentEncode(Scheme, Result);
161 Result.push_back(':');
162 if (Authority.empty() && Body.empty())
163 return Result;
164 // If authority if empty, we only print body if it starts with "/"; otherwise,
165 // the URI is invalid.
166 if (!Authority.empty() || llvm::StringRef(Body).startswith("/")) {
167 Result.append("//");
168 percentEncode(Authority, Result);
169 }
170 percentEncode(Body, Result);
171 return Result;
172}
173
174llvm::Expected<URI> URI::parse(llvm::StringRef OrigUri) {
175 URI U;
176 llvm::StringRef Uri = OrigUri;
177
178 auto Pos = Uri.find(':');
179 if (Pos == llvm::StringRef::npos)
180 return error("Scheme must be provided in URI: {0}", OrigUri);
181 auto SchemeStr = Uri.substr(0, Pos);
182 U.Scheme = percentDecode(SchemeStr);
183 if (!isValidScheme(U.Scheme))
184 return error("Invalid scheme: {0} (decoded: {1})", SchemeStr, U.Scheme);
185 Uri = Uri.substr(Pos + 1);
186 if (Uri.consume_front("//")) {
187 Pos = Uri.find('/');
188 U.Authority = percentDecode(Uri.substr(0, Pos));
189 Uri = Uri.substr(Pos);
190 }
191 U.Body = percentDecode(Uri);
192 return U;
193}
194
195llvm::Expected<std::string> URI::resolve(llvm::StringRef FileURI,
196 llvm::StringRef HintPath) {
197 auto Uri = URI::parse(FileURI);
198 if (!Uri)
199 return Uri.takeError();
200 auto Path = URI::resolve(*Uri, HintPath);
201 if (!Path)
202 return Path.takeError();
203 return *Path;
204}
205
206llvm::Expected<URI> URI::create(llvm::StringRef AbsolutePath,
207 llvm::StringRef Scheme) {
208 if (!llvm::sys::path::is_absolute(AbsolutePath))
209 return error("Not a valid absolute path: {0}", AbsolutePath);
210 auto S = findSchemeByName(Scheme);
211 if (!S)
212 return S.takeError();
213 return S->get()->uriFromAbsolutePath(AbsolutePath);
214}
215
216URI URI::create(llvm::StringRef AbsolutePath) {
217 if (!llvm::sys::path::is_absolute(AbsolutePath))
219 ("Not a valid absolute path: " + AbsolutePath).str().c_str());
220 for (auto &Entry : URISchemeRegistry::entries()) {
221 auto URI = Entry.instantiate()->uriFromAbsolutePath(AbsolutePath);
222 // For some paths, conversion to different URI schemes is impossible. These
223 // should be just skipped.
224 if (!URI) {
225 // Ignore the error.
226 llvm::consumeError(URI.takeError());
227 continue;
228 }
229 return std::move(*URI);
230 }
231 // Fallback to file: scheme which should work for any paths.
233}
234
236 auto U = FileSystemScheme().uriFromAbsolutePath(AbsolutePath);
237 if (!U)
238 llvm_unreachable(llvm::toString(U.takeError()).c_str());
239 return std::move(*U);
240}
241
242llvm::Expected<std::string> URI::resolve(const URI &Uri,
243 llvm::StringRef HintPath) {
244 auto S = findSchemeByName(Uri.Scheme);
245 if (!S)
246 return S.takeError();
247 return S->get()->getAbsolutePath(Uri.Authority, Uri.Body, HintPath);
248}
249
250llvm::Expected<std::string> URI::resolvePath(llvm::StringRef AbsPath,
251 llvm::StringRef HintPath) {
252 if (!llvm::sys::path::is_absolute(AbsPath))
253 llvm_unreachable(("Not a valid absolute path: " + AbsPath).str().c_str());
254 for (auto &Entry : URISchemeRegistry::entries()) {
255 auto S = Entry.instantiate();
256 auto U = S->uriFromAbsolutePath(AbsPath);
257 // For some paths, conversion to different URI schemes is impossible. These
258 // should be just skipped.
259 if (!U) {
260 // Ignore the error.
261 llvm::consumeError(U.takeError());
262 continue;
263 }
264 return S->getAbsolutePath(U->Authority, U->Body, HintPath);
265 }
266 // Fallback to file: scheme which doesn't do any canonicalization.
267 return std::string(AbsPath);
268}
269
270llvm::Expected<std::string> URI::includeSpelling(const URI &Uri) {
271 auto S = findSchemeByName(Uri.Scheme);
272 if (!S)
273 return S.takeError();
274 return S->get()->getIncludeSpelling(Uri);
275}
276
277} // namespace lspserver
static llvm::Expected< std::string > resolve(const URI &U, llvm::StringRef HintPath="")
Definition URI.cpp:242
static llvm::Expected< URI > parse(llvm::StringRef Uri)
Definition URI.cpp:174
static llvm::Expected< URI > create(llvm::StringRef AbsolutePath, llvm::StringRef Scheme)
Definition URI.cpp:206
std::string toString() const
Returns a string URI with all components percent-encoded.
Definition URI.cpp:158
static llvm::Expected< std::string > includeSpelling(const URI &U)
Definition URI.cpp:270
static URI createFile(llvm::StringRef AbsolutePath)
This creates a file:// URI for AbsolutePath. The path must be absolute.
Definition URI.cpp:235
static llvm::Expected< std::string > resolvePath(llvm::StringRef AbsPath, llvm::StringRef HintPath="")
Definition URI.cpp:250
Whether current platform treats paths case insensitively.
Definition Connection.h:11
std::string Path
Definition Path.h:24
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&...Vals)
Definition Logger.h:70
bool fromJSON(const llvm::json::Value &, URIForFile &, llvm::json::Path)
llvm::Registry< URIScheme > URISchemeRegistry
Definition URI.h:121