Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 57 additions & 24 deletions source/common/http/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -254,46 +254,79 @@ bool maybeAdjustForIpv6(absl::string_view absolute_url, uint64_t& offset, uint64
return true;
}

absl::string_view parseCookie(absl::string_view cookie_value, absl::string_view key) {
// Split the cookie header into individual cookies.
for (const auto& s : StringUtil::splitToken(cookie_value, ";")) {
// Find the key part of the cookie (i.e. the name of the cookie).
size_t first_non_space = s.find_first_not_of(' ');
size_t equals_index = s.find('=');
if (equals_index == absl::string_view::npos) {
// The cookie is malformed if it does not have an `=`. Continue
// checking other cookies in this header.
continue;
}
absl::string_view k = s.substr(first_non_space, equals_index - first_non_space);
// If the key matches, parse the value from the rest of the cookie string.
if (k == key) {
void forEachCookie(
const HeaderMap& headers, const LowerCaseString& cookie_header,
const std::function<bool(absl::string_view, absl::string_view)>& cookie_consumer) {
const Http::HeaderMap::GetResult cookie_headers = headers.get(cookie_header);

for (size_t index = 0; index < cookie_headers.size(); index++) {
auto cookie_header_value = cookie_headers[index]->value().getStringView();

// Split the cookie header into individual cookies.
for (const auto& s : StringUtil::splitToken(cookie_header_value, ";")) {
// Find the key part of the cookie (i.e. the name of the cookie).
size_t first_non_space = s.find_first_not_of(' ');
size_t equals_index = s.find('=');
if (equals_index == absl::string_view::npos) {
// The cookie is malformed if it does not have an `=`. Continue
// checking other cookies in this header.
continue;
}
absl::string_view k = s.substr(first_non_space, equals_index - first_non_space);
absl::string_view v = s.substr(equals_index + 1, s.size() - 1);

// Cookie values may be wrapped in double quotes.
// https://tools.ietf.org/html/rfc6265#section-4.1.1
if (v.size() >= 2 && v.back() == '"' && v[0] == '"') {
v = v.substr(1, v.size() - 2);
}
return v;

if (!cookie_consumer(k, v)) {
return;
}
}
}
return EMPTY_STRING;
}

std::string parseCookie(const HeaderMap& headers, const std::string& key,
const LowerCaseString& cookie) {
const Http::HeaderMap::GetResult cookie_headers = headers.get(cookie);
std::string value;

for (size_t index = 0; index < cookie_headers.size(); index++) {
auto cookie_header_value = cookie_headers[index]->value().getStringView();
absl::string_view result = parseCookie(cookie_header_value, key);
if (!result.empty()) {
return std::string{result};
// Iterate over each cookie & return if its value is not empty.
forEachCookie(headers, cookie, [&key, &value](absl::string_view k, absl::string_view v) -> bool {
if (key == k) {
value = std::string{v};
return false;
}
}

return EMPTY_STRING;
// continue iterating until a cookie that matches `key` is found.
return true;
});

return value;
}

absl::flat_hash_map<std::string, std::string>
Utility::parseCookies(const RequestHeaderMap& headers) {
return Utility::parseCookies(headers, [](absl::string_view) -> bool { return true; });
}

absl::flat_hash_map<std::string, std::string>
Utility::parseCookies(const RequestHeaderMap& headers,
const std::function<bool(absl::string_view)>& key_filter) {
absl::flat_hash_map<std::string, std::string> cookies;

forEachCookie(headers, Http::Headers::get().Cookie,
[&cookies, &key_filter](absl::string_view k, absl::string_view v) -> bool {
if (key_filter(k)) {
cookies.emplace(k, v);
}

// continue iterating until all cookies are processed.
return true;
});

return cookies;
}

bool Utility::Url::initialize(absl::string_view absolute_url, bool is_connect) {
Expand Down
17 changes: 17 additions & 0 deletions source/common/http/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,23 @@ std::string stripQueryString(const HeaderString& path);
**/
std::string parseCookieValue(const HeaderMap& headers, const std::string& key);

/**
* Parse cookies from header into a map.
* @param headers supplies the headers to get cookies from.
* @param key_filter predicate that returns true for every cookie key to be included.
* @return absl::flat_hash_map cookie map.
**/
absl::flat_hash_map<std::string, std::string>
parseCookies(const RequestHeaderMap& headers,
const std::function<bool(absl::string_view)>& key_filter);

/**
* Parse cookies from header into a map.
* @param headers supplies the headers to get cookies from.
* @return absl::flat_hash_map cookie map.
**/
absl::flat_hash_map<std::string, std::string> parseCookies(const RequestHeaderMap& headers);

/**
* Parse a particular value out of a set-cookie
* @param headers supplies the headers to get the set-cookie from.
Expand Down
16 changes: 13 additions & 3 deletions source/extensions/filters/http/oauth2/filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ std::string encodeResourceList(const Protobuf::RepeatedPtrField<std::string>& re
void setBearerToken(Http::RequestHeaderMap& headers, const std::string& token) {
headers.setInline(authorization_handle.handle(), absl::StrCat("Bearer ", token));
}

std::string findValue(const absl::flat_hash_map<std::string, std::string>& map,
const std::string& key) {
const auto value_it = map.find(key);
return value_it != map.end() ? value_it->second : EMPTY_STRING;
}
} // namespace

FilterConfig::FilterConfig(
Expand Down Expand Up @@ -138,9 +144,13 @@ FilterStats FilterConfig::generateStats(const std::string& prefix, Stats::Scope&

void OAuth2CookieValidator::setParams(const Http::RequestHeaderMap& headers,
const std::string& secret) {
expires_ = Http::Utility::parseCookieValue(headers, "OauthExpires");
token_ = Http::Utility::parseCookieValue(headers, "BearerToken");
hmac_ = Http::Utility::parseCookieValue(headers, "OauthHMAC");
const auto& cookies = Http::Utility::parseCookies(headers, [](absl::string_view key) -> bool {
return key == "OauthExpires" || key == "BearerToken" || key == "OauthHMAC";
});

expires_ = findValue(cookies, "OauthExpires");
token_ = findValue(cookies, "BearerToken");
hmac_ = findValue(cookies, "OauthHMAC");
host_ = headers.Host()->value().getStringView();

secret_.assign(secret.begin(), secret.end());
Expand Down
37 changes: 37 additions & 0 deletions test/common/http/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,16 @@ TEST(HttpUtility, TestParseCookie) {
EXPECT_EQ(value, "abc123");
}

TEST(HttpUtility, TestParseCookieDuplicates) {
TestRequestHeaderMapImpl headers{{"someheader", "10.0.0.1"},
{"cookie", "a=; b=1; a=2"},
{"cookie", "a=3; b=2"},
{"cookie", "b=3"}};

EXPECT_EQ(Utility::parseCookieValue(headers, "a"), "");
EXPECT_EQ(Utility::parseCookieValue(headers, "b"), "1");
}

TEST(HttpUtility, TestParseSetCookie) {
TestRequestHeaderMapImpl headers{
{"someheader", "10.0.0.1"},
Expand Down Expand Up @@ -598,6 +608,33 @@ TEST(HttpUtility, TestParseCookieWithQuotes) {
EXPECT_EQ(Utility::parseCookieValue(headers, "leadingdquote"), "\"foobar");
}

TEST(HttpUtility, TestParseCookies) {
TestRequestHeaderMapImpl headers{
{"someheader", "10.0.0.1"},
{"cookie", "dquote=\"; quoteddquote=\"\"\""},
{"cookie", "leadingdquote=\"foobar;"},
{"cookie", "abc=def; token=\"abc123\"; Expires=Wed, 09 Jun 2021 10:18:14 GMT"}};

const auto& cookies = Utility::parseCookies(headers);

EXPECT_EQ(cookies.at("token"), "abc123");
EXPECT_EQ(cookies.at("dquote"), "\"");
EXPECT_EQ(cookies.at("quoteddquote"), "\"");
EXPECT_EQ(cookies.at("leadingdquote"), "\"foobar");
}

TEST(HttpUtility, TestParseCookiesDuplicates) {
TestRequestHeaderMapImpl headers{{"someheader", "10.0.0.1"},
{"cookie", "a=; b=1; a=2"},
{"cookie", "a=3; b=2"},
{"cookie", "b=3"}};

const auto& cookies = Utility::parseCookies(headers);

EXPECT_EQ(cookies.at("a"), "");
EXPECT_EQ(cookies.at("b"), "1");
}

TEST(HttpUtility, TestParseSetCookieWithQuotes) {
TestRequestHeaderMapImpl headers{
{"someheader", "10.0.0.1"},
Expand Down