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/tools/quic/quic_in_memory_cache.h"
6
7 #include "base/file_util.h"
8 #include "base/files/file_enumerator.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "net/tools/balsa/balsa_headers.h"
12
13 using base::FilePath;
14 using base::StringPiece;
15 using std::string;
16
17 // Specifies the directory used during QuicInMemoryCache
18 // construction to seed the cache. Cache directory can be
19 // generated using `wget -p --save-headers <url>
20
21 namespace net {
22 namespace tools {
23
24 std::string FLAGS_quic_in_memory_cache_dir = "";
25
26 namespace {
27
28 // BalsaVisitor implementation (glue) which caches response bodies.
29 class CachingBalsaVisitor : public NoOpBalsaVisitor {
30 public:
CachingBalsaVisitor()31 CachingBalsaVisitor() : done_framing_(false) {}
ProcessBodyData(const char * input,size_t size)32 virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE {
33 AppendToBody(input, size);
34 }
MessageDone()35 virtual void MessageDone() OVERRIDE {
36 done_framing_ = true;
37 }
HandleHeaderError(BalsaFrame * framer)38 virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE {
39 UnhandledError();
40 }
HandleHeaderWarning(BalsaFrame * framer)41 virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {
42 UnhandledError();
43 }
HandleChunkingError(BalsaFrame * framer)44 virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE {
45 UnhandledError();
46 }
HandleBodyError(BalsaFrame * framer)47 virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE {
48 UnhandledError();
49 }
UnhandledError()50 void UnhandledError() {
51 LOG(DFATAL) << "Unhandled error framing HTTP.";
52 }
AppendToBody(const char * input,size_t size)53 void AppendToBody(const char* input, size_t size) {
54 body_.append(input, size);
55 }
done_framing() const56 bool done_framing() const { return done_framing_; }
body() const57 const string& body() const { return body_; }
58
59 private:
60 bool done_framing_;
61 string body_;
62 };
63
64 } // namespace
65
66 // static
GetInstance()67 QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
68 return Singleton<QuicInMemoryCache>::get();
69 }
70
GetResponse(const BalsaHeaders & request_headers) const71 const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse(
72 const BalsaHeaders& request_headers) const {
73 ResponseMap::const_iterator it = responses_.find(GetKey(request_headers));
74 if (it == responses_.end()) {
75 return NULL;
76 }
77 return it->second;
78 }
79
AddSimpleResponse(StringPiece method,StringPiece path,StringPiece version,StringPiece response_code,StringPiece response_detail,StringPiece body)80 void QuicInMemoryCache::AddSimpleResponse(StringPiece method,
81 StringPiece path,
82 StringPiece version,
83 StringPiece response_code,
84 StringPiece response_detail,
85 StringPiece body) {
86 BalsaHeaders request_headers, response_headers;
87 request_headers.SetRequestFirstlineFromStringPieces(method,
88 path,
89 version);
90 response_headers.SetRequestFirstlineFromStringPieces(version,
91 response_code,
92 response_detail);
93 response_headers.AppendHeader("content-length",
94 base::IntToString(body.length()));
95
96 AddResponse(request_headers, response_headers, body);
97 }
98
AddResponse(const BalsaHeaders & request_headers,const BalsaHeaders & response_headers,StringPiece response_body)99 void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers,
100 const BalsaHeaders& response_headers,
101 StringPiece response_body) {
102 VLOG(1) << "Adding response for: " << GetKey(request_headers);
103 if (ContainsKey(responses_, GetKey(request_headers))) {
104 LOG(DFATAL) << "Response for given request already exists!";
105 return;
106 }
107 Response* new_response = new Response();
108 new_response->set_headers(response_headers);
109 new_response->set_body(response_body);
110 responses_[GetKey(request_headers)] = new_response;
111 }
112
AddSpecialResponse(StringPiece method,StringPiece path,StringPiece version,SpecialResponseType response_type)113 void QuicInMemoryCache::AddSpecialResponse(StringPiece method,
114 StringPiece path,
115 StringPiece version,
116 SpecialResponseType response_type) {
117 BalsaHeaders request_headers, response_headers;
118 request_headers.SetRequestFirstlineFromStringPieces(method,
119 path,
120 version);
121 AddResponse(request_headers, response_headers, "");
122 responses_[GetKey(request_headers)]->response_type_ = response_type;
123 }
124
QuicInMemoryCache()125 QuicInMemoryCache::QuicInMemoryCache() {
126 Initialize();
127 }
128
ResetForTests()129 void QuicInMemoryCache::ResetForTests() {
130 STLDeleteValues(&responses_);
131 Initialize();
132 }
133
Initialize()134 void QuicInMemoryCache::Initialize() {
135 // If there's no defined cache dir, we have no initialization to do.
136 if (FLAGS_quic_in_memory_cache_dir.empty()) {
137 VLOG(1) << "No cache directory found. Skipping initialization.";
138 return;
139 }
140 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
141 << FLAGS_quic_in_memory_cache_dir;
142
143 FilePath directory(FLAGS_quic_in_memory_cache_dir);
144 base::FileEnumerator file_list(directory,
145 true,
146 base::FileEnumerator::FILES);
147
148 FilePath file = file_list.Next();
149 while (!file.empty()) {
150 // Need to skip files in .svn directories
151 if (file.value().find("/.svn/") != std::string::npos) {
152 file = file_list.Next();
153 continue;
154 }
155
156 BalsaHeaders request_headers, response_headers;
157
158 string file_contents;
159 base::ReadFileToString(file, &file_contents);
160
161 // Frame HTTP.
162 CachingBalsaVisitor caching_visitor;
163 BalsaFrame framer;
164 framer.set_balsa_headers(&response_headers);
165 framer.set_balsa_visitor(&caching_visitor);
166 size_t processed = 0;
167 while (processed < file_contents.length() &&
168 !caching_visitor.done_framing()) {
169 processed += framer.ProcessInput(file_contents.c_str() + processed,
170 file_contents.length() - processed);
171 }
172
173 string response_headers_str;
174 response_headers.DumpToString(&response_headers_str);
175 if (!caching_visitor.done_framing()) {
176 LOG(DFATAL) << "Did not frame entire message from file: " << file.value()
177 << " (" << processed << " of " << file_contents.length()
178 << " bytes).";
179 }
180 if (processed < file_contents.length()) {
181 // Didn't frame whole file. Assume remainder is body.
182 // This sometimes happens as a result of incompatibilities between
183 // BalsaFramer and wget's serialization of HTTP sans content-length.
184 caching_visitor.AppendToBody(file_contents.c_str() + processed,
185 file_contents.length() - processed);
186 processed += file_contents.length();
187 }
188
189 StringPiece base = file.value();
190 if (response_headers.HasHeader("X-Original-Url")) {
191 base = response_headers.GetHeader("X-Original-Url");
192 response_headers.RemoveAllOfHeader("X-Original-Url");
193 // Remove the protocol so that the string is of the form host + path,
194 // which is parsed properly below.
195 if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) {
196 base.remove_prefix(8);
197 } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) {
198 base.remove_prefix(7);
199 }
200 }
201 int path_start = base.find_first_of('/');
202 DCHECK_LT(0, path_start);
203 StringPiece host(base.substr(0, path_start));
204 StringPiece path(base.substr(path_start));
205 if (path[path.length() - 1] == ',') {
206 path.remove_suffix(1);
207 }
208 // Set up request headers. Assume method is GET and protocol is HTTP/1.1.
209 request_headers.SetRequestFirstlineFromStringPieces("GET",
210 path,
211 "HTTP/1.1");
212 request_headers.ReplaceOrAppendHeader("host", host);
213
214 VLOG(1) << "Inserting 'http://" << GetKey(request_headers)
215 << "' into QuicInMemoryCache.";
216
217 AddResponse(request_headers, response_headers, caching_visitor.body());
218
219 file = file_list.Next();
220 }
221 }
222
~QuicInMemoryCache()223 QuicInMemoryCache::~QuicInMemoryCache() {
224 STLDeleteValues(&responses_);
225 }
226
GetKey(const BalsaHeaders & request_headers) const227 string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const {
228 StringPiece uri = request_headers.request_uri();
229 if (uri.size() == 0) {
230 return "";
231 }
232 StringPiece host;
233 if (uri[0] == '/') {
234 host = request_headers.GetHeader("host");
235 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) {
236 uri.remove_prefix(8);
237 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) {
238 uri.remove_prefix(7);
239 }
240 return host.as_string() + uri.as_string();
241 }
242
243 } // namespace tools
244 } // namespace net
245