Skip to content

Commit

Permalink
Merge pull request chipsalliance#1941 from antmicro/lsp-uri-decoding
Browse files Browse the repository at this point in the history
Add proper handling of escape characters in LSP URIs
  • Loading branch information
hzeller committed Jun 15, 2023
2 parents c719ed3 + bb87daf commit ddcea37
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 113 deletions.
10 changes: 10 additions & 0 deletions common/lsp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ cc_library(
],
)

cc_test(
name = "lsp-file-utils_test",
srcs = ["lsp-file-utils_test.cc"],
deps = [
":lsp-file-utils",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
],
)

cc_binary(
name = "json-rpc-expect",
srcs = ["json-rpc-expect.cc"],
Expand Down
98 changes: 95 additions & 3 deletions common/lsp/lsp-file-utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

#include "common/lsp/lsp-file-utils.h"

#include <algorithm>
#include <filesystem>

#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
Expand All @@ -25,14 +27,104 @@ namespace verible::lsp {

static constexpr absl::string_view kFileSchemePrefix = "file://";

absl::string_view LSPUriToPath(absl::string_view uri) {
namespace {

bool NeedsEscape(char c) {
if (isalnum(c)) return false;
switch (c) {
case '-':
case '/':
case '.':
case '_':
case '~':
case '\\':
return false;
}
return true;
}

std::string DecodeURI(absl::string_view uri) {
std::string result;
result.reserve(uri.size() - 2 * std::count(uri.begin(), uri.end(), '%'));
absl::string_view::size_type pos = 0;

while (pos < uri.size()) {
if (uri[pos] == '%') {
pos++;
if (pos + 2 <= uri.size() && std::isxdigit(uri[pos]) &&
std::isxdigit(uri[pos + 1])) {
std::string hex = absl::HexStringToBytes(uri.substr(pos, 2));
absl::StrAppend(&result, hex.length() == 1 ? hex : uri.substr(pos, 2));
pos += 2;
} else {
absl::StrAppend(&result, "%");
}
}
absl::string_view::size_type nextpos = uri.find('%', pos);
if (nextpos > absl::string_view::npos) nextpos = uri.size();
absl::StrAppend(&result, uri.substr(pos, nextpos - pos));
pos = nextpos;
}
return result;
}

std::string EncodeURI(absl::string_view uri) {
std::string result;

absl::string_view::size_type pos = 0;

int prevpos = 0;
while (pos < uri.size()) {
if (NeedsEscape(uri[pos])) {
absl::StrAppend(&result, uri.substr(prevpos, pos - prevpos));
absl::StrAppend(&result, "%", absl::Hex(uri[pos], absl::kZeroPad2));
prevpos = ++pos;
} else {
pos++;
}
}
absl::StrAppend(&result, uri.substr(prevpos, pos - prevpos));
return result;
}
} // namespace

std::string LSPUriToPath(absl::string_view uri) {
if (!absl::StartsWith(uri, kFileSchemePrefix)) return "";
return uri.substr(kFileSchemePrefix.size());
std::string path = DecodeURI(uri.substr(kFileSchemePrefix.size()));
// In Windows, paths in URIs are represented as
// file:///c:/Users/user/project/file.sv
// Which results in paths:
// /c:/Users/user/project/file.sv
// The prefix "/" needs to be removed from the path
#ifdef _WIN32
if (path.length() >= 3 && path[0] == '/' && isalpha(path[1]) &&
path[2] == ':') {
path = path.substr(1);
}
#endif
return path;
}

std::string PathToLSPUri(absl::string_view path) {
std::filesystem::path p(path.begin(), path.end());
return absl::StrCat(kFileSchemePrefix, std::filesystem::absolute(p).string());
std::string normalized_path;
normalized_path = std::filesystem::absolute(p).string();
std::transform(normalized_path.cbegin(), normalized_path.cend(),
normalized_path.begin(),
[](char c) { return c == '\\' ? '/' : c; });
#ifdef _WIN32
if (normalized_path.length() >= 2 && isalpha(normalized_path[0]) &&
normalized_path[1] == ':') {
// In Windows, paths in URIs are represented as
// file:///c:/Users/user/project/file.sv
// Which results in paths:
// /c:/Users/user/project/file.sv
// The prefix "/" needs to be added
normalized_path = absl::StrCat("/", normalized_path);
}
#endif
normalized_path = EncodeURI(normalized_path);
return absl::StrCat(kFileSchemePrefix, normalized_path);
}

} // namespace verible::lsp
2 changes: 1 addition & 1 deletion common/lsp/lsp-file-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace verible::lsp {
// If other scheme is provided, method returns empty string_view.
// TODO (glatosinski) current resolving of LSP URIs is very naive
// and supports only narrow use cases of file:// specifier.
absl::string_view LSPUriToPath(absl::string_view uri);
std::string LSPUriToPath(absl::string_view uri);

// Converts filesystem paths to file:// scheme entries.
std::string PathToLSPUri(absl::string_view path);
Expand Down
86 changes: 86 additions & 0 deletions common/lsp/lsp-file-utils_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2023 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "common/lsp/lsp-file-utils.h"

#include "absl/strings/str_cat.h"
#include "gtest/gtest.h"

namespace verible {
namespace lsp {

std::string PathPrefix(const std::string &path) {
#ifdef _WIN32
return absl::StrCat("y:/", path);
#else
return absl::StrCat("/", path);
#endif
}

std::string URIPrefix(const std::string &path) {
#ifdef _WIN32
return absl::StrCat("file:///y%3a/", path);
#else
return absl::StrCat("file:///", path);
#endif
}

TEST(URIDecodingTest, SimplePathDecoding) {
ASSERT_EQ(PathPrefix("test-project/add.sv"),
LSPUriToPath(URIPrefix("test-project/add.sv")));
}

TEST(URIEncodingTest, SimplePathEncoding) {
ASSERT_EQ(URIPrefix("test-project/add.sv"),
PathToLSPUri(PathPrefix("test-project/add.sv")));
}

TEST(URIDecodingtest, PathWithSpacesDecoding) {
ASSERT_EQ(PathPrefix("test project/my module.sv"),
LSPUriToPath(URIPrefix("test%20project/my%20module.sv")));
}

TEST(URIEncodingTest, PathWithSpacesEncoding) {
ASSERT_EQ(URIPrefix("test%20project/my%20module.sv"),
PathToLSPUri(PathPrefix("test project/my module.sv")));
}

TEST(URIDecodingtest, PathWithConsecutiveEscapes) {
ASSERT_EQ(PathPrefix("test project/my module.sv"),
LSPUriToPath(URIPrefix("test%20project/my%20%20%20%20module.sv")));
}

TEST(URIEncodingTest, PathWithConsecutiveEscapes) {
ASSERT_EQ(URIPrefix("test%20project/my%20%20%20%20module.sv"),
PathToLSPUri(PathPrefix("test project/my module.sv")));
}

TEST(URIDecodingTest, InvalidHex) {
ASSERT_EQ(PathPrefix("test-project/my-module-%qz.sv"),
LSPUriToPath(URIPrefix("test-project/my-module-%qz.sv")));
}

TEST(URIDecodingTest, HexConversions) {
ASSERT_EQ(PathPrefix("test-project/my-module-%q"),
LSPUriToPath(URIPrefix("test-project/my-module-%q")));
ASSERT_EQ(PathPrefix("test-project/my-module-%xyz"),
LSPUriToPath(URIPrefix("test-project/my-module-%xyz")));
ASSERT_EQ(PathPrefix(" "), LSPUriToPath(URIPrefix("%20")));
ASSERT_EQ(PathPrefix("%2"), LSPUriToPath(URIPrefix("%2")));
ASSERT_EQ(PathPrefix("%"), LSPUriToPath(URIPrefix("%")));
}

} // namespace lsp
} // namespace verible
1 change: 1 addition & 0 deletions verilog/tools/ls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ cc_test(
":verilog-language-server",
"//common/lsp:lsp-protocol",
"//common/lsp:lsp-protocol-enums",
"//common/lsp:lsp-file-utils",
"//common/util:file-util",
"//verilog/analysis:verilog-linter",
"@com_google_absl//absl/flags:flag",
Expand Down
2 changes: 1 addition & 1 deletion verilog/tools/ls/lsp-parse-buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ static absl::StatusOr<std::vector<verible::LintRuleStatus>> RunLinter(
const auto &text_structure = parser.Data();

verilog::LinterConfiguration config;
const absl::string_view file_path = verible::lsp::LSPUriToPath(filename);
const std::string file_path = verible::lsp::LSPUriToPath(filename);
if (auto from_flags = LinterConfigurationFromFlags(file_path);
from_flags.ok()) {
config = *from_flags;
Expand Down
2 changes: 1 addition & 1 deletion verilog/tools/ls/symbol-table-handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ std::vector<verible::lsp::Location> SymbolTableHandler::FindDefinitionLocation(
const verilog::BufferTrackerContainer &parsed_buffers) {
// TODO add iterating over multiple definitions
Prepare();
const absl::string_view filepath = LSPUriToPath(params.textDocument.uri);
const std::string filepath = LSPUriToPath(params.textDocument.uri);
std::string relativepath = curr_project_->GetRelativePathToSource(filepath);
absl::string_view symbol =
GetTokenAtTextDocumentPosition(params, parsed_buffers);
Expand Down
4 changes: 2 additions & 2 deletions verilog/tools/ls/verilog-language-server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ verible::lsp::InitializeResult VerilogLanguageServer::InitializeRequestHandler(
const verible::lsp::InitializeParams &p) {
// set VerilogProject for the symbol table, if possible
if (!p.rootUri.empty()) {
absl::string_view path = verible::lsp::LSPUriToPath(p.rootUri);
std::string path = verible::lsp::LSPUriToPath(p.rootUri);
if (path.empty()) {
LOG(ERROR) << "Unsupported rootUri in initialize request: " << p.rootUri
<< std::endl;
Expand Down Expand Up @@ -232,7 +232,7 @@ void VerilogLanguageServer::SendDiagnostics(

void VerilogLanguageServer::UpdateEditedFileInProject(
const std::string &uri, const verilog::BufferTracker *buffer_tracker) {
const absl::string_view path = verible::lsp::LSPUriToPath(uri);
const std::string path = verible::lsp::LSPUriToPath(uri);
if (path.empty()) {
LOG(ERROR) << "Could not convert LS URI to path: " << uri;
return;
Expand Down
Loading

0 comments on commit ddcea37

Please sign in to comment.