blob: f8a9f59ca13781ca91e4c7d35bbfe1d4ddc7e2a2 [file] [log] [blame]
Eric Liu9dcc79f2018-01-22 11:48:20 +00001//===---- URI.h - File URIs with schemes -------------------------*- C++-*-===//
2//
Chandler Carruthb1ace232019-01-19 08:50:56 +00003// 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
Eric Liu9dcc79f2018-01-22 11:48:20 +00006//
7//===----------------------------------------------------------------------===//
8
9#include "URI.h"
Eric Liu550f2ef2018-09-25 11:47:14 +000010#include "llvm/ADT/StringExtras.h"
Eric Liu9dcc79f2018-01-22 11:48:20 +000011#include "llvm/ADT/Twine.h"
12#include "llvm/Support/Error.h"
13#include "llvm/Support/Format.h"
Eric Liu42e82af2018-09-25 10:47:46 +000014#include "llvm/Support/FormatVariadic.h"
Eric Liu9dcc79f2018-01-22 11:48:20 +000015#include "llvm/Support/Path.h"
Eric Liu42e82af2018-09-25 10:47:46 +000016#include <algorithm>
Eric Liu9dcc79f2018-01-22 11:48:20 +000017
18LLVM_INSTANTIATE_REGISTRY(clang::clangd::URISchemeRegistry)
19
20namespace clang {
21namespace clangd {
22namespace {
23
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +000024inline llvm::Error make_string_error(const llvm::Twine &Message) {
25 return llvm::make_error<llvm::StringError>(Message,
26 llvm::inconvertibleErrorCode());
Eric Liu9dcc79f2018-01-22 11:48:20 +000027}
28
Dmitri Gribenko409457d2019-08-22 11:32:57 +000029/// This manages file paths in the file system. All paths in the scheme
Eric Liu9dcc79f2018-01-22 11:48:20 +000030/// are absolute (with leading '/').
Eric Liu124fa5d2018-11-28 10:30:42 +000031/// Note that this scheme is hardcoded into the library and not registered in
32/// registry.
Eric Liu9dcc79f2018-01-22 11:48:20 +000033class FileSystemScheme : public URIScheme {
34public:
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +000035 llvm::Expected<std::string>
36 getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
37 llvm::StringRef /*HintPath*/) const override {
Eric Liu9dcc79f2018-01-22 11:48:20 +000038 if (!Body.startswith("/"))
39 return make_string_error("File scheme: expect body to be an absolute "
40 "path starting with '/': " +
41 Body);
42 // For Windows paths e.g. /X:
43 if (Body.size() > 2 && Body[0] == '/' && Body[2] == ':')
44 Body.consume_front("/");
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +000045 llvm::SmallVector<char, 16> Path(Body.begin(), Body.end());
46 llvm::sys::path::native(Path);
Eric Liu9dcc79f2018-01-22 11:48:20 +000047 return std::string(Path.begin(), Path.end());
48 }
49
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +000050 llvm::Expected<URI>
51 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
Eric Liu9dcc79f2018-01-22 11:48:20 +000052 std::string Body;
53 // For Windows paths e.g. X:
54 if (AbsolutePath.size() > 1 && AbsolutePath[1] == ':')
55 Body = "/";
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +000056 Body += llvm::sys::path::convert_to_slash(AbsolutePath);
Eric Liu124fa5d2018-11-28 10:30:42 +000057 return URI("file", /*Authority=*/"", Body);
Eric Liu9dcc79f2018-01-22 11:48:20 +000058 }
59};
60
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +000061llvm::Expected<std::unique_ptr<URIScheme>>
62findSchemeByName(llvm::StringRef Scheme) {
Eric Liu124fa5d2018-11-28 10:30:42 +000063 if (Scheme == "file")
Jonas Devlieghereaaed81d2019-08-14 23:52:23 +000064 return std::make_unique<FileSystemScheme>();
Eric Liu124fa5d2018-11-28 10:30:42 +000065
Eric Liu9dcc79f2018-01-22 11:48:20 +000066 for (auto I = URISchemeRegistry::begin(), E = URISchemeRegistry::end();
67 I != E; ++I) {
68 if (I->getName() != Scheme)
69 continue;
70 return I->instantiate();
71 }
72 return make_string_error("Can't find scheme: " + Scheme);
73}
74
75bool shouldEscape(unsigned char C) {
76 // Unreserved characters.
Eric Liu8ce01a92018-02-07 12:12:06 +000077 if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||
78 (C >= '0' && C <= '9'))
Eric Liu9dcc79f2018-01-22 11:48:20 +000079 return false;
80 switch (C) {
81 case '-':
82 case '_':
83 case '.':
84 case '~':
85 case '/': // '/' is only reserved when parsing.
Kadir Cetinkaya8f253512018-10-09 10:29:54 +000086 // ':' is only reserved for relative URI paths, which clangd doesn't produce.
87 case ':':
Eric Liu9dcc79f2018-01-22 11:48:20 +000088 return false;
89 }
90 return true;
91}
92
93/// Encodes a string according to percent-encoding.
94/// - Unreserved characters are not escaped.
95/// - Reserved characters always escaped with exceptions like '/'.
96/// - All other characters are escaped.
Sam McCallf65bad22019-07-08 02:46:21 +000097void percentEncode(llvm::StringRef Content, std::string &Out) {
Eric Liu9dcc79f2018-01-22 11:48:20 +000098 std::string Result;
Eric Liu9dcc79f2018-01-22 11:48:20 +000099 for (unsigned char C : Content)
100 if (shouldEscape(C))
Sam McCallf65bad22019-07-08 02:46:21 +0000101 {
102 Out.push_back('%');
103 Out.push_back(llvm::hexdigit(C / 16));
104 Out.push_back(llvm::hexdigit(C % 16));
105 } else
106 { Out.push_back(C); }
Eric Liu9dcc79f2018-01-22 11:48:20 +0000107}
108
109/// Decodes a string according to percent-encoding.
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000110std::string percentDecode(llvm::StringRef Content) {
Eric Liu9dcc79f2018-01-22 11:48:20 +0000111 std::string Result;
112 for (auto I = Content.begin(), E = Content.end(); I != E; ++I) {
113 if (*I != '%') {
114 Result += *I;
115 continue;
116 }
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000117 if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) &&
118 llvm::isHexDigit(*(I + 2))) {
119 Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2)));
Eric Liu9dcc79f2018-01-22 11:48:20 +0000120 I += 2;
121 } else
122 Result.push_back(*I);
123 }
124 return Result;
125}
126
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000127bool isValidScheme(llvm::StringRef Scheme) {
Eric Liu42e82af2018-09-25 10:47:46 +0000128 if (Scheme.empty())
129 return false;
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000130 if (!llvm::isAlpha(Scheme[0]))
Eric Liu42e82af2018-09-25 10:47:46 +0000131 return false;
132 return std::all_of(Scheme.begin() + 1, Scheme.end(), [](char C) {
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000133 return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-';
Eric Liu42e82af2018-09-25 10:47:46 +0000134 });
135}
136
Eric Liu9dcc79f2018-01-22 11:48:20 +0000137} // namespace
138
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000139URI::URI(llvm::StringRef Scheme, llvm::StringRef Authority,
140 llvm::StringRef Body)
Eric Liuc94f5a92018-01-29 15:37:46 +0000141 : Scheme(Scheme), Authority(Authority), Body(Body) {
142 assert(!Scheme.empty());
143 assert((Authority.empty() || Body.startswith("/")) &&
144 "URI body must start with '/' when authority is present.");
Eric Liu9dcc79f2018-01-22 11:48:20 +0000145}
146
Eric Liuc94f5a92018-01-29 15:37:46 +0000147std::string URI::toString() const {
Eric Liu9dcc79f2018-01-22 11:48:20 +0000148 std::string Result;
Sam McCallf65bad22019-07-08 02:46:21 +0000149 percentEncode(Scheme, Result);
150 Result.push_back(':');
Eric Liu9dcc79f2018-01-22 11:48:20 +0000151 if (Authority.empty() && Body.empty())
Sam McCallf65bad22019-07-08 02:46:21 +0000152 return Result;
Eric Liu9dcc79f2018-01-22 11:48:20 +0000153 // If authority if empty, we only print body if it starts with "/"; otherwise,
154 // the URI is invalid.
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000155 if (!Authority.empty() || llvm::StringRef(Body).startswith("/"))
Sam McCallf65bad22019-07-08 02:46:21 +0000156 {
157 Result.append("//");
158 percentEncode(Authority, Result);
159 }
160 percentEncode(Body, Result);
Eric Liu9dcc79f2018-01-22 11:48:20 +0000161 return Result;
162}
163
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000164llvm::Expected<URI> URI::parse(llvm::StringRef OrigUri) {
Eric Liuc94f5a92018-01-29 15:37:46 +0000165 URI U;
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000166 llvm::StringRef Uri = OrigUri;
Eric Liu9dcc79f2018-01-22 11:48:20 +0000167
168 auto Pos = Uri.find(':');
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000169 if (Pos == llvm::StringRef::npos)
Eric Liu9dcc79f2018-01-22 11:48:20 +0000170 return make_string_error("Scheme must be provided in URI: " + OrigUri);
Eric Liu42e82af2018-09-25 10:47:46 +0000171 auto SchemeStr = Uri.substr(0, Pos);
172 U.Scheme = percentDecode(SchemeStr);
173 if (!isValidScheme(U.Scheme))
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000174 return make_string_error(llvm::formatv("Invalid scheme: {0} (decoded: {1})",
175 SchemeStr, U.Scheme));
Eric Liu9dcc79f2018-01-22 11:48:20 +0000176 Uri = Uri.substr(Pos + 1);
177 if (Uri.consume_front("//")) {
178 Pos = Uri.find('/');
179 U.Authority = percentDecode(Uri.substr(0, Pos));
180 Uri = Uri.substr(Pos);
181 }
182 U.Body = percentDecode(Uri);
183 return U;
184}
185
Haojian Wuf1f4b3f2019-09-23 14:39:37 +0000186llvm::Expected<std::string> URI::resolve(llvm::StringRef FileURI,
187 llvm::StringRef HintPath) {
188 auto Uri = URI::parse(FileURI);
189 if (!Uri)
190 return Uri.takeError();
191 auto Path = URI::resolve(*Uri, HintPath);
192 if (!Path)
193 return Path.takeError();
194 return *Path;
195}
196
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000197llvm::Expected<URI> URI::create(llvm::StringRef AbsolutePath,
198 llvm::StringRef Scheme) {
199 if (!llvm::sys::path::is_absolute(AbsolutePath))
Eric Liu9dcc79f2018-01-22 11:48:20 +0000200 return make_string_error("Not a valid absolute path: " + AbsolutePath);
201 auto S = findSchemeByName(Scheme);
202 if (!S)
203 return S.takeError();
204 return S->get()->uriFromAbsolutePath(AbsolutePath);
205}
206
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000207URI URI::create(llvm::StringRef AbsolutePath) {
208 if (!llvm::sys::path::is_absolute(AbsolutePath))
Eric Liu94caf6c2018-11-22 15:02:05 +0000209 llvm_unreachable(
210 ("Not a valid absolute path: " + AbsolutePath).str().c_str());
211 for (auto &Entry : URISchemeRegistry::entries()) {
Eric Liu94caf6c2018-11-22 15:02:05 +0000212 auto URI = Entry.instantiate()->uriFromAbsolutePath(AbsolutePath);
Kirill Bobyrev8ee3adc2018-09-06 12:54:43 +0000213 // For some paths, conversion to different URI schemes is impossible. These
214 // should be just skipped.
215 if (!URI) {
216 // Ignore the error.
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000217 llvm::consumeError(URI.takeError());
Kirill Bobyrev8ee3adc2018-09-06 12:54:43 +0000218 continue;
219 }
Eric Liu94caf6c2018-11-22 15:02:05 +0000220 return std::move(*URI);
Kirill Bobyrev8ee3adc2018-09-06 12:54:43 +0000221 }
Eric Liu94caf6c2018-11-22 15:02:05 +0000222 // Fallback to file: scheme which should work for any paths.
223 return URI::createFile(AbsolutePath);
Kirill Bobyrev8ee3adc2018-09-06 12:54:43 +0000224}
225
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000226URI URI::createFile(llvm::StringRef AbsolutePath) {
Eric Liu124fa5d2018-11-28 10:30:42 +0000227 auto U = FileSystemScheme().uriFromAbsolutePath(AbsolutePath);
Eric Liuc94f5a92018-01-29 15:37:46 +0000228 if (!U)
229 llvm_unreachable(llvm::toString(U.takeError()).c_str());
230 return std::move(*U);
231}
232
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000233llvm::Expected<std::string> URI::resolve(const URI &Uri,
234 llvm::StringRef HintPath) {
Eric Liu9dcc79f2018-01-22 11:48:20 +0000235 auto S = findSchemeByName(Uri.Scheme);
236 if (!S)
237 return S.takeError();
238 return S->get()->getAbsolutePath(Uri.Authority, Uri.Body, HintPath);
239}
240
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000241llvm::Expected<std::string> URI::resolvePath(llvm::StringRef AbsPath,
242 llvm::StringRef HintPath) {
243 if (!llvm::sys::path::is_absolute(AbsPath))
Eric Liu124fa5d2018-11-28 10:30:42 +0000244 llvm_unreachable(("Not a valid absolute path: " + AbsPath).str().c_str());
245 for (auto &Entry : URISchemeRegistry::entries()) {
Ilya Biryukov74a4acf2019-01-03 13:28:05 +0000246 auto S = Entry.instantiate();
Eric Liu124fa5d2018-11-28 10:30:42 +0000247 auto U = S->uriFromAbsolutePath(AbsPath);
248 // For some paths, conversion to different URI schemes is impossible. These
249 // should be just skipped.
250 if (!U) {
251 // Ignore the error.
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000252 llvm::consumeError(U.takeError());
Eric Liu124fa5d2018-11-28 10:30:42 +0000253 continue;
254 }
255 return S->getAbsolutePath(U->Authority, U->Body, HintPath);
256 }
257 // Fallback to file: scheme which doesn't do any canonicalization.
258 return AbsPath;
259}
260
Ilya Biryukovd0dee7a2019-01-07 15:45:19 +0000261llvm::Expected<std::string> URI::includeSpelling(const URI &Uri) {
Haojian Wu264eacf2018-04-09 15:09:44 +0000262 auto S = findSchemeByName(Uri.Scheme);
263 if (!S)
264 return S.takeError();
265 return S->get()->getIncludeSpelling(Uri);
266}
267
Eric Liu9dcc79f2018-01-22 11:48:20 +0000268} // namespace clangd
269} // namespace clang