//===---- 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 #include 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(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 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 Path(Body.begin(), Body.end()); llvm::sys::path::native(Path); return std::string(Path.begin(), Path.end()); } llvm::Expected 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 X(FileSystemScheme::Scheme, "URI scheme for absolute paths in the file system."); llvm::Expected> 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::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::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 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 URI::includeSpelling(const URI &Uri) { auto S = findSchemeByName(Uri.Scheme); if (!S) return S.takeError(); return S->get()->getIncludeSpelling(Uri); } } // namespace clangd } // namespace clang