// Copyright (c) 2012 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/tools/quic/quic_in_memory_cache.h" #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" #include "net/quic/quic_bug_tracker.h" #include "net/spdy/spdy_http_utils.h" using base::FilePath; using base::IntToString; using base::StringPiece; using std::string; namespace net { namespace { class ResourceFileImpl : public net::QuicInMemoryCache::ResourceFile { public: ResourceFileImpl(const base::FilePath& file_name) : ResourceFile(file_name) {} void Read() override { base::ReadFileToString(FilePath(file_name_), &file_contents_); 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_name_.value(); return; } http_headers_ = new HttpResponseHeaders( HttpUtil::AssembleRawHeaders(file_contents_.data(), headers_end)); if (http_headers_->GetNormalizedHeader("X-Original-Url", &url_)) { x_original_url_ = StringPiece(url_); HandleXOriginalUrl(); } // X-Push-URL header is a relatively quick way to support sever push // in the toy server. A production server should use link=preload // stuff as described in https://w3c.github.io/preload/. StringPiece x_push_url("X-Push-Url"); if (http_headers_->HasHeader(x_push_url)) { size_t iter = 0; std::unique_ptr push_url(new string()); while ( http_headers_->EnumerateHeader(&iter, x_push_url, push_url.get())) { push_urls_.push_back(StringPiece(*push_url)); push_url_values_.push_back(std::move(push_url)); push_url.reset(new string()); } } body_ = StringPiece(file_contents_.data() + headers_end, file_contents_.size() - headers_end); CreateSpdyHeadersFromHttpResponse(*http_headers_, HTTP2, &spdy_headers_); } private: scoped_refptr http_headers_; std::string url_; std::list> push_url_values_; DISALLOW_COPY_AND_ASSIGN(ResourceFileImpl); }; } // namespace QuicInMemoryCache::ServerPushInfo::ServerPushInfo( GURL request_url, const SpdyHeaderBlock& headers, net::SpdyPriority priority, string body) : request_url(request_url), headers(headers), priority(priority), body(body) {} QuicInMemoryCache::ServerPushInfo::ServerPushInfo(const ServerPushInfo& other) = default; QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) {} QuicInMemoryCache::Response::~Response() {} QuicInMemoryCache::ResourceFile::ResourceFile(const base::FilePath& file_name) : file_name_(file_name), file_name_string_(file_name.AsUTF8Unsafe()) {} QuicInMemoryCache::ResourceFile::~ResourceFile() {} void QuicInMemoryCache::ResourceFile::SetHostPathFromBase(StringPiece base) { size_t path_start = base.find_first_of('/'); DCHECK_LT(0UL, path_start); host_ = base.substr(0, path_start); size_t query_start = base.find_first_of(','); if (query_start > 0) { path_ = base.substr(path_start, query_start - 1); } else { path_ = base.substr(path_start); } } StringPiece QuicInMemoryCache::ResourceFile::RemoveScheme(StringPiece url) { if (url.starts_with("https://")) { url.remove_prefix(8); } else if (url.starts_with("http://")) { url.remove_prefix(7); } return url; } void QuicInMemoryCache::ResourceFile::HandleXOriginalUrl() { StringPiece url(x_original_url_); // Remove the protocol so we can add it below. url = RemoveScheme(url); SetHostPathFromBase(url); } // static QuicInMemoryCache* QuicInMemoryCache::GetInstance() { return base::Singleton::get(); } const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( StringPiece host, StringPiece path) const { ResponseMap::const_iterator it = responses_.find(GetKey(host, path)); if (it == responses_.end()) { DVLOG(1) << "Get response for resource failed: host " << host << " path " << path; if (default_response_.get()) { return default_response_.get(); } return nullptr; } return it->second; } typedef QuicInMemoryCache::ServerPushInfo ServerPushInfo; void QuicInMemoryCache::AddSimpleResponse(StringPiece host, StringPiece path, int response_code, StringPiece body) { SpdyHeaderBlock response_headers; response_headers[":status"] = IntToString(response_code); response_headers["content-length"] = IntToString(static_cast(body.length())); AddResponse(host, path, response_headers, body); } void QuicInMemoryCache::AddSimpleResponseWithServerPushResources( StringPiece host, StringPiece path, int response_code, StringPiece body, list push_resources) { AddSimpleResponse(host, path, response_code, body); MaybeAddServerPushResources(host, path, push_resources); } void QuicInMemoryCache::AddDefaultResponse(Response* response) { default_response_.reset(response); } void QuicInMemoryCache::AddResponse(StringPiece host, StringPiece path, const SpdyHeaderBlock& response_headers, StringPiece response_body) { AddResponseImpl(host, path, REGULAR_RESPONSE, response_headers, response_body, SpdyHeaderBlock()); } void QuicInMemoryCache::AddResponse(StringPiece host, StringPiece path, const SpdyHeaderBlock& response_headers, StringPiece response_body, const SpdyHeaderBlock& response_trailers) { AddResponseImpl(host, path, REGULAR_RESPONSE, response_headers, response_body, response_trailers); } void QuicInMemoryCache::AddSpecialResponse(StringPiece host, StringPiece path, SpecialResponseType response_type) { AddResponseImpl(host, path, response_type, SpdyHeaderBlock(), "", SpdyHeaderBlock()); } QuicInMemoryCache::QuicInMemoryCache() {} void QuicInMemoryCache::ResetForTests() { STLDeleteValues(&responses_); server_push_resources_.clear(); } void QuicInMemoryCache::InitializeFromDirectory(const string& cache_directory) { if (cache_directory.empty()) { QUIC_BUG << "cache_directory must not be empty."; return; } VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: " << cache_directory; FilePath directory(FilePath::FromUTF8Unsafe(cache_directory)); base::FileEnumerator file_list(directory, true, base::FileEnumerator::FILES); list> resource_files; for (FilePath file_iter = file_list.Next(); !file_iter.empty(); file_iter = file_list.Next()) { // Need to skip files in .svn directories if (file_iter.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) { continue; } std::unique_ptr resource_file( new ResourceFileImpl(file_iter)); // Tease apart filename into host and path. StringPiece base(resource_file->file_name()); base.remove_prefix(cache_directory.length()); if (base[0] == '/') { base.remove_prefix(1); } resource_file->SetHostPathFromBase(base); resource_file->Read(); AddResponse(resource_file->host(), resource_file->path(), resource_file->spdy_headers(), resource_file->body()); resource_files.push_back(std::move(resource_file)); } for (const auto& resource_file : resource_files) { list push_resources; for (const auto& push_url : resource_file->push_urls()) { GURL url(push_url); const Response* response = GetResponse(url.host(), url.path()); if (!response) { QUIC_BUG << "Push URL '" << push_url << "' not found."; return; } push_resources.push_back(ServerPushInfo(url, response->headers(), net::kV3LowestPriority, response->body().as_string())); } MaybeAddServerPushResources(resource_file->host(), resource_file->path(), push_resources); } } list QuicInMemoryCache::GetServerPushResources( string request_url) { list resources; auto resource_range = server_push_resources_.equal_range(request_url); for (auto it = resource_range.first; it != resource_range.second; ++it) { resources.push_back(it->second); } DVLOG(1) << "Found " << resources.size() << " push resources for " << request_url; return resources; } QuicInMemoryCache::~QuicInMemoryCache() { STLDeleteValues(&responses_); } void QuicInMemoryCache::AddResponseImpl( StringPiece host, StringPiece path, SpecialResponseType response_type, const SpdyHeaderBlock& response_headers, StringPiece response_body, const SpdyHeaderBlock& response_trailers) { DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\""; string key = GetKey(host, path); if (ContainsKey(responses_, key)) { QUIC_BUG << "Response for '" << key << "' already exists!"; return; } Response* new_response = new Response(); new_response->set_response_type(response_type); new_response->set_headers(response_headers); new_response->set_body(response_body); new_response->set_trailers(response_trailers); DVLOG(1) << "Add response with key " << key; responses_[key] = new_response; } string QuicInMemoryCache::GetKey(StringPiece host, StringPiece path) const { return host.as_string() + path.as_string(); } void QuicInMemoryCache::MaybeAddServerPushResources( StringPiece request_host, StringPiece request_path, list push_resources) { string request_url = GetKey(request_host, request_path); for (const auto& push_resource : push_resources) { if (PushResourceExistsInCache(request_url, push_resource)) { continue; } DVLOG(1) << "Add request-resource association: request url " << request_url << " push url " << push_resource.request_url << " response headers " << push_resource.headers.DebugString(); server_push_resources_.insert(std::make_pair(request_url, push_resource)); string host = push_resource.request_url.host(); if (host.empty()) { host = request_host.as_string(); } string path = push_resource.request_url.path(); if (responses_.find(GetKey(host, path)) == responses_.end()) { // Add a server push response to responses map, if it is not in the map. SpdyHeaderBlock headers = push_resource.headers; StringPiece body = push_resource.body; DVLOG(1) << "Add response for push resource: host " << host << " path " << path; AddResponse(host, path, headers, body); } } } bool QuicInMemoryCache::PushResourceExistsInCache(string original_request_url, ServerPushInfo resource) { auto resource_range = server_push_resources_.equal_range(original_request_url); for (auto it = resource_range.first; it != resource_range.second; ++it) { ServerPushInfo push_resource = it->second; if (push_resource.request_url.spec() == resource.request_url.spec()) { return true; } } return false; } } // namespace net