File: http_cache_util.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (163 lines) | stat: -rw-r--r-- 5,600 bytes parent folder | download | duplicates (5)
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