Skip to content

Commit 79c164c

Browse files
authored
Merge pull request #1319 from mickem/bug/1318_fixed_password_header_should_be_case_insensetive
Fixed #1318 https headers should be case-insensitive
2 parents 972c01f + 1f91248 commit 79c164c

4 files changed

Lines changed: 43 additions & 5 deletions

File tree

.github/workflows/build-windows-legacy.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,14 @@ env:
4040

4141
jobs:
4242
build:
43-
runs-on: windows-latest
43+
# Pinned to windows-2022 (not windows-latest): the legacy XP build needs
44+
# the v141_xp toolset, the Windows 8.1 SDK, the "Visual Studio 17"
45+
# (VS 2022) cmake generator, and ./.github/actions/update-msvc installs
46+
# components into ...\Microsoft Visual Studio\2022\Enterprise. The
47+
# windows-latest image has moved to Visual Studio 18, which ships none of
48+
# those, so ilammy/msvc-dev-cmd fails with "Toolset directory for version
49+
# '14.1' was not found".
50+
runs-on: windows-2022
4451
outputs:
4552
platform: ${{ steps.setup.outputs.platform }}
4653

.github/workflows/build-windows.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,14 @@ env:
4040

4141
jobs:
4242
build:
43-
runs-on: windows-latest
43+
# Pinned to windows-2022 (not windows-latest): this build targets the
44+
# v141 toolset via vcvars 14.1, builds with the "Visual Studio 17"
45+
# (VS 2022) cmake generator, and ./.github/actions/update-msvc installs
46+
# components into ...\Microsoft Visual Studio\2022\Enterprise. The
47+
# windows-latest image has moved to Visual Studio 18, which ships none of
48+
# those, so ilammy/msvc-dev-cmd fails with "Toolset directory for version
49+
# '14.1' was not found".
50+
runs-on: windows-2022
4451
outputs:
4552
platform: ${{ steps.setup.outputs.platform }}
4653

libs/mongoose-cpp/Request.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#pragma warning(disable : 4251)
1111
#endif
1212

13+
#include <algorithm>
14+
#include <cctype>
1315
#include <map>
1416
#include <string>
1517
#include <vector>
@@ -22,7 +24,17 @@ class NSCAPI_EXPORT Request {
2224
public:
2325
typedef std::pair<std::string, std::string> arg_entry;
2426
typedef std::vector<arg_entry> arg_vector;
25-
typedef std::map<std::string, std::string> headers_type;
27+
28+
// HTTP header names are case-insensitive (RFC 7230 3.2), so the header map
29+
// compares keys without regard to case. This lets clients send e.g. either
30+
// `password` or `Password` and have it resolve to the same entry.
31+
struct ci_less {
32+
bool operator()(const std::string& lhs, const std::string& rhs) const {
33+
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(),
34+
[](unsigned char a, unsigned char b) { return std::tolower(a) < std::tolower(b); });
35+
}
36+
};
37+
typedef std::map<std::string, std::string, ci_less> headers_type;
2638

2739
Request(std::string ip, bool is_ssl, std::string method, std::string url, std::string query, headers_type headers, std::string data);
2840

libs/mongoose-cpp/Request_test.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ TEST(Request, HasVariableChecksHeaders) {
3838
EXPECT_TRUE(r.hasVariable("Host"));
3939
EXPECT_TRUE(r.hasVariable("X-Test"));
4040
EXPECT_FALSE(r.hasVariable("Missing"));
41-
// Map is case-sensitive on the contained keys.
42-
EXPECT_FALSE(r.hasVariable("host"));
41+
// Header names are case-insensitive (RFC 7230 3.2).
42+
EXPECT_TRUE(r.hasVariable("host"));
43+
EXPECT_TRUE(r.hasVariable("x-test"));
4344
}
4445

4546
TEST(Request, ReadHeaderReturnsValueOrEmpty) {
@@ -49,6 +50,17 @@ TEST(Request, ReadHeaderReturnsValueOrEmpty) {
4950
EXPECT_EQ(r.readHeader("Missing"), "");
5051
}
5152

53+
TEST(Request, ReadHeaderIsCaseInsensitive) {
54+
// Clients send the legacy credential header as either `password` or
55+
// `Password` (e.g. Go's net/http canonicalizes to `Password`); both must
56+
// resolve to the same value. See session_manager_interface::is_logged_in.
57+
const Request::headers_type h{{"Password", "secret"}};
58+
const auto r = make_request("GET", "/", "", h);
59+
EXPECT_EQ(r.readHeader("password"), "secret");
60+
EXPECT_EQ(r.readHeader("Password"), "secret");
61+
EXPECT_EQ(r.readHeader("PASSWORD"), "secret");
62+
}
63+
5264
TEST(Request, ReadHeaderDoesNotMutateHeaders) {
5365
const Request::headers_type h{{"Host", "example.com"}};
5466
auto r = make_request("GET", "/", "", h);

0 commit comments

Comments
 (0)