-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ensure that the inline cookie header will be folded correctly #17560
Changes from 3 commits
0da881f
0b12394
56027f0
58659ad
8703819
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -285,6 +285,17 @@ template <> HeaderMapImpl::StaticLookupTable<ResponseTrailerMap>::StaticLookupTa | |
finalizeTable(); | ||
} | ||
|
||
absl::string_view HeaderMapImpl::delimiterByHeader(const LowerCaseString& key, | ||
bool correctly_coalesce_cookies) { | ||
// TODO(wbpcode): 'correctly_coalesce_cookies' feature is enabled by default and is allowed to be | ||
// disabled via runtime. But I doubt if any user will actually disable it. The comma separator is | ||
// obviously problematic for cookies. Maybe we can consider removing it. | ||
if (correctly_coalesce_cookies && key == Http::Headers::get().Cookie) { | ||
return "; "; | ||
} | ||
return ","; | ||
} | ||
|
||
uint64_t HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data, | ||
absl::string_view delimiter) { | ||
if (data.empty()) { | ||
|
@@ -368,8 +379,10 @@ void HeaderMapImpl::insertByKey(HeaderString&& key, HeaderString&& value) { | |
if (*lookup.value().entry_ == nullptr) { | ||
maybeCreateInline(lookup.value().entry_, *lookup.value().key_, std::move(value)); | ||
} else { | ||
auto delimiter = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: const There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get it. |
||
delimiterByHeader(*lookup.value().key_, header_map_correctly_coalesce_cookies_); | ||
const uint64_t added_size = | ||
appendToHeader((*lookup.value().entry_)->value(), value.getStringView()); | ||
appendToHeader((*lookup.value().entry_)->value(), value.getStringView(), delimiter); | ||
alyssawilk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
addSize(added_size); | ||
value.clear(); | ||
} | ||
|
@@ -434,10 +447,8 @@ void HeaderMapImpl::appendCopy(const LowerCaseString& key, absl::string_view val | |
// TODO(#9221): converge on and document a policy for coalescing multiple headers. | ||
auto entry = getExisting(key); | ||
if (!entry.empty()) { | ||
const std::string delimiter = (key == Http::Headers::get().Cookie ? "; " : ","); | ||
const uint64_t added_size = header_map_correctly_coalesce_cookies_ | ||
? appendToHeader(entry[0]->value(), value, delimiter) | ||
: appendToHeader(entry[0]->value(), value); | ||
auto delimiter = delimiterByHeader(key, header_map_correctly_coalesce_cookies_); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: const There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get it. |
||
const uint64_t added_size = appendToHeader(entry[0]->value(), value, delimiter); | ||
addSize(added_size); | ||
} else { | ||
addCopy(key, value); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -319,6 +319,8 @@ class HeaderMapImpl : NonCopyable { | |
void insertByKey(HeaderString&& key, HeaderString&& value); | ||
static uint64_t appendToHeader(HeaderString& header, absl::string_view data, | ||
absl::string_view delimiter = ","); | ||
static absl::string_view delimiterByHeader(const LowerCaseString& key, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is not used outside of the cc file it can be a helper function in empty namespace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get it. |
||
bool correctly_coalesce_cookies); | ||
HeaderEntryImpl& maybeCreateInline(HeaderEntryImpl** entry, const LowerCaseString& key); | ||
HeaderEntryImpl& maybeCreateInline(HeaderEntryImpl** entry, const LowerCaseString& key, | ||
HeaderString&& value); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
#include "source/common/http/header_map_impl.h" | ||
#include "source/common/http/header_utility.h" | ||
|
||
#include "test/mocks/runtime/mocks.h" | ||
#include "test/test_common/utility.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
namespace Envoy { | ||
namespace Http { | ||
namespace { | ||
|
||
// Test that the cookie header can work correctly after being registered as an inline header. The | ||
// test will register the cookie as an inline header. In order to avoid affecting other tests, the | ||
// test is placed in this separate source file. | ||
TEST(InlineCookieTest, InlineCookieTest) { | ||
Http::CustomInlineHeaderRegistry::registerInlineHeader<Http::RequestHeaderMap::header_map_type>( | ||
Http::Headers::get().Cookie); | ||
Http::CustomInlineHeaderRegistry::registerInlineHeader<Http::RequestHeaderMap::header_map_type>( | ||
Http::LowerCaseString("header_for_compare")); | ||
|
||
auto mock_snapshot = std::make_shared<testing::NiceMock<Runtime::MockSnapshot>>(); | ||
testing::NiceMock<Runtime::MockLoader> mock_loader; | ||
Runtime::LoaderSingleton::initialize(&mock_loader); | ||
|
||
{ | ||
// Enable 'envoy.reloadable_features.header_map_correctly_coalesce_cookies' feature. | ||
ON_CALL(mock_loader, threadsafeSnapshot()).WillByDefault(testing::Return(mock_snapshot)); | ||
ON_CALL(*mock_snapshot, runtimeFeatureEnabled(_)).WillByDefault(testing::Return(true)); | ||
|
||
Http::TestRequestHeaderMapImpl headers{{"cookie", "key1:value1"}, | ||
{"cookie", "key2:value2"}, | ||
{"header_for_compare", "value1"}, | ||
{"header_for_compare", "value2"}}; | ||
|
||
// Delimiter for inline 'cookie' header is specialized '; '. | ||
EXPECT_EQ("key1:value1; key2:value2", headers.get_("cookie")); | ||
// Delimiter for inline 'header_for_compare' header is default ','. | ||
EXPECT_EQ("value1,value2", headers.get_("header_for_compare")); | ||
} | ||
|
||
{ | ||
// Disable 'envoy.reloadable_features.header_map_correctly_coalesce_cookies' feature. | ||
ON_CALL(mock_loader, threadsafeSnapshot()).WillByDefault(testing::Return(mock_snapshot)); | ||
ON_CALL(*mock_snapshot, runtimeFeatureEnabled(_)).WillByDefault(testing::Return(false)); | ||
|
||
Http::TestRequestHeaderMapImpl headers{{"cookie", "key1:value1"}, | ||
{"cookie", "key2:value2"}, | ||
{"header_for_compare", "value1"}, | ||
{"header_for_compare", "value2"}}; | ||
|
||
// 'envoy.reloadable_features.header_map_correctly_coalesce_cookies' is disabled then default | ||
// ',' will be used as delimiter. | ||
EXPECT_EQ("key1:value1,key2:value2", headers.get_("cookie")); | ||
EXPECT_EQ("value1,value2", headers.get_("header_for_compare")); | ||
} | ||
} | ||
|
||
} // namespace | ||
} // namespace Http | ||
} // namespace Envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll be removed in a few months per the process documented in CONTRIBUTING.md so I think you can take this TODO out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get it.