summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/net.gyp3
-rw-r--r--net/tools/dump_cache/cache_dumper.cc190
-rw-r--r--net/tools/dump_cache/cache_dumper.h82
-rw-r--r--net/tools/dump_cache/dump_cache.cc35
-rw-r--r--net/tools/dump_cache/upgrade.cc92
-rw-r--r--net/tools/dump_cache/url_to_filename_encoder.h119
6 files changed, 476 insertions, 45 deletions
diff --git a/net/net.gyp b/net/net.gyp
index da8f4b5..509f9ec 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -732,9 +732,12 @@
'../base/base.gyp:base',
],
'sources': [
+ 'tools/dump_cache/cache_dumper.cc',
+ 'tools/dump_cache/cache_dumper.h',
'tools/dump_cache/dump_cache.cc',
'tools/dump_cache/dump_files.cc',
'tools/dump_cache/upgrade.cc',
+ 'tools/dump_cache/url_to_filename_encoder.h',
],
},
],
diff --git a/net/tools/dump_cache/cache_dumper.cc b/net/tools/dump_cache/cache_dumper.cc
new file mode 100644
index 0000000..29f7be4
--- /dev/null
+++ b/net/tools/dump_cache/cache_dumper.cc
@@ -0,0 +1,190 @@
+// 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/dump_cache/cache_dumper.h"
+
+#include "net/base/io_buffer.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+bool CacheDumper::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry) {
+ return cache_->CreateEntry(key, entry);
+}
+
+bool CacheDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len) {
+ int written = entry->WriteData(index, offset, buf, buf_len, NULL, false);
+ return written == buf_len;
+}
+
+void CacheDumper::CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) {
+ if (entry) {
+ static_cast<disk_cache::EntryImpl*>(entry)->SetTimes(last_used,
+ last_modified);
+ entry->Close();
+ }
+}
+
+// A version of CreateDirectory which supports lengthy filenames.
+// Returns true on success, false on failure.
+bool SafeCreateDirectory(const std::wstring& path) {
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ // Due to large paths on windows, it can't simply do a
+ // CreateDirectory("a/b/c"). Instead, create each subdirectory manually.
+ bool rv = false;
+ std::wstring::size_type pos(0);
+ std::wstring backslash(L"\\");
+
+ // If the path starts with the long file header, skip over that
+ const std::wstring kLargeFilenamePrefix(L"\\\\?\\");
+ std::wstring header(kLargeFilenamePrefix);
+ if (path.find(header) == 0)
+ pos = 4;
+
+ // Create the subdirectories individually
+ while((pos = path.find(backslash, pos)) != std::wstring::npos) {
+ std::wstring subdir = path.substr(0, pos);
+ CreateDirectoryW(subdir.c_str(), NULL);
+ // we keep going even if directory creation failed.
+ pos++;
+ }
+ // Now create the full path
+ return CreateDirectoryW(path.c_str(), NULL) == TRUE;
+#else
+ return file_util::CreateDirectory(path);
+#endif
+}
+
+bool DiskDumper::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry) {
+ FilePath path(path_);
+ entry_path_ = net::UrlToFilenameEncoder::Encode(key, path);
+
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ // In order for long filenames to work, we'll need to prepend
+ // the windows magic token.
+ const std::wstring kLongFilenamePrefix(L"\\\\?\\");
+ entry_path_ = FilePath(kLongFilenamePrefix).Append(entry_path_);
+#endif
+
+ entry_url_ = key;
+
+ FilePath directory = entry_path_.DirName();
+ SafeCreateDirectory(directory.value());
+
+ std::wstring file = entry_path_.value();
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ entry_ = CreateFileW(file.c_str(), GENERIC_WRITE|GENERIC_READ, 0, 0,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
+ return entry_ != INVALID_HANDLE_VALUE;
+#else
+ entry_ = file_util::OpenFile(entry_path_, "w+");
+ return entry_ != NULL;
+#endif
+}
+
+// Utility Function to create a normalized header string from a
+// HttpResponseInfo. The output will be formatted exactly
+// like so:
+// HTTP/<version> <status_code> <status_text>\n
+// [<header-name>: <header-values>\n]*
+// meaning, each line is \n-terminated, and there is no extra whitespace
+// beyond the single space separators shown (of course, values can contain
+// whitespace within them). If a given header-name appears more than once
+// in the set of headers, they are combined into a single line like so:
+// <header-name>: <header-value1>, <header-value2>, ...<header-valueN>\n
+//
+// DANGER: For some headers (e.g., "Set-Cookie"), the normalized form can be
+// a lossy format. This is due to the fact that some servers generate
+// Set-Cookie headers that contain unquoted commas (usually as part of the
+// value of an "expires" attribute). So, use this function with caution. Do
+// not expect to be able to re-parse Set-Cookie headers from this output.
+//
+// NOTE: Do not make any assumptions about the encoding of this output
+// string. It may be non-ASCII, and the encoding used by the server is not
+// necessarily known to us. Do not assume that this output is UTF-8!
+void GetNormalizedHeaders(const net::HttpResponseInfo& info,
+ std::string* output) {
+ // Start with the status line
+ output->assign(info.headers->GetStatusLine());
+ output->append("\r\n");
+
+ // Enumerate the headers
+ void* iter = 0;
+ std::string name, value;
+ while (info.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ output->append(name);
+ output->append(": ");
+ output->append(value);
+ output->append("\r\n");
+ }
+
+ // Mark the end of headers
+ output->append("\r\n");
+}
+
+bool DiskDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len) {
+ if (!entry_)
+ return false;
+
+ std::string headers;
+ const char *data;
+ int len;
+ if (index == 0) { // Stream 0 is the headers.
+ net::HttpResponseInfo response_info;
+ if (!net::HttpCache::ParseResponseInfo(buf->data(), buf_len,
+ &response_info))
+ return false;
+
+ // Remove the size headers.
+ response_info.headers->RemoveHeader("transfer-encoding");
+ response_info.headers->RemoveHeader("content-length");
+ response_info.headers->RemoveHeader("x-original-url");
+
+ // Convert the headers into a string ending with LF.
+ GetNormalizedHeaders(response_info, &headers);
+
+ // Append a header for the original URL.
+ std::string url = entry_url_;
+ // strip off the "XXGET" which may be in the key.
+ std::string::size_type pos(0);
+ if ((pos = url.find("http")) != 0) {
+ if (pos != std::string::npos)
+ url = url.substr(pos);
+ }
+ std::string x_original_url = "X-Original-Url: " + url + "\r\n";
+ // we know that the last two bytes are CRLF.
+ headers.replace(headers.length() - 2, 0, x_original_url);
+
+ data = headers.c_str();
+ len = headers.size();
+ } else if (index == 1) { // Stream 1 is the data.
+ data = buf->data();
+ len = buf_len;
+ }
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ DWORD bytes;
+ DWORD rv = WriteFile(entry_, data, len, &bytes, 0);
+ return rv == TRUE && bytes == len;
+#else
+ int bytes = fwrite(data, 1, len, entry_);
+ return bytes == len;
+#endif
+}
+
+void DiskDumper::CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) {
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ CloseHandle(entry_);
+#else
+ file_util::CloseFile(entry_);
+#endif
+}
+
diff --git a/net/tools/dump_cache/cache_dumper.h b/net/tools/dump_cache/cache_dumper.h
new file mode 100644
index 0000000..8a11269
--- /dev/null
+++ b/net/tools/dump_cache/cache_dumper.h
@@ -0,0 +1,82 @@
+// 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.
+
+#ifndef NET_TOOLS_DUMP_CACHE_DUMPER_H_
+#define NET_TOOLS_DUMP_CACHE_DUMPER_H_
+
+#include <string>
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "net/disk_cache/backend_impl.h"
+
+#ifdef WIN32
+// Dumping the cache often creates very large filenames, which are tricky
+// on windows. Most API calls don't support large filenames, including
+// most of the base library functions. Unfortunately, adding "\\?\" into
+// the filename support is tricky. Instead, if WIN32_LARGE_FILENAME_SUPPORT
+// is set, we use direct WIN32 APIs for manipulating the files.
+#define WIN32_LARGE_FILENAME_SUPPORT
+#endif
+
+// An abstract class for writing cache dump data.
+class CacheDumpWriter {
+ public:
+ // Creates an entry to be written.
+ // On success, populates the |entry|.
+ // Returns true on success, false otherwise.
+ virtual bool CreateEntry(const std::string& key,
+ disk_cache::Entry** entry) = 0;
+
+ // Write to the current entry.
+ // Returns true on success, false otherwise.
+ virtual bool WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len) = 0;
+
+ // Close the current entry.
+ virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) = 0;
+};
+
+// Writes data to a cache.
+class CacheDumper : public CacheDumpWriter {
+ public:
+ CacheDumper(disk_cache::BackendImpl* cache) : cache_(cache) {};
+
+ virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry);
+ virtual bool WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len);
+ virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified);
+
+ private:
+ disk_cache::BackendImpl* cache_;
+};
+
+// Writes data to a disk.
+class DiskDumper : public CacheDumpWriter {
+ public:
+ DiskDumper(const std::wstring& path) : path_(path), entry_(NULL) {
+ file_util::CreateDirectory(path);
+ };
+ virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry);
+ virtual bool WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len);
+ virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified);
+
+ private:
+ std::wstring path_;
+ // This is a bit of a hack. As we get a CreateEntry, we coin the current
+ // entry_path_ where we write that entry to disk. Subsequent calls to
+ // WriteEntry() utilize this path for writing to disk.
+ FilePath entry_path_;
+ std::string entry_url_;
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ HANDLE entry_;
+#else
+ FILE* entry_;
+#endif
+};
+
+#endif // NET_TOOLS_DUMP_CACHE_DUMPER_H_
diff --git a/net/tools/dump_cache/dump_cache.cc b/net/tools/dump_cache/dump_cache.cc
index f14c7a9..748bf0f 100644
--- a/net/tools/dump_cache/dump_cache.cc
+++ b/net/tools/dump_cache/dump_cache.cc
@@ -29,7 +29,7 @@ int GetMajorVersion(const std::wstring& input_path);
int DumpContents(const std::wstring& input_path);
int DumpHeaders(const std::wstring& input_path);
int RunSlave(const std::wstring& input_path, const std::wstring& pipe_number);
-int Upgrade(const std::wstring& output_path, HANDLE pipe);
+int CopyCache(const std::wstring& output_path, HANDLE pipe, bool copy_to_text);
HANDLE CreateServer(std::wstring* pipe_number);
const char kUpgradeHelp[] =
@@ -50,6 +50,9 @@ const wchar_t kDumpHeaders[] = L"dump-headers";
// Dumps all entries to stdout.
const wchar_t kDumpContents[] = L"dump-contents";
+// Convert the cache to files.
+const wchar_t kDumpToFiles[] = L"dump-to-files";
+
// Upgrade an old version to the current one.
const wchar_t kUpgrade[] = L"upgrade";
@@ -63,6 +66,7 @@ int Help() {
printf("--dump-headers: display file headers\n");
printf("--dump-contents: display all entries\n");
printf("--upgrade: copy contents to the output path\n");
+ printf("--dump-to-files: write the contents of the cache to files\n");
return INVALID_ARGUMENT;
}
@@ -75,14 +79,21 @@ int LaunchSlave(const CommandLine& command_line,
size_t to_remove = hacked_command_line.find(old_exe);
hacked_command_line.erase(to_remove, old_exe.size());
- std::wstring new_program = StringPrintf(L"%ls%d.exe", L"dump_cache_",
- version);
+ bool do_upgrade = command_line.HasSwitch(kUpgrade);
+ bool do_convert_to_text = command_line.HasSwitch(kDumpToFiles);
+
+ std::wstring new_program;
+ if (do_upgrade)
+ new_program = StringPrintf(L"%ls%d.exe", L"dump_cache_", version);
+ else
+ new_program = StringPrintf(L"dump_cache.exe");
+
hacked_command_line.insert(to_remove, new_program);
CommandLine new_command_line(L"");
new_command_line.ParseFromString(hacked_command_line);
- if (command_line.HasSwitch(kUpgrade))
+ if (do_upgrade || do_convert_to_text)
new_command_line.AppendSwitch(kSlave);
new_command_line.AppendSwitchWithValue(kPipe, pipe_number);
@@ -110,13 +121,17 @@ int main(int argc, const char* argv[]) {
bool upgrade = false;
bool slave_required = false;
- std::wstring output_path;
- if (command_line.HasSwitch(kUpgrade)) {
- output_path = command_line.GetSwitchValue(kOutputPath);
+ bool copy_to_text = false;
+ std::wstring output_path = command_line.GetSwitchValue(kOutputPath);
+ if (command_line.HasSwitch(kUpgrade))
+ upgrade = true;
+ if (command_line.HasSwitch(kDumpToFiles))
+ copy_to_text = true;
+
+ if (upgrade || copy_to_text) {
if (output_path.empty())
return Help();
slave_required = true;
- upgrade = true;
}
int version = GetMajorVersion(input_path);
@@ -148,8 +163,8 @@ int main(int argc, const char* argv[]) {
return ret;
}
- if (upgrade)
- return Upgrade(output_path, server);
+ if (upgrade || copy_to_text)
+ return CopyCache(output_path, server, copy_to_text);
if (slave_required) {
// Wait until the slave starts dumping data before we quit. Lazy "fix" for a
diff --git a/net/tools/dump_cache/upgrade.cc b/net/tools/dump_cache/upgrade.cc
index bf27c9d..de0f878 100644
--- a/net/tools/dump_cache/upgrade.cc
+++ b/net/tools/dump_cache/upgrade.cc
@@ -4,10 +4,16 @@
#include "base/logging.h"
#include "base/message_loop.h"
+#include "base/scoped_ptr.h"
#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
#include "net/disk_cache/backend_impl.h"
#include "net/disk_cache/entry_impl.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/tools/dump_cache/cache_dumper.h"
namespace {
@@ -102,7 +108,7 @@ enum {
class BaseSM : public MessageLoopForIO::IOHandler {
public:
- BaseSM(disk_cache::BackendImpl* cache, HANDLE channel);
+ BaseSM(HANDLE channel);
virtual ~BaseSM();
protected:
@@ -113,7 +119,6 @@ class BaseSM : public MessageLoopForIO::IOHandler {
MessageLoopForIO::IOContext in_context_;
MessageLoopForIO::IOContext out_context_;
- disk_cache::BackendImpl* cache_;
disk_cache::EntryImpl* entry_;
HANDLE channel_;
int state_;
@@ -125,9 +130,8 @@ class BaseSM : public MessageLoopForIO::IOHandler {
DISALLOW_COPY_AND_ASSIGN(BaseSM);
};
-BaseSM::BaseSM(disk_cache::BackendImpl* cache, HANDLE channel)
- : cache_(cache), entry_(NULL), channel_(channel), state_(0),
- pending_count_(0) {
+BaseSM::BaseSM(HANDLE channel)
+ : entry_(NULL), channel_(channel), state_(0), pending_count_(0) {
in_buffer_.reset(new char[kChannelSize]);
out_buffer_.reset(new char[kChannelSize]);
input_ = reinterpret_cast<IoBuffer*>(in_buffer_.get());
@@ -195,9 +199,12 @@ bool BaseSM::IsPending() {
class MasterSM : public BaseSM {
public:
- MasterSM(disk_cache::BackendImpl* cache, HANDLE channel)
- : BaseSM(cache, channel) {}
- virtual ~MasterSM() {}
+ MasterSM(const std::wstring& path, HANDLE channel, bool dump_to_disk)
+ : BaseSM(channel), path_(path), dump_to_disk_(dump_to_disk) {
+ }
+ virtual ~MasterSM() {
+ delete writer_;
+ }
bool DoInit();
virtual void OnIOCompleted(MessageLoopForIO::IOContext* context,
@@ -236,6 +243,10 @@ class MasterSM : public BaseSM {
int bytes_remaining_;
int offset_;
int copied_entries_;
+ scoped_ptr<disk_cache::BackendImpl> cache_;
+ CacheDumpWriter* writer_;
+ const std::wstring& path_;
+ bool dump_to_disk_;
};
void MasterSM::OnIOCompleted(MessageLoopForIO::IOContext* context,
@@ -287,6 +298,19 @@ bool MasterSM::DoInit() {
DEBUGMSG("Master DoInit\n");
DCHECK(state_ == MASTER_INITIAL);
+ if (dump_to_disk_) {
+ writer_ = new DiskDumper(path_);
+ } else {
+ cache_.reset(new disk_cache::BackendImpl(path_));
+ if (!cache_->Init()) {
+ printf("Unable to initialize new files\n");
+ return false;
+ }
+ writer_ = new CacheDumper(cache_.get());
+ }
+ if (!writer_)
+ return false;
+
copied_entries_ = 0;
remote_entry_ = 0;
@@ -343,17 +367,20 @@ void MasterSM::DoGetKey(int bytes_read) {
std::string key(input_->buffer);
DCHECK(key.size() == input_->msg.buffer_bytes - 1);
- if (!cache_->CreateEntry(key,
- reinterpret_cast<disk_cache::Entry**>(&entry_))) {
+
+ if (!writer_->CreateEntry(key,
+ reinterpret_cast<disk_cache::Entry**>(&entry_))) {
printf("Skipping entry \"%s\" (name conflict!)\n", key.c_str());
return SendGetPrevEntry();
}
- if (key.size() < 60) {
- DEBUGMSG("Entry \"%s\" created\n", key.c_str());
- } else {
- DEBUGMSG("Entry (long name) created\n", key.c_str());
+ if (key.size() >= 64) {
+ key[60] = '.';
+ key[61] = '.';
+ key[62] = '.';
+ key[63] = '\0';
}
+ DEBUGMSG("Entry \"%s\" created\n", key.c_str());
state_ = MASTER_GET_USE_TIMES;
Message msg;
msg.command = GET_USE_TIMES;
@@ -403,8 +430,7 @@ void MasterSM::DoGetDataSize() {
void MasterSM::CloseEntry() {
DEBUGMSG("Master CloseEntry\n");
printf("%c\r", copied_entries_ % 2 ? 'x' : '+');
- entry_->SetTimes(last_used_, last_modified_);
- entry_->Close();
+ writer_->CloseEntry(entry_, last_used_, last_modified_);
entry_ = NULL;
copied_entries_++;
SendGetPrevEntry();
@@ -447,8 +473,7 @@ void MasterSM::DoReadData(int bytes_read) {
scoped_refptr<net::WrappedIOBuffer> buf =
new net::WrappedIOBuffer(input_->buffer);
- if (read_size != entry_->WriteData(stream_, offset_, buf, read_size, NULL,
- false))
+ if (!writer_->WriteEntry(entry_, stream_, offset_, buf, read_size))
return Fail();
offset_ += read_size;
@@ -482,8 +507,15 @@ void MasterSM::Fail() {
class SlaveSM : public BaseSM {
public:
- SlaveSM(disk_cache::BackendImpl* cache, HANDLE channel)
- : BaseSM(cache, channel), iterator_(NULL) {}
+ SlaveSM(const std::wstring& path, HANDLE channel)
+ : BaseSM(channel), iterator_(NULL) {
+ cache_.reset(new disk_cache::BackendImpl(path));
+ if (!cache_->Init()) {
+ printf("Unable to open cache files\n");
+ return;
+ }
+ cache_->SetUpgradeMode();
+ }
virtual ~SlaveSM();
bool DoInit();
@@ -509,6 +541,8 @@ class SlaveSM : public BaseSM {
void Fail();
void* iterator_;
+
+ scoped_ptr<disk_cache::BackendImpl> cache_;
};
SlaveSM::~SlaveSM() {
@@ -760,15 +794,10 @@ HANDLE CreateServer(std::wstring* pipe_number) {
}
// This is the controller process for an upgrade operation.
-int Upgrade(const std::wstring& output_path, HANDLE pipe) {
+int CopyCache(const std::wstring& output_path, HANDLE pipe, bool copy_to_text) {
MessageLoop loop(MessageLoop::TYPE_IO);
- disk_cache::BackendImpl cache(output_path);
- if (!cache.Init()) {
- printf("Unable to initialize new files\n");
- return -1;
- }
- MasterSM master(&cache, pipe);
+ MasterSM master(output_path, pipe, copy_to_text);
if (!master.DoInit()) {
printf("Unable to talk with the helper\n");
return -1;
@@ -788,14 +817,7 @@ int RunSlave(const std::wstring& input_path, const std::wstring& pipe_number) {
return -1;
}
- disk_cache::BackendImpl cache(input_path);
- if (!cache.Init()) {
- printf("Unable to open cache files\n");
- return -1;
- }
- cache.SetUpgradeMode();
-
- SlaveSM slave(&cache, pipe);
+ SlaveSM slave(input_path, pipe);
if (!slave.DoInit()) {
printf("Unable to talk with the main process\n");
return -1;
diff --git a/net/tools/dump_cache/url_to_filename_encoder.h b/net/tools/dump_cache/url_to_filename_encoder.h
new file mode 100644
index 0000000..e71d7a0
--- /dev/null
+++ b/net/tools/dump_cache/url_to_filename_encoder.h
@@ -0,0 +1,119 @@
+// 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.
+
+#ifndef NET_TOOLS_DUMP_CACHE_URL_TO_FILE_ENCODER_H_
+#define NET_TOOLS_DUMP_CACHE_URL_TO_FILE_ENCODER_H_
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+
+// Helper class for converting a URL into a filename.
+class UrlToFilenameEncoder {
+ public:
+ // Given a |url| and a |base_path|, returns a FilePath which represents this
+ // |url|.
+ static FilePath Encode(const std::string& url, FilePath base_path) {
+ std::string clean_url(url);
+ if (clean_url.length() && clean_url[clean_url.length()-1] == '/')
+ clean_url.append("index.html");
+
+ GURL gurl(clean_url);
+ FilePath filename(base_path);
+ filename = filename.Append(ASCIIToWide(gurl.host()));
+
+ std::wstring url_filename = ASCIIToWide(gurl.PathForRequest());
+ // Strip the leading '/'
+ if (url_filename[0] == L'/')
+ url_filename = url_filename.substr(1);
+
+ // replace '/' with '\'
+ ConvertToSlashes(url_filename);
+
+ // strip double slashes ("\\")
+ StripDoubleSlashes(url_filename);
+
+ // Save path as filesystem-safe characters
+ url_filename = Escape(url_filename);
+ filename = filename.Append(url_filename);
+
+ return filename;
+ }
+
+ private:
+ // This is the length at which we chop individual subdirectories.
+ // Technically, we shouldn't need to do this, but I found that
+ // even with long-filename support, windows had trouble creating
+ // long subdirectories, and making them shorter helps.
+ static const int kMaximumSubdirectoryLength = 128;
+
+ // Escape the given input |path| and chop any individual components
+ // of the path which are greater than kMaximumSubdirectoryLength characters
+ // into two chunks.
+ static std::wstring Escape(const std::wstring& path) {
+ std::wstring output;
+ int last_slash = 0;
+ for (size_t index = 0; index < path.length(); index++) {
+ wchar_t wide_char = path[index];
+ DCHECK((wide_char & 0xff) == wide_char);
+ char ch = static_cast<char>(wide_char & 0xff);
+ if (ch == 0x5C)
+ last_slash = index;
+ if ((ch == 0x2D) || // hyphen
+ (ch == 0x5C) || (ch == 0x5F) || // backslash, underscore
+ ((0x30 <= ch) && (ch <= 0x39)) || // Digits [0-9]
+ ((0x41 <= ch) && (ch <= 0x5A)) || // Uppercase [A-Z]
+ ((0x61 <= ch) && (ch <= 0x7A))) { // Lowercase [a-z]
+ output.append(&path[index],1);
+ } else {
+ wchar_t encoded[3];
+ encoded[0] = L'x';
+ encoded[1] = ch / 16;
+ encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+ encoded[2] = ch % 16;
+ encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+ output.append(encoded, 3);
+ }
+ if (index - last_slash > kMaximumSubdirectoryLength) {
+ wchar_t backslash = L'\\';
+ output.append(&backslash, 1);
+ last_slash = index;
+ }
+ }
+ return output;
+ }
+
+ // Replace all instances of |from| within |str| as |to|.
+ static void ReplaceAll(std::wstring& str, const std::wstring& from,
+ const std::wstring& to) {
+ std::wstring::size_type pos(0);
+ while((pos = str.find(from, pos)) != std::wstring::npos) {
+ str.replace(pos, from.size(), to);
+ pos += from.size();
+ }
+ }
+
+ // Replace all instances of "/" with "\" in |path|.
+ static void ConvertToSlashes(std::wstring& path) {
+ std::wstring slash(L"/");
+ std::wstring backslash(L"\\");
+ ReplaceAll(path, slash, backslash);
+ }
+
+ // Replace all instances of "\\" with "%5C%5C" in |path|.
+ static void StripDoubleSlashes(std::wstring& path) {
+ std::wstring::size_type pos(0);
+ std::wstring doubleslash(L"\\\\");
+ std::wstring escaped_doubleslash(L"%5C%5C");
+ ReplaceAll(path, doubleslash, escaped_doubleslash);
+ }
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_DUMP_CACHE_URL_TO_FILE_ENCODER_H__
+