910e62b5创建于 1月15日历史提交
// 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