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