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/files/file_enumerator.h"
8#include "base/stl_util.h"
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/string_util.h"
11#include "net/http/http_util.h"
12#include "url/gurl.h"
13
14using base::FilePath;
15using base::StringPiece;
16using std::string;
17
18// Specifies the directory used during QuicInMemoryCache
19// construction to seed the cache. Cache directory can be
20// generated using `wget -p --save-headers <url>
21
22namespace net {
23
24FilePath::StringType g_quic_in_memory_cache_dir = FILE_PATH_LITERAL("");
25
26QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) {
27}
28
29QuicInMemoryCache::Response::~Response() {
30}
31
32// static
33QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
34  return Singleton<QuicInMemoryCache>::get();
35}
36
37const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse(
38    const GURL& url) const {
39  ResponseMap::const_iterator it = responses_.find(GetKey(url));
40  if (it == responses_.end()) {
41    return NULL;
42  }
43  return it->second;
44}
45
46void QuicInMemoryCache::AddSimpleResponse(StringPiece path,
47                                          StringPiece version,
48                                          StringPiece response_code,
49                                          StringPiece response_detail,
50                                          StringPiece body) {
51  GURL url("http://" + path.as_string());
52
53  string status_line = version.as_string() + " " +
54                       response_code.as_string() + " " +
55                       response_detail.as_string();
56
57  string header = "content-length: " +
58                  base::Uint64ToString(static_cast<uint64>(body.length()));
59
60  scoped_refptr<HttpResponseHeaders> response_headers =
61      new HttpResponseHeaders(status_line + '\0' + header + '\0' + '\0');
62
63  AddResponse(url, response_headers, body);
64}
65
66void QuicInMemoryCache::AddResponse(
67    const GURL& url,
68    scoped_refptr<HttpResponseHeaders> response_headers,
69    StringPiece response_body) {
70  string key = GetKey(url);
71  VLOG(1) << "Adding response for: " << key;
72  if (ContainsKey(responses_, key)) {
73    LOG(DFATAL) << "Response for given request already exists!";
74    return;
75  }
76  Response* new_response = new Response();
77  new_response->set_headers(response_headers);
78  new_response->set_body(response_body);
79  responses_[key] = new_response;
80}
81
82void QuicInMemoryCache::AddSpecialResponse(StringPiece path,
83                                           SpecialResponseType response_type) {
84  GURL url("http://" + path.as_string());
85
86  AddResponse(url, NULL, string());
87  responses_[GetKey(url)]->response_type_ = response_type;
88}
89
90QuicInMemoryCache::QuicInMemoryCache() {
91  Initialize();
92}
93
94void QuicInMemoryCache::ResetForTests() {
95  STLDeleteValues(&responses_);
96  Initialize();
97}
98
99void QuicInMemoryCache::Initialize() {
100  // If there's no defined cache dir, we have no initialization to do.
101  if (g_quic_in_memory_cache_dir.size() == 0) {
102    VLOG(1) << "No cache directory found. Skipping initialization.";
103    return;
104  }
105  VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
106          << g_quic_in_memory_cache_dir;
107
108  FilePath directory(g_quic_in_memory_cache_dir);
109  base::FileEnumerator file_list(directory,
110                                 true,
111                                 base::FileEnumerator::FILES);
112
113  FilePath file = file_list.Next();
114  for (; !file.empty(); file = file_list.Next()) {
115    // Need to skip files in .svn directories
116    if (file.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) {
117      continue;
118    }
119
120    string file_contents;
121    base::ReadFileToString(file, &file_contents);
122
123    if (file_contents.length() > INT_MAX) {
124      LOG(WARNING) << "File '" << file.value() << "' too large: "
125                   << file_contents.length();
126      continue;
127    }
128    int file_len = static_cast<int>(file_contents.length());
129
130    int headers_end = HttpUtil::LocateEndOfHeaders(file_contents.data(),
131                                                   file_len);
132    if (headers_end < 1) {
133      LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file.value();
134      continue;
135    }
136
137    string raw_headers =
138        HttpUtil::AssembleRawHeaders(file_contents.data(), headers_end);
139
140    scoped_refptr<HttpResponseHeaders> response_headers =
141        new HttpResponseHeaders(raw_headers);
142
143    string base;
144    if (response_headers->GetNormalizedHeader("X-Original-Url", &base)) {
145      response_headers->RemoveHeader("X-Original-Url");
146      // Remove the protocol so we can add it below.
147      if (StartsWithASCII(base, "https://", false)) {
148        base = base.substr(8);
149      } else if (StartsWithASCII(base, "http://", false)) {
150        base = base.substr(7);
151      }
152    } else {
153      base = file.AsUTF8Unsafe();
154    }
155    if (base.length() == 0 || base[0] == '/') {
156      LOG(DFATAL) << "Invalid path, ignoring: " << base;
157      continue;
158    }
159    if (base[base.length() - 1] == ',') {
160      base = base.substr(0, base.length() - 1);
161    }
162
163    GURL url("http://" + base);
164
165    VLOG(1) << "Inserting '" << GetKey(url) << "' into QuicInMemoryCache.";
166
167    StringPiece body(file_contents.data() + headers_end,
168                     file_contents.size() - headers_end);
169
170    AddResponse(url, response_headers, body);
171  }
172}
173
174QuicInMemoryCache::~QuicInMemoryCache() {
175  STLDeleteValues(&responses_);
176}
177
178string QuicInMemoryCache::GetKey(const GURL& url) const {
179  // Take everything but the scheme portion of the URL.
180  return url.host() + url.PathForRequest();
181}
182
183}  // namespace net
184