Skip to content

Commit a4e7267

Browse files
authored
[csolution-rpc] Handle content-length header CRLF line endings for LSP compliance
1 parent eda99da commit a4e7267

File tree

9 files changed

+69
-4
lines changed

9 files changed

+69
-4
lines changed

libs/crossplatform/include/CrossPlatformUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ class CrossPlatformUtils
108108
* @return The umask value as a std::filesystem::perms type
109109
*/
110110
static std::filesystem::perms GetCurrentUmask();
111+
112+
/**
113+
* @brief Get CRLF line endings regardless of platform
114+
* @return <CR><LF>
115+
*/
116+
static std::string Crlf();
111117
};
112118

113119
#endif /* CROSSPLATFORM_UTILS_H */

libs/crossplatform/src/CrossPlatformUtils.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,10 @@ const std::pair<std::string, int> CrossPlatformUtils::ExecCommand(const std::str
8888
return std::make_pair(result, ret_code);
8989
}
9090

91+
std::string CrossPlatformUtils::Crlf()
92+
{
93+
static const std::string crlf = CRLF;
94+
return crlf;
95+
}
96+
9197
// end of CrossplatformUtils.cpp

libs/crossplatform/src/Darwin/constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ constexpr const char* USER_PROFILE = "HOME";
1212
constexpr const char* PACK_ROOT_DIR = "/arm/packs";
1313
constexpr const char* CACHE_DIR = "/.cache";
1414
constexpr const char* HOST_TYPE = "mac";
15+
constexpr const char* CRLF = "\r\n";
1516

1617
#endif // CROSSPLATFORM_CONSTANTS_H

libs/crossplatform/src/Emscripten/constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ constexpr const char* USER_PROFILE = "";
1212
constexpr const char* PACK_ROOT_DIR = "";
1313
constexpr const char* CACHE_DIR = "";
1414
constexpr const char* HOST_TYPE = "";
15+
constexpr const char* CRLF = "";
1516

1617
#endif // CROSSPLATFORM_CONSTANTS_H

libs/crossplatform/src/Linux/constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ constexpr const char* PACK_ROOT_DIR = "/arm/packs";
1313
constexpr const char* PROC_SELF_EXE = "/proc/self/exe";
1414
constexpr const char* CACHE_DIR = "/.cache";
1515
constexpr const char* HOST_TYPE = "linux";
16+
constexpr const char* CRLF = "\r\n";
1617

1718
#endif // CROSSPLATFORM_CONSTANTS_H

libs/crossplatform/src/Windows/constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ constexpr const char* USER_PROFILE = "USERPROFILE";
1212
constexpr const char* PACK_ROOT_DIR = "\\Arm\\Packs";
1313
constexpr const char* CACHE_DIR = "\\AppData\\Local";
1414
constexpr const char* HOST_TYPE = "win";
15+
constexpr const char* CRLF = "\n";
1516

1617
#endif // CROSSPLATFORM_CONSTANTS_H

tools/projmgr/include/ProjMgrRpcServer.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,23 @@ class ProjMgrRpcServer {
5656
*/
5757
void SetContentLengthHeader(bool value) { m_contextLength = value; }
5858

59+
/**
60+
* @brief get request from stdin with content length header
61+
* @return string request
62+
*/
63+
const std::string GetRequestFromStdinWithLength(void);
64+
65+
/**
66+
* @brief get request from stdin
67+
* @param string request
68+
*/
69+
const std::string GetRequestFromStdin(void);
70+
5971
protected:
6072
ProjMgr& m_manager;
6173
bool m_debug = false;
6274
bool m_shutdown = false;
6375
bool m_contextLength = false;
64-
const std::string GetRequestFromStdinWithLength(void);
65-
const std::string GetRequestFromStdin(void);
6676
};
6777

6878
#endif // PROJMGRRPCSERVER_H

tools/projmgr/src/ProjMgrRpcServer.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "ProjMgrLogger.h"
1010
#include "ProjMgr.h"
1111
#include "ProductInfo.h"
12+
13+
#include "CrossPlatformUtils.h"
1214
#include <RteFsUtils.h>
1315

