blob: 12733ec825839be4700fd8cca3a7d756b7ed4bc4 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_vary_data.h"
#include <array>
#include <string_view>
#include <variant>
#include "base/pickle.h"
#include "base/strings/string_util.h"
#include "crypto/hash.h"
#include "crypto/obsolete/md5.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
namespace net {
crypto::obsolete::Md5 MakeMd5HasherForHttpVaryData() {
return {};
}
namespace {
using HasherVariant = std::variant<crypto::obsolete::Md5, crypto::hash::Hasher>;
// Append to the given hash context for the given request header.
template <typename Hasher>
void AddField(const HttpRequestInfo& request_info,
std::string_view request_header,
Hasher& context) {
std::string request_value =
request_info.extra_headers.GetHeader(request_header)
.value_or(std::string());
// Append a character that cannot appear in the request header line so that we
// protect against case where the concatenation of two request headers could
// look the same for a variety of values for the individual request headers.
// For example, "foo: 12\nbar: 3" looks like "foo: 1\nbar: 23" otherwise.
request_value.append(1, '\n');
context.Update(request_value);
}
// A helper to abstract away the hashing logic for different context types.
bool UpdateVaryContext(const HttpRequestInfo& request_info,
const HttpResponseHeaders& response_headers,
HasherVariant& context_variant) {
bool processed_header = false;
size_t iter = 0;
constexpr std::string_view name = "vary";
std::optional<std::string_view> request_header;
while ((request_header = response_headers.EnumerateHeader(&iter, name))) {
// The caller of this function is responsible for handling the "*" case.
DCHECK_NE("*", *request_header);
std::visit(
[&](auto& context) {
AddField(request_info, *request_header, context);
},
context_variant);
processed_header = true;
}
return processed_header;
}
} // namespace
HttpVaryData::HttpVaryData() = default;
bool HttpVaryData::Init(const HttpRequestInfo& request_info,
const HttpResponseHeaders& response_headers,
HashType hash_type) {
is_valid_ = false;
// If the Vary header contains '*' then we can just notice it based on
// |cached_response_headers| in MatchesRequest(), and don't have to worry
// about the specific headers. We still want an HttpVaryData around, to let
// us handle this case. See section 4.1 of RFC 7234.
//
if (response_headers.HasHeaderValue("vary", "*")) {
// What's in request_digest_ will never be looked at, but make it
// deterministic so we don't serialize out uninitialized memory content.
hash_ = Sha256Hash{};
return is_valid_ = true;
}
HasherVariant context_variant =
(hash_type == HashType::kSHA256)
? HasherVariant(std::in_place_type<crypto::hash::Hasher>,
crypto::hash::HashKind::kSha256)
: HasherVariant(MakeMd5HasherForHttpVaryData());
if (!UpdateVaryContext(request_info, response_headers, context_variant)) {
return false;
}
std::visit(
[this](auto& context) {
using T = std::decay_t<decltype(context)>;
if constexpr (std::is_same_v<T, crypto::hash::Hasher>) {
Sha256Hash sha_hash;
context.Finish(sha_hash);
hash_ = sha_hash;
} else {
Md5Hash md5_hash;
context.Finish(md5_hash);
hash_ = md5_hash;
}
},
context_variant);
return is_valid_ = true;
}
bool HttpVaryData::InitFromPickle(base::PickleIterator* iter) {
is_valid_ = false;
// Create a copy of the iterator to probe for the old format. This avoids
// advancing `iter` before we know which format we are parsing.
base::PickleIterator probe_iter(*iter);
std::optional<base::span<const uint8_t>> bytes_for_probe =
probe_iter.ReadBytes(crypto::obsolete::Md5::kSize);
// Support for old cache entries that stored a raw 16-byte MD5 digest.
// We detect 16-byte MD5 hash by checking if the pickle's total size is
// exactly 16 bytes.
if (bytes_for_probe && !probe_iter.ReadBytes(1).has_value()) {
// It's an old-style entry. Read the data from the original iterator to
// consume it.
std::optional<base::span<const uint8_t>> bytes =
iter->ReadBytes(crypto::obsolete::Md5::kSize);
Md5Hash md5_hash;
base::span(md5_hash).copy_from(*bytes);
hash_ = md5_hash;
return is_valid_ = true;
}
// It's a new-style entry, so we parse it from the original `iter`.
// New format: HashType enum followed by the digest.
int read_hash_type;
if (!iter->ReadInt(&read_hash_type)) {
return false;
}
if (read_hash_type == static_cast<int>(HashType::kSHA256)) {
std::optional<base::span<const uint8_t>> bytes =
iter->ReadBytes(crypto::hash::kSha256Size);
if (!bytes) {
return false;
}
Sha256Hash sha_hash;
base::span(sha_hash).copy_from(*bytes);
hash_ = sha_hash;
} else if (read_hash_type == static_cast<int>(HashType::kMD5)) {
std::optional<base::span<const uint8_t>> bytes =
iter->ReadBytes(crypto::obsolete::Md5::kSize);
if (!bytes) {
return false;
}
Md5Hash md5_hash;
base::span(md5_hash).copy_from(*bytes);
hash_ = md5_hash;
} else {
return false; // Invalid hash type.
}
return is_valid_ = true;
}
void HttpVaryData::Persist(base::Pickle* pickle) const {
DCHECK(is_valid());
std::visit(
[&](const auto& hash) {
pickle->WriteInt(static_cast<int>(hash_type()));
pickle->WriteBytes(hash);
},
hash_);
}
bool HttpVaryData::MatchesRequest(
const HttpRequestInfo& request_info,
const HttpResponseHeaders& cached_response_headers) const {
// Vary: * never matches.
if (cached_response_headers.HasHeaderValue("vary", "*")) {
return false;
}
HttpVaryData new_vary_data;
if (!new_vary_data.Init(request_info, cached_response_headers, hash_type())) {
// This case can happen if |this| was loaded from a cache that was populated
// by a build before crbug.com/469675 was fixed.
return false;
}
return new_vary_data.hash_ == hash_;
}
} // namespace net