1// Copyright (c) 2009 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/tools/flip_server/mem_cache.h"
6
7#include <dirent.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <stdio.h>
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <unistd.h>
14
15#include <deque>
16#include <map>
17#include <string>
18
19#include "base/strings/string_util.h"
20#include "net/tools/balsa/balsa_frame.h"
21#include "net/tools/balsa/balsa_headers.h"
22#include "net/tools/dump_cache/url_to_filename_encoder.h"
23#include "net/tools/dump_cache/url_utilities.h"
24
25namespace {
26// The directory where cache locates);
27const char FLAGS_cache_base_dir[] = ".";
28}  // namespace
29
30namespace net {
31
32void StoreBodyAndHeadersVisitor::ProcessBodyData(const char* input,
33                                                 size_t size) {
34  body.append(input, size);
35}
36
37void StoreBodyAndHeadersVisitor::HandleHeaderError(BalsaFrame* framer) {
38  HandleError();
39}
40
41void StoreBodyAndHeadersVisitor::HandleHeaderWarning(BalsaFrame* framer) {
42  HandleError();
43}
44
45void StoreBodyAndHeadersVisitor::HandleChunkingError(BalsaFrame* framer) {
46  HandleError();
47}
48
49void StoreBodyAndHeadersVisitor::HandleBodyError(BalsaFrame* framer) {
50  HandleError();
51}
52
53FileData::FileData(const BalsaHeaders* headers,
54                   const std::string& filename,
55                   const std::string& body)
56    : filename_(filename), body_(body) {
57  if (headers) {
58    headers_.reset(new BalsaHeaders);
59    headers_->CopyFrom(*headers);
60  }
61}
62
63FileData::FileData() {}
64
65FileData::~FileData() {}
66
67MemoryCache::MemoryCache() : cwd_(FLAGS_cache_base_dir) {}
68
69MemoryCache::~MemoryCache() { ClearFiles(); }
70
71void MemoryCache::CloneFrom(const MemoryCache& mc) {
72  DCHECK_NE(this, &mc);
73  ClearFiles();
74  files_ = mc.files_;
75  cwd_ = mc.cwd_;
76}
77
78void MemoryCache::AddFiles() {
79  std::deque<std::string> paths;
80  paths.push_back(cwd_ + "/GET_");
81  DIR* current_dir = NULL;
82  while (!paths.empty()) {
83    while (current_dir == NULL && !paths.empty()) {
84      std::string current_dir_name = paths.front();
85      VLOG(1) << "Attempting to open dir: \"" << current_dir_name << "\"";
86      current_dir = opendir(current_dir_name.c_str());
87      paths.pop_front();
88
89      if (current_dir == NULL) {
90        perror("Unable to open directory. ");
91        current_dir_name.clear();
92        continue;
93      }
94
95      if (current_dir) {
96        VLOG(1) << "Succeeded opening";
97        for (struct dirent* dir_data = readdir(current_dir); dir_data != NULL;
98             dir_data = readdir(current_dir)) {
99          std::string current_entry_name =
100              current_dir_name + "/" + dir_data->d_name;
101          if (dir_data->d_type == DT_REG) {
102            VLOG(1) << "Found file: " << current_entry_name;
103            ReadAndStoreFileContents(current_entry_name.c_str());
104          } else if (dir_data->d_type == DT_DIR) {
105            VLOG(1) << "Found subdir: " << current_entry_name;
106            if (std::string(dir_data->d_name) != "." &&
107                std::string(dir_data->d_name) != "..") {
108              VLOG(1) << "Adding to search path: " << current_entry_name;
109              paths.push_front(current_entry_name);
110            }
111          }
112        }
113        VLOG(1) << "Oops, no data left. Closing dir.";
114        closedir(current_dir);
115        current_dir = NULL;
116      }
117    }
118  }
119}
120
121void MemoryCache::ReadToString(const char* filename, std::string* output) {
122  output->clear();
123  int fd = open(filename, 0, "r");
124  if (fd == -1)
125    return;
126  char buffer[4096];
127  ssize_t read_status = read(fd, buffer, sizeof(buffer));
128  while (read_status > 0) {
129    output->append(buffer, static_cast<size_t>(read_status));
130    do {
131      read_status = read(fd, buffer, sizeof(buffer));
132    } while (read_status <= 0 && errno == EINTR);
133  }
134  close(fd);
135}
136
137void MemoryCache::ReadAndStoreFileContents(const char* filename) {
138  StoreBodyAndHeadersVisitor visitor;
139  BalsaFrame framer;
140  framer.set_balsa_visitor(&visitor);
141  framer.set_balsa_headers(&(visitor.headers));
142  std::string filename_contents;
143  ReadToString(filename, &filename_contents);
144
145  // Ugly hack to make everything look like 1.1.
146  if (filename_contents.find("HTTP/1.0") == 0)
147    filename_contents[7] = '1';
148
149  size_t pos = 0;
150  size_t old_pos = 0;
151  while (true) {
152    old_pos = pos;
153    pos += framer.ProcessInput(filename_contents.data() + pos,
154                               filename_contents.size() - pos);
155    if (framer.Error() || pos == old_pos) {
156      LOG(ERROR) << "Unable to make forward progress, or error"
157                    " framing file: " << filename;
158      if (framer.Error()) {
159        LOG(INFO) << "********************************************ERROR!";
160        return;
161      }
162      return;
163    }
164    if (framer.MessageFullyRead()) {
165      // If no Content-Length or Transfer-Encoding was captured in the
166      // file, then the rest of the data is the body.  Many of the captures
167      // from within Chrome don't have content-lengths.
168      if (!visitor.body.length())
169        visitor.body = filename_contents.substr(pos);
170      break;
171    }
172  }
173  visitor.headers.RemoveAllOfHeader("content-length");
174  visitor.headers.RemoveAllOfHeader("transfer-encoding");
175  visitor.headers.RemoveAllOfHeader("connection");
176  visitor.headers.AppendHeader("transfer-encoding", "chunked");
177  visitor.headers.AppendHeader("connection", "keep-alive");
178
179// Experiment with changing headers for forcing use of cached
180// versions of content.
181// TODO(mbelshe) REMOVE ME
182#if 0
183  // TODO(mbelshe) append current date.
184  visitor.headers.RemoveAllOfHeader("date");
185  if (visitor.headers.HasHeader("expires")) {
186    visitor.headers.RemoveAllOfHeader("expires");
187    visitor.headers.AppendHeader("expires",
188                               "Fri, 30 Aug, 2019 12:00:00 GMT");
189  }
190#endif
191  DCHECK_GE(std::string(filename).size(), cwd_.size() + 1);
192  DCHECK_EQ(std::string(filename).substr(0, cwd_.size()), cwd_);
193  DCHECK_EQ(filename[cwd_.size()], '/');
194  std::string filename_stripped = std::string(filename).substr(cwd_.size() + 1);
195  LOG(INFO) << "Adding file (" << visitor.body.length()
196            << " bytes): " << filename_stripped;
197  size_t slash_pos = filename_stripped.find('/');
198  if (slash_pos == std::string::npos) {
199    slash_pos = filename_stripped.size();
200  }
201  InsertFile(
202      &visitor.headers, filename_stripped.substr(0, slash_pos), visitor.body);
203}
204
205FileData* MemoryCache::GetFileData(const std::string& filename) {
206  Files::iterator fi = files_.end();
207  if (EndsWith(filename, ".html", true)) {
208    fi = files_.find(filename.substr(0, filename.size() - 5) + ".http");
209  }
210  if (fi == files_.end())
211    fi = files_.find(filename);
212
213  if (fi == files_.end()) {
214    return NULL;
215  }
216  return fi->second;
217}
218
219bool MemoryCache::AssignFileData(const std::string& filename,
220                                 MemCacheIter* mci) {
221  mci->file_data = GetFileData(filename);
222  if (mci->file_data == NULL) {
223    LOG(ERROR) << "Could not find file data for " << filename;
224    return false;
225  }
226  return true;
227}
228
229void MemoryCache::InsertFile(const BalsaHeaders* headers,
230                             const std::string& filename,
231                             const std::string& body) {
232  InsertFile(new FileData(headers, filename, body));
233}
234
235void MemoryCache::InsertFile(FileData* file_data) {
236  Files::iterator it = files_.find(file_data->filename());
237  if (it != files_.end()) {
238    delete it->second;
239    it->second = file_data;
240  } else {
241    files_.insert(std::make_pair(file_data->filename(), file_data));
242  }
243}
244
245void MemoryCache::ClearFiles() {
246  for (Files::const_iterator i = files_.begin(); i != files_.end(); ++i) {
247    delete i->second;
248  }
249  files_.clear();
250}
251
252}  // namespace net
253