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