This is an automated email from the ASF dual-hosted git repository.
chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new cefc0913e Fix stale Set-Cookie pointer after HttpHeader mutations
(#3606)
cefc0913e is described below
commit cefc0913ed06ab58676dceb7c248bb155f7b7002
Author: nkysg <[email protected]>
AuthorDate: Sun Oct 25 20:30:04 2026 +0800
Fix stale Set-Cookie pointer after HttpHeader mutations (#3606)
* Fix stale Set-Cookie pointer after HttpHeader mutations
* Strengthen HttpHeader rehash regression test
* Fix Set-Cookie header cache invalidation
* Preserve AddHeader behavior for non-cookie headers
* Avoid repeated Set-Cookie header synchronization
* Add Set-Cookie collision regression test
* Simplify Set-Cookie header storage
---
src/brpc/details/http_message.cpp | 3 +
src/brpc/http_header.cpp | 106 +++++++++++++++++++++++++++---------
src/brpc/http_header.h | 27 +++++++--
test/brpc_http_message_unittest.cpp | 92 ++++++++++++++++++++++++++++++-
4 files changed, 195 insertions(+), 33 deletions(-)
diff --git a/src/brpc/details/http_message.cpp
b/src/brpc/details/http_message.cpp
index 14a81e553..e039719ce 100644
--- a/src/brpc/details/http_message.cpp
+++ b/src/brpc/details/http_message.cpp
@@ -186,6 +186,9 @@ int HttpMessage::on_headers_complete(http_parser *parser) {
parser->http_major = 1;
}
HttpHeader& headers = http_message->header();
+ if (headers.HasSetCookieHeaders()) {
+ headers.SyncSetCookieHeaders();
+ }
headers.set_version(parser->http_major, parser->http_minor);
// Only for response
// http_parser may set status_code to 0 when the field is not needed,
diff --git a/src/brpc/http_header.cpp b/src/brpc/http_header.cpp
index 6ef074712..1404940ec 100644
--- a/src/brpc/http_header.cpp
+++ b/src/brpc/http_header.cpp
@@ -30,8 +30,28 @@ HttpHeader::HttpHeader()
: _status_code(HTTP_STATUS_OK)
, _method(HTTP_METHOD_GET)
, _version(1, 1)
- , _first_set_cookie(nullptr) {
- // NOTE: don't forget to clear the field in Clear() as well.
+ , _set_cookie_headers_dirty(false) {}
+
+HttpHeader::HttpHeader(const HttpHeader& rhs)
+ : _headers(rhs._headers)
+ , _uri(rhs._uri)
+ , _status_code(rhs._status_code)
+ , _method(rhs._method)
+ , _content_type(rhs._content_type)
+ , _unresolved_path(rhs._unresolved_path)
+ , _version(rhs._version)
+ , _set_cookie_headers_dirty(rhs._set_cookie_headers_dirty) {
+ if (rhs._set_cookie_headers) {
+ _set_cookie_headers.reset(new
SetCookieHeaders(*rhs._set_cookie_headers));
+ }
+}
+
+HttpHeader& HttpHeader::operator=(const HttpHeader& rhs) {
+ if (this != &rhs) {
+ HttpHeader tmp(rhs);
+ Swap(tmp);
+ }
+ return *this;
}
void HttpHeader::Swap(HttpHeader &rhs) {
@@ -42,6 +62,8 @@ void HttpHeader::Swap(HttpHeader &rhs) {
_content_type.swap(rhs._content_type);
_unresolved_path.swap(rhs._unresolved_path);
std::swap(_version, rhs._version);
+ _set_cookie_headers.swap(rhs._set_cookie_headers);
+ std::swap(_set_cookie_headers_dirty, rhs._set_cookie_headers_dirty);
}
void HttpHeader::Clear() {
@@ -52,6 +74,8 @@ void HttpHeader::Clear() {
_content_type.clear();
_unresolved_path.clear();
_version = std::make_pair(1, 1);
+ _set_cookie_headers.reset();
+ _set_cookie_headers_dirty = false;
}
const std::string* HttpHeader::GetHeader(const char* key) const {
@@ -60,22 +84,19 @@ const std::string* HttpHeader::GetHeader(const char* key)
const {
const std::string* HttpHeader::GetHeader(const std::string& key) const {
if (IsSetCookie(key)) {
- return _first_set_cookie;
+ return HasSetCookieHeaders()
+ ? &_set_cookie_headers->front() : nullptr;
}
std::string* val = _headers.seek(key);
return val;
}
std::vector<const std::string*> HttpHeader::GetAllSetCookieHeader() const {
- return GetMultiLineHeaders(SET_COOKIE);
-}
-
-std::vector<const std::string*>
-HttpHeader::GetMultiLineHeaders(const std::string& key) const {
std::vector<const std::string*> headers;
- for (const auto& iter : _headers) {
- if (_header_key_equal(iter.first, key)) {
- headers.push_back(&iter.second);
+ if (_set_cookie_headers) {
+ headers.reserve(_set_cookie_headers->size());
+ for (const auto& header : *_set_cookie_headers) {
+ headers.push_back(&header);
}
}
return headers;
@@ -83,6 +104,19 @@ HttpHeader::GetMultiLineHeaders(const std::string& key)
const {
void HttpHeader::SetHeader(const std::string& key,
const std::string& value) {
+ if (IsSetCookie(key)) {
+ if (!HasSetCookieHeaders()) {
+ if (!_set_cookie_headers) {
+ _set_cookie_headers.reset(new SetCookieHeaders);
+ }
+ _set_cookie_headers->push_back(value);
+ } else {
+ _set_cookie_headers->front() = value;
+ }
+ _set_cookie_headers_dirty = true;
+ SyncSetCookieHeaders();
+ return;
+ }
GetOrAddHeader(key) = value;
}
@@ -92,7 +126,8 @@ void HttpHeader::RemoveHeader(const char* key) {
} else {
_headers.erase(key);
if (IsSetCookie(key)) {
- _first_set_cookie = nullptr;
+ _set_cookie_headers.reset();
+ _set_cookie_headers_dirty = false;
}
}
}
@@ -101,8 +136,14 @@ void HttpHeader::AppendHeader(const std::string& key,
const butil::StringPiece& value) {
if (!CanFoldedInLine(key)) {
// Add a new Set-Cookie header field.
- std::string& slot = AddHeader(key);
- slot.assign(value.data(), value.size());
+ if (!_set_cookie_headers) {
+ _set_cookie_headers.reset(new SetCookieHeaders);
+ }
+ if (_set_cookie_headers_dirty) {
+ SyncSetCookieHeaders();
+ }
+ _set_cookie_headers->push_back(std::string(value.data(),
value.size()));
+ _headers.insert({ SET_COOKIE, _set_cookie_headers->back() });
} else {
std::string& slot = GetOrAddHeader(key);
if (slot.empty()) {
@@ -128,28 +169,39 @@ std::string& HttpHeader::GetOrAddHeader(const
std::string& key) {
return _content_type;
}
- bool is_set_cookie = IsSetCookie(key);
- // Only returns the first Set-Cookie header field for compatibility.
- if (is_set_cookie && nullptr != _first_set_cookie) {
- return *_first_set_cookie;
- }
-
std::string* val = _headers.seek(key);
if (nullptr == val) {
val = _headers.insert({ key, "" });
- if (is_set_cookie) {
- _first_set_cookie = val;
- }
}
return *val;
}
std::string& HttpHeader::AddHeader(const std::string& key) {
- std::string* val = _headers.insert({ key, "" });
- if (IsSetCookie(key) && nullptr == _first_set_cookie) {
- _first_set_cookie = val;
+ if (!IsSetCookie(key)) {
+ return *_headers.insert({ key, "" });
}
- return *val;
+ if (!_set_cookie_headers) {
+ _set_cookie_headers.reset(new SetCookieHeaders);
+ }
+ if (!_set_cookie_headers_dirty) {
+ _headers.erase(SET_COOKIE);
+ _set_cookie_headers_dirty = true;
+ }
+ _set_cookie_headers->push_back(std::string());
+ return _set_cookie_headers->back();
+}
+
+void HttpHeader::SyncSetCookieHeaders() {
+ if (!_set_cookie_headers_dirty) {
+ return;
+ }
+ _headers.erase(SET_COOKIE);
+ if (_set_cookie_headers) {
+ for (const auto& header : *_set_cookie_headers) {
+ _headers.insert({ SET_COOKIE, header });
+ }
+ }
+ _set_cookie_headers_dirty = false;
}
const HttpHeader& DefaultHttpHeader() {
diff --git a/src/brpc/http_header.h b/src/brpc/http_header.h
index 58b92ecb1..cbaaf6415 100644
--- a/src/brpc/http_header.h
+++ b/src/brpc/http_header.h
@@ -19,6 +19,8 @@
#ifndef BRPC_HTTP_HEADER_H
#define BRPC_HTTP_HEADER_H
+#include <deque>
+#include <memory>
#include <vector>
#include "butil/strings/string_piece.h" // StringPiece
#include "butil/containers/case_ignored_flat_map.h"
@@ -45,6 +47,8 @@ public:
typedef HeaderMap::key_equal HeaderKeyEqual;
HttpHeader();
+ HttpHeader(const HttpHeader& rhs);
+ HttpHeader& operator=(const HttpHeader& rhs);
// Exchange internal fields with another HttpHeader.
void Swap(HttpHeader &rhs);
@@ -120,7 +124,12 @@ public:
HeaderIterator HeaderBegin() const { return _headers.begin(); }
HeaderIterator HeaderEnd() const { return _headers.end(); }
// #headers
- size_t HeaderCount() const { return _headers.size(); }
+ size_t HeaderCount() const {
+ if (_set_cookie_headers_dirty) {
+ return _headers.size() + _set_cookie_headers->size();
+ }
+ return _headers.size();
+ }
// Get the URI object, check src/brpc/uri.h for details.
const URI& uri() const { return _uri; }
@@ -165,16 +174,20 @@ friend void policy::ProcessHttpRequest(InputMessageBase
*msg);
static const char* COOKIE;
static const char* CONTENT_TYPE;
- std::vector<const std::string*> GetMultiLineHeaders(const std::string&
key) const;
-
std::string& GetOrAddHeader(const std::string& key);
std::string& AddHeader(const std::string& key);
+ void SyncSetCookieHeaders();
+
bool IsSetCookie(const std::string& key) const {
return _header_key_equal(key, SET_COOKIE);
}
+ bool HasSetCookieHeaders() const {
+ return _set_cookie_headers && !_set_cookie_headers->empty();
+ }
+
bool IsCookie(const std::string& key) const {
return _header_key_equal(key, COOKIE);
}
@@ -202,7 +215,13 @@ friend void policy::ProcessHttpRequest(InputMessageBase
*msg);
std::string _content_type;
std::string _unresolved_path;
std::pair<int, int> _version;
- std::string* _first_set_cookie;
+ // HeaderMap rehashing invalidates value pointers and does not preserve
+ // duplicate-key order. deque keeps existing value pointers stable when
+ // another Set-Cookie header is appended.
+ typedef std::deque<std::string> SetCookieHeaders;
+ std::unique_ptr<SetCookieHeaders> _set_cookie_headers;
+ // When true, Set-Cookie headers are only stored in _set_cookie_headers.
+ bool _set_cookie_headers_dirty;
};
const HttpHeader& DefaultHttpHeader();
diff --git a/test/brpc_http_message_unittest.cpp
b/test/brpc_http_message_unittest.cpp
index 078659bef..916a6e482 100644
--- a/test/brpc_http_message_unittest.cpp
+++ b/test/brpc_http_message_unittest.cpp
@@ -341,6 +341,25 @@ TEST(HttpMessageTest, parse_http_set_cookie) {
}
ASSERT_EQ(1, set_cookie_value1_count);
ASSERT_EQ(1, set_cookie_value2_count);
+
+ const char first_part[] = "HTTP/1.1 200 OK\r\nSet-Cookie: a";
+ const char second_part[] = "=1\r\nSet-Cookie: b=2\r\n\r\n";
+ brpc::HttpMessage fragmented_message(false, brpc::HTTP_METHOD_HEAD);
+ ASSERT_EQ((ssize_t)sizeof(first_part) - 1,
+ fragmented_message.ParseFromArray(first_part,
+ sizeof(first_part) - 1));
+ ASSERT_FALSE(fragmented_message.Completed());
+ ASSERT_EQ((ssize_t)sizeof(second_part) - 1,
+ fragmented_message.ParseFromArray(second_part,
+ sizeof(second_part) - 1));
+ ASSERT_TRUE(fragmented_message.Completed());
+ set_cookie = fragmented_message.header().GetHeader("set-cookie");
+ ASSERT_NE(nullptr, set_cookie);
+ ASSERT_EQ("a=1", *set_cookie);
+ all_set_cookie = fragmented_message.header().GetAllSetCookieHeader();
+ ASSERT_EQ(2u, all_set_cookie.size());
+ ASSERT_EQ("a=1", *all_set_cookie[0]);
+ ASSERT_EQ("b=2", *all_set_cookie[1]);
}
TEST(HttpMessageTest, cl_and_te) {
@@ -852,12 +871,28 @@ TEST(HttpMessageTest, http_header) {
header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value1);
value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
ASSERT_TRUE(value && *value == set_cookie_value1);
+ const std::string* first_set_cookie = value;
header.AppendHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value2);
+ ASSERT_EQ(set_cookie_value1, *first_set_cookie);
value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
ASSERT_TRUE(value && *value == set_cookie_value1);
header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value3);
value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
ASSERT_TRUE(value && *value == set_cookie_value3);
+
+ // set-cookie and user-agent collide in the default HeaderMap bucket
+ // layout. Updating Set-Cookie must not corrupt the surviving header.
+ brpc::HttpHeader collision_header;
+ collision_header.SetHeader(brpc::HttpHeader::SET_COOKIE,
+ set_cookie_value1);
+ collision_header.SetHeader("user-agent", "brpc");
+ collision_header.SetHeader(brpc::HttpHeader::SET_COOKIE,
+ set_cookie_value2);
+ value = collision_header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value2);
+ value = collision_header.GetHeader("user-agent");
+ ASSERT_TRUE(value && *value == "brpc");
+
std::vector<const std::string*> all_set_cookie
= header.GetAllSetCookieHeader();
ASSERT_EQ(2u, all_set_cookie.size());
@@ -868,7 +903,7 @@ TEST(HttpMessageTest, http_header) {
int set_cookie_value2_count = 0;
int set_cookie_value3_count = 0;
for (auto iter = header.HeaderBegin(); iter != header.HeaderEnd(); ++iter)
{
- if (!header.IsSetCookie(brpc::HttpHeader::SET_COOKIE)) {
+ if (!header.IsSetCookie(iter->first)) {
continue;
}
if (iter->second == set_cookie_value2) {
@@ -879,9 +914,62 @@ TEST(HttpMessageTest, http_header) {
}
ASSERT_EQ(1, set_cookie_value2_count);
ASSERT_EQ(1, set_cookie_value3_count);
+ brpc::HttpHeader copied_header(header);
+ copied_header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value1);
+ value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value3);
+ value = copied_header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value1);
+ brpc::HttpHeader assigned_header;
+ assigned_header = header;
+ assigned_header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value2);
+ value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value3);
+ value = assigned_header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value2);
header.RemoveHeader(brpc::HttpHeader::SET_COOKIE);
ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::SET_COOKIE));
- ASSERT_EQ(header._first_set_cookie, nullptr);
+ ASSERT_TRUE(header.GetAllSetCookieHeader().empty());
+
+ header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value1);
+ const std::string* first_set_cookie_after_reset =
+ header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_NE(nullptr, first_set_cookie_after_reset);
+ header.AppendHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value2);
+ const size_t bucket_count = header._headers.bucket_count();
+ ASSERT_TRUE(header._headers.resize(bucket_count * 2));
+ ASSERT_NE(bucket_count, header._headers.bucket_count());
+ ASSERT_EQ(set_cookie_value1, *first_set_cookie_after_reset);
+ value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value1);
+ all_set_cookie = header.GetAllSetCookieHeader();
+ ASSERT_EQ(2u, all_set_cookie.size());
+ ASSERT_EQ(set_cookie_value1, *all_set_cookie[0]);
+ ASSERT_EQ(set_cookie_value2, *all_set_cookie[1]);
+ int set_cookie_value1_after_rehash_count = 0;
+ int set_cookie_value2_after_rehash_count = 0;
+ for (auto iter = header.HeaderBegin(); iter != header.HeaderEnd(); ++iter)
{
+ if (!header.IsSetCookie(iter->first)) {
+ continue;
+ }
+ if (iter->second == set_cookie_value1) {
+ ++set_cookie_value1_after_rehash_count;
+ } else if (iter->second == set_cookie_value2) {
+ ++set_cookie_value2_after_rehash_count;
+ }
+ }
+ ASSERT_EQ(1, set_cookie_value1_after_rehash_count);
+ ASSERT_EQ(1, set_cookie_value2_after_rehash_count);
+
+ brpc::HttpHeader other_header;
+ other_header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value2);
+ header.Swap(other_header);
+ value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value2);
+ value = other_header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value1);
+ header.Clear();
+ ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::SET_COOKIE));
ASSERT_EQ(brpc::HTTP_METHOD_GET, header.method());
header.set_method(brpc::HTTP_METHOD_POST);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]