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.
5 #include "net/tools/quic/quic_in_memory_cache.h"
7 #include "base/files/file_enumerator.h"
8 #include "base/files/file_util.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "net/http/http_response_headers.h"
13 #include "net/http/http_util.h"
14 #include "net/spdy/spdy_http_utils.h"
17 using base::IntToString
;
18 using base::StringPiece
;
24 QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE
) {}
26 QuicInMemoryCache::Response::~Response() {}
29 QuicInMemoryCache
* QuicInMemoryCache::GetInstance() {
30 return base::Singleton
<QuicInMemoryCache
>::get();
33 const QuicInMemoryCache::Response
* QuicInMemoryCache::GetResponse(
35 StringPiece path
) const {
36 ResponseMap::const_iterator it
= responses_
.find(GetKey(host
, path
));
37 if (it
== responses_
.end()) {
38 if (default_response_
.get()) {
39 return default_response_
.get();
46 void QuicInMemoryCache::AddSimpleResponse(StringPiece host
,
49 StringPiece response_detail
,
51 SpdyHeaderBlock response_headers
;
52 response_headers
[":version"] = "HTTP/1.1";
53 string status
= IntToString(response_code
) + " ";
54 response_detail
.AppendToString(&status
);
55 response_headers
[":status"] = status
;
56 response_headers
["content-length"] =
57 IntToString(static_cast<int>(body
.length()));
58 AddResponse(host
, path
, response_headers
, body
);
61 void QuicInMemoryCache::AddDefaultResponse(Response
* response
) {
62 default_response_
.reset(response
);
65 void QuicInMemoryCache::AddResponse(StringPiece host
,
67 const SpdyHeaderBlock
& response_headers
,
68 StringPiece response_body
) {
69 AddResponseImpl(host
, path
, REGULAR_RESPONSE
, response_headers
,
73 void QuicInMemoryCache::AddSpecialResponse(StringPiece host
,
75 SpecialResponseType response_type
) {
76 AddResponseImpl(host
, path
, response_type
, SpdyHeaderBlock(), "");
79 QuicInMemoryCache::QuicInMemoryCache() {}
81 void QuicInMemoryCache::ResetForTests() {
82 STLDeleteValues(&responses_
);
85 void QuicInMemoryCache::InitializeFromDirectory(const string
& cache_directory
) {
86 if (cache_directory
.empty()) {
87 LOG(DFATAL
) << "cache_directory must not be empty.";
90 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
92 FilePath
directory(FilePath::FromUTF8Unsafe(cache_directory
));
93 base::FileEnumerator
file_list(directory
,
95 base::FileEnumerator::FILES
);
97 for (FilePath file_iter
= file_list
.Next(); !file_iter
.empty();
98 file_iter
= file_list
.Next()) {
99 // Need to skip files in .svn directories
100 if (file_iter
.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos
) {
104 // Tease apart filename into host and path.
105 string file
= file_iter
.AsUTF8Unsafe();
106 file
.erase(0, cache_directory
.length());
107 if (file
[0] == '/') {
111 string file_contents
;
112 base::ReadFileToString(file_iter
, &file_contents
);
113 int file_len
= static_cast<int>(file_contents
.length());
114 int headers_end
= HttpUtil::LocateEndOfHeaders(file_contents
.data(),
116 if (headers_end
< 1) {
117 LOG(DFATAL
) << "Headers invalid or empty, ignoring: " << file
;
122 HttpUtil::AssembleRawHeaders(file_contents
.data(), headers_end
);
124 scoped_refptr
<HttpResponseHeaders
> response_headers
=
125 new HttpResponseHeaders(raw_headers
);
128 if (response_headers
->GetNormalizedHeader("X-Original-Url", &base
)) {
129 response_headers
->RemoveHeader("X-Original-Url");
130 // Remove the protocol so we can add it below.
131 if (base::StartsWith(base
, "https://",
132 base::CompareCase::INSENSITIVE_ASCII
)) {
133 base
= base
.substr(8);
134 } else if (base::StartsWith(base
, "http://",
135 base::CompareCase::INSENSITIVE_ASCII
)) {
136 base
= base
.substr(7);
142 size_t path_start
= base
.find_first_of('/');
143 StringPiece
host(StringPiece(base
).substr(0, path_start
));
144 StringPiece
path(StringPiece(base
).substr(path_start
));
145 if (path
[path
.length() - 1] == ',') {
146 path
.remove_suffix(1);
149 StringPiece
body(file_contents
.data() + headers_end
,
150 file_contents
.size() - headers_end
);
151 // QUIC_VERSION_24 and below use SPDY/3 headers, which includes the
152 // status reason in :status, and "HTTP/1.1" in :version. Since this
153 // format is a strict superset of SPDY/4 headers, the cache uses this
154 // format to store response. Once SPDY/3 responses are no longer
155 // required, the cache can store SPDY/4 headers.
156 SpdyHeaderBlock header_block
;
157 CreateSpdyHeadersFromHttpResponse(*response_headers
, HTTP2
, &header_block
);
158 AddResponse(host
, path
, header_block
, body
);
162 QuicInMemoryCache::~QuicInMemoryCache() {
163 STLDeleteValues(&responses_
);
166 void QuicInMemoryCache::AddResponseImpl(
169 SpecialResponseType response_type
,
170 const SpdyHeaderBlock
& response_headers
,
171 StringPiece response_body
) {
172 string key
= GetKey(host
, path
);
173 VLOG(1) << "Adding response for: " << key
;
174 if (ContainsKey(responses_
, key
)) {
175 LOG(DFATAL
) << "Response for '" << key
<< "' already exists!";
178 Response
* new_response
= new Response();
179 new_response
->set_response_type(response_type
);
180 new_response
->set_headers(response_headers
);
181 new_response
->set_body(response_body
);
182 responses_
[key
] = new_response
;
185 string
QuicInMemoryCache::GetKey(StringPiece host
, StringPiece path
) const {
186 return host
.as_string() + path
.as_string();