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