Skip to content

Commit e6e6128

Browse files
authored
impl: add HttpHeader class (#15099)
1 parent 94afe17 commit e6e6128

File tree

6 files changed

+329
-0
lines changed

6 files changed

+329
-0
lines changed

google/cloud/google_cloud_cpp_rest_internal.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ google_cloud_cpp_rest_internal_hdrs = [
3131
"internal/external_account_token_source_aws.h",
3232
"internal/external_account_token_source_file.h",
3333
"internal/external_account_token_source_url.h",
34+
"internal/http_header.h",
3435
"internal/http_payload.h",
3536
"internal/json_parsing.h",
3637
"internal/make_jwt_assertion.h",
@@ -91,6 +92,7 @@ google_cloud_cpp_rest_internal_srcs = [
9192
"internal/external_account_token_source_aws.cc",
9293
"internal/external_account_token_source_file.cc",
9394
"internal/external_account_token_source_url.cc",
95+
"internal/http_header.cc",
9496
"internal/json_parsing.cc",
9597
"internal/make_jwt_assertion.cc",
9698
"internal/oauth2_access_token_credentials.cc",

google/cloud/google_cloud_cpp_rest_internal.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ add_library(
5050
internal/external_account_token_source_file.h
5151
internal/external_account_token_source_url.cc
5252
internal/external_account_token_source_url.h
53+
internal/http_header.cc
54+
internal/http_header.h
5355
internal/http_payload.h
5456
internal/json_parsing.cc
5557
internal/json_parsing.h
@@ -255,6 +257,7 @@ if (BUILD_TESTING)
255257
internal/external_account_token_source_aws_test.cc
256258
internal/external_account_token_source_file_test.cc
257259
internal/external_account_token_source_url_test.cc
260+
internal/http_header_test.cc
258261
internal/json_parsing_test.cc
259262
internal/make_jwt_assertion_test.cc
260263
internal/oauth2_access_token_credentials_test.cc

google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ google_cloud_cpp_rest_internal_unit_tests = [
3434
"internal/external_account_token_source_aws_test.cc",
3535
"internal/external_account_token_source_file_test.cc",
3636
"internal/external_account_token_source_url_test.cc",
37+
"internal/http_header_test.cc",
3738
"internal/json_parsing_test.cc",
3839
"internal/make_jwt_assertion_test.cc",
3940
"internal/oauth2_access_token_credentials_test.cc",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/http_header.h"
16+
#include "google/cloud/internal/absl_str_cat_quiet.h"
17+
#include "google/cloud/internal/absl_str_join_quiet.h"
18+
#include "absl/strings/strip.h"
19+
20+
namespace google {
21+
namespace cloud {
22+
namespace rest_internal {
23+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
24+
25+
HttpHeader::HttpHeader(std::string key) : key_(std::move(key)) {}
26+
27+
HttpHeader::HttpHeader(std::string key, std::string value)
28+
: key_(std::move(key)), values_({std::move(value)}) {}
29+
30+
HttpHeader::HttpHeader(std::string key, std::vector<std::string> values)
31+
: key_(std::move(key)), values_(std::move(values)) {}
32+
33+
HttpHeader::HttpHeader(std::string key,
34+
std::initializer_list<char const*> values)
35+
: key_(std::move(key)) {
36+
for (auto&& v : values) values_.emplace_back(v);
37+
}
38+
39+
bool operator==(HttpHeader const& lhs, HttpHeader const& rhs) {
40+
return absl::AsciiStrToLower(lhs.key_) == absl::AsciiStrToLower(rhs.key_) &&
41+
lhs.values_ == rhs.values_;
42+
}
43+
44+
bool operator<(HttpHeader const& lhs, HttpHeader const& rhs) {
45+
return absl::AsciiStrToLower(lhs.key_) < absl::AsciiStrToLower(rhs.key_);
46+
}
47+
48+
bool HttpHeader::IsSameKey(std::string const& key) const {
49+
return absl::AsciiStrToLower(key_) == absl::AsciiStrToLower(key);
50+
}
51+
52+
bool HttpHeader::IsSameKey(HttpHeader const& other) const {
53+
return IsSameKey(other.key_);
54+
}
55+
56+
HttpHeader::operator std::string() const {
57+
if (key_.empty()) return {};
58+
if (values_.empty()) return absl::StrCat(key_, ":");
59+
return absl::StrCat(key_, ": ", absl::StrJoin(values_, ","));
60+
}
61+
62+
std::string HttpHeader::DebugString() const {
63+
if (key_.empty()) return {};
64+
if (values_.empty()) return absl::StrCat(key_, ":");
65+
return absl::StrCat(
66+
key_, ": ",
67+
absl::StrJoin(values_, ",", [](std::string* out, std::string const& v) {
68+
absl::StrAppend(out, v.substr(0, 10));
69+
}));
70+
}
71+
72+
HttpHeader& HttpHeader::MergeHeader(HttpHeader const& other) {
73+
if (!IsSameKey(other)) return *this;
74+
values_.insert(values_.end(), other.values_.begin(), other.values_.end());
75+
return *this;
76+
}
77+
78+
HttpHeader& HttpHeader::MergeHeader(HttpHeader&& other) {
79+
if (!IsSameKey(other)) return *this;
80+
values_.insert(values_.end(), std::make_move_iterator(other.values_.begin()),
81+
std::make_move_iterator(other.values_.end()));
82+
return *this;
83+
}
84+
85+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
86+
} // namespace rest_internal
87+
} // namespace cloud
88+
} // namespace google
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H
17+
18+
#include "google/cloud/version.h"
19+
#include <string>
20+
#include <vector>
21+
22+
namespace google {
23+
namespace cloud {
24+
namespace rest_internal {
25+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
26+
27+
/**
28+
* This class represents an HTTP header field.
29+
*/
30+
class HttpHeader {
31+
public:
32+
HttpHeader() = default;
33+
explicit HttpHeader(std::string key);
34+
HttpHeader(std::string key, std::string value);
35+
HttpHeader(std::string key, std::initializer_list<char const*> values);
36+
37+
HttpHeader(std::string key, std::vector<std::string> values);
38+
39+
HttpHeader(HttpHeader&&) = default;
40+
HttpHeader& operator=(HttpHeader&&) = default;
41+
HttpHeader(HttpHeader const&) = default;
42+
HttpHeader& operator=(HttpHeader const&) = default;
43+
44+
// Equality is determined by a case-insensitive comparison of the key and
45+
// a case-sensitive comparison of the values. Ordering of the values is
46+
// significant and two HttpHeaders of the same key must have the same ordering
47+
// of the same values in order to be considered equal.
48+
//
49+
// HTTP/1.1 https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2
50+
friend bool operator==(HttpHeader const& lhs, HttpHeader const& rhs);
51+
friend bool operator!=(HttpHeader const& lhs, HttpHeader const& rhs) {
52+
return !(lhs == rhs);
53+
}
54+
55+
// Lower case lexicographic comparison of keys without inspecting the values.
56+
// This class provides operator< only for sorting purposes.
57+
friend bool operator<(HttpHeader const& lhs, HttpHeader const& rhs);
58+
59+
// If the key is empty, the entire HttpHeader is considered empty.
60+
bool empty() const { return key_.empty(); }
61+
62+
// Checks to see if the values are empty. Does not inspect the key field.
63+
bool EmptyValues() const { return values_.empty(); }
64+
65+
// Performs a case-insensitive comparison of the key.
66+
bool IsSameKey(HttpHeader const& other) const;
67+
bool IsSameKey(std::string const& key) const;
68+
69+
// While the RFCs indicate that header keys are case-insensitive, no attempt
70+
// to convert them to all lowercase is made. Header keys are printed in the
71+
// case they were constructed with. We rely on libcurl to encode them per the
72+
// HTTP version used.
73+
//
74+
// HTTP/1.1 https://www.rfc-editor.org/rfc/rfc7230#section-3.2
75+
// HTTP/2 https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2
76+
explicit operator std::string() const;
77+
78+
// Formats header as a string, but truncates the value if it is too long, or
79+
// if it could contain a secret.
80+
std::string DebugString() const;
81+
82+
// Merges the values from other into this if the keys are the same.
83+
HttpHeader& MergeHeader(HttpHeader const& other);
84+
HttpHeader& MergeHeader(HttpHeader&& other);
85+
86+
private:
87+
std::string key_;
88+
std::vector<std::string> values_;
89+
};
90+
91+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
92+
} // namespace rest_internal
93+
} // namespace cloud
94+
} // namespace google
95+
96+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/http_header.h"
16+
#include <gmock/gmock.h>
17+
18+
namespace google {
19+
namespace cloud {
20+
namespace rest_internal {
21+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
22+
namespace {
23+
24+
using ::testing::Eq;
25+
26+
TEST(HttpHeader, ConstructionAndStringFormatting) {
27+
HttpHeader empty;
28+
EXPECT_THAT(std::string(empty), Eq(""));
29+
30+
HttpHeader no_value("key");
31+
EXPECT_THAT(std::string(no_value), Eq("key:"));
32+
33+
HttpHeader single_value("key", "value");
34+
EXPECT_THAT(std::string(single_value), Eq("key: value"));
35+
36+
HttpHeader multi_value("key", std::vector<std::string>{"value1", "value2"});
37+
EXPECT_THAT(std::string(multi_value), Eq("key: value1,value2"));
38+
HttpHeader multi_literal_value("key", {"value1", "value2"});
39+
EXPECT_THAT(std::string(multi_literal_value), Eq("key: value1,value2"));
40+
}
41+
42+
TEST(HttpHeader, Equality) {
43+
HttpHeader empty;
44+
// Key field tests
45+
EXPECT_TRUE(empty == empty);
46+
EXPECT_FALSE(empty != empty);
47+
EXPECT_FALSE(empty == HttpHeader("key"));
48+
EXPECT_FALSE(HttpHeader("key") == empty);
49+
EXPECT_TRUE(HttpHeader("Key") == HttpHeader("key"));
50+
EXPECT_TRUE(HttpHeader("key") == HttpHeader("Key"));
51+
EXPECT_TRUE(HttpHeader("Content-Length") == HttpHeader("content-length"));
52+
53+
// Values field tests
54+
EXPECT_TRUE(HttpHeader("key", "value") == HttpHeader("key", "value"));
55+
EXPECT_FALSE(HttpHeader("key", "value") == HttpHeader("key", "Value"));
56+
EXPECT_FALSE(HttpHeader("key", {"v1", "v2"}) == HttpHeader("Key", "v1"));
57+
EXPECT_FALSE(HttpHeader("key", {"v1", "v2"}) == HttpHeader("Key", {"v1"}));
58+
EXPECT_FALSE(HttpHeader("key", {"v1", "v2"}) ==
59+
HttpHeader("Key", {"v1", "V2"}));
60+
EXPECT_FALSE(HttpHeader("key", {"V1", "v2"}) ==
61+
HttpHeader("Key", {"v1", "V2"}));
62+
EXPECT_TRUE(HttpHeader("key", {"v1", "v2"}) ==
63+
HttpHeader("Key", {"v1", "v2"}));
64+
EXPECT_FALSE(HttpHeader("key", {"v1", "v2"}) ==
65+
HttpHeader("Key", {"v2", "v1"}));
66+
}
67+
68+
TEST(HttpHeader, LessThan) {
69+
EXPECT_TRUE(HttpHeader("hey") < HttpHeader("key"));
70+
EXPECT_FALSE(HttpHeader("key") < HttpHeader("key"));
71+
EXPECT_FALSE(HttpHeader("key") < HttpHeader("hey"));
72+
EXPECT_TRUE(HttpHeader("Hey") < HttpHeader("key"));
73+
EXPECT_FALSE(HttpHeader("key") < HttpHeader("Key"));
74+
EXPECT_FALSE(HttpHeader("key") < HttpHeader("Hey"));
75+
}
76+
77+
TEST(HttpHeader, IsSameKey) {
78+
EXPECT_TRUE(HttpHeader("key").IsSameKey("key"));
79+
EXPECT_TRUE(HttpHeader("Key").IsSameKey("key"));
80+
EXPECT_TRUE(HttpHeader("Key").IsSameKey("Key"));
81+
EXPECT_FALSE(HttpHeader("Key").IsSameKey("ey"));
82+
83+
EXPECT_TRUE(HttpHeader("key").IsSameKey(HttpHeader("key")));
84+
EXPECT_TRUE(HttpHeader("Key").IsSameKey(HttpHeader("key")));
85+
EXPECT_TRUE(HttpHeader("Key").IsSameKey(HttpHeader("Key")));
86+
EXPECT_FALSE(HttpHeader("Key").IsSameKey(HttpHeader("ey")));
87+
}
88+
89+
TEST(HttpHeader, DebugString) {
90+
HttpHeader empty;
91+
EXPECT_THAT(empty.DebugString(), Eq(""));
92+
93+
HttpHeader no_value("key");
94+
EXPECT_THAT(no_value.DebugString(), Eq("key:"));
95+
96+
HttpHeader short_value("key", "short");
97+
EXPECT_THAT(short_value.DebugString(), Eq("key: short"));
98+
99+
HttpHeader long_value("key", "valuelongerthantruncatelength");
100+
EXPECT_THAT(long_value.DebugString(), Eq("key: valuelonge"));
101+
}
102+
103+
TEST(HttpHeader, MergeHeader) {
104+
HttpHeader k1_v1("k1", "k1-value1");
105+
HttpHeader k2_v1("k2", "k2-value1");
106+
EXPECT_THAT(k1_v1.MergeHeader(k2_v1), Eq(HttpHeader("k1", "k1-value1")));
107+
EXPECT_THAT(k1_v1.MergeHeader(std::move(k2_v1)),
108+
Eq(HttpHeader("k1", "k1-value1")));
109+
110+
HttpHeader k1_v2("k1", "k1-value2");
111+
EXPECT_THAT(k1_v1.MergeHeader(k1_v2),
112+
Eq(HttpHeader("k1", {"k1-value1", "k1-value2"})));
113+
EXPECT_THAT(k1_v2, Eq(HttpHeader("k1", "k1-value2")));
114+
k1_v1 = HttpHeader("k1", "k1-value1");
115+
EXPECT_THAT(k1_v1.MergeHeader(std::move(k1_v2)),
116+
Eq(HttpHeader("k1", {"k1-value1", "k1-value2"})));
117+
118+
HttpHeader k1_v3("k1", "k1-value3");
119+
k1_v1 = HttpHeader("k1", {"k1-value1"});
120+
EXPECT_THAT(k1_v3.MergeHeader(k1_v1),
121+
Eq(HttpHeader("k1", {"k1-value3", "k1-value1"})));
122+
k1_v3 = HttpHeader("k1", "k1-value3");
123+
EXPECT_THAT(k1_v3.MergeHeader(std::move(k1_v1)),
124+
Eq(HttpHeader("k1", {"k1-value3", "k1-value1"})));
125+
126+
HttpHeader k3_v1("k3", "k3-value10");
127+
HttpHeader k3_v5("k3", "k3-value5");
128+
EXPECT_THAT(k3_v1.MergeHeader(HttpHeader("k3", {"k3-value2", "k3-value3"}))
129+
.MergeHeader(k3_v5)
130+
.MergeHeader((std::move(k3_v5))),
131+
Eq(HttpHeader("k3", {"k3-value10", "k3-value2", "k3-value3",
132+
"k3-value5", "k3-value5"})));
133+
}
134+
135+
} // namespace
136+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
137+
} // namespace rest_internal
138+
} // namespace cloud
139+
} // namespace google

0 commit comments

Comments
 (0)