| // 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::list; | 
 | using std::string; | 
 |  | 
 | namespace net { | 
 |  | 
 | namespace { | 
 |  | 
 | class ResourceFileImpl : public net::QuicInMemoryCache::ResourceFile { | 
 |  public: | 
 |   explicit ResourceFileImpl(const base::FilePath& file_name) | 
 |       : ResourceFile(file_name) {} | 
 |  | 
 |   void Read() override { | 
 |     base::ReadFileToString(FilePath(file_name_), &file_contents_); | 
 |  | 
 |     int file_len = static_cast<int>(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<string> 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<HttpResponseHeaders> http_headers_; | 
 |   string url_; | 
 |   list<std::unique_ptr<string>> 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<QuicInMemoryCache>::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<int>(body.length())); | 
 |   AddResponse(host, path, response_headers, body); | 
 | } | 
 |  | 
 | void QuicInMemoryCache::AddSimpleResponseWithServerPushResources( | 
 |     StringPiece host, | 
 |     StringPiece path, | 
 |     int response_code, | 
 |     StringPiece body, | 
 |     list<ServerPushInfo> 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<std::unique_ptr<ResourceFile>> 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<ResourceFile> 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<ServerPushInfo> 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<ServerPushInfo> QuicInMemoryCache::GetServerPushResources( | 
 |     string request_url) { | 
 |   list<ServerPushInfo> 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<ServerPushInfo> 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 |