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: 31 CachingBalsaVisitor() : done_framing_(false) {} 32 virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE { 33 AppendToBody(input, size); 34 } 35 virtual void MessageDone() OVERRIDE { 36 done_framing_ = true; 37 } 38 virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE { 39 UnhandledError(); 40 } 41 virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE { 42 UnhandledError(); 43 } 44 virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE { 45 UnhandledError(); 46 } 47 virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE { 48 UnhandledError(); 49 } 50 void UnhandledError() { 51 LOG(DFATAL) << "Unhandled error framing HTTP."; 52 } 53 void AppendToBody(const char* input, size_t size) { 54 body_.append(input, size); 55 } 56 bool done_framing() const { return done_framing_; } 57 const string& body() const { return body_; } 58 59 private: 60 bool done_framing_; 61 string body_; 62 }; 63 64 } // namespace 65 66 // static 67 QuicInMemoryCache* QuicInMemoryCache::GetInstance() { 68 return Singleton<QuicInMemoryCache>::get(); 69 } 70 71 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 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 99 void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers, 100 const BalsaHeaders& response_headers, 101 StringPiece response_body) { 102 LOG(INFO) << "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 113 QuicInMemoryCache::QuicInMemoryCache() { 114 Initialize(); 115 } 116 117 void QuicInMemoryCache::ResetForTests() { 118 STLDeleteValues(&responses_); 119 Initialize(); 120 } 121 122 void QuicInMemoryCache::Initialize() { 123 // If there's no defined cache dir, we have no initialization to do. 124 if (FLAGS_quic_in_memory_cache_dir.empty()) { 125 LOG(WARNING) << "No cache directory found. Skipping initialization."; 126 return; 127 } 128 LOG(INFO) << "Attempting to initialize QuicInMemoryCache from directory: " 129 << FLAGS_quic_in_memory_cache_dir; 130 131 FilePath directory(FLAGS_quic_in_memory_cache_dir); 132 base::FileEnumerator file_list(directory, 133 true, 134 base::FileEnumerator::FILES); 135 136 FilePath file = file_list.Next(); 137 while (!file.empty()) { 138 // Need to skip files in .svn directories 139 if (file.value().find("/.svn/") != std::string::npos) { 140 file = file_list.Next(); 141 continue; 142 } 143 144 BalsaHeaders request_headers, response_headers; 145 146 string file_contents; 147 base::ReadFileToString(file, &file_contents); 148 149 // Frame HTTP. 150 CachingBalsaVisitor caching_visitor; 151 BalsaFrame framer; 152 framer.set_balsa_headers(&response_headers); 153 framer.set_balsa_visitor(&caching_visitor); 154 size_t processed = 0; 155 while (processed < file_contents.length() && 156 !caching_visitor.done_framing()) { 157 processed += framer.ProcessInput(file_contents.c_str() + processed, 158 file_contents.length() - processed); 159 } 160 161 string response_headers_str; 162 response_headers.DumpToString(&response_headers_str); 163 if (!caching_visitor.done_framing()) { 164 LOG(DFATAL) << "Did not frame entire message from file: " << file.value() 165 << " (" << processed << " of " << file_contents.length() 166 << " bytes)."; 167 } 168 if (processed < file_contents.length()) { 169 // Didn't frame whole file. Assume remainder is body. 170 // This sometimes happens as a result of incompatibilities between 171 // BalsaFramer and wget's serialization of HTTP sans content-length. 172 caching_visitor.AppendToBody(file_contents.c_str() + processed, 173 file_contents.length() - processed); 174 processed += file_contents.length(); 175 } 176 177 StringPiece base = file.value(); 178 if (response_headers.HasHeader("X-Original-Url")) { 179 base = response_headers.GetHeader("X-Original-Url"); 180 response_headers.RemoveAllOfHeader("X-Original-Url"); 181 // Remove the protocol so that the string is of the form host + path, 182 // which is parsed properly below. 183 if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) { 184 base.remove_prefix(8); 185 } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) { 186 base.remove_prefix(7); 187 } 188 } 189 int path_start = base.find_first_of('/'); 190 DCHECK_LT(0, path_start); 191 StringPiece host(base.substr(0, path_start)); 192 StringPiece path(base.substr(path_start)); 193 if (path[path.length() - 1] == ',') { 194 path.remove_suffix(1); 195 } 196 // Set up request headers. Assume method is GET and protocol is HTTP/1.1. 197 request_headers.SetRequestFirstlineFromStringPieces("GET", 198 path, 199 "HTTP/1.1"); 200 request_headers.ReplaceOrAppendHeader("host", host); 201 202 LOG(INFO) << "Inserting 'http://" << GetKey(request_headers) 203 << "' into QuicInMemoryCache."; 204 205 AddResponse(request_headers, response_headers, caching_visitor.body()); 206 207 file = file_list.Next(); 208 } 209 } 210 211 QuicInMemoryCache::~QuicInMemoryCache() { 212 STLDeleteValues(&responses_); 213 } 214 215 string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const { 216 StringPiece uri = request_headers.request_uri(); 217 StringPiece host; 218 if (uri[0] == '/') { 219 host = request_headers.GetHeader("host"); 220 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) { 221 uri.remove_prefix(8); 222 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) { 223 uri.remove_prefix(7); 224 } 225 return host.as_string() + uri.as_string(); 226 } 227 228 } // namespace tools 229 } // namespace net 230