1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
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 "base/pickle.h"
10#include "base/strings/string_util.h"
11#include "net/http/http_request_headers.h"
12#include "net/http/http_request_info.h"
13#include "net/http/http_response_headers.h"
14#include "net/http/http_util.h"
15
16namespace net {
17
18HttpVaryData::HttpVaryData() : is_valid_(false) {
19}
20
21bool HttpVaryData::Init(const HttpRequestInfo& request_info,
22                        const HttpResponseHeaders& response_headers) {
23  base::MD5Context ctx;
24  base::MD5Init(&ctx);
25
26  is_valid_ = false;
27  bool processed_header = false;
28
29  // Feed the MD5 context in the order of the Vary header enumeration.  If the
30  // Vary header repeats a header name, then that's OK.
31  //
32  // If the Vary header contains '*' then we should not construct any vary data
33  // since it is all usurped by a '*'.  See section 13.6 of RFC 2616.
34  //
35  void* iter = NULL;
36  std::string name = "vary", request_header;
37  while (response_headers.EnumerateHeader(&iter, name, &request_header)) {
38    if (request_header == "*")
39      return false;
40    AddField(request_info, request_header, &ctx);
41    processed_header = true;
42  }
43
44  // Add an implicit 'Vary: cookie' header to any redirect to avoid redirect
45  // loops which may result from redirects that are incorrectly marked as
46  // cachable by the server.  Unfortunately, other browsers do not cache
47  // redirects that result from requests containing a cookie header.  We are
48  // treading on untested waters here, so we want to be extra careful to make
49  // sure we do not end up with a redirect loop served from cache.
50  //
51  // If there is an explicit 'Vary: cookie' header, then we will just end up
52  // digesting the cookie header twice.  Not a problem.
53  //
54  std::string location;
55  if (response_headers.IsRedirect(&location)) {
56    AddField(request_info, "cookie", &ctx);
57    processed_header = true;
58  }
59
60  if (!processed_header)
61    return false;
62
63  base::MD5Final(&request_digest_, &ctx);
64  return is_valid_ = true;
65}
66
67bool HttpVaryData::InitFromPickle(const Pickle& pickle, PickleIterator* iter) {
68  is_valid_ = false;
69  const char* data;
70  if (pickle.ReadBytes(iter, &data, sizeof(request_digest_))) {
71    memcpy(&request_digest_, data, sizeof(request_digest_));
72    return is_valid_ = true;
73  }
74  return false;
75}
76
77void HttpVaryData::Persist(Pickle* pickle) const {
78  DCHECK(is_valid());
79  pickle->WriteBytes(&request_digest_, sizeof(request_digest_));
80}
81
82bool HttpVaryData::MatchesRequest(
83    const HttpRequestInfo& request_info,
84    const HttpResponseHeaders& cached_response_headers) const {
85  HttpVaryData new_vary_data;
86  if (!new_vary_data.Init(request_info, cached_response_headers)) {
87    // This shouldn't happen provided the same response headers passed here
88    // were also used when initializing |this|.
89    NOTREACHED();
90    return false;
91  }
92  return memcmp(&new_vary_data.request_digest_, &request_digest_,
93                sizeof(request_digest_)) == 0;
94}
95
96// static
97std::string HttpVaryData::GetRequestValue(
98    const HttpRequestInfo& request_info,
99    const std::string& request_header) {
100  // Unfortunately, we do not have access to all of the request headers at this
101  // point.  Most notably, we do not have access to an Authorization header if
102  // one will be added to the request.
103
104  std::string result;
105  if (request_info.extra_headers.GetHeader(request_header, &result))
106    return result;
107
108  return std::string();
109}
110
111// static
112void HttpVaryData::AddField(const HttpRequestInfo& request_info,
113                            const std::string& request_header,
114                            base::MD5Context* ctx) {
115  std::string request_value = GetRequestValue(request_info, request_header);
116
117  // Append a character that cannot appear in the request header line so that we
118  // protect against case where the concatenation of two request headers could
119  // look the same for a variety of values for the individual request headers.
120  // For example, "foo: 12\nbar: 3" looks like "foo: 1\nbar: 23" otherwise.
121  request_value.append(1, '\n');
122
123  base::MD5Update(ctx, request_value);
124}
125
126}  // namespace net
127