|  | //===---- URI.h - File URIs with schemes -------------------------*- C++-*-===// | 
|  | // | 
|  | //                     The LLVM Compiler Infrastructure | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "URI.h" | 
|  | #include "llvm/ADT/Twine.h" | 
|  | #include "llvm/Support/Error.h" | 
|  | #include "llvm/Support/Format.h" | 
|  | #include "llvm/Support/Path.h" | 
|  | #include <iomanip> | 
|  | #include <sstream> | 
|  |  | 
|  | LLVM_INSTANTIATE_REGISTRY(clang::clangd::URISchemeRegistry) | 
|  |  | 
|  | namespace clang { | 
|  | namespace clangd { | 
|  | namespace { | 
|  |  | 
|  | inline llvm::Error make_string_error(const llvm::Twine &Message) { | 
|  | return llvm::make_error<llvm::StringError>(Message, | 
|  | llvm::inconvertibleErrorCode()); | 
|  | } | 
|  |  | 
|  | /// \brief This manages file paths in the file system. All paths in the scheme | 
|  | /// are absolute (with leading '/'). | 
|  | class FileSystemScheme : public URIScheme { | 
|  | public: | 
|  | static const char *Scheme; | 
|  |  | 
|  | llvm::Expected<std::string> | 
|  | getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body, | 
|  | llvm::StringRef /*HintPath*/) const override { | 
|  | if (!Body.startswith("/")) | 
|  | return make_string_error("File scheme: expect body to be an absolute " | 
|  | "path starting with '/': " + | 
|  | Body); | 
|  | // For Windows paths e.g. /X: | 
|  | if (Body.size() > 2 && Body[0] == '/' && Body[2] == ':') | 
|  | Body.consume_front("/"); | 
|  | llvm::SmallVector<char, 16> Path(Body.begin(), Body.end()); | 
|  | llvm::sys::path::native(Path); | 
|  | return std::string(Path.begin(), Path.end()); | 
|  | } | 
|  |  | 
|  | llvm::Expected<URI> | 
|  | uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { | 
|  | using namespace llvm::sys; | 
|  |  | 
|  | std::string Body; | 
|  | // For Windows paths e.g. X: | 
|  | if (AbsolutePath.size() > 1 && AbsolutePath[1] == ':') | 
|  | Body = "/"; | 
|  | Body += path::convert_to_slash(AbsolutePath); | 
|  | return URI(Scheme, /*Authority=*/"", Body); | 
|  | } | 
|  | }; | 
|  |  | 
|  | const char *FileSystemScheme::Scheme = "file"; | 
|  |  | 
|  | static URISchemeRegistry::Add<FileSystemScheme> | 
|  | X(FileSystemScheme::Scheme, | 
|  | "URI scheme for absolute paths in the file system."); | 
|  |  | 
|  | llvm::Expected<std::unique_ptr<URIScheme>> | 
|  | findSchemeByName(llvm::StringRef Scheme) { | 
|  | for (auto I = URISchemeRegistry::begin(), E = URISchemeRegistry::end(); | 
|  | I != E; ++I) { | 
|  | if (I->getName() != Scheme) | 
|  | continue; | 
|  | return I->instantiate(); | 
|  | } | 
|  | return make_string_error("Can't find scheme: " + Scheme); | 
|  | } | 
|  |  | 
|  | bool shouldEscape(unsigned char C) { | 
|  | // Unreserved characters. | 
|  | if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || | 
|  | (C >= '0' && C <= '9')) | 
|  | return false; | 
|  | switch (C) { | 
|  | case '-': | 
|  | case '_': | 
|  | case '.': | 
|  | case '~': | 
|  | case '/': // '/' is only reserved when parsing. | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /// Encodes a string according to percent-encoding. | 
|  | /// - Unreserved characters are not escaped. | 
|  | /// - Reserved characters always escaped with exceptions like '/'. | 
|  | /// - All other characters are escaped. | 
|  | std::string percentEncode(llvm::StringRef Content) { | 
|  | std::string Result; | 
|  | llvm::raw_string_ostream OS(Result); | 
|  | for (unsigned char C : Content) | 
|  | if (shouldEscape(C)) | 
|  | OS << '%' << llvm::format_hex_no_prefix(C, 2); | 
|  | else | 
|  | OS << C; | 
|  |  | 
|  | OS.flush(); | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | /// Decodes a string according to percent-encoding. | 
|  | std::string percentDecode(llvm::StringRef Content) { | 
|  | std::string Result; | 
|  | for (auto I = Content.begin(), E = Content.end(); I != E; ++I) { | 
|  | if (*I != '%') { | 
|  | Result += *I; | 
|  | continue; | 
|  | } | 
|  | if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) && | 
|  | llvm::isHexDigit(*(I + 2))) { | 
|  | Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2))); | 
|  | I += 2; | 
|  | } else | 
|  | Result.push_back(*I); | 
|  | } | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | URI::URI(llvm::StringRef Scheme, llvm::StringRef Authority, | 
|  | llvm::StringRef Body) | 
|  | : Scheme(Scheme), Authority(Authority), Body(Body) { | 
|  | assert(!Scheme.empty()); | 
|  | assert((Authority.empty() || Body.startswith("/")) && | 
|  | "URI body must start with '/' when authority is present."); | 
|  | } | 
|  |  | 
|  | std::string URI::toString() const { | 
|  | std::string Result; | 
|  | llvm::raw_string_ostream OS(Result); | 
|  | OS << percentEncode(Scheme) << ":"; | 
|  | if (Authority.empty() && Body.empty()) | 
|  | return OS.str(); | 
|  | // If authority if empty, we only print body if it starts with "/"; otherwise, | 
|  | // the URI is invalid. | 
|  | if (!Authority.empty() || llvm::StringRef(Body).startswith("/")) | 
|  | OS << "//" << percentEncode(Authority); | 
|  | OS << percentEncode(Body); | 
|  | OS.flush(); | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | llvm::Expected<URI> URI::parse(llvm::StringRef OrigUri) { | 
|  | URI U; | 
|  | llvm::StringRef Uri = OrigUri; | 
|  |  | 
|  | auto Pos = Uri.find(':'); | 
|  | if (Pos == 0 || Pos == llvm::StringRef::npos) | 
|  | return make_string_error("Scheme must be provided in URI: " + OrigUri); | 
|  | U.Scheme = percentDecode(Uri.substr(0, Pos)); | 
|  | Uri = Uri.substr(Pos + 1); | 
|  | if (Uri.consume_front("//")) { | 
|  | Pos = Uri.find('/'); | 
|  | U.Authority = percentDecode(Uri.substr(0, Pos)); | 
|  | Uri = Uri.substr(Pos); | 
|  | } | 
|  | U.Body = percentDecode(Uri); | 
|  | return U; | 
|  | } | 
|  |  | 
|  | llvm::Expected<URI> URI::create(llvm::StringRef AbsolutePath, | 
|  | llvm::StringRef Scheme) { | 
|  | if (!llvm::sys::path::is_absolute(AbsolutePath)) | 
|  | return make_string_error("Not a valid absolute path: " + AbsolutePath); | 
|  | auto S = findSchemeByName(Scheme); | 
|  | if (!S) | 
|  | return S.takeError(); | 
|  | return S->get()->uriFromAbsolutePath(AbsolutePath); | 
|  | } | 
|  |  | 
|  | URI URI::createFile(llvm::StringRef AbsolutePath) { | 
|  | auto U = create(AbsolutePath, "file"); | 
|  | if (!U) | 
|  | llvm_unreachable(llvm::toString(U.takeError()).c_str()); | 
|  | return std::move(*U); | 
|  | } | 
|  |  | 
|  | llvm::Expected<std::string> URI::resolve(const URI &Uri, | 
|  | llvm::StringRef HintPath) { | 
|  | auto S = findSchemeByName(Uri.Scheme); | 
|  | if (!S) | 
|  | return S.takeError(); | 
|  | return S->get()->getAbsolutePath(Uri.Authority, Uri.Body, HintPath); | 
|  | } | 
|  |  | 
|  | llvm::Expected<std::string> URI::includeSpelling(const URI &Uri) { | 
|  | auto S = findSchemeByName(Uri.Scheme); | 
|  | if (!S) | 
|  | return S.takeError(); | 
|  | return S->get()->getIncludeSpelling(Uri); | 
|  | } | 
|  |  | 
|  | } // namespace clangd | 
|  | } // namespace clang |