2018-01-22 19:48:20 +08:00
|
|
|
//===---- URI.h - File URIs with schemes -------------------------*- C++-*-===//
|
|
|
|
//
|
2019-01-19 16:50:56 +08:00
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
2018-01-22 19:48:20 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "URI.h"
|
2018-09-25 19:47:14 +08:00
|
|
|
#include "llvm/ADT/StringExtras.h"
|
2018-01-22 19:48:20 +08:00
|
|
|
#include "llvm/ADT/Twine.h"
|
|
|
|
#include "llvm/Support/Error.h"
|
|
|
|
#include "llvm/Support/Format.h"
|
2018-09-25 18:47:46 +08:00
|
|
|
#include "llvm/Support/FormatVariadic.h"
|
2018-01-22 19:48:20 +08:00
|
|
|
#include "llvm/Support/Path.h"
|
2018-09-25 18:47:46 +08:00
|
|
|
#include <algorithm>
|
2018-01-22 19:48:20 +08:00
|
|
|
#include <iomanip>
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
LLVM_INSTANTIATE_REGISTRY(clang::clangd::URISchemeRegistry)
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
|
|
|
namespace {
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
inline llvm::Error make_string_error(const llvm::Twine &Message) {
|
|
|
|
return llvm::make_error<llvm::StringError>(Message,
|
|
|
|
llvm::inconvertibleErrorCode());
|
2018-01-22 19:48:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief This manages file paths in the file system. All paths in the scheme
|
|
|
|
/// are absolute (with leading '/').
|
2018-11-28 18:30:42 +08:00
|
|
|
/// Note that this scheme is hardcoded into the library and not registered in
|
|
|
|
/// registry.
|
2018-01-22 19:48:20 +08:00
|
|
|
class FileSystemScheme : public URIScheme {
|
|
|
|
public:
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<std::string>
|
|
|
|
getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
|
|
|
|
llvm::StringRef /*HintPath*/) const override {
|
2018-01-22 19:48:20 +08:00
|
|
|
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("/");
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::SmallVector<char, 16> Path(Body.begin(), Body.end());
|
|
|
|
llvm::sys::path::native(Path);
|
2018-01-22 19:48:20 +08:00
|
|
|
return std::string(Path.begin(), Path.end());
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<URI>
|
|
|
|
uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
|
2018-01-22 19:48:20 +08:00
|
|
|
std::string Body;
|
|
|
|
// For Windows paths e.g. X:
|
|
|
|
if (AbsolutePath.size() > 1 && AbsolutePath[1] == ':')
|
|
|
|
Body = "/";
|
2019-01-07 23:45:19 +08:00
|
|
|
Body += llvm::sys::path::convert_to_slash(AbsolutePath);
|
2018-11-28 18:30:42 +08:00
|
|
|
return URI("file", /*Authority=*/"", Body);
|
2018-01-22 19:48:20 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<std::unique_ptr<URIScheme>>
|
|
|
|
findSchemeByName(llvm::StringRef Scheme) {
|
2018-11-28 18:30:42 +08:00
|
|
|
if (Scheme == "file")
|
2019-01-07 23:45:19 +08:00
|
|
|
return llvm::make_unique<FileSystemScheme>();
|
2018-11-28 18:30:42 +08:00
|
|
|
|
2018-01-22 19:48:20 +08:00
|
|
|
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.
|
2018-02-07 20:12:06 +08:00
|
|
|
if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||
|
|
|
|
(C >= '0' && C <= '9'))
|
2018-01-22 19:48:20 +08:00
|
|
|
return false;
|
|
|
|
switch (C) {
|
|
|
|
case '-':
|
|
|
|
case '_':
|
|
|
|
case '.':
|
|
|
|
case '~':
|
|
|
|
case '/': // '/' is only reserved when parsing.
|
2018-10-09 18:29:54 +08:00
|
|
|
// ':' is only reserved for relative URI paths, which clangd doesn't produce.
|
|
|
|
case ':':
|
2018-01-22 19:48:20 +08:00
|
|
|
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.
|
2019-01-07 23:45:19 +08:00
|
|
|
std::string percentEncode(llvm::StringRef Content) {
|
2018-01-22 19:48:20 +08:00
|
|
|
std::string Result;
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::raw_string_ostream OS(Result);
|
2018-01-22 19:48:20 +08:00
|
|
|
for (unsigned char C : Content)
|
|
|
|
if (shouldEscape(C))
|
2019-01-07 23:45:19 +08:00
|
|
|
OS << '%' << llvm::format_hex_no_prefix(C, 2, /*Upper = */ true);
|
2018-01-22 19:48:20 +08:00
|
|
|
else
|
|
|
|
OS << C;
|
|
|
|
|
|
|
|
OS.flush();
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Decodes a string according to percent-encoding.
|
2019-01-07 23:45:19 +08:00
|
|
|
std::string percentDecode(llvm::StringRef Content) {
|
2018-01-22 19:48:20 +08:00
|
|
|
std::string Result;
|
|
|
|
for (auto I = Content.begin(), E = Content.end(); I != E; ++I) {
|
|
|
|
if (*I != '%') {
|
|
|
|
Result += *I;
|
|
|
|
continue;
|
|
|
|
}
|
2019-01-07 23:45:19 +08:00
|
|
|
if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) &&
|
|
|
|
llvm::isHexDigit(*(I + 2))) {
|
|
|
|
Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2)));
|
2018-01-22 19:48:20 +08:00
|
|
|
I += 2;
|
|
|
|
} else
|
|
|
|
Result.push_back(*I);
|
|
|
|
}
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
bool isValidScheme(llvm::StringRef Scheme) {
|
2018-09-25 18:47:46 +08:00
|
|
|
if (Scheme.empty())
|
|
|
|
return false;
|
2019-01-07 23:45:19 +08:00
|
|
|
if (!llvm::isAlpha(Scheme[0]))
|
2018-09-25 18:47:46 +08:00
|
|
|
return false;
|
|
|
|
return std::all_of(Scheme.begin() + 1, Scheme.end(), [](char C) {
|
2019-01-07 23:45:19 +08:00
|
|
|
return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-';
|
2018-09-25 18:47:46 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-01-22 19:48:20 +08:00
|
|
|
} // namespace
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
URI::URI(llvm::StringRef Scheme, llvm::StringRef Authority,
|
|
|
|
llvm::StringRef Body)
|
2018-01-29 23:37:46 +08:00
|
|
|
: Scheme(Scheme), Authority(Authority), Body(Body) {
|
|
|
|
assert(!Scheme.empty());
|
|
|
|
assert((Authority.empty() || Body.startswith("/")) &&
|
|
|
|
"URI body must start with '/' when authority is present.");
|
2018-01-22 19:48:20 +08:00
|
|
|
}
|
|
|
|
|
2018-01-29 23:37:46 +08:00
|
|
|
std::string URI::toString() const {
|
2018-01-22 19:48:20 +08:00
|
|
|
std::string Result;
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::raw_string_ostream OS(Result);
|
2018-01-22 19:48:20 +08:00
|
|
|
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.
|
2019-01-07 23:45:19 +08:00
|
|
|
if (!Authority.empty() || llvm::StringRef(Body).startswith("/"))
|
2018-01-22 19:48:20 +08:00
|
|
|
OS << "//" << percentEncode(Authority);
|
|
|
|
OS << percentEncode(Body);
|
|
|
|
OS.flush();
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<URI> URI::parse(llvm::StringRef OrigUri) {
|
2018-01-29 23:37:46 +08:00
|
|
|
URI U;
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::StringRef Uri = OrigUri;
|
2018-01-22 19:48:20 +08:00
|
|
|
|
|
|
|
auto Pos = Uri.find(':');
|
2019-01-07 23:45:19 +08:00
|
|
|
if (Pos == llvm::StringRef::npos)
|
2018-01-22 19:48:20 +08:00
|
|
|
return make_string_error("Scheme must be provided in URI: " + OrigUri);
|
2018-09-25 18:47:46 +08:00
|
|
|
auto SchemeStr = Uri.substr(0, Pos);
|
|
|
|
U.Scheme = percentDecode(SchemeStr);
|
|
|
|
if (!isValidScheme(U.Scheme))
|
2019-01-07 23:45:19 +08:00
|
|
|
return make_string_error(llvm::formatv("Invalid scheme: {0} (decoded: {1})",
|
|
|
|
SchemeStr, U.Scheme));
|
2018-01-22 19:48:20 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<URI> URI::create(llvm::StringRef AbsolutePath,
|
|
|
|
llvm::StringRef Scheme) {
|
|
|
|
if (!llvm::sys::path::is_absolute(AbsolutePath))
|
2018-01-22 19:48:20 +08:00
|
|
|
return make_string_error("Not a valid absolute path: " + AbsolutePath);
|
|
|
|
auto S = findSchemeByName(Scheme);
|
|
|
|
if (!S)
|
|
|
|
return S.takeError();
|
|
|
|
return S->get()->uriFromAbsolutePath(AbsolutePath);
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
URI URI::create(llvm::StringRef AbsolutePath) {
|
|
|
|
if (!llvm::sys::path::is_absolute(AbsolutePath))
|
[clangd] Cleanup: stop passing around list of supported URI schemes.
Summary:
Instead of passing around a list of supported URI schemes in clangd, we
expose an interface to convert a path to URI using any compatible scheme
that has been registered. It favors customized schemes and falls
back to "file" when no other scheme works.
Changes in this patch are:
- URI::create(AbsPath, URISchemes) -> URI::create(AbsPath). The new API finds a
compatible scheme from the registry.
- Remove URISchemes option everywhere (ClangdServer, SymbolCollecter, FileIndex etc).
- Unit tests will use "unittest" by default.
- Move "test" scheme from ClangdLSPServer to ClangdMain.cpp, and only
register the test scheme when lit-test or enable-lit-scheme is set.
(The new flag is added to make lit protocol.test work; I wonder if there
is alternative here.)
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54800
llvm-svn: 347467
2018-11-22 23:02:05 +08:00
|
|
|
llvm_unreachable(
|
|
|
|
("Not a valid absolute path: " + AbsolutePath).str().c_str());
|
|
|
|
for (auto &Entry : URISchemeRegistry::entries()) {
|
|
|
|
auto URI = Entry.instantiate()->uriFromAbsolutePath(AbsolutePath);
|
2018-09-06 20:54:43 +08:00
|
|
|
// For some paths, conversion to different URI schemes is impossible. These
|
|
|
|
// should be just skipped.
|
|
|
|
if (!URI) {
|
|
|
|
// Ignore the error.
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::consumeError(URI.takeError());
|
2018-09-06 20:54:43 +08:00
|
|
|
continue;
|
|
|
|
}
|
[clangd] Cleanup: stop passing around list of supported URI schemes.
Summary:
Instead of passing around a list of supported URI schemes in clangd, we
expose an interface to convert a path to URI using any compatible scheme
that has been registered. It favors customized schemes and falls
back to "file" when no other scheme works.
Changes in this patch are:
- URI::create(AbsPath, URISchemes) -> URI::create(AbsPath). The new API finds a
compatible scheme from the registry.
- Remove URISchemes option everywhere (ClangdServer, SymbolCollecter, FileIndex etc).
- Unit tests will use "unittest" by default.
- Move "test" scheme from ClangdLSPServer to ClangdMain.cpp, and only
register the test scheme when lit-test or enable-lit-scheme is set.
(The new flag is added to make lit protocol.test work; I wonder if there
is alternative here.)
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54800
llvm-svn: 347467
2018-11-22 23:02:05 +08:00
|
|
|
return std::move(*URI);
|
2018-09-06 20:54:43 +08:00
|
|
|
}
|
[clangd] Cleanup: stop passing around list of supported URI schemes.
Summary:
Instead of passing around a list of supported URI schemes in clangd, we
expose an interface to convert a path to URI using any compatible scheme
that has been registered. It favors customized schemes and falls
back to "file" when no other scheme works.
Changes in this patch are:
- URI::create(AbsPath, URISchemes) -> URI::create(AbsPath). The new API finds a
compatible scheme from the registry.
- Remove URISchemes option everywhere (ClangdServer, SymbolCollecter, FileIndex etc).
- Unit tests will use "unittest" by default.
- Move "test" scheme from ClangdLSPServer to ClangdMain.cpp, and only
register the test scheme when lit-test or enable-lit-scheme is set.
(The new flag is added to make lit protocol.test work; I wonder if there
is alternative here.)
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54800
llvm-svn: 347467
2018-11-22 23:02:05 +08:00
|
|
|
// Fallback to file: scheme which should work for any paths.
|
|
|
|
return URI::createFile(AbsolutePath);
|
2018-09-06 20:54:43 +08:00
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
URI URI::createFile(llvm::StringRef AbsolutePath) {
|
2018-11-28 18:30:42 +08:00
|
|
|
auto U = FileSystemScheme().uriFromAbsolutePath(AbsolutePath);
|
2018-01-29 23:37:46 +08:00
|
|
|
if (!U)
|
|
|
|
llvm_unreachable(llvm::toString(U.takeError()).c_str());
|
|
|
|
return std::move(*U);
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<std::string> URI::resolve(const URI &Uri,
|
|
|
|
llvm::StringRef HintPath) {
|
2018-01-22 19:48:20 +08:00
|
|
|
auto S = findSchemeByName(Uri.Scheme);
|
|
|
|
if (!S)
|
|
|
|
return S.takeError();
|
|
|
|
return S->get()->getAbsolutePath(Uri.Authority, Uri.Body, HintPath);
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<std::string> URI::resolvePath(llvm::StringRef AbsPath,
|
|
|
|
llvm::StringRef HintPath) {
|
|
|
|
if (!llvm::sys::path::is_absolute(AbsPath))
|
2018-11-28 18:30:42 +08:00
|
|
|
llvm_unreachable(("Not a valid absolute path: " + AbsPath).str().c_str());
|
|
|
|
for (auto &Entry : URISchemeRegistry::entries()) {
|
2019-01-03 21:28:05 +08:00
|
|
|
auto S = Entry.instantiate();
|
2018-11-28 18:30:42 +08:00
|
|
|
auto U = S->uriFromAbsolutePath(AbsPath);
|
|
|
|
// For some paths, conversion to different URI schemes is impossible. These
|
|
|
|
// should be just skipped.
|
|
|
|
if (!U) {
|
|
|
|
// Ignore the error.
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::consumeError(U.takeError());
|
2018-11-28 18:30:42 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return S->getAbsolutePath(U->Authority, U->Body, HintPath);
|
|
|
|
}
|
|
|
|
// Fallback to file: scheme which doesn't do any canonicalization.
|
|
|
|
return AbsPath;
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::Expected<std::string> URI::includeSpelling(const URI &Uri) {
|
2018-04-09 23:09:44 +08:00
|
|
|
auto S = findSchemeByName(Uri.Scheme);
|
|
|
|
if (!S)
|
|
|
|
return S.takeError();
|
|
|
|
return S->get()->getIncludeSpelling(Uri);
|
|
|
|
}
|
|
|
|
|
2018-01-22 19:48:20 +08:00
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|