Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ TEST(ThingAdminRestMetadataDecoratorTest, DropDatabaseExplicitRoutingMatch) {
EXPECT_THAT(context.GetHeader("x-goog-quota-user"), IsEmpty());
EXPECT_THAT(context.GetHeader("x-server-timeout"), IsEmpty());
EXPECT_THAT(
context.GetHeader("x-goog-request-params")[0],
context.GetHeader("x-goog-request-params").values().front(),
AllOf(
HasSubstr(std::string("project=projects%2Fmy_project")),
HasSubstr(std::string("instance=instances%2Fmy_instance")),
Expand Down
1 change: 1 addition & 0 deletions google/cloud/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ cc_library(
visibility = ["//:__subpackages__"],
deps = [
":google_cloud_cpp_common",
"@abseil-cpp//absl/container:flat_hash_map",
"@abseil-cpp//absl/functional:function_ref",
"@abseil-cpp//absl/types:span",
"@curl",
Expand Down
9 changes: 5 additions & 4 deletions google/cloud/bigquery/v2/minimal/internal/log_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ Result LogWrapper(Functor&& functor, rest_internal::RestContext& context,
TracingOptions const& options) {
auto formatter = [options](std::string* out, auto const& header) {
auto const* delim = options.single_line_mode() ? "&" : "\n";
absl::StrAppend(
out, " { name: \"", header.first, "\" value: \"",
internal::DebugString(absl::StrJoin(header.second, delim), options),
"\" }");
absl::StrAppend(out, " { name: \"", std::string_view{header.first},
"\" value: \"",
internal::DebugString(
absl::StrJoin(header.second.values(), delim), options),
"\" }");
};
GCP_LOG(DEBUG) << where << "() << "
<< request.DebugString(request_name, options) << ", Context {"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ StatusOr<rest_internal::RestRequest> PrepareRestRequest(
if (!rest_context.headers().empty()) {
for (auto const& h : rest_context.headers()) {
if (!h.second.empty()) {
rest_request->AddHeader(h.first, absl::StrJoin(h.second, "&"));
rest_request->AddHeader(h.first, absl::StrJoin(h.second.values(), "&"));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ static auto const kUserProject = "test-only-project";
static auto const kQuotaUser = "test-quota-user";

void VerifyMetadataContext(rest_internal::RestContext& context) {
EXPECT_THAT(context.GetHeader("x-goog-api-client"),
EXPECT_THAT(context.GetHeader("x-goog-api-client").values(),
ElementsAre(internal::HandCraftedLibClientHeader()));
EXPECT_THAT(context.GetHeader("x-goog-request-params"), IsEmpty());
EXPECT_THAT(context.GetHeader("x-goog-user-project"),
EXPECT_THAT(context.GetHeader("x-goog-request-params").values(), IsEmpty());
EXPECT_THAT(context.GetHeader("x-goog-user-project").values(),
ElementsAre(kUserProject));
EXPECT_THAT(context.GetHeader("x-goog-quota-user"), ElementsAre(kQuotaUser));
EXPECT_THAT(context.GetHeader("x-server-timeout"), ElementsAre("3.141"));
EXPECT_THAT(context.GetHeader("x-goog-quota-user").values(),
ElementsAre(kQuotaUser));
EXPECT_THAT(context.GetHeader("x-server-timeout").values(),
ElementsAre("3.141"));
}

Options GetMetadataOptions() {
Expand Down
3 changes: 2 additions & 1 deletion google/cloud/google_cloud_cpp_rest_internal.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ add_library(
target_link_libraries(
google_cloud_cpp_rest_internal
PUBLIC absl::span google-cloud-cpp::common CURL::libcurl
nlohmann_json::nlohmann_json)
absl::flat_hash_map nlohmann_json::nlohmann_json)
if (WIN32)
target_compile_definitions(google_cloud_cpp_rest_internal
PRIVATE WIN32_LEAN_AND_MEAN)
Expand Down Expand Up @@ -197,6 +197,7 @@ google_cloud_cpp_add_pkgconfig(
"Provides REST Transport for the Google Cloud C++ Client Library."
"google_cloud_cpp_common"
"libcurl"
"absl_flat_hash_map"
NON_WIN32_REQUIRES
openssl
WIN32_LIBS
Expand Down
5 changes: 2 additions & 3 deletions google/cloud/internal/curl_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,9 @@ void CurlImpl::MergeAndWriteHeaders(
}
}

void CurlImpl::SetHeaders(
std::unordered_map<std::string, std::vector<std::string>> const& headers) {
void CurlImpl::SetHeaders(HttpHeaders const& headers) {
for (auto const& header : headers) {
SetHeader(HttpHeader(header.first, header.second));
SetHeader(header.second);
}
}

Expand Down
4 changes: 1 addition & 3 deletions google/cloud/internal/curl_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace google {
Expand Down Expand Up @@ -84,8 +83,7 @@ class CurlImpl {
CurlImpl& operator=(CurlImpl&&) = default;

void SetHeader(HttpHeader header);
void SetHeaders(
std::unordered_map<std::string, std::vector<std::string>> const& headers);
void SetHeaders(HttpHeaders const& headers);

std::string MakeEscapedString(std::string const& s);

Expand Down
10 changes: 6 additions & 4 deletions google/cloud/internal/curl_rest_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,12 @@ StatusOr<std::unique_ptr<CurlImpl>> CurlRestClient::CreateCurlImpl(
auto impl =
std::make_unique<CurlImpl>(std::move(handle), handle_factory_, options);
if (credentials_) {
auto auth_header =
credentials_->AuthenticationHeader(std::chrono::system_clock::now());
if (!auth_header.ok()) return std::move(auth_header).status();
impl->SetHeader(HttpHeader(auth_header->first, auth_header->second));
auto auth_headers =
credentials_->AuthenticationHeaders(std::chrono::system_clock::now());
if (!auth_headers.ok()) return std::move(auth_headers).status();
for (auto& header : *auth_headers) {
impl->SetHeader(std::move(header));
}
}
impl->SetHeader(HostHeader(options, endpoint_address_));
impl->SetHeaders(context.headers());
Expand Down
18 changes: 9 additions & 9 deletions google/cloud/internal/curl_rest_client_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@ TEST(CurlRestClientStandaloneFunctions, HostHeader) {
std::string expected;
} cases[] = {
{"https://storage.googleapis.com", "storage.googleapis.com",
"Host: storage.googleapis.com"},
{"https://storage.googleapis.com", "", "Host: storage.googleapis.com"},
{"https://storage.googleapis.com", "auth", "Host: auth"},
"host: storage.googleapis.com"},
{"https://storage.googleapis.com", "", "host: storage.googleapis.com"},
{"https://storage.googleapis.com", "auth", "host: auth"},
{"https://storage.googleapis.com:443", "storage.googleapis.com",
"Host: storage.googleapis.com"},
"host: storage.googleapis.com"},
{"https://restricted.googleapis.com", "storage.googleapis.com",
"Host: storage.googleapis.com"},
"host: storage.googleapis.com"},
{"https://private.googleapis.com", "storage.googleapis.com",
"Host: storage.googleapis.com"},
"host: storage.googleapis.com"},
{"https://restricted.googleapis.com", "iamcredentials.googleapis.com",
"Host: iamcredentials.googleapis.com"},
"host: iamcredentials.googleapis.com"},
{"https://private.googleapis.com", "iamcredentials.googleapis.com",
"Host: iamcredentials.googleapis.com"},
"host: iamcredentials.googleapis.com"},
{"http://localhost:8080", "", ""},
{"http://localhost:8080", "auth", "Host: auth"},
{"http://localhost:8080", "auth", "host: auth"},
{"http://[::1]", "", ""},
{"http://[::1]/", "", ""},
{"http://[::1]/foo/bar", "", ""},
Expand Down
40 changes: 21 additions & 19 deletions google/cloud/internal/http_header.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,50 @@ namespace cloud {
namespace rest_internal {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

HttpHeader::HttpHeader(std::string key) : key_(std::move(key)) {}
HttpHeader::HttpHeader(HttpHeaderName key) : name_(std::move(key)) {}

HttpHeader::HttpHeader(std::string key, std::string value)
: key_(std::move(key)), values_({std::move(value)}) {}
HttpHeader::HttpHeader(std::pair<std::string, std::string> header)
: HttpHeader(std::move(header.first), std::move(header.second)) {}

HttpHeader::HttpHeader(std::string key, std::vector<std::string> values)
: key_(std::move(key)), values_(std::move(values)) {}
HttpHeader::HttpHeader(HttpHeaderName key, std::string value)
: name_(std::move(key)), values_({std::move(value)}) {}

HttpHeader::HttpHeader(std::string key,
HttpHeader::HttpHeader(HttpHeaderName key, std::vector<std::string> values)
: name_(std::move(key)), values_(std::move(values)) {}

HttpHeader::HttpHeader(HttpHeaderName key,
std::initializer_list<char const*> values)
: key_(std::move(key)) {
: name_(std::move(key)) {
for (auto&& v : values) values_.emplace_back(v);
}

bool operator==(HttpHeader const& lhs, HttpHeader const& rhs) {
return absl::AsciiStrToLower(lhs.key_) == absl::AsciiStrToLower(rhs.key_) &&
lhs.values_ == rhs.values_;
return lhs.name_ == rhs.name_ && lhs.values_ == rhs.values_;
}

bool operator<(HttpHeader const& lhs, HttpHeader const& rhs) {
return absl::AsciiStrToLower(lhs.key_) < absl::AsciiStrToLower(rhs.key_);
return lhs.name_ < rhs.name_;
}

bool HttpHeader::IsSameKey(std::string const& key) const {
return absl::AsciiStrToLower(key_) == absl::AsciiStrToLower(key);
bool HttpHeader::IsSameKey(HttpHeaderName const& name) const {
return name_ == name.name();
}

bool HttpHeader::IsSameKey(HttpHeader const& other) const {
return IsSameKey(other.key_);
return name_ == other.name_;
}

HttpHeader::operator std::string() const {
if (key_.empty()) return {};
if (values_.empty()) return absl::StrCat(key_, ":");
return absl::StrCat(key_, ": ", absl::StrJoin(values_, ","));
if (name_.empty()) return {};
if (values_.empty()) return absl::StrCat(name_.name(), ":");
return absl::StrCat(name_.name(), ": ", absl::StrJoin(values_, ","));
}

std::string HttpHeader::DebugString() const {
if (key_.empty()) return {};
if (values_.empty()) return absl::StrCat(key_, ":");
if (name_.empty()) return {};
if (values_.empty()) return absl::StrCat(name_.name(), ":");
return absl::StrCat(
key_, ": ",
name_.name(), ": ",
absl::StrJoin(values_, ",", [](std::string* out, std::string const& v) {
absl::StrAppend(out, v.substr(0, 10));
}));
Expand Down
115 changes: 103 additions & 12 deletions google/cloud/internal/http_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H

#include "google/cloud/version.h"
#if UINTPTR_MAX == UINT64_MAX
#include "absl/container/flat_hash_map.h"
#else
#include <unordered_map>
#endif
#include "absl/strings/ascii.h"
#include <cstdint>
#include <string>
#include <vector>

Expand All @@ -24,17 +31,67 @@ namespace cloud {
namespace rest_internal {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

// This class represents a case-insensitive HTTP header name by storing all
// strings in lower-case.
class HttpHeaderName {
public:
HttpHeaderName() = default;
HttpHeaderName(std::string name) // NOLINT(google-explicit-constructor)
: name_(std::move(name)) {
absl::AsciiStrToLower(&name_);
}
HttpHeaderName(std::string_view name) // NOLINT(google-explicit-constructor)
: HttpHeaderName(std::string{name}) {}
HttpHeaderName(char const* name) // NOLINT(google-explicit-constructor)
: HttpHeaderName(std::string{name}) {}

operator std::string() const { // NOLINT(google-explicit-constructor)
return name_;
}
operator std::string_view() const { // NOLINT(google-explicit-constructor)
return name_;
}
operator char const*() const { // NOLINT(google-explicit-constructor)
return name_.c_str();
}

bool empty() const { return name_.empty(); }
std::string const& name() const { return name_; }

friend bool operator==(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return lhs.name_ == rhs.name_;
}
friend bool operator<(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return lhs.name_ < rhs.name_;
}
friend bool operator!=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs == rhs);
}
friend bool operator>(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs < rhs) && (lhs != rhs);
}
friend bool operator>=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs < rhs);
}
friend bool operator<=(HttpHeaderName const& lhs, HttpHeaderName const& rhs) {
return !(lhs > rhs);
}

private:
std::string name_;
};

/**
* This class represents an HTTP header field.
*/
class HttpHeader {
public:
HttpHeader() = default;
explicit HttpHeader(std::string key);
HttpHeader(std::string key, std::string value);
HttpHeader(std::string key, std::initializer_list<char const*> values);

HttpHeader(std::string key, std::vector<std::string> values);
explicit HttpHeader(HttpHeaderName key);
explicit HttpHeader(std::pair<std::string, std::string> header);
HttpHeader(HttpHeaderName key, std::string value);
HttpHeader(HttpHeaderName key, std::initializer_list<char const*> values);
HttpHeader(HttpHeaderName key, std::vector<std::string> values);

HttpHeader(HttpHeader&&) = default;
HttpHeader& operator=(HttpHeader&&) = default;
Expand All @@ -57,19 +114,23 @@ class HttpHeader {
friend bool operator<(HttpHeader const& lhs, HttpHeader const& rhs);

// If the key is empty, the entire HttpHeader is considered empty.
bool empty() const { return key_.empty(); }
bool empty() const { return name_.empty(); }

// Number of values.
std::size_t size() const { return values_.size(); }

// Checks to see if the values are empty. Does not inspect the key field.
bool EmptyValues() const { return values_.empty(); }

// Performs a case-insensitive comparison of the key.
bool IsSameKey(HttpHeader const& other) const;
bool IsSameKey(std::string const& key) const;
bool IsSameKey(HttpHeaderName const& name) const;

// While the RFCs indicate that header keys are case-insensitive, no attempt
// to convert them to all lowercase is made. Header keys are printed in the
// case they were constructed with. We rely on libcurl to encode them per the
// HTTP version used.
std::string name() const { return name_; }
std::vector<std::string> const& values() const { return values_; }

// The RFCs indicate that header names are case-insensitive. The
// HttpHeaderName type converts them to all lowercase.
//
// HTTP/1.1 https://www.rfc-editor.org/rfc/rfc7230#section-3.2
// HTTP/2 https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2
Expand All @@ -83,14 +144,44 @@ class HttpHeader {
HttpHeader& MergeHeader(HttpHeader const& other);
HttpHeader& MergeHeader(HttpHeader&& other);

using value_type = std::string;
using const_iterator = std::vector<value_type>::const_iterator;
const_iterator begin() const { return values_.begin(); }
const_iterator end() const { return values_.end(); }
const_iterator cbegin() const { return begin(); }
const_iterator cend() const { return end(); }

private:
std::string key_;
HttpHeaderName name_;
std::vector<std::string> values_;
};

// Abseil does not guarantee compatibility with 32-bit platforms that they do
// not test with. Support for such platforms is a community effort. Using
// std::unordered_map on 32-bit platforms reduces the likelihood of issues
// arising due to this arrangement.
#if UINTPTR_MAX == UINT64_MAX
// 64-bit architecture
using HttpHeaders = absl::flat_hash_map<HttpHeaderName, HttpHeader>;
#else
// 32-bit architecture
using HttpHeaders = std::unordered_map<HttpHeaderName, HttpHeader>;
#endif // UINTPTR_MAX == UINT64_MAX

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace rest_internal
} // namespace cloud
} // namespace google

#if UINTPTR_MAX != UINT64_MAX
// This specialization has to be in the global namespace.
template <>
struct std::hash<google::cloud::rest_internal::HttpHeaderName> {
std::size_t operator()(
google::cloud::rest_internal::HttpHeaderName const& k) const noexcept {
return std::hash<std::string>()(k.name());
}
};
#endif // UINTPTR_MAX != UINT64_MAX

#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_HTTP_HEADER_H
Loading
Loading