blob: efe747524532c08052cf27e4e102fb24222b9039 [file] [log] [blame]
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Author: jmarantz@google.com (Joshua Marantz)
// Unit-test SimpleUrlData, in particular it's HTTP header parser.
#include "pagespeed/kernel/http/response_headers.h"
#include <cstddef> // for size_t
#include <algorithm>
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/google_message_handler.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/string_writer.h"
#include "pagespeed/kernel/base/time_util.h"
#include "pagespeed/kernel/base/timer.h" // for Timer
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/google_url.h"
#include "pagespeed/kernel/http/http.pb.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/request_headers.h"
#include "pagespeed/kernel/http/response_headers_parser.h"
namespace net_instaweb {
class ResponseHeadersTest : public testing::Test {
protected:
ResponseHeadersTest()
: parser_(&response_headers_),
max_age_300_("max-age=300") {
ConvertTimeToString(MockTimer::kApr_5_2010_ms, &start_time_string_);
ConvertTimeToString(MockTimer::kApr_5_2010_ms + 5 * Timer::kMinuteMs,
&start_time_plus_5_minutes_string_);
ConvertTimeToString(MockTimer::kApr_5_2010_ms + 6 * Timer::kMinuteMs,
&start_time_plus_6_minutes_string_);
with_auth_.Add(HttpAttributes::kAuthorization, "iris scan");
}
void CheckGoogleHeaders(const ResponseHeaders& response_headers) {
EXPECT_EQ(200, response_headers.status_code());
EXPECT_EQ(1, response_headers.major_version());
EXPECT_EQ(0, response_headers.minor_version());
EXPECT_EQ(GoogleString("OK"),
GoogleString(response_headers.reason_phrase()));
ConstStringStarVector values;
EXPECT_TRUE(response_headers.Lookup("X-Google-Experiment", &values));
EXPECT_EQ(GoogleString("23729,24249,24253"), *(values[0]));
EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kSetCookie, &values));
EXPECT_EQ(2, values.size());
EXPECT_EQ(GoogleString("PREF=ID=3935f510d83d2a7a:TM=1270493386:LM=127049338"
"6:S=u_18e6r8aJ83N6P1; "
"expires=Wed, 04-Apr-2012 18:49:46 GMT; path=/; "
"domain=.google.com"),
*(values[0]));
EXPECT_EQ(GoogleString("NID=33=aGkk7cKzznoUuCd19qTgXlBjXC8fc_luIo2Yk9BmrevU"
"gXYPTazDF8Q6JvsO6LvTu4mfI8_44iIBLu4pF-Mvpe4wb7pYwej"
"4q9HvbMLRxt-OzimIxmd-bwyYVfZ2PY1B; "
"expires=Tue, 05-Oct-2010 18:49:46 GMT; path=/; "
"domain=.google.com; HttpOnly"),
*(values[1]));
EXPECT_EQ(12, response_headers.NumAttributes());
EXPECT_EQ(GoogleString("X-Google-GFE-Response-Body-Transformations"),
GoogleString(response_headers.Name(11)));
EXPECT_EQ(GoogleString("gunzipped"),
GoogleString(response_headers.Value(11)));
}
void ParseHeaders(const StringPiece& headers) {
parser_.Clear();
parser_.ParseChunk(headers, &message_handler_);
}
// Check sizes of the header vector and map.
void ExpectSizes(int num_headers, int num_header_names) {
EXPECT_EQ(num_headers, response_headers_.NumAttributes());
EXPECT_EQ(num_header_names, response_headers_.NumAttributeNames());
}
bool ComputeImplicitCaching(
int status_code, const char* content_type,
const GoogleString& max_age_string,
const GoogleString& start_time_plus_implicit_ttl_string) {
GoogleString header_text =
StringPrintf("HTTP/1.0 %d OK\r\n"
"Date: %s\r\n"
"Content-type: %s\r\n\r\n",
status_code, start_time_string_.c_str(), content_type);
response_headers_.Clear();
ParseHeaders(header_text);
bool cacheable = response_headers_.IsProxyCacheable();
if (!cacheable) {
EXPECT_EQ(NULL, response_headers_.Lookup1(HttpAttributes::kCacheControl));
EXPECT_EQ(NULL, response_headers_.Lookup1(HttpAttributes::kExpires));
} else {
EXPECT_STREQ(max_age_string,
response_headers_.Lookup1(HttpAttributes::kCacheControl));
EXPECT_STREQ(start_time_plus_implicit_ttl_string,
response_headers_.Lookup1(HttpAttributes::kExpires));
}
return cacheable;
}
bool ComputeImplicitCaching(int status_code, const char* content_type) {
return ComputeImplicitCaching(status_code, content_type, max_age_300_,
start_time_plus_5_minutes_string_);
}
bool IsHtmlLike(const StringPiece& type) {
response_headers_.Clear();
GoogleString header_text = "HTTP/1.1 200 OK\r\n";
if (!type.empty()) {
StrAppend(&header_text, "Content-Type: ", type, "\r\n");
}
header_text += "\r\n";
ParseHeaders(header_text);
return response_headers_.IsHtmlLike();
}
// At the end of every test, check to make sure that clearing the meta-data
// produces an equivalent structure to a freshly initialized one.
virtual void TearDown() {
response_headers_.Clear();
ResponseHeaders empty_response_headers;
// TODO(jmarantz): at present we lack a comprehensive serialization
// that covers all the member variables, but at least we can serialize
// to an HTTP-compatible string.
EXPECT_EQ(empty_response_headers.ToString(), response_headers_.ToString());
}
// Wrapper method to expose the cache-dirty bit so we can test to
// ensure it gets set on mutations.
bool ResponseCachingDirty() const {
return response_headers_.cache_fields_dirty_;
}
bool IsProxyCacheable(const RequestHeaders& request_headers,
ResponseHeaders::VaryOption respect_vary) {
return response_headers_.IsProxyCacheable(request_headers.GetProperties(),
respect_vary,
ResponseHeaders::kNoValidator);
}
bool IsProxyCacheable(const RequestHeaders& request_headers) {
return response_headers_.IsProxyCacheable(
request_headers.GetProperties(),
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kNoValidator);
}
bool IsVaryCacheable(bool has_cookie, bool has_cookie2,
ResponseHeaders::VaryOption respect_vary,
ResponseHeaders::ValidatorOption has_validator) {
RequestHeaders::Properties properties;
properties.has_cookie = has_cookie;
properties.has_cookie2 = has_cookie2;
return response_headers_.IsProxyCacheable(
properties, respect_vary, has_validator);
}
void CheckCookies(const ResponseHeaders& headers, StringPiece name,
StringPiece value, int64 expiration) {
GoogleString expiration_string;
ConvertTimeToString(expiration, &expiration_string);
expiration_string = StrCat(" Expires=", expiration_string);
StringPieceVector values;
StringPieceVector attributes;
EXPECT_TRUE(headers.HasCookie(name, &values, &attributes));
EXPECT_EQ(1, values.size());
EXPECT_EQ(value, values[0]);
EXPECT_EQ(4, attributes.size());
EXPECT_EQ(expiration_string, attributes[0]);
EXPECT_EQ(" Domain=test.com", attributes[1]);
EXPECT_EQ(" Path=/", attributes[2]);
EXPECT_EQ(" HttpOnly", attributes[3]);
}
template<class Proto>
void RemoveIfNotInOverrideWrapper(const Headers<Proto>& keep,
Headers<Proto>* headers) {
headers->RemoveIfNotIn(keep);
}
// Initiates a ResponseHeaders instance with the specified cache-control
// value, calls SetCacheControlPublic, and returns the resulting cached
// control as a joined string.
GoogleString AddPublicToCacheControl(const StringVector& cache_control) {
ResponseHeaders headers;
for (int i = 0, n = cache_control.size(); i < n; ++i) {
headers.Add(HttpAttributes::kCacheControl, cache_control[i]);
}
headers.SetCacheControlPublic();
return headers.LookupJoined(HttpAttributes::kCacheControl);
}
GoogleMessageHandler message_handler_;
ResponseHeaders response_headers_;
ResponseHeadersParser parser_;
// RequestHeaders with and without an 'Authorization:' header.
RequestHeaders with_auth_;
RequestHeaders without_auth_;
GoogleString start_time_string_;
GoogleString start_time_plus_5_minutes_string_;
GoogleString start_time_plus_6_minutes_string_;
const GoogleString max_age_300_;
private:
DISALLOW_COPY_AND_ASSIGN(ResponseHeadersTest);
};
// Parse the headers from google.com
TEST_F(ResponseHeadersTest, TestParseAndWrite) {
const GoogleString http_data = StrCat(
"HTTP/1.0 200 OK\r\n"
"X-Google-Experiment: 23729,24249,24253\r\n",
"Date: ", start_time_string_, "\r\n",
"Expires: -1\r\n"
"Cache-Control: private, max-age=0\r\n"
"Content-Type: text/html; charset=ISO-8859-1\r\n"
"Set-Cookie: PREF=ID=3935f510d83d2a7a:TM=1270493386:LM=1270493386:S="
"u_18e6r8aJ83N6P1; expires=Wed, 04-Apr-2012 18:49:46 GMT; path=/; do"
"main=.google.com\r\n"
"Set-Cookie: NID=33=aGkk7cKzznoUuCd19qTgXlBjXC8fc_luIo2Yk9BmrevUgXYP"
"TazDF8Q6JvsO6LvTu4mfI8_44iIBLu4pF-Mvpe4wb7pYwej4q9HvbMLRxt-OzimIxmd"
"-bwyYVfZ2PY1B; expires=Tue, 05-Oct-2010 18:49:46 GMT; path=/; domai"
"n=.google.com; HttpOnly\r\n"
"Server: gws\r\n"
"X-XSS-Protection: 0\r\n"
"ntend.gws/50,qyva4:80\r\n"
"taticweb.staticfrontend.gws/50,qyva4:80\r\n"
"X-Google-GFE-Response-Body-Transformations: gunzipped\r\n"
"\r\n"
"<!doctype html><html><head>"
"<meta http-equiv=\"content-type\" content=\"");
// Make a small buffer to test that we will successfully parse headers
// that are split across buffers. This is from
// wget --save-headers http://www.google.com
const int bufsize = 100;
int num_consumed = 0;
for (int i = 0, n = http_data.size(); i < n; i += bufsize) {
int size = std::min(bufsize, n - i);
num_consumed += parser_.ParseChunk(StringPiece(http_data).substr(i, size),
&message_handler_);
if (parser_.headers_complete()) {
break;
}
}
// Verifies that after the headers, we see the content. Note that this
// test uses 'wget' style output, and wget takes care of any unzipping,
// so this should not be mistaken for a content decoder, such as the
// net/instaweb/latencylabs/http_response_serializer.h.
static const char start_of_doc[] = "<!doctype html>";
EXPECT_EQ(0, strncmp(start_of_doc, http_data.c_str() + num_consumed,
STATIC_STRLEN(start_of_doc)));
CheckGoogleHeaders(response_headers_);
// Now write the headers into a string.
GoogleString outbuf;
StringWriter writer(&outbuf);
response_headers_.WriteAsHttp(&writer, &message_handler_);
// Re-read into a fresh meta-data object and parse again.
ResponseHeaders response_headers2;
ResponseHeadersParser parser2(&response_headers2);
num_consumed = parser2.ParseChunk(outbuf, &message_handler_);
EXPECT_EQ(outbuf.size(), static_cast<size_t>(num_consumed));
CheckGoogleHeaders(response_headers2);
// Write the headers as binary into a string.
outbuf.clear();
response_headers_.WriteAsBinary(&writer, &message_handler_);
// Re-read into a fresh meta-data object and compare.
ResponseHeaders response_headers3;
ASSERT_TRUE(response_headers3.ReadFromBinary(outbuf, &message_handler_));
CheckGoogleHeaders(response_headers3);
}
TEST_F(ResponseHeadersTest, TestSizeEstimate) {
GoogleString headers = StrCat(
"HTTP/1.0 200 OK\r\n"
"Cache-control: max-age=300\r\n"
"Date: ", start_time_string_, "\r\n",
"X-Pagespeed: Fast\r\n"
"\r\n");
ParseHeaders(headers);
EXPECT_EQ(headers.length(), response_headers_.SizeEstimate());
}
// Test caching header interpretation. Note that the detailed testing
// of permutations is done in pagespeed/core/resource_util_test.cc. We
// are just trying to ensure that we have populated the Resource object
// properly and that we have extracted the bits we need.
TEST_F(ResponseHeadersTest, TestCachingNeedDate) {
ParseHeaders("HTTP/1.0 200 OK\r\n"
"Cache-control: max-age=300\r\n\r\n");
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_EQ(0, response_headers_.CacheExpirationTimeMs());
}
// Make sure we deal correctly when we have no Date or Cache-Control headers.
TEST_F(ResponseHeadersTest, TestNoHeaders) {
ParseHeaders("HTTP/1.0 200 OK\r\n\r\n");
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_EQ(0, response_headers_.CacheExpirationTimeMs());
}
// Corner case, bug noticed when we have Content-Type, but no Date header.
TEST_F(ResponseHeadersTest, TestNoContentTypeNoDate) {
ParseHeaders("HTTP/1.0 200 OK\r\n"
"Content-Type: text/css\r\n\r\n");
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_EQ(0, response_headers_.CacheExpirationTimeMs());
}
TEST_F(ResponseHeadersTest, TestNoContentTypeCacheNoDate) {
ParseHeaders("HTTP/1.0 200 OK\r\n"
"Content-Type: text/css\r\n"
"Cache-Control: max-age=301\r\n\r\n");
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_EQ(0, response_headers_.CacheExpirationTimeMs());
}
TEST_F(ResponseHeadersTest, TestCachingPublic) {
// In this test we'll leave the explicit "public" flag in to make sure
// we can parse it.
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_TRUE(IsProxyCacheable(with_auth_));
EXPECT_TRUE(IsProxyCacheable(without_auth_));
EXPECT_EQ(300 * 1000,
response_headers_.CacheExpirationTimeMs() -
response_headers_.date_ms());
}
TEST_F(ResponseHeadersTest, TestCachingPartialReply) {
// Make sure we don't cache a partial reply.
ParseHeaders(StrCat("HTTP/1.0 206 Partial Reply\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsBrowserCacheable());
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_FALSE(IsProxyCacheable(with_auth_));
EXPECT_FALSE(IsProxyCacheable(without_auth_));
}
// Private caching
TEST_F(ResponseHeadersTest, TestCachingPrivate) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: private, max-age=10\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_FALSE(IsProxyCacheable(with_auth_));
EXPECT_FALSE(IsProxyCacheable(without_auth_));
EXPECT_EQ(10 * 1000,
response_headers_.CacheExpirationTimeMs() -
response_headers_.date_ms());
}
// Default caching (public unless request has authorization headers)
TEST_F(ResponseHeadersTest, TestCachingDefault) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: max-age=100\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_FALSE(IsProxyCacheable(with_auth_));
EXPECT_TRUE(IsProxyCacheable(without_auth_));
EXPECT_EQ(100 * 1000,
response_headers_.CacheExpirationTimeMs() -
response_headers_.date_ms());
}
// By default, cache permanent redirects.
TEST_F(ResponseHeadersTest, TestCachingDefaultPermRedirect) {
ParseHeaders(StrCat("HTTP/1.1 301 Moved Permanently\r\n"
"Date: ", start_time_string_, "\r\n"
"\r\n"));
EXPECT_TRUE(response_headers_.IsProxyCacheable());
}
// Even when explicitly set, don't cache temporary redirects.
TEST_F(ResponseHeadersTest, TestCachingExplicitTempRedirect302) {
ParseHeaders(StrCat("HTTP/1.1 302 Found\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: max-age=300\r\n"
"\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
}
TEST_F(ResponseHeadersTest, TestCachingExplicitTempRedirect307) {
ParseHeaders(StrCat("HTTP/1.1 307 Temporary Redirect\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: max-age=300\r\n"
"\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
}
// Test that we don't erroneously cache a 204 even though it is marked
// explicitly as cacheable. Note: We could cache this, but many status codes
// are only cacheable depending on precise input headers, to be cautious, we
// blacklist everything other than 200.
TEST_F(ResponseHeadersTest, TestCachingInvalidStatus) {
ParseHeaders(StrCat("HTTP/1.0 204 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
}
// Test that we don't erroneously cache a 304.
// Note: Even though it claims to be publicly cacheable, that cacheability only
// applies to the response based on the precise request headers or it applies
// to the original 200 response.
TEST_F(ResponseHeadersTest, TestCachingNotModified) {
ParseHeaders(StrCat("HTTP/1.0 304 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsBrowserCacheable());
EXPECT_FALSE(response_headers_.IsProxyCacheable());
}
// Test that we don't cache an HTML file without explicit caching, but
// that we do cache images, css, and javascript.
TEST_F(ResponseHeadersTest, TestImplicitCache) {
EXPECT_FALSE(ComputeImplicitCaching(200, "text/html"));
EXPECT_FALSE(ComputeImplicitCaching(200, "unknown"));
EXPECT_TRUE(ComputeImplicitCaching(200, "text/javascript"));
EXPECT_TRUE(ComputeImplicitCaching(200, "text/css"));
EXPECT_TRUE(ComputeImplicitCaching(200, "image/jpeg"));
EXPECT_TRUE(ComputeImplicitCaching(200, "image/gif"));
EXPECT_TRUE(ComputeImplicitCaching(200, "image/png"));
EXPECT_FALSE(ComputeImplicitCaching(204, "text/html"));
EXPECT_FALSE(ComputeImplicitCaching(204, "unknown"));
EXPECT_FALSE(ComputeImplicitCaching(204, "text/javascript"));
EXPECT_FALSE(ComputeImplicitCaching(204, "text/css"));
EXPECT_FALSE(ComputeImplicitCaching(204, "image/jpeg"));
EXPECT_FALSE(ComputeImplicitCaching(204, "image/gif"));
EXPECT_FALSE(ComputeImplicitCaching(204, "image/png"));
}
// Test that we don't cache an HTML file without explicit caching, but
// that we do cache images, css, and javascript.
TEST_F(ResponseHeadersTest, TestModifiedImplicitCache) {
GoogleString max_age_500 = "max-age=500";
GoogleString start_time_plus_implicit_ttl_string;
ConvertTimeToString(MockTimer::kApr_5_2010_ms + 500 * Timer::kSecondMs,
&start_time_plus_implicit_ttl_string);
response_headers_.set_implicit_cache_ttl_ms(500 * Timer::kSecondMs);
EXPECT_FALSE(ComputeImplicitCaching(200, "text/html", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(200, "unknown", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_TRUE(ComputeImplicitCaching(200, "text/javascript", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_TRUE(ComputeImplicitCaching(200, "text/css", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_TRUE(ComputeImplicitCaching(200, "image/jpeg", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_TRUE(ComputeImplicitCaching(200, "image/gif", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_TRUE(ComputeImplicitCaching(200, "image/png", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(204, "text/html", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(204, "unknown", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(204, "text/javascript", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(204, "text/css", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(204, "image/jpeg", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(204, "image/gif", max_age_500,
start_time_plus_implicit_ttl_string));
EXPECT_FALSE(ComputeImplicitCaching(204, "image/png", max_age_500,
start_time_plus_implicit_ttl_string));
}
TEST_F(ResponseHeadersTest, TestSetCookieCacheabilityForHtml) {
// HTML is cacheable if there are explicit caching directives, but no
// Set-Cookie headers.
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Type: text/html\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_TRUE(response_headers_.IsProxyCacheable());
response_headers_.Clear();
// HTML is not cacheable if there is a Set-Cookie header even though there are
// explicit caching directives.
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Type: text/html\r\n"
"Set-Cookie: cookie\r\n"
"Set-Cookie: cookie2\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_FALSE(response_headers_.IsProxyCacheable());
response_headers_.Clear();
// HTML is not cacheable if there is a Set-Cookie2 header even though there
// are explicit caching directives.
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Type: text/html\r\n"
"Set-Cookie2: cookie\r\n"
"Set-Cookie2: cookie2\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_FALSE(response_headers_.IsProxyCacheable());
}
TEST_F(ResponseHeadersTest, TestSetCookieCacheabilityForNonHtml) {
// CSS is cacheable if there are explicit caching directives, but no
// Set-Cookie headers.
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Type: text/css\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsProxyCacheable());
response_headers_.Clear();
// CSS is still cacheable even if there is a Set-Cookie.
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Type: text/css\r\n"
"Set-Cookie: cookie\r\n"
"Set-Cookie: cookie2\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsProxyCacheable());
}
TEST_F(ResponseHeadersTest, TestSetCookieCacheabilityFor301Redirect) {
// 301 Redirects are cacheable if there are explicit caching directives, but
// no Set-Cookie headers.
ParseHeaders(StrCat(
"HTTP/1.0 301 Moved Permanently\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsProxyCacheable());
// 301 Redirects are cacheable if there are no caching directives and no
// Set-Cookie headers.
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 301 Moved Permanently\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsProxyCacheable());
// 301 Redirects are not cacheable if there are cookies.
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 301 Moved Permanently\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie: cookie\r\n"
"Set-Cookie: cookie2\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 301 Moved Permanently\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie2: cookie\r\n"
"Set-Cookie2: cookie2\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
// 301 Redirects are not cacheable if there are cookies and no caching
// headers.
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 301 Moved Permanently\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie: cookie\r\n"
"Set-Cookie: cookie2\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 301 Moved Permanently\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie2: cookie\r\n"
"Set-Cookie2: cookie2\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
}
TEST_F(ResponseHeadersTest, TestSetCookieCacheabilityFor302Redirect) {
// 302 Redirects are not cacheable if there are explicit caching directives,
// but no Set-Cookie headers.
ParseHeaders(StrCat(
"HTTP/1.0 302 Moved\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
// 302 Redirects are not cacheable if there are no caching directives and no
// Set-Cookie headers.
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 302 Moved\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
// 302 Redirects are not cacheable if there are cookies.
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 302 Moved\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie: cookie\r\n"
"Set-Cookie: cookie2\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 302 Moved\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie2: cookie\r\n"
"Set-Cookie2: cookie2\r\n"
"Cache-control: max-age=300\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
// 302 Redirects are not cacheable if there are cookies and no caching
// headers.
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 302 Moved\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie: cookie\r\n"
"Set-Cookie: cookie2\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
response_headers_.Clear();
ParseHeaders(StrCat(
"HTTP/1.0 302 Moved\r\n"
"Date: ",
start_time_string_,
"\r\n"
"Location: http://www.foo.com/\r\n"
"Set-Cookie2: cookie\r\n"
"Set-Cookie2: cookie2\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
}
TEST_F(ResponseHeadersTest, GetSanitizedProto) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Set-Cookie: CG=US:CA:Mountain+View\r\n"
"Set-Cookie: UA=chrome\r\n"
"Cache-Control: max-age=100\r\n"
"Set-Cookie: path=/\r\n"
"Vary: User-Agent\r\n"
"Set-Cookie2: LA=1275937193\r\n"
"Vary: Accept-Encoding\r\n"
"\r\n"));
HttpResponseHeaders proto;
response_headers_.GetSanitizedProto(&proto);
ASSERT_EQ(proto.header_size(), 4);
EXPECT_EQ(proto.header(0).name(), HttpAttributes::kDate);
EXPECT_EQ(proto.header(1).name(), HttpAttributes::kCacheControl);
EXPECT_EQ(proto.header(1).value(), "max-age=100");
EXPECT_EQ(proto.header(2).name(), HttpAttributes::kVary);
EXPECT_EQ(proto.header(2).value(), "User-Agent");
EXPECT_EQ(proto.header(3).name(), HttpAttributes::kVary);
EXPECT_EQ(proto.status_code(), 200);
}
TEST_F(ResponseHeadersTest, TestRemoveAll) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Set-Cookie: CG=US:CA:Mountain+View\r\n"
"Set-Cookie: UA=chrome\r\n"
"Cache-Control: max-age=100\r\n"
"Set-Cookie: path=/\r\n"
"Vary: User-Agent\r\n"
"Set-Cookie: LA=1275937193\r\n"
"Vary: Accept-Encoding\r\n"
"\r\n"));
ConstStringStarVector vs;
ExpectSizes(8, 4);
// Removing a header which isn't there removes nothing and returns false.
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kLocation, &vs));
EXPECT_FALSE(response_headers_.RemoveAll(HttpAttributes::kLocation));
ExpectSizes(8, 4);
// Removing a headers which is there works.
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kVary, &vs));
EXPECT_TRUE(response_headers_.RemoveAll(HttpAttributes::kVary));
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kVary, &vs));
ExpectSizes(6, 3);
// Removing something which has already been removed has no effect.
EXPECT_FALSE(response_headers_.RemoveAll(HttpAttributes::kVary));
ExpectSizes(6, 3);
// Remove the rest one-by-one.
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kSetCookie, &vs));
EXPECT_TRUE(response_headers_.RemoveAll(HttpAttributes::kSetCookie));
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kSetCookie, &vs));
ExpectSizes(2, 2);
EXPECT_EQ(2, response_headers_.NumAttributes());
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kDate, &vs));
EXPECT_TRUE(response_headers_.RemoveAll(HttpAttributes::kDate));
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kDate, &vs));
ExpectSizes(1, 1);
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kCacheControl, &vs));
EXPECT_TRUE(response_headers_.RemoveAll(HttpAttributes::kCacheControl));
ExpectSizes(0, 0);
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kCacheControl, &vs));
}
TEST_F(ResponseHeadersTest, TestRemoveAllFromSortedArray) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Set-Cookie: CG=US:CA:Mountain+View\r\n"
"Set-Cookie: UA=chrome\r\n"
"Cache-Control: max-age=100\r\n"
"Set-Cookie: path=/\r\n"
"Vary: User-Agent\r\n"
"Set-Cookie: LA=1275937193\r\n"
"Vary: Accept-Encoding\r\n"
"\r\n"));
ConstStringStarVector vs;
ExpectSizes(8, 4);
// Empty set means remove nothing and return false.
EXPECT_FALSE(response_headers_.RemoveAllFromSortedArray(NULL, 0));
ExpectSizes(8, 4);
// Removing headers which aren't there removes nothing and returns false.
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kLocation, &vs));
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kGzip, &vs));
const StringPiece removes0[] = {
HttpAttributes::kGzip,
HttpAttributes::kLocation,
};
EXPECT_FALSE(response_headers_.RemoveAllFromSortedArray(
removes0, arraysize(removes0)));
ExpectSizes(8, 4);
// Removing multiple headers works.
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kVary, &vs));
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kSetCookie, &vs));
const StringPiece removes1[] = {
HttpAttributes::kSetCookie,
HttpAttributes::kVary,
};
EXPECT_TRUE(response_headers_.RemoveAllFromSortedArray(
removes1, arraysize(removes1)));
ExpectSizes(2, 2);
EXPECT_EQ(2, response_headers_.NumAttributes());
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kVary, &vs));
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kSetCookie, &vs));
// Removing something which has already been removed has no effect.
EXPECT_FALSE(response_headers_.RemoveAllFromSortedArray(
removes1, arraysize(removes1)));
ExpectSizes(2, 2);
// Removing one header works.
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kDate, &vs));
static StringPiece removes2[] = {
HttpAttributes::kDate
};
EXPECT_TRUE(response_headers_.RemoveAllFromSortedArray(
removes2, arraysize(removes2)));
ExpectSizes(1, 1);
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kDate, &vs));
// Removing a header that is there after one that isn't works.
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kCacheControl, &vs));
const StringPiece removes3[] = {
HttpAttributes::kCacheControl,
"X-Bogus-Attribute",
};
EXPECT_TRUE(response_headers_.RemoveAllFromSortedArray(
removes3, arraysize(removes3)));
ExpectSizes(0, 0);
EXPECT_FALSE(response_headers_.Lookup(HttpAttributes::kCacheControl, &vs));
}
TEST_F(ResponseHeadersTest, TestRemoveIfNotIn) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Set-Cookie: CG=US:CA:Mountain+View\r\n"
"Set-Cookie: UA=chrome\r\n"
"Set-Cookie: UA=chrome\r\n" // kept: 2 in keep_set.
"Set-Cookie: UA=chrome\r\n" // pruned: bag will be empty.
"Cache-Control: max-age=100, private, must-revalidate\r\n"
"Set-Cookie: path=/\r\n"
"Vary: User-Agent,User-Agent,User-Agent\r\n" // keep 2/3
"Set-Cookie: LA=1275937193\r\n"
"\r\n"));
ResponseHeaders keep_set;
keep_set.Add(HttpAttributes::kSetCookie, "UA=chrome");
keep_set.Add(HttpAttributes::kSetCookie, "UA=chrome");
keep_set.Add(HttpAttributes::kSetCookie, "LA=1275937193");
keep_set.Add(HttpAttributes::kVary, "User-Agent, User-Agent");
keep_set.Add("cache-control", "max-age=100"); // case-insensitive.
keep_set.Add("CACHE-CONTROL", "must-revalidate");
keep_set.Add("not-in-original", "won't-be-added");
response_headers_.ComputeCaching();
EXPECT_FALSE(ResponseCachingDirty());
RemoveIfNotInOverrideWrapper(keep_set, &response_headers_);
EXPECT_TRUE(ResponseCachingDirty());
ExpectSizes(5, 3);
EXPECT_TRUE(response_headers_.HasValue(HttpAttributes::kCacheControl,
"max-age=100"));
EXPECT_TRUE(response_headers_.HasValue(HttpAttributes::kCacheControl,
"must-revalidate"));
EXPECT_TRUE(response_headers_.HasValue(HttpAttributes::kSetCookie,
"LA=1275937193"));
EXPECT_TRUE(response_headers_.HasValue(HttpAttributes::kSetCookie,
"UA=chrome"));
EXPECT_TRUE(response_headers_.HasValue(HttpAttributes::kVary,
"User-Agent"));
EXPECT_FALSE(response_headers_.HasValue(HttpAttributes::kCacheControl,
"private"));
EXPECT_FALSE(response_headers_.HasValue(HttpAttributes::kSetCookie,
"CG=US:CA:Mountain+View"));
EXPECT_FALSE(response_headers_.Has("Date"));
EXPECT_STREQ(
"HTTP/1.0 200 OK\r\n"
"Set-Cookie: UA=chrome\r\n"
"Set-Cookie: UA=chrome\r\n" // kept: 2 in keep_set.
"Cache-Control: max-age=100, must-revalidate\r\n"
"Vary: User-Agent, User-Agent\r\n" // note third 'User-Agent' is gone.
"Set-Cookie: LA=1275937193\r\n"
"\r\n",
response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, TestReasonPhrase) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
EXPECT_EQ(HttpStatus::kOK, response_headers_.status_code());
EXPECT_EQ(GoogleString("OK"),
GoogleString(response_headers_.reason_phrase()));
}
TEST_F(ResponseHeadersTest, TestReasonPhraseMissing) {
static const char kText[] = "HTTP/1.0 200\r\nContent-type: text/html\r\n\r\n";
ParseHeaders(kText);
EXPECT_EQ(HttpStatus::kOK, response_headers_.status_code());
EXPECT_STREQ("OK", response_headers_.reason_phrase());
}
TEST_F(ResponseHeadersTest, TestReasonPhraseHasOnlySpace) {
static const char kText[] =
"HTTP/1.0 200 \r\nContent-type: text/html\r\n\r\n";
ParseHeaders(kText);
EXPECT_EQ(HttpStatus::kOK, response_headers_.status_code());
EXPECT_STREQ("OK", response_headers_.reason_phrase());
}
TEST_F(ResponseHeadersTest, TestReasonPhraseBogusCode) {
static const char kText[] =
"HTTP/1.0 6765 \r\nContent-type: text/html\r\n\r\n";
ParseHeaders(kText);
EXPECT_EQ(6765, response_headers_.status_code());
EXPECT_STREQ("Internal Server Error", response_headers_.reason_phrase());
}
TEST_F(ResponseHeadersTest, TestSetDate) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=100");
ConstStringStarVector date;
ASSERT_TRUE(response_headers_.Lookup("Date", &date));
EXPECT_EQ(1, date.size());
response_headers_.ComputeCaching();
const int64 k100_sec = 100 * 1000;
ASSERT_EQ(MockTimer::kApr_5_2010_ms + k100_sec,
response_headers_.CacheExpirationTimeMs());
}
TEST_F(ResponseHeadersTest, TestUpdateFrom) {
const char old_header_string[] =
"HTTP/1.1 200 OK\r\n"
"Date: Fri, 22 Apr 2011 19:34:33 GMT\r\n"
"Server: Apache/2.2.3 (CentOS)\r\n"
"Last-Modified: Tue, 08 Mar 2011 18:28:32 GMT\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 241260\r\n"
"Cache-control: public, max-age=600\r\n"
"Content-Type: image/jpeg\r\n"
"\r\n";
const char new_header_string[] =
"HTTP/1.1 304 Not Modified\r\n"
"Date: Fri, 22 Apr 2011 19:49:59 GMT\r\n"
"Server: Apache/2.2.3 (CentOS)\r\n"
"Cache-control: public, max-age=3600\r\n"
"Set-Cookie: LA=1275937193\r\n"
"Set-Cookie: UA=chrome\r\n"
"\r\n";
const char expected_merged_header_string[] =
"HTTP/1.1 200 OK\r\n"
"Last-Modified: Tue, 08 Mar 2011 18:28:32 GMT\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 241260\r\n"
"Content-Type: image/jpeg\r\n"
"Date: Fri, 22 Apr 2011 19:49:59 GMT\r\n"
"Server: Apache/2.2.3 (CentOS)\r\n"
"Cache-control: public, max-age=3600\r\n"
"Set-Cookie: LA=1275937193\r\n"
"Set-Cookie: UA=chrome\r\n"
"\r\n";
// Setup old and new headers
ResponseHeaders old_headers, new_headers;
ResponseHeadersParser old_parser(&old_headers), new_parser(&new_headers);
old_parser.ParseChunk(old_header_string, &message_handler_);
new_parser.ParseChunk(new_header_string, &message_handler_);
// Update old_headers from new_headers.
old_headers.UpdateFrom(new_headers);
// Make sure in memory map is updated.
ConstStringStarVector date_strings;
EXPECT_TRUE(old_headers.Lookup("Date", &date_strings));
EXPECT_EQ(1, date_strings.size());
EXPECT_EQ("Fri, 22 Apr 2011 19:49:59 GMT", *date_strings[0]);
ConstStringStarVector set_cookie_strings;
EXPECT_TRUE(old_headers.Lookup(HttpAttributes::kSetCookie,
&set_cookie_strings));
EXPECT_EQ(8, old_headers.NumAttributeNames());
// Make sure protobuf is updated.
GoogleString actual_merged_header_string;
StringWriter merged_writer(&actual_merged_header_string);
old_headers.WriteAsHttp(&merged_writer, &message_handler_);
EXPECT_EQ(expected_merged_header_string, actual_merged_header_string);
}
TEST_F(ResponseHeadersTest, TestCachingVaryStar) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Vary: *\r\n\r\n\r\n"));
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_FALSE(response_headers_.IsProxyCacheable(
RequestHeaders::Properties(),
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kNoValidator));
EXPECT_FALSE(response_headers_.IsProxyCacheable(
RequestHeaders::Properties(),
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kNoValidator));
}
TEST_F(ResponseHeadersTest, TestCachingVaryCookieNonHtml) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Vary: Cookie\r\n\r\n\r\n"));
// Verify that all 16 combinations of having cookies, cookie2, respecting
// and ignoring vary, and claiming a validator, result in this pattern
// being uncacheable.
for (int has_cookie = 0; has_cookie < 2; ++has_cookie) {
for (int has_cookie2 = 0; has_cookie2 < 2; ++has_cookie2) {
for (int vary = 0; vary < 2; ++vary) {
for (int validator = 0; validator < 2; ++validator) {
EXPECT_FALSE(IsVaryCacheable(
has_cookie != 0,
has_cookie2 != 0,
ResponseHeaders::GetVaryOption(vary != 0),
(validator != 0) ? ResponseHeaders::kHasValidator
: ResponseHeaders::kNoValidator));
}
}
}
}
}
TEST_F(ResponseHeadersTest, TestCachingVaryCookieHtml) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Content-Type: text/html\r\n"
"Vary: Cookie\r\n\r\n\r\n"));
EXPECT_FALSE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_TRUE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_FALSE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_TRUE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_FALSE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kNoValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kNoValidator));
EXPECT_FALSE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kNoValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kNoValidator));
}
TEST_F(ResponseHeadersTest, TestCachingVaryCookie2Html) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Content-Type: text/html\r\n"
"Vary: Cookie2\r\n\r\n\r\n"));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
true, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_TRUE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
true, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_TRUE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
true, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kNoValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kNoValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
true, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kNoValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kIgnoreVaryOnResources,
ResponseHeaders::kNoValidator));
}
TEST_F(ResponseHeadersTest, TestCachingVaryCookieUserAgent) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Vary: Cookie,User-Agent\r\n\r\n\r\n"));
EXPECT_FALSE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
}
TEST_F(ResponseHeadersTest, TestCachingVaryAcceptEncoding) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Vary: Accept-Encoding\r\n\r\n\r\n"));
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_TRUE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_TRUE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
}
TEST_F(ResponseHeadersTest, TestCachingVaryAcceptEncodingCookieNonHtml) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Vary: Accept-Encoding,Cookie\r\n\r\n\r\n"));
EXPECT_FALSE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_FALSE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
}
TEST_F(ResponseHeadersTest, TestCachingVaryAcceptEncodingCookieHtml) {
ParseHeaders(StrCat("HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-control: public, max-age=300\r\n"
"Content-Type: text/html\r\n"
"Vary: Accept-Encoding,Cookie\r\n\r\n\r\n"));
EXPECT_FALSE(IsVaryCacheable(
true, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
EXPECT_TRUE(IsVaryCacheable(
false, // has_cookie
false, // has_cookie2
ResponseHeaders::kRespectVaryOnResources,
ResponseHeaders::kHasValidator));
}
TEST_F(ResponseHeadersTest, TestSetDateAndCaching) {
response_headers_.SetDateAndCaching(MockTimer::kApr_5_2010_ms,
6 * Timer::kMinuteMs);
const GoogleString expected_headers = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360\r\n"
"\r\n");
EXPECT_EQ(expected_headers, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, CommaSeparatedEmptyValues) {
const GoogleString comma_headers = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: \r\n"
"Vary: Accept-Encoding, User-Agent\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(comma_headers);
EXPECT_FALSE(response_headers_.RequiresBrowserRevalidation());
EXPECT_FALSE(response_headers_.RequiresProxyRevalidation());
EXPECT_TRUE(response_headers_.Has(HttpAttributes::kCacheControl));
EXPECT_STREQ("", response_headers_.Lookup1(HttpAttributes::kCacheControl));
response_headers_.Clear();
response_headers_.Add(HttpAttributes::kCacheControl, "");
EXPECT_TRUE(response_headers_.Has(HttpAttributes::kCacheControl));
}
TEST_F(ResponseHeadersTest, TestReserializingCommaValues) {
const GoogleString comma_headers = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360, private, must-revalidate\r\n"
"Vary: Accept-Encoding, User-Agent\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(comma_headers);
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(3, values.size());
EXPECT_TRUE(response_headers_.RequiresBrowserRevalidation());
EXPECT_TRUE(response_headers_.RequiresProxyRevalidation());
values.clear();
response_headers_.Lookup(HttpAttributes::kVary, &values);
EXPECT_EQ(2, values.size());
EXPECT_EQ(comma_headers, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, TestMustRevalidate) {
const GoogleString comma_headers = StrCat(
"HTTP/1.0 200 (OK)\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-Control: max-age=360, must-revalidate\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(comma_headers);
EXPECT_TRUE(response_headers_.RequiresBrowserRevalidation());
EXPECT_TRUE(response_headers_.RequiresProxyRevalidation());
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_TRUE(response_headers_.IsProxyCacheable());
}
TEST_F(ResponseHeadersTest, TestRequiresProxyRevalidation) {
const GoogleString comma_headers = StrCat(
"HTTP/1.0 200 (OK)\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-Control: max-age=360, proxy-revalidate\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(comma_headers);
EXPECT_FALSE(response_headers_.RequiresBrowserRevalidation());
EXPECT_TRUE(response_headers_.RequiresProxyRevalidation());
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_TRUE(response_headers_.IsProxyCacheable());
}
TEST_F(ResponseHeadersTest, TestProxyAndMustRevalidate) {
const GoogleString comma_headers = StrCat(
"HTTP/1.0 200 (OK)\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-Control: max-age=360, must-revalidate, proxy-revalidate\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(comma_headers);
EXPECT_TRUE(response_headers_.RequiresBrowserRevalidation());
EXPECT_TRUE(response_headers_.RequiresProxyRevalidation());
EXPECT_TRUE(response_headers_.IsBrowserCacheable());
EXPECT_TRUE(response_headers_.IsProxyCacheable());
}
// There was a bug that calling RemoveAll would re-populate the proto from
// map_ which would separate all comma-separated values.
TEST_F(ResponseHeadersTest, TestRemoveDoesntSeparateCommaValues) {
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=0, no-cache");
response_headers_.Add(HttpAttributes::kSetCookie, "blah");
response_headers_.Add(HttpAttributes::kVary, "Accept-Encoding, Cookie");
// 1) RemoveAll
EXPECT_TRUE(response_headers_.RemoveAll(HttpAttributes::kSetCookie));
ConstStringStarVector values;
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kCacheControl, &values));
EXPECT_EQ(2, values.size());
values.clear();
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kVary, &values));
EXPECT_EQ(2, values.size());
const char expected_headers[] =
"HTTP/1.0 0 (null)\r\n"
"Cache-Control: max-age=0, no-cache\r\n"
"Vary: Accept-Encoding, Cookie\r\n"
"\r\n";
EXPECT_EQ(expected_headers, response_headers_.ToString());
// 2) Remove
EXPECT_TRUE(response_headers_.Remove(HttpAttributes::kVary, "Cookie"));
const char expected_headers2[] =
"HTTP/1.0 0 (null)\r\n"
"Cache-Control: max-age=0, no-cache\r\n"
"Vary: Accept-Encoding\r\n"
"\r\n";
EXPECT_EQ(expected_headers2, response_headers_.ToString());
// 3) RemoveAllFromSortedArray
const StringPiece remove_vector[] = {
HttpAttributes::kVary,
};
EXPECT_TRUE(response_headers_.RemoveAllFromSortedArray(
remove_vector, arraysize(remove_vector)));
const char expected_headers3[] =
"HTTP/1.0 0 (null)\r\n"
"Cache-Control: max-age=0, no-cache\r\n"
"\r\n";
EXPECT_EQ(expected_headers3, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, TestKeepSeparateCommaValues) {
response_headers_.Add(HttpAttributes::kVary, "Accept-Encoding");
response_headers_.Add(HttpAttributes::kVary, "User-Agent");
response_headers_.Add(HttpAttributes::kVary, "Cookie");
ConstStringStarVector values;
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kVary, &values));
EXPECT_EQ(3, values.size());
// We keep values separate by default.
const char expected_headers[] =
"HTTP/1.0 0 (null)\r\n"
"Vary: Accept-Encoding\r\n"
"Vary: User-Agent\r\n"
"Vary: Cookie\r\n"
"\r\n";
EXPECT_EQ(expected_headers, response_headers_.ToString());
EXPECT_TRUE(response_headers_.Remove(HttpAttributes::kVary, "User-Agent"));
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kVary, &values));
EXPECT_EQ(2, values.size());
// But they are combined after a Remove.
//
// NOTE: This is mostly to document current behavior. Feel free to re-gold
// this if you update the Remove method to not combine headers.
const char expected_headers2[] =
"HTTP/1.0 0 (null)\r\n"
"Vary: Accept-Encoding, Cookie\r\n"
"\r\n";
EXPECT_EQ(expected_headers2, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, TestKeepTogetherCommaValues) {
response_headers_.Add(HttpAttributes::kVary,
"Accept-Encoding, User-Agent, Cookie");
ConstStringStarVector values;
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kVary, &values));
EXPECT_EQ(3, values.size());
const char expected_headers[] =
"HTTP/1.0 0 (null)\r\n"
"Vary: Accept-Encoding, User-Agent, Cookie\r\n"
"\r\n";
EXPECT_EQ(expected_headers, response_headers_.ToString());
EXPECT_TRUE(response_headers_.Remove(HttpAttributes::kVary, "User-Agent"));
EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kVary, &values));
EXPECT_EQ(2, values.size());
const char expected_headers2[] =
"HTTP/1.0 0 (null)\r\n"
"Vary: Accept-Encoding, Cookie\r\n"
"\r\n";
EXPECT_EQ(expected_headers2, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, TestGzipped) {
const GoogleString comma_headers = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360\r\n"
"Content-Encoding: deflate, gzip\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(comma_headers);
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kContentEncoding, &values);
EXPECT_EQ(2, values.size());
EXPECT_TRUE(response_headers_.IsGzipped());
EXPECT_TRUE(response_headers_.WasGzippedLast());
}
TEST_F(ResponseHeadersTest, TestGzippedNotLast) {
const GoogleString comma_headers = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360\r\n"
"Content-Encoding: gzip, deflate\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(comma_headers);
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kContentEncoding, &values);
EXPECT_EQ(2, values.size());
EXPECT_TRUE(response_headers_.IsGzipped());
EXPECT_FALSE(response_headers_.WasGzippedLast());
}
TEST_F(ResponseHeadersTest, TestRemove) {
const GoogleString headers = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360\r\n"
"Content-Encoding: chunked, deflate, chunked, gzip\r\n"
"\r\n");
const GoogleString headers_removed = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360\r\n"
"Content-Encoding: chunked, deflate, gzip\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(headers);
EXPECT_TRUE(response_headers_.Remove(HttpAttributes::kContentEncoding,
"chunked"));
EXPECT_EQ(headers_removed, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, TestRemoveConcat) {
const GoogleString headers = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Encoding: gzip\r\n"
"\r\n");
const GoogleString headers_removed = StrCat(
"HTTP/1.0 0 (null)\r\n"
"Date: ", start_time_string_, "\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(headers);
EXPECT_TRUE(response_headers_.Remove(HttpAttributes::kContentEncoding,
"gzip"));
EXPECT_EQ(headers_removed, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, TestParseFirstLineOk) {
response_headers_.ParseFirstLine("HTTP/1.0 200 OK");
EXPECT_EQ(1, response_headers_.major_version());
EXPECT_EQ(0, response_headers_.minor_version());
EXPECT_EQ(200, response_headers_.status_code());
EXPECT_EQ(GoogleString("OK"),
GoogleString(response_headers_.reason_phrase()));
}
TEST_F(ResponseHeadersTest, TestParseFirstLinePermanentRedirect) {
response_headers_.ParseFirstLine("HTTP/1.1 301 Moved Permanently");
EXPECT_EQ(1, response_headers_.major_version());
EXPECT_EQ(1, response_headers_.minor_version());
EXPECT_EQ(301, response_headers_.status_code());
EXPECT_EQ(GoogleString("Moved Permanently"),
GoogleString(response_headers_.reason_phrase()));
}
TEST_F(ResponseHeadersTest, RemoveAllCaseInsensitivity) {
ResponseHeaders headers;
headers.Add("content-encoding", "gzip");
EXPECT_STREQ("gzip", headers.Lookup1("Content-Encoding"));
headers.RemoveAll("Content-Encoding");
EXPECT_EQ(NULL, headers.Lookup1("content-encoding"));
EXPECT_EQ(NULL, headers.Lookup1("Content-Encoding"));
EXPECT_EQ(0, headers.NumAttributes()) << headers.Name(0);
}
TEST_F(ResponseHeadersTest, DetermineContentType) {
static const char headers[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers);
EXPECT_EQ(&kContentTypePng, response_headers_.DetermineContentType());
}
TEST_F(ResponseHeadersTest, DetermineContentTypeMulti) {
// Per the mime sniffing spec, the -last- content-type header wins.
static const char headers[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"Content-Type: image/webp\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers);
EXPECT_EQ(&kContentTypeWebp, response_headers_.DetermineContentType());
static const char headers2[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"Content-Type: nonsense\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers2);
EXPECT_EQ(NULL, response_headers_.DetermineContentType());
}
TEST_F(ResponseHeadersTest, DetermineContentTypeWithCharset) {
static const char headers[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers);
EXPECT_EQ(&kContentTypeHtml, response_headers_.DetermineContentType());
const ContentType* content_type;
GoogleString charset;
response_headers_.DetermineContentTypeAndCharset(&content_type, &charset);
EXPECT_EQ(&kContentTypeHtml, content_type);
EXPECT_STREQ("UTF-8", charset);
}
TEST_F(ResponseHeadersTest, DetermineContentTypeAndCharsetNonExisting) {
static const char headers[] =
"HTTP/1.1 200 OK\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers);
const ContentType* content_type = &kContentTypeHtml;
GoogleString charset = "EBCDIC";
response_headers_.DetermineContentTypeAndCharset(&content_type, &charset);
EXPECT_EQ(NULL, content_type);
EXPECT_TRUE(charset.empty());
}
TEST_F(ResponseHeadersTest, DetermineCharset) {
static const char headers_no_charset[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"Content-Type: image/png\r\n"
"Content-Type: image/png\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers_no_charset);
EXPECT_TRUE(response_headers_.DetermineCharset().empty());
static const char headers_with_charset[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"Content-Type: image/png; charset=utf-8\r\n"
"Content-Type: image/png; charset=koi8-r\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers_with_charset);
EXPECT_EQ("koi8-r", response_headers_.DetermineCharset());
// We take the charset that goes with the last content-type
// header, since that's the one that matches.
static const char multiple_headers_with_charset[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"Content-Type: image/png; charset=iso-8859-1\r\n"
"Content-Type: image/png\r\n"
"Content-Type: image/png; charset=utf-8\r\n"
"Content-Type: image/png\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(multiple_headers_with_charset);
EXPECT_TRUE(response_headers_.DetermineCharset().empty());
}
TEST_F(ResponseHeadersTest, FixupMissingDate) {
static const char headers[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"\r\n";
response_headers_.Clear();
ParseHeaders(headers);
response_headers_.FixDateHeaders(MockTimer::kApr_5_2010_ms);
response_headers_.ComputeCaching();
EXPECT_EQ(MockTimer::kApr_5_2010_ms, response_headers_.date_ms());
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) == NULL);
}
TEST_F(ResponseHeadersTest, LastModifiedAsInt64) {
response_headers_.Clear();
response_headers_.SetLastModified(MockTimer::kApr_5_2010_ms);
response_headers_.ComputeCaching();
EXPECT_STREQ("Mon, 05 Apr 2010 18:51:26 GMT", response_headers_.Lookup1(
HttpAttributes::kLastModified));
EXPECT_EQ(MockTimer::kApr_5_2010_ms,
response_headers_.last_modified_time_ms());
}
TEST_F(ResponseHeadersTest, DoNotCorrectValidDate) {
const GoogleString headers = StrCat(
"HTTP/1.1 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(headers);
response_headers_.ComputeCaching();
// Setting clock back by 1 second will not affect the date.
int64 prev_date = response_headers_.date_ms();
response_headers_.FixDateHeaders(prev_date - 1000);
EXPECT_EQ(prev_date, response_headers_.date_ms());
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) == NULL);
}
TEST_F(ResponseHeadersTest, FixupStaleDate) {
const GoogleString headers = StrCat(
"HTTP/1.1 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(headers);
response_headers_.ComputeCaching();
// Setting clock *forward* by 1 second *will* affect the date.
int64 new_date = response_headers_.date_ms() + 1000;
response_headers_.FixDateHeaders(new_date);
EXPECT_EQ(new_date, response_headers_.date_ms());
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) == NULL);
}
TEST_F(ResponseHeadersTest, FixupStaleDateWithExpires) {
const GoogleString headers = StrCat(
"HTTP/1.1 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_5_minutes_string_, "\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(headers);
response_headers_.ComputeCaching();
// Setting clock *forward* by 1 second *will* affect the date, and
// also push the Expires along with it.
int64 orig_date = response_headers_.date_ms();
ASSERT_EQ(orig_date + 5 * Timer::kMinuteMs,
response_headers_.CacheExpirationTimeMs());
int64 new_date = orig_date + 1000;
response_headers_.FixDateHeaders(new_date);
EXPECT_EQ(new_date, response_headers_.date_ms());
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) != NULL);
EXPECT_EQ(new_date + 5 * Timer::kMinuteMs,
response_headers_.CacheExpirationTimeMs());
}
TEST_F(ResponseHeadersTest, FixupStaleDateWithMaxAge) {
const GoogleString headers = StrCat(
"HTTP/1.1 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Cache-Control: max-age=300\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(headers);
response_headers_.ComputeCaching();
// Setting clock *forward* by 1 second *will* affect the date, and
// also push the Expires along with it.
int64 orig_date = response_headers_.date_ms();
ASSERT_EQ(orig_date + 5 * Timer::kMinuteMs,
response_headers_.CacheExpirationTimeMs());
ASSERT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) == NULL);
int64 new_date = orig_date + 1000;
response_headers_.FixDateHeaders(new_date);
EXPECT_EQ(new_date, response_headers_.date_ms());
// Still no Expires entry, but the cache expiration time is still 5 minutes.
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) == NULL);
EXPECT_EQ(new_date + 5 * Timer::kMinuteMs,
response_headers_.CacheExpirationTimeMs());
}
TEST_F(ResponseHeadersTest, MissingDateRemoveExpires) {
const GoogleString headers = StrCat(
"HTTP/1.1 200 OK\r\n"
"Expires: ", start_time_plus_5_minutes_string_, "\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"\r\n");
response_headers_.Clear();
ParseHeaders(headers);
response_headers_.ComputeCaching();
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kDate) == NULL);
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) != NULL);
response_headers_.FixDateHeaders(MockTimer::kApr_5_2010_ms);
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kDate) != NULL);
EXPECT_TRUE(response_headers_.Lookup1(HttpAttributes::kExpires) == NULL);
}
TEST_F(ResponseHeadersTest, TestSetCacheControlMaxAge) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=0, no-cache");
response_headers_.ComputeCaching();
response_headers_.SetCacheControlMaxAge(300 * Timer::kSecondMs);
const GoogleString expected_headers = StrCat(
"HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_5_minutes_string_, "\r\n"
"Cache-Control: max-age=300,no-cache\r\n"
"\r\n");
EXPECT_EQ(expected_headers, response_headers_.ToString());
response_headers_.RemoveAll(HttpAttributes::kCacheControl);
response_headers_.ComputeCaching();
response_headers_.SetCacheControlMaxAge(360 * Timer::kSecondMs);
GoogleString expected_headers2 = StrCat(
"HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360\r\n"
"\r\n");
EXPECT_EQ(expected_headers2, response_headers_.ToString());
response_headers_.RemoveAll(HttpAttributes::kCacheControl);
response_headers_.Add(HttpAttributes::kCacheControl,
"max-age=10,private,no-cache,max-age=20,max-age=30");
response_headers_.ComputeCaching();
response_headers_.SetCacheControlMaxAge(360 * Timer::kSecondMs);
GoogleString expected_headers3 = StrCat(
"HTTP/1.0 200 OK\r\n"
"Date: ", start_time_string_, "\r\n"
"Expires: ", start_time_plus_6_minutes_string_, "\r\n"
"Cache-Control: max-age=360,private,no-cache\r\n"
"\r\n");
EXPECT_EQ(expected_headers3, response_headers_.ToString());
}
TEST_F(ResponseHeadersTest, CheckErrorCodes) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
EXPECT_FALSE(response_headers_.IsErrorStatus());
EXPECT_FALSE(response_headers_.IsServerErrorStatus());
response_headers_.SetStatusAndReason(HttpStatus::kNotModified);
EXPECT_FALSE(response_headers_.IsErrorStatus());
EXPECT_FALSE(response_headers_.IsServerErrorStatus());
response_headers_.SetStatusAndReason(HttpStatus::kNotFound);
EXPECT_TRUE(response_headers_.IsErrorStatus());
EXPECT_FALSE(response_headers_.IsServerErrorStatus());
response_headers_.SetStatusAndReason(HttpStatus::kInternalServerError);
EXPECT_TRUE(response_headers_.IsErrorStatus());
EXPECT_TRUE(response_headers_.IsServerErrorStatus());
}
TEST_F(ResponseHeadersTest, CheckRedirectStatus) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
EXPECT_FALSE(response_headers_.IsRedirectStatus());
response_headers_.SetStatusAndReason(HttpStatus::kNotModified);
EXPECT_FALSE(response_headers_.IsRedirectStatus());
response_headers_.SetStatusAndReason(HttpStatus::kBadRequest);
EXPECT_FALSE(response_headers_.IsRedirectStatus());
response_headers_.SetStatusAndReason(HttpStatus::kBadRequest);
EXPECT_FALSE(response_headers_.IsRedirectStatus());
response_headers_.SetStatusAndReason(HttpStatus::kMovedPermanently);
EXPECT_TRUE(response_headers_.IsRedirectStatus());
response_headers_.SetStatusAndReason(HttpStatus::kTemporaryRedirect);
EXPECT_TRUE(response_headers_.IsRedirectStatus());
response_headers_.SetStatusAndReason(HttpStatus::kFound);
EXPECT_TRUE(response_headers_.IsRedirectStatus());
}
TEST_F(ResponseHeadersTest, IsHtmlLike) {
// No header means, not html-like.
EXPECT_FALSE(IsHtmlLike(""));
EXPECT_FALSE(IsHtmlLike("text/css"));
EXPECT_TRUE(IsHtmlLike("text/html"));
EXPECT_TRUE(IsHtmlLike("application/xhtml+xml"));
}
TEST_F(ResponseHeadersTest, ForceCachingForNoCache) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=0, no-cache");
response_headers_.ForceCaching(360 * 1000);
response_headers_.ComputeCaching();
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_EQ(360 * 1000, response_headers_.cache_ttl_ms());
EXPECT_FALSE(response_headers_.Has(HttpAttributes::kExpires));
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(2, values.size());
EXPECT_STREQ("max-age=0", *(values[0]));
EXPECT_STREQ("no-cache", *(values[1]));
response_headers_.UpdateCacheHeadersIfForceCached();
EXPECT_STREQ("max-age=360",
response_headers_.Lookup1(HttpAttributes::kCacheControl));
EXPECT_STREQ(start_time_plus_6_minutes_string_,
response_headers_.Lookup1(HttpAttributes::kExpires));
}
TEST_F(ResponseHeadersTest, ForceCachingForPrivate) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kCacheControl,
"private, max-age=30000000");
response_headers_.ForceCaching(360 * 1000);
response_headers_.ComputeCaching();
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_EQ(360 * 1000, response_headers_.cache_ttl_ms());
EXPECT_FALSE(response_headers_.Has(HttpAttributes::kExpires));
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(2, values.size());
EXPECT_STREQ("private", *(values[0]));
EXPECT_STREQ("max-age=30000000", *(values[1]));
response_headers_.UpdateCacheHeadersIfForceCached();
EXPECT_STREQ("max-age=360",
response_headers_.Lookup1(HttpAttributes::kCacheControl));
EXPECT_STREQ(start_time_plus_6_minutes_string_,
response_headers_.Lookup1(HttpAttributes::kExpires));
}
TEST_F(ResponseHeadersTest, ForceCachingForAlreadyPublic) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kCacheControl,
"public, max-age=3456");
response_headers_.ForceCaching(360 * 1000);
response_headers_.ComputeCaching();
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_EQ(3456 * 1000, response_headers_.cache_ttl_ms());
EXPECT_FALSE(response_headers_.Has(HttpAttributes::kExpires));
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(2, values.size());
EXPECT_STREQ("public", *(values[0]));
EXPECT_STREQ("max-age=3456", *(values[1]));
response_headers_.UpdateCacheHeadersIfForceCached();
EXPECT_FALSE(response_headers_.Has(HttpAttributes::kExpires));
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(2, values.size());
EXPECT_STREQ("public", *(values[0]));
EXPECT_STREQ("max-age=3456", *(values[1]));
}
TEST_F(ResponseHeadersTest, MinCacheTtlWithMaxAge) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.MergeContentType("text/css");
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=250");
response_headers_.set_min_cache_ttl_ms(500 * 1000);
response_headers_.ComputeCaching();
// min-caching ttl overrides the explicit max age and changes the cache
// headers accordingly for down stream caching.
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_EQ(500 * 1000, response_headers_.cache_ttl_ms());
EXPECT_EQ(300 * 1000, response_headers_.implicit_cache_ttl_ms());
EXPECT_TRUE(response_headers_.Has(HttpAttributes::kExpires));
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(1, values.size());
EXPECT_STREQ("max-age=500", *(values[0]));
}
TEST_F(ResponseHeadersTest, MinCacheTtlWithMaxAgeZero) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.MergeContentType("text/css");
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=0");
response_headers_.set_min_cache_ttl_ms(500 * 1000);
response_headers_.ComputeCaching();
// min_cache_ttl_ms does not apply here as the max-age is 0 and hence
// considered not cacheable.
EXPECT_FALSE(response_headers_.IsProxyCacheable());
EXPECT_EQ(0, response_headers_.cache_ttl_ms());
EXPECT_EQ(500 * 1000, response_headers_.min_cache_ttl_ms());
EXPECT_FALSE(response_headers_.Has(HttpAttributes::kExpires));
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(1, values.size());
EXPECT_STREQ("max-age=0", *(values[0]));
}
TEST_F(ResponseHeadersTest, MinCacheTtlWithHtml) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.MergeContentType("text/html");
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=250");
response_headers_.set_min_cache_ttl_ms(500 * 1000);
response_headers_.ComputeCaching();
// Since the content type is html, min caching will not override the original
// header values.
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_EQ(250 * 1000, response_headers_.cache_ttl_ms());
EXPECT_FALSE(response_headers_.Has(HttpAttributes::kExpires));
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(1, values.size());
EXPECT_STREQ("max-age=250", *(values[0]));
}
TEST_F(ResponseHeadersTest, MinCacheTtlWithForceCaching) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kCacheControl, "max-age=250");
response_headers_.set_min_cache_ttl_ms(400 * 1000);
response_headers_.ForceCaching(500 * 1000);
response_headers_.ComputeCaching();
EXPECT_TRUE(response_headers_.IsProxyCacheable());
EXPECT_EQ(500 * 1000, response_headers_.cache_ttl_ms());
EXPECT_EQ(400 * 1000, response_headers_.min_cache_ttl_ms());
EXPECT_EQ(300 * 1000, response_headers_.implicit_cache_ttl_ms());
EXPECT_FALSE(response_headers_.Has(HttpAttributes::kExpires));
ConstStringStarVector values;
response_headers_.Lookup(HttpAttributes::kCacheControl, &values);
EXPECT_EQ(1, values.size());
EXPECT_STREQ("max-age=250", *(values[0]));
}
TEST_F(ResponseHeadersTest, GetCookieString) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kSetCookie, "CG=US:CA:Mountain+View");
response_headers_.Add(HttpAttributes::kSetCookie, "UA=chrome");
response_headers_.Add(HttpAttributes::kSetCookie, "path=/");
GoogleString cookie_str;
response_headers_.GetCookieString(&cookie_str);
EXPECT_STREQ("[\"CG=US:CA:Mountain+View\",\"UA=chrome\",\"path=/\"]",
cookie_str);
}
TEST_F(ResponseHeadersTest, HasCookie) {
response_headers_.SetStatusAndReason(HttpStatus::kOK);
response_headers_.SetDate(MockTimer::kApr_5_2010_ms);
response_headers_.Add(HttpAttributes::kSetCookie, "CG=US:CA:Mountain+View");
response_headers_.Add(HttpAttributes::kSetCookie, "UA=chrome");
response_headers_.Add(HttpAttributes::kSetCookie, "UA=ie");
response_headers_.Add(HttpAttributes::kSetCookie, "UA=;path=/");
StringPieceVector values;
StringPieceVector attributes;
StringPiece attribute_value;
EXPECT_FALSE(response_headers_.HasCookie("HttpOnly", NULL, NULL));
EXPECT_TRUE(response_headers_.HasCookie("UA", &values, &attributes));
ASSERT_EQ(3, values.size());
EXPECT_EQ("chrome", values[0]);
EXPECT_EQ("ie", values[1]);
EXPECT_EQ("", values[2]);
ASSERT_EQ(1, attributes.size());
EXPECT_EQ("path=/", attributes[0]);
EXPECT_TRUE(response_headers_.FindValueForName(attributes, "path",
&attribute_value));
EXPECT_EQ("/", attribute_value);
EXPECT_TRUE(response_headers_.HasAnyCookiesWithAttribute("path", NULL));
EXPECT_FALSE(response_headers_.HasAnyCookiesWithAttribute("HttpOnly", NULL));
response_headers_.Add(HttpAttributes::kSetCookie, "JSESSIONID=123; HttpOnly");
EXPECT_TRUE(response_headers_.HasAnyCookiesWithAttribute("HttpOnly", NULL));
EXPECT_FALSE(response_headers_.HasAnyCookiesWithAttribute("yaddayadda",
NULL));
response_headers_.RemoveAll(HttpAttributes::kSetCookie);
values.clear();
attributes.clear();
EXPECT_FALSE(response_headers_.HasCookie("JSESSIONID", NULL, NULL));
EXPECT_FALSE(response_headers_.HasAnyCookiesWithAttribute("HttpOnly", NULL));
response_headers_.Add(HttpAttributes::kSetCookie, "ID=ABC; HttpOnly ;path=/");
response_headers_.Add(HttpAttributes::kSetCookie, "UA=chrome");
response_headers_.Add(HttpAttributes::kSetCookie, "UA=ie");
response_headers_.Add(HttpAttributes::kSetCookie, "UA=");
EXPECT_TRUE(response_headers_.HasCookie("ID", &values, &attributes));
ASSERT_EQ(1, values.size());
EXPECT_EQ("ABC", values[0]);
ASSERT_EQ(2, attributes.size());
EXPECT_EQ(" HttpOnly ", attributes[0]); // Note, not trimmed.
EXPECT_EQ("path=/", attributes[1]);
EXPECT_TRUE(response_headers_.FindValueForName(attributes, "HttpOnly", NULL));
values.clear();
attributes.clear();
EXPECT_TRUE(response_headers_.HasCookie("UA", &values, &attributes));
EXPECT_EQ(0, attributes.size());
ASSERT_EQ(3, values.size());
EXPECT_EQ("chrome", values[0]);
EXPECT_EQ("ie", values[1]);
EXPECT_EQ("", values[2]);
}
TEST_F(ResponseHeadersTest, CopyToProto) {
ResponseHeaders headers;
headers.set_status_code(200);
headers.Add("foo", "bar");
headers.Add("baz", "boo");
HttpResponseHeaders headers_proto;
headers.CopyToProto(&headers_proto);
EXPECT_EQ(200, headers_proto.status_code());
EXPECT_EQ(2, headers_proto.header_size());
EXPECT_EQ("foo", headers_proto.header(0).name());
EXPECT_EQ("bar", headers_proto.header(0).value());
EXPECT_EQ("baz", headers_proto.header(1).name());
EXPECT_EQ("boo", headers_proto.header(1).value());
}
TEST_F(ResponseHeadersTest, SetQueryParamsAsCookies) {
const char kBaseHeaders[] =
"HTTP/1.0 0 (null)\r\nfoo: bar\r\nbaz: boo\r\n\r\n";
ResponseHeaders headers;
headers.Add("foo", "bar");
headers.Add("baz", "boo");
EXPECT_EQ(kBaseHeaders, headers.ToString());
const GoogleUrl kTestUrl("http://test.com/index.html");
const char kPageSpeedQueryParams[] =
"PageSpeedFilters=+inline_css&xyzzy=plugh&notme=nuh-uh&empty=&null";
StringPieceVector to_exclude;
to_exclude.push_back("notme");
EXPECT_FALSE(headers.SetQueryParamsAsCookies(
kTestUrl, "", to_exclude, MockTimer::kApr_5_2010_ms));
EXPECT_TRUE(headers.SetQueryParamsAsCookies(
kTestUrl, kPageSpeedQueryParams, to_exclude, MockTimer::kApr_5_2010_ms));
CheckCookies(headers, "PageSpeedFilters", "%2binline_css",
MockTimer::kApr_5_2010_ms);
CheckCookies(headers, "xyzzy", "plugh", MockTimer::kApr_5_2010_ms);
CheckCookies(headers, "empty", "", MockTimer::kApr_5_2010_ms);
CheckCookies(headers, "null", "", MockTimer::kApr_5_2010_ms);
EXPECT_TRUE(headers.Sanitize());
EXPECT_EQ(kBaseHeaders, headers.ToString());
}
TEST_F(ResponseHeadersTest, ClearOptionCookies) {
const char kBaseHeaders[] =
"HTTP/1.0 0 (null)\r\nfoo: bar\r\nbaz: boo\r\n\r\n";
ResponseHeaders headers;
headers.Add("foo", "bar");
headers.Add("baz", "boo");
EXPECT_EQ(kBaseHeaders, headers.ToString());
const GoogleUrl kTestUrl("http://test.com/index.html");
const char kPageSpeedQueryParams[] =
"PageSpeedFilters=+inline_css&xyzzy=plugh&notme=nuh-uh&empty=&null";
StringPieceVector to_exclude;
to_exclude.push_back("notme");
EXPECT_FALSE(headers.ClearOptionCookies(kTestUrl, "", to_exclude));
EXPECT_TRUE(headers.ClearOptionCookies(kTestUrl, kPageSpeedQueryParams,
to_exclude));
CheckCookies(headers, "PageSpeedFilters", "", 0);
CheckCookies(headers, "xyzzy", "", 0);
CheckCookies(headers, "empty", "", 0);
CheckCookies(headers, "null", "", 0);
EXPECT_TRUE(headers.Sanitize());
EXPECT_EQ(kBaseHeaders, headers.ToString());
}
TEST_F(ResponseHeadersTest, CacheControlPublic) {
StringVector strvec;
EXPECT_STREQ("public", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("max-age=100");
EXPECT_STREQ("max-age=100, public", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("public, max-age=100");
EXPECT_STREQ("public, max-age=100", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("public");
strvec.push_back("max-age=100");
EXPECT_STREQ("public, max-age=100", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("max-age=100,private");
EXPECT_STREQ("max-age=100, private", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("max-age=100");
strvec.push_back("private");
EXPECT_STREQ("max-age=100, private", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("no-store");
EXPECT_STREQ("no-store", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("no-cache");
EXPECT_STREQ("no-cache", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("No-Store");
EXPECT_STREQ("No-Store", AddPublicToCacheControl(strvec));
strvec.clear();
strvec.push_back("No-Cache");
EXPECT_STREQ("No-Cache", AddPublicToCacheControl(strvec));
}
} // namespace net_instaweb