// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_vary_data.h"
#include <stdlib.h>
#include "base/pickle.h"
#include "base/strings/string_util.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
namespace net {
HttpVaryData::HttpVaryData() : is_valid_(false) {
}
bool HttpVaryData::Init(const HttpRequestInfo& request_info,
const HttpResponseHeaders& response_headers) {
base::MD5Context ctx;
base::MD5Init(&ctx);
is_valid_ = false;
bool processed_header = false;
// Feed the MD5 context in the order of the Vary header enumeration. If the
// Vary header repeats a header name, then that's OK.
//
// If the Vary header contains '*' then we should not construct any vary data
// since it is all usurped by a '*'. See section 13.6 of RFC 2616.
//
void* iter = NULL;
std::string name = "vary", request_header;
while (response_headers.EnumerateHeader(&iter, name, &request_header)) {
if (request_header == "*")
return false;
AddField(request_info, request_header, &ctx);
processed_header = true;
}
// Add an implicit 'Vary: cookie' header to any redirect to avoid redirect
// loops which may result from redirects that are incorrectly marked as
// cachable by the server. Unfortunately, other browsers do not cache
// redirects that result from requests containing a cookie header. We are
// treading on untested waters here, so we want to be extra careful to make
// sure we do not end up with a redirect loop served from cache.
//
// If there is an explicit 'Vary: cookie' header, then we will just end up
// digesting the cookie header twice. Not a problem.
//
std::string location;
if (response_headers.IsRedirect(&location)) {
AddField(request_info, "cookie", &ctx);
processed_header = true;
}
if (!processed_header)
return false;
base::MD5Final(&request_digest_, &ctx);
return is_valid_ = true;
}
bool HttpVaryData::InitFromPickle(const Pickle& pickle, PickleIterator* iter) {
is_valid_ = false;
const char* data;
if (pickle.ReadBytes(iter, &data, sizeof(request_digest_))) {
memcpy(&request_digest_, data, sizeof(request_digest_));
return is_valid_ = true;
}
return false;
}
void HttpVaryData::Persist(Pickle* pickle) const {
DCHECK(is_valid());
pickle->WriteBytes(&request_digest_, sizeof(request_digest_));
}
bool HttpVaryData::MatchesRequest(
const HttpRequestInfo& request_info,
const HttpResponseHeaders& cached_response_headers) const {
HttpVaryData new_vary_data;
if (!new_vary_data.Init(request_info, cached_response_headers)) {
// This shouldn't happen provided the same response headers passed here
// were also used when initializing |this|.
NOTREACHED();
return false;
}
return memcmp(&new_vary_data.request_digest_, &request_digest_,
sizeof(request_digest_)) == 0;
}
// static
std::string HttpVaryData::GetRequestValue(
const HttpRequestInfo& request_info,
const std::string& request_header) {
// Unfortunately, we do not have access to all of the request headers at this
// point. Most notably, we do not have access to an Authorization header if
// one will be added to the request.
std::string result;
if (request_info.extra_headers.GetHeader(request_header, &result))
return result;
return std::string();
}
// static
void HttpVaryData::AddField(const HttpRequestInfo& request_info,
const std::string& request_header,
base::MD5Context* ctx) {
std::string request_value = GetRequestValue(request_info, request_header);
// Append a character that cannot appear in the request header line so that we
// protect against case where the concatenation of two request headers could
// look the same for a variety of values for the individual request headers.
// For example, "foo: 12\nbar: 3" looks like "foo: 1\nbar: 23" otherwise.
request_value.append(1, '\n');
base::MD5Update(ctx, request_value);
}
} // namespace net