1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
// Copyright 2025 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_cache_util.h"
#include <array>
#include <optional>
#include <string_view>
#include "base/containers/span.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/strings/string_util.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
namespace net::http_cache_util {
namespace {
// If the request includes one of these request headers, then avoid caching
// to avoid getting confused.
struct HeaderNameAndValue {
std::string_view name;
std::optional<std::string_view> value;
};
// If the request includes one of these request headers, then avoid caching
// to avoid getting confused.
constexpr auto kPassThroughHeaders = std::to_array(
{HeaderNameAndValue{"if-unmodified-since",
std::nullopt}, // causes unexpected 412s
HeaderNameAndValue{"if-match", std::nullopt}, // causes unexpected 412s
HeaderNameAndValue{"if-range", std::nullopt}});
// If the request includes one of these request headers, then avoid reusing
// our cached copy if any.
constexpr auto kForceFetchHeaders =
std::to_array({HeaderNameAndValue{"cache-control", "no-cache"},
HeaderNameAndValue{"pragma", "no-cache"}});
// If the request includes one of these request headers, then force our
// cached copy (if any) to be revalidated before reusing it.
constexpr auto kForceValidateHeaders =
std::to_array({HeaderNameAndValue{"cache-control", "max-age=0"}});
bool HeaderMatches(const HttpRequestHeaders& headers,
base::span<const HeaderNameAndValue> search_headers) {
for (const auto& search_header : search_headers) {
std::optional<std::string> header_value =
headers.GetHeader(search_header.name);
if (!header_value) {
continue;
}
if (!search_header.value) {
return true;
}
HttpUtil::ValuesIterator v(*header_value, ',');
while (v.GetNext()) {
if (base::EqualsCaseInsensitiveASCII(v.value(), *search_header.value)) {
return true;
}
}
}
return false;
}
struct ValidationHeaderInfo {
std::string_view request_header_name;
std::string_view related_response_header_name;
};
constexpr auto kValidationHeaders = std::to_array<ValidationHeaderInfo>(
{{"if-modified-since", "last-modified"}, {"if-none-match", "etag"}});
} // namespace
int GetLoadFlagsForExtraHeaders(const HttpRequestHeaders& extra_headers) {
// Some headers imply load flags. The order here is significant.
//
// LOAD_DISABLE_CACHE : no cache read or write
// LOAD_BYPASS_CACHE : no cache read
// LOAD_VALIDATE_CACHE : no cache read unless validation
//
// The former modes trump latter modes, so if we find a matching header we
// can stop iterating kSpecialHeaders.
static const struct {
// RAW_PTR_EXCLUSION: Never allocated by PartitionAlloc (always points to
// constexpr tables), so there is no benefit to using a raw_ptr, only cost.
RAW_PTR_EXCLUSION const base::span<const HeaderNameAndValue> search;
int load_flag;
} kSpecialHeaders[] = {
{kPassThroughHeaders, LOAD_DISABLE_CACHE},
{kForceFetchHeaders, LOAD_BYPASS_CACHE},
{kForceValidateHeaders, LOAD_VALIDATE_CACHE},
};
for (const auto& special_header : kSpecialHeaders) {
if (HeaderMatches(extra_headers, special_header.search)) {
return special_header.load_flag;
}
}
static_assert(LOAD_NORMAL == 0);
return LOAD_NORMAL;
}
// static
base::expected<std::optional<ValidationHeaders>, std::string_view>
ValidationHeaders::MaybeCreate(const HttpRequestHeaders& extra_headers) {
static_assert(kNumValidationHeaders == std::size(kValidationHeaders),
"invalid number of validation headers");
ValidationHeaderValues values;
bool validation_header_found = false;
// Check for conditionalization headers which may correspond with a
// cache validation request.
for (size_t i = 0; i < std::size(kValidationHeaders); ++i) {
const ValidationHeaderInfo& info = kValidationHeaders[i];
if (std::optional<std::string> validation_value =
extra_headers.GetHeader(info.request_header_name)) {
if (validation_value->empty()) {
return base::unexpected("Empty validation header value found");
}
values[i] = std::move(*validation_value);
validation_header_found = true;
}
}
if (validation_header_found) {
return ValidationHeaders(std::move(values));
}
return std::nullopt;
}
ValidationHeaders::ValidationHeaders(ValidationHeaderValues values)
: values_(std::move(values)) {}
ValidationHeaders::~ValidationHeaders() = default;
ValidationHeaders::ValidationHeaders(ValidationHeaders&&) = default;
ValidationHeaders& ValidationHeaders::operator=(ValidationHeaders&&) = default;
bool ValidationHeaders::Match(
const HttpResponseHeaders& response_headers) const {
for (size_t i = 0; i < std::size(kValidationHeaders); i++) {
if (values_[i].empty()) {
continue;
}
// Retrieve either the cached response's "etag" or "last-modified" header.
std::optional<std::string_view> validator =
response_headers.EnumerateHeader(
nullptr, kValidationHeaders[i].related_response_header_name);
if (validator && *validator != values_[i]) {
return false;
}
}
return true;
}
} // namespace net::http_cache_util
|