1416
#include <fstream>
@@ -31,10 +33,13 @@ const string ProjMgrRpcServer::GetRequestFromStdinWithLength(void) {
3133
string line;
3234
int contentLength = 0;
3335
const string& header = CONTENT_LENGTH_HEADER;
34-
while (getline(cin, line) && !line.empty() && !cin.fail()) {
36+
while (getline(cin, line) && !cin.fail()) {
3537
if (line.find(header) == 0) {
3638
contentLength = RteUtils::StringToInt(line.substr(header.length()), 0);
3739
}
40+
if (line.empty() || line.front() == '\r' || line.front() == '\n') {
41+
break;
42+
}
3843
}
3944
string request(contentLength, '\0');
4045
cin.read(&request[0], contentLength);
@@ -138,7 +143,9 @@ bool ProjMgrRpcServer::Run(void) {
138143

139144
// Send response
140145
if (m_contextLength) {
141-
cout << CONTENT_LENGTH_HEADER << response.size() << std::endl << std::endl << response << std::flush;
146+
// compliant to https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#baseProtocol
147+
cout << CONTENT_LENGTH_HEADER << response.size() <<
148+
CrossPlatformUtils::Crlf() << CrossPlatformUtils::Crlf() << response << std::flush;
142149
} else {
143150
cout << response << std::endl;
144151
}

tools/projmgr/test/src/ProjMgrRpcTests.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "ProjMgrRpcServerData.h"
1111
#include "ProjMgrLogger.h"
1212

13+
#include "CrossPlatformUtils.h"
1314
#include "ProductInfo.h"
1415

1516
using namespace std;
@@ -20,6 +21,7 @@ class ProjMgrRpcTests : public ProjMgr, public ::testing::Test {
2021
virtual ~ProjMgrRpcTests() {}
2122
string FormatRequest(const int id, const string& method, const json& params);
2223
vector<json> RunRpcMethods(const string& strIn);
24+
string RunRpcMethodsWithContent(const string& strIn);
2325
};
2426

2527
string ProjMgrRpcTests::FormatRequest(const int id, const string& method, const json& params = json()) {
@@ -47,6 +49,30 @@ vector<json> ProjMgrRpcTests::RunRpcMethods(const string& strIn) {
4749
return responses;
4850
}
4951

52+
string ProjMgrRpcTests::RunRpcMethodsWithContent(const string& strIn) {
53+
StdStreamRedirect streamRedirect;
54+
streamRedirect.SetInString(strIn);
55+
char* argv[] = { (char*)"csolution", (char*)"rpc", (char*)"--content-length" };
56+
EXPECT_EQ(0, RunProjMgr(3, argv, 0));
57+
return streamRedirect.GetOutString();
58+
}
59+
60+
TEST_F(ProjMgrRpcTests, ContentLength) {
61+
StdStreamRedirect streamRedirect;
62+
ProjMgrRpcServer server(*this);
63+
const auto& request = FormatRequest(1, "GetVersion");
64+
65+
auto requestWithHeader = "Content-Length:46\n\n" + request;
66+
streamRedirect.SetInString(requestWithHeader);
67+
auto parsedRequest = server.GetRequestFromStdinWithLength();
68+
EXPECT_EQ(request, parsedRequest);
69+
70+
requestWithHeader = "Content-Length:46\r\n\r\n" + request;
71+
streamRedirect.SetInString(requestWithHeader);
72+
parsedRequest = server.GetRequestFromStdinWithLength();
73+
EXPECT_EQ(request, parsedRequest);
74+
}
75+
5076
TEST_F(ProjMgrRpcTests, RpcGetVersion) {
5177
const auto& requests = FormatRequest(1, "GetVersion");
5278
const auto& responses = RunRpcMethods(requests);
@@ -55,6 +81,12 @@ TEST_F(ProjMgrRpcTests, RpcGetVersion) {
5581
EXPECT_EQ(string(VERSION_STRING), responses[0]["result"]);
5682
}
5783

84+
TEST_F(ProjMgrRpcTests, RpcGetVersionWithContent) {
85+
const auto& requests = "Content-Length:46\n\n" + FormatRequest(1, "GetVersion");
86+
const auto& responses = RunRpcMethodsWithContent(requests);
87+
EXPECT_TRUE(responses.find(CrossPlatformUtils::Crlf() + CrossPlatformUtils::Crlf() + "{") != string::npos);
88+
}
89+
5890
TEST_F(ProjMgrRpcTests, RpcLoadSolution) {
5991
const string& csolution = testinput_folder + "/TestRpc/minimal.csolution.yml";
6092
const auto& requests =

0 commit comments

Comments
 (0)