llvm-project/clang-tools-extra/clangd/URI.cpp

208 lines
6.1 KiB
C++

//===---- 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