// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/quic/quic_in_memory_cache.h" #include "base/files/file_enumerator.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "net/http/http_util.h" #include "url/gurl.h" using base::FilePath; using base::StringPiece; using std::string; // Specifies the directory used during QuicInMemoryCache // construction to seed the cache. Cache directory can be // generated using `wget -p --save-headers namespace net { FilePath::StringType g_quic_in_memory_cache_dir = FILE_PATH_LITERAL(""); QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) { } QuicInMemoryCache::Response::~Response() { } // static QuicInMemoryCache* QuicInMemoryCache::GetInstance() { return Singleton::get(); } const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( const GURL& url) const { ResponseMap::const_iterator it = responses_.find(GetKey(url)); if (it == responses_.end()) { return nullptr; } return it->second; } void QuicInMemoryCache::AddSimpleResponse(StringPiece path, StringPiece version, StringPiece response_code, StringPiece response_detail, StringPiece body) { GURL url("http://" + path.as_string()); string status_line = version.as_string() + " " + response_code.as_string() + " " + response_detail.as_string(); string header = "content-length: " + base::Uint64ToString(static_cast(body.length())); scoped_refptr response_headers = new HttpResponseHeaders(status_line + '\0' + header + '\0' + '\0'); AddResponse(url, response_headers, body); } void QuicInMemoryCache::AddResponse( const GURL& url, scoped_refptr response_headers, StringPiece response_body) { string key = GetKey(url); VLOG(1) << "Adding response for: " << key; if (ContainsKey(responses_, key)) { LOG(DFATAL) << "Response for given request already exists!"; return; } Response* new_response = new Response(); new_response->set_headers(response_headers); new_response->set_body(response_body); responses_[key] = new_response; } void QuicInMemoryCache::AddSpecialResponse(StringPiece path, SpecialResponseType response_type) { GURL url("http://" + path.as_string()); AddResponse(url, nullptr, string()); responses_[GetKey(url)]->response_type_ = response_type; } QuicInMemoryCache::QuicInMemoryCache() { Initialize(); } void QuicInMemoryCache::ResetForTests() { STLDeleteValues(&responses_); Initialize(); } void QuicInMemoryCache::Initialize() { // If there's no defined cache dir, we have no initialization to do. if (g_quic_in_memory_cache_dir.size() == 0) { VLOG(1) << "No cache directory found. Skipping initialization."; return; } VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: " << g_quic_in_memory_cache_dir; FilePath directory(g_quic_in_memory_cache_dir); base::FileEnumerator file_list(directory, true, base::FileEnumerator::FILES); FilePath file = file_list.Next(); for (; !file.empty(); file = file_list.Next()) { // Need to skip files in .svn directories if (file.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) { continue; } string file_contents; base::ReadFileToString(file, &file_contents); if (file_contents.length() > INT_MAX) { LOG(WARNING) << "File '" << file.value() << "' too large: " << file_contents.length(); continue; } int file_len = static_cast(file_contents.length()); int headers_end = HttpUtil::LocateEndOfHeaders(file_contents.data(), file_len); if (headers_end < 1) { LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file.value(); continue; } string raw_headers = HttpUtil::AssembleRawHeaders(file_contents.data(), headers_end); scoped_refptr response_headers = new HttpResponseHeaders(raw_headers); string base; if (response_headers->GetNormalizedHeader("X-Original-Url", &base)) { response_headers->RemoveHeader("X-Original-Url"); // Remove the protocol so we can add it below. if (StartsWithASCII(base, "https://", false)) { base = base.substr(8); } else if (StartsWithASCII(base, "http://", false)) { base = base.substr(7); } } else { base = file.AsUTF8Unsafe(); } if (base.length() == 0 || base[0] == '/') { LOG(DFATAL) << "Invalid path, ignoring: " << base; continue; } if (base[base.length() - 1] == ',') { base = base.substr(0, base.length() - 1); } GURL url("http://" + base); VLOG(1) << "Inserting '" << GetKey(url) << "' into QuicInMemoryCache."; StringPiece body(file_contents.data() + headers_end, file_contents.size() - headers_end); AddResponse(url, response_headers, body); } } QuicInMemoryCache::~QuicInMemoryCache() { STLDeleteValues(&responses_); } string QuicInMemoryCache::GetKey(const GURL& url) const { // Take everything but the scheme portion of the URL. return url.host() + url.PathForRequest(); } } // namespace net