Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 438f655

Browse filesBrowse files
scotthartdevbww
authored andcommitted
impl: add HttpHeader class (googleapis#15099)
1 parent cc3d1ed commit 438f655
Copy full SHA for 438f655

File tree

Expand file treeCollapse file tree

6 files changed

+329
-0
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+329
-0
lines changed

‎google/cloud/google_cloud_cpp_rest_internal.bzl

Copy file name to clipboardExpand all lines: google/cloud/google_cloud_cpp_rest_internal.bzl
+2Lines 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

Copy file name to clipboardExpand all lines: google/cloud/google_cloud_cpp_rest_internal.cmake
+3Lines 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

Copy file name to clipboardExpand all lines: google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl
+1Lines 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",

‎google/cloud/internal/http_header.cc

Copy file name to clipboard
+88Lines 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

‎google/cloud/internal/http_header.h

Copy file name to clipboard
+96Lines 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
+139Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.