1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/http/http_vary_data.h"
6
7 #include <stdlib.h>
8
9 #include <string_view>
10
11 #include "base/pickle.h"
12 #include "base/strings/string_util.h"
13 #include "net/http/http_request_headers.h"
14 #include "net/http/http_request_info.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/http/http_util.h"
17
18 namespace net {
19
20 HttpVaryData::HttpVaryData() = default;
21
Init(const HttpRequestInfo & request_info,const HttpResponseHeaders & response_headers)22 bool HttpVaryData::Init(const HttpRequestInfo& request_info,
23 const HttpResponseHeaders& response_headers) {
24 base::MD5Context ctx;
25 base::MD5Init(&ctx);
26
27 is_valid_ = false;
28 bool processed_header = false;
29
30 // Feed the MD5 context in the order of the Vary header enumeration. If the
31 // Vary header repeats a header name, then that's OK.
32 //
33 // If the Vary header contains '*' then we can just notice it based on
34 // |cached_response_headers| in MatchesRequest(), and don't have to worry
35 // about the specific headers. We still want an HttpVaryData around, to let
36 // us handle this case. See section 4.1 of RFC 7234.
37 //
38 size_t iter = 0;
39 constexpr std::string_view name = "vary";
40 std::optional<std::string_view> request_header;
41 while ((request_header = response_headers.EnumerateHeader(&iter, name))) {
42 if (*request_header == "*") {
43 // What's in request_digest_ will never be looked at, but make it
44 // deterministic so we don't serialize out uninitialized memory content.
45 memset(&request_digest_, 0, sizeof(request_digest_));
46 return is_valid_ = true;
47 }
48 AddField(request_info, *request_header, &ctx);
49 processed_header = true;
50 }
51
52 if (!processed_header)
53 return false;
54
55 base::MD5Final(&request_digest_, &ctx);
56 return is_valid_ = true;
57 }
58
InitFromPickle(base::PickleIterator * iter)59 bool HttpVaryData::InitFromPickle(base::PickleIterator* iter) {
60 is_valid_ = false;
61 const char* data;
62 if (iter->ReadBytes(&data, sizeof(request_digest_))) {
63 memcpy(&request_digest_, data, sizeof(request_digest_));
64 return is_valid_ = true;
65 }
66 return false;
67 }
68
Persist(base::Pickle * pickle) const69 void HttpVaryData::Persist(base::Pickle* pickle) const {
70 DCHECK(is_valid());
71 pickle->WriteBytes(&request_digest_, sizeof(request_digest_));
72 }
73
MatchesRequest(const HttpRequestInfo & request_info,const HttpResponseHeaders & cached_response_headers) const74 bool HttpVaryData::MatchesRequest(
75 const HttpRequestInfo& request_info,
76 const HttpResponseHeaders& cached_response_headers) const {
77 // Vary: * never matches.
78 if (cached_response_headers.HasHeaderValue("vary", "*"))
79 return false;
80
81 HttpVaryData new_vary_data;
82 if (!new_vary_data.Init(request_info, cached_response_headers)) {
83 // This case can happen if |this| was loaded from a cache that was populated
84 // by a build before crbug.com/469675 was fixed.
85 return false;
86 }
87 return memcmp(&new_vary_data.request_digest_, &request_digest_,
88 sizeof(request_digest_)) == 0;
89 }
90
91 // static
AddField(const HttpRequestInfo & request_info,std::string_view request_header,base::MD5Context * ctx)92 void HttpVaryData::AddField(const HttpRequestInfo& request_info,
93 std::string_view request_header,
94 base::MD5Context* ctx) {
95 std::string request_value =
96 request_info.extra_headers.GetHeader(request_header)
97 .value_or(std::string());
98
99 // Append a character that cannot appear in the request header line so that we
100 // protect against case where the concatenation of two request headers could
101 // look the same for a variety of values for the individual request headers.
102 // For example, "foo: 12\nbar: 3" looks like "foo: 1\nbar: 23" otherwise.
103 request_value.append(1, '\n');
104
105 base::MD5Update(ctx, request_value);
106 }
107
108 } // namespace net
109