// Copyright (c) 2009 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/flip_server/mem_cache.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <deque>
#include <map>
#include <string>
#include "base/strings/string_util.h"
#include "net/tools/balsa/balsa_frame.h"
#include "net/tools/balsa/balsa_headers.h"
#include "net/tools/dump_cache/url_to_filename_encoder.h"
#include "net/tools/dump_cache/url_utilities.h"
namespace {
// The directory where cache locates);
const char FLAGS_cache_base_dir[] = ".";
} // namespace
namespace net {
void StoreBodyAndHeadersVisitor::ProcessBodyData(const char* input,
size_t size) {
body.append(input, size);
}
void StoreBodyAndHeadersVisitor::HandleHeaderError(BalsaFrame* framer) {
HandleError();
}
void StoreBodyAndHeadersVisitor::HandleHeaderWarning(BalsaFrame* framer) {
HandleError();
}
void StoreBodyAndHeadersVisitor::HandleChunkingError(BalsaFrame* framer) {
HandleError();
}
void StoreBodyAndHeadersVisitor::HandleBodyError(BalsaFrame* framer) {
HandleError();
}
FileData::FileData(const BalsaHeaders* headers,
const std::string& filename,
const std::string& body)
: filename_(filename), body_(body) {
if (headers) {
headers_.reset(new BalsaHeaders);
headers_->CopyFrom(*headers);
}
}
FileData::FileData() {}
FileData::~FileData() {}
MemoryCache::MemoryCache() : cwd_(FLAGS_cache_base_dir) {}
MemoryCache::~MemoryCache() { ClearFiles(); }
void MemoryCache::CloneFrom(const MemoryCache& mc) {
DCHECK_NE(this, &mc);
ClearFiles();
files_ = mc.files_;
cwd_ = mc.cwd_;
}
void MemoryCache::AddFiles() {
std::deque<std::string> paths;
paths.push_back(cwd_ + "/GET_");
DIR* current_dir = NULL;
while (!paths.empty()) {
while (current_dir == NULL && !paths.empty()) {
std::string current_dir_name = paths.front();
VLOG(1) << "Attempting to open dir: \"" << current_dir_name << "\"";
current_dir = opendir(current_dir_name.c_str());
paths.pop_front();
if (current_dir == NULL) {
perror("Unable to open directory. ");
current_dir_name.clear();
continue;
}
if (current_dir) {
VLOG(1) << "Succeeded opening";
for (struct dirent* dir_data = readdir(current_dir); dir_data != NULL;
dir_data = readdir(current_dir)) {
std::string current_entry_name =
current_dir_name + "/" + dir_data->d_name;
if (dir_data->d_type == DT_REG) {
VLOG(1) << "Found file: " << current_entry_name;
ReadAndStoreFileContents(current_entry_name.c_str());
} else if (dir_data->d_type == DT_DIR) {
VLOG(1) << "Found subdir: " << current_entry_name;
if (std::string(dir_data->d_name) != "." &&
std::string(dir_data->d_name) != "..") {
VLOG(1) << "Adding to search path: " << current_entry_name;
paths.push_front(current_entry_name);
}
}
}
VLOG(1) << "Oops, no data left. Closing dir.";
closedir(current_dir);
current_dir = NULL;
}
}
}
}
void MemoryCache::ReadToString(const char* filename, std::string* output) {
output->clear();
int fd = open(filename, 0, "r");
if (fd == -1)
return;
char buffer[4096];
ssize_t read_status = read(fd, buffer, sizeof(buffer));
while (read_status > 0) {
output->append(buffer, static_cast<size_t>(read_status));
do {
read_status = read(fd, buffer, sizeof(buffer));
} while (read_status <= 0 && errno == EINTR);
}
close(fd);
}
void MemoryCache::ReadAndStoreFileContents(const char* filename) {
StoreBodyAndHeadersVisitor visitor;
BalsaFrame framer;
framer.set_balsa_visitor(&visitor);
framer.set_balsa_headers(&(visitor.headers));
std::string filename_contents;
ReadToString(filename, &filename_contents);
// Ugly hack to make everything look like 1.1.
if (filename_contents.find("HTTP/1.0") == 0)
filename_contents[7] = '1';
size_t pos = 0;
size_t old_pos = 0;
while (true) {
old_pos = pos;
pos += framer.ProcessInput(filename_contents.data() + pos,
filename_contents.size() - pos);
if (framer.Error() || pos == old_pos) {
LOG(ERROR) << "Unable to make forward progress, or error"
" framing file: " << filename;
if (framer.Error()) {
LOG(INFO) << "********************************************ERROR!";
return;
}
return;
}
if (framer.MessageFullyRead()) {
// If no Content-Length or Transfer-Encoding was captured in the
// file, then the rest of the data is the body. Many of the captures
// from within Chrome don't have content-lengths.
if (!visitor.body.length())
visitor.body = filename_contents.substr(pos);
break;
}
}
visitor.headers.RemoveAllOfHeader("content-length");
visitor.headers.RemoveAllOfHeader("transfer-encoding");
visitor.headers.RemoveAllOfHeader("connection");
visitor.headers.AppendHeader("transfer-encoding", "chunked");
visitor.headers.AppendHeader("connection", "keep-alive");
// Experiment with changing headers for forcing use of cached
// versions of content.
// TODO(mbelshe) REMOVE ME
#if 0
// TODO(mbelshe) append current date.
visitor.headers.RemoveAllOfHeader("date");
if (visitor.headers.HasHeader("expires")) {
visitor.headers.RemoveAllOfHeader("expires");
visitor.headers.AppendHeader("expires",
"Fri, 30 Aug, 2019 12:00:00 GMT");
}
#endif
DCHECK_GE(std::string(filename).size(), cwd_.size() + 1);
DCHECK_EQ(std::string(filename).substr(0, cwd_.size()), cwd_);
DCHECK_EQ(filename[cwd_.size()], '/');
std::string filename_stripped = std::string(filename).substr(cwd_.size() + 1);
LOG(INFO) << "Adding file (" << visitor.body.length()
<< " bytes): " << filename_stripped;
size_t slash_pos = filename_stripped.find('/');
if (slash_pos == std::string::npos) {
slash_pos = filename_stripped.size();
}
InsertFile(
&visitor.headers, filename_stripped.substr(0, slash_pos), visitor.body);
}
FileData* MemoryCache::GetFileData(const std::string& filename) {
Files::iterator fi = files_.end();
if (EndsWith(filename, ".html", true)) {
fi = files_.find(filename.substr(0, filename.size() - 5) + ".http");
}
if (fi == files_.end())
fi = files_.find(filename);
if (fi == files_.end()) {
return NULL;
}
return fi->second;
}
bool MemoryCache::AssignFileData(const std::string& filename,
MemCacheIter* mci) {
mci->file_data = GetFileData(filename);
if (mci->file_data == NULL) {
LOG(ERROR) << "Could not find file data for " << filename;
return false;
}
return true;
}
void MemoryCache::InsertFile(const BalsaHeaders* headers,
const std::string& filename,
const std::string& body) {
InsertFile(new FileData(headers, filename, body));
}
void MemoryCache::InsertFile(FileData* file_data) {
Files::iterator it = files_.find(file_data->filename());
if (it != files_.end()) {
delete it->second;
it->second = file_data;
} else {
files_.insert(std::make_pair(file_data->filename(), file_data));
}
}
void MemoryCache::ClearFiles() {
for (Files::const_iterator i = files_.begin(); i != files_.end(); ++i) {
delete i->second;
}
files_.clear();
}
} // namespace net