1 //===---- URI.h - File URIs with schemes -------------------------*- C++-*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "URI.h"
11 #include "llvm/ADT/Twine.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/Format.h"
14 #include "llvm/Support/Path.h"
15 #include <iomanip>
16 #include <sstream>
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 /// \brief This manages file paths in the file system. All paths in the scheme
30 /// are absolute (with leading '/').
31 class FileSystemScheme : public URIScheme {
32 public:
33   static const char *Scheme;
34 
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     using namespace llvm::sys;
53 
54     std::string Body;
55     // For Windows paths e.g. X:
56     if (AbsolutePath.size() > 1 && AbsolutePath[1] == ':')
57       Body = "/";
58     Body += path::convert_to_slash(AbsolutePath);
59     return URI(Scheme, /*Authority=*/"", Body);
60   }
61 };
62 
63 const char *FileSystemScheme::Scheme = "file";
64 
65 static URISchemeRegistry::Add<FileSystemScheme>
66     X(FileSystemScheme::Scheme,
67       "URI scheme for absolute paths in the file system.");
68 
69 llvm::Expected<std::unique_ptr<URIScheme>>
70 findSchemeByName(llvm::StringRef Scheme) {
71   for (auto I = URISchemeRegistry::begin(), E = URISchemeRegistry::end();
72        I != E; ++I) {
73     if (I->getName() != Scheme)
74       continue;
75     return I->instantiate();
76   }
77   return make_string_error("Can't find scheme: " + Scheme);
78 }
79 
80 bool shouldEscape(unsigned char C) {
81   // Unreserved characters.
82   if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||
83       (C >= '0' && C <= '9'))
84     return false;
85   switch (C) {
86   case '-':
87   case '_':
88   case '.':
89   case '~':
90   case '/': // '/' is only reserved when parsing.
91     return false;
92   }
93   return true;
94 }
95 
96 /// Encodes a string according to percent-encoding.
97 /// - Unreserved characters are not escaped.
98 /// - Reserved characters always escaped with exceptions like '/'.
99 /// - All other characters are escaped.
100 std::string percentEncode(llvm::StringRef Content) {
101   std::string Result;
102   llvm::raw_string_ostream OS(Result);
103   for (unsigned char C : Content)
104     if (shouldEscape(C))
105       OS << '%' << llvm::format_hex_no_prefix(C, 2);
106     else
107       OS << C;
108 
109   OS.flush();
110   return Result;
111 }
112 
113 /// Decodes a string according to percent-encoding.
114 std::string percentDecode(llvm::StringRef Content) {
115   std::string Result;
116   for (auto I = Content.begin(), E = Content.end(); I != E; ++I) {
117     if (*I != '%') {
118       Result += *I;
119       continue;
120     }
121     if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) &&
122         llvm::isHexDigit(*(I + 2))) {
123       Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2)));
124       I += 2;
125     } else
126       Result.push_back(*I);
127   }
128   return Result;
129 }
130 
131 } // namespace
132 
133 URI::URI(llvm::StringRef Scheme, llvm::StringRef Authority,
134          llvm::StringRef Body)
135     : Scheme(Scheme), Authority(Authority), Body(Body) {
136   assert(!Scheme.empty());
137   assert((Authority.empty() || Body.startswith("/")) &&
138          "URI body must start with '/' when authority is present.");
139 }
140 
141 std::string URI::toString() const {
142   std::string Result;
143   llvm::raw_string_ostream OS(Result);
144   OS << percentEncode(Scheme) << ":";
145   if (Authority.empty() && Body.empty())
146     return OS.str();
147   // If authority if empty, we only print body if it starts with "/"; otherwise,
148   // the URI is invalid.
149   if (!Authority.empty() || llvm::StringRef(Body).startswith("/"))
150     OS << "//" << percentEncode(Authority);
151   OS << percentEncode(Body);
152   OS.flush();
153   return Result;
154 }
155 
156 llvm::Expected<URI> URI::parse(llvm::StringRef OrigUri) {
157   URI U;
158   llvm::StringRef Uri = OrigUri;
159 
160   auto Pos = Uri.find(':');
161   if (Pos == 0 || Pos == llvm::StringRef::npos)
162     return make_string_error("Scheme must be provided in URI: " + OrigUri);
163   U.Scheme = percentDecode(Uri.substr(0, Pos));
164   Uri = Uri.substr(Pos + 1);
165   if (Uri.consume_front("//")) {
166     Pos = Uri.find('/');
167     U.Authority = percentDecode(Uri.substr(0, Pos));
168     Uri = Uri.substr(Pos);
169   }
170   U.Body = percentDecode(Uri);
171   return U;
172 }
173 
174 llvm::Expected<URI> URI::create(llvm::StringRef AbsolutePath,
175                                 llvm::StringRef Scheme) {
176   if (!llvm::sys::path::is_absolute(AbsolutePath))
177     return make_string_error("Not a valid absolute path: " + AbsolutePath);
178   auto S = findSchemeByName(Scheme);
179   if (!S)
180     return S.takeError();
181   return S->get()->uriFromAbsolutePath(AbsolutePath);
182 }
183 
184 URI URI::createFile(llvm::StringRef AbsolutePath) {
185   auto U = create(AbsolutePath, "file");
186   if (!U)
187     llvm_unreachable(llvm::toString(U.takeError()).c_str());
188   return std::move(*U);
189 }
190 
191 llvm::Expected<std::string> URI::resolve(const URI &Uri,
192                                          llvm::StringRef HintPath) {
193   auto S = findSchemeByName(Uri.Scheme);
194   if (!S)
195     return S.takeError();
196   return S->get()->getAbsolutePath(Uri.Authority, Uri.Body, HintPath);
197 }
198 
199 } // namespace clangd
200 } // namespace clang
201