1 // Copyright (c) 2012 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/dump_cache/cache_dumper.h" 6 7 #include "base/files/file_util.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "net/base/io_buffer.h" 10 #include "net/base/net_errors.h" 11 #include "net/disk_cache/blockfile/entry_impl.h" 12 #include "net/http/http_cache.h" 13 #include "net/http/http_response_headers.h" 14 #include "net/http/http_response_info.h" 15 #include "net/tools/dump_cache/url_to_filename_encoder.h" 16 17 CacheDumper::CacheDumper(disk_cache::Backend* cache) 18 : cache_(cache) { 19 } 20 21 int CacheDumper::CreateEntry(const std::string& key, 22 disk_cache::Entry** entry, 23 const net::CompletionCallback& callback) { 24 return cache_->CreateEntry(key, entry, callback); 25 } 26 27 int CacheDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset, 28 net::IOBuffer* buf, int buf_len, 29 const net::CompletionCallback& callback) { 30 return entry->WriteData(index, offset, buf, buf_len, callback, false); 31 } 32 33 void CacheDumper::CloseEntry(disk_cache::Entry* entry, base::Time last_used, 34 base::Time last_modified) { 35 if (entry) { 36 static_cast<disk_cache::EntryImpl*>(entry)->SetTimes(last_used, 37 last_modified); 38 entry->Close(); 39 } 40 } 41 42 // A version of CreateDirectory which supports lengthy filenames. 43 // Returns true on success, false on failure. 44 bool SafeCreateDirectory(const base::FilePath& path) { 45 #ifdef WIN32_LARGE_FILENAME_SUPPORT 46 // Due to large paths on windows, it can't simply do a 47 // CreateDirectory("a/b/c"). Instead, create each subdirectory manually. 48 bool rv = false; 49 std::wstring::size_type pos(0); 50 std::wstring backslash(L"\\"); 51 52 // If the path starts with the long file header, skip over that 53 const std::wstring kLargeFilenamePrefix(L"\\\\?\\"); 54 std::wstring header(kLargeFilenamePrefix); 55 if (path.value().find(header) == 0) 56 pos = 4; 57 58 // Create the subdirectories individually 59 while ((pos = path.value().find(backslash, pos)) != std::wstring::npos) { 60 base::FilePath::StringType subdir = path.value().substr(0, pos); 61 CreateDirectoryW(subdir.c_str(), NULL); 62 // we keep going even if directory creation failed. 63 pos++; 64 } 65 // Now create the full path 66 return CreateDirectoryW(path.value().c_str(), NULL) == TRUE; 67 #else 68 return base::CreateDirectory(path); 69 #endif 70 } 71 72 DiskDumper::DiskDumper(const base::FilePath& path) 73 : path_(path), entry_(NULL) { 74 base::CreateDirectory(path); 75 } 76 77 int DiskDumper::CreateEntry(const std::string& key, 78 disk_cache::Entry** entry, 79 const net::CompletionCallback& callback) { 80 // The URL may not start with a valid protocol; search for it. 81 int urlpos = key.find("http"); 82 std::string url = urlpos > 0 ? key.substr(urlpos) : key; 83 std::string base_path = path_.MaybeAsASCII(); 84 std::string new_path = 85 net::UrlToFilenameEncoder::Encode(url, base_path, false); 86 entry_path_ = base::FilePath::FromUTF8Unsafe(new_path); 87 88 #ifdef WIN32_LARGE_FILENAME_SUPPORT 89 // In order for long filenames to work, we'll need to prepend 90 // the windows magic token. 91 const std::wstring kLongFilenamePrefix(L"\\\\?\\"); 92 // There is no way to prepend to a filename. We simply *have* 93 // to convert to a wstring to do this. 94 std::wstring name = kLongFilenamePrefix; 95 name.append(entry_path_.value()); 96 entry_path_ = base::FilePath(name); 97 #endif 98 99 entry_url_ = key; 100 101 SafeCreateDirectory(entry_path_.DirName()); 102 103 base::FilePath::StringType file = entry_path_.value(); 104 #ifdef WIN32_LARGE_FILENAME_SUPPORT 105 entry_ = CreateFileW(file.c_str(), GENERIC_WRITE|GENERIC_READ, 0, 0, 106 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 107 if (entry_ == INVALID_HANDLE_VALUE) 108 wprintf(L"CreateFileW (%s) failed: %d\n", file.c_str(), GetLastError()); 109 return (entry_ != INVALID_HANDLE_VALUE) ? net::OK : net::ERR_FAILED; 110 #else 111 entry_ = base::OpenFile(entry_path_, "w+"); 112 return (entry_ != NULL) ? net::OK : net::ERR_FAILED; 113 #endif 114 } 115 116 // Utility Function to create a normalized header string from a 117 // HttpResponseInfo. The output will be formatted exactly 118 // like so: 119 // HTTP/<version> <status_code> <status_text>\n 120 // [<header-name>: <header-values>\n]* 121 // meaning, each line is \n-terminated, and there is no extra whitespace 122 // beyond the single space separators shown (of course, values can contain 123 // whitespace within them). If a given header-name appears more than once 124 // in the set of headers, they are combined into a single line like so: 125 // <header-name>: <header-value1>, <header-value2>, ...<header-valueN>\n 126 // 127 // DANGER: For some headers (e.g., "Set-Cookie"), the normalized form can be 128 // a lossy format. This is due to the fact that some servers generate 129 // Set-Cookie headers that contain unquoted commas (usually as part of the 130 // value of an "expires" attribute). So, use this function with caution. Do 131 // not expect to be able to re-parse Set-Cookie headers from this output. 132 // 133 // NOTE: Do not make any assumptions about the encoding of this output 134 // string. It may be non-ASCII, and the encoding used by the server is not 135 // necessarily known to us. Do not assume that this output is UTF-8! 136 void GetNormalizedHeaders(const net::HttpResponseInfo& info, 137 std::string* output) { 138 // Start with the status line 139 output->assign(info.headers->GetStatusLine()); 140 output->append("\r\n"); 141 142 // Enumerate the headers 143 void* iter = 0; 144 std::string name, value; 145 while (info.headers->EnumerateHeaderLines(&iter, &name, &value)) { 146 output->append(name); 147 output->append(": "); 148 output->append(value); 149 output->append("\r\n"); 150 } 151 152 // Mark the end of headers 153 output->append("\r\n"); 154 } 155 156 int DiskDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset, 157 net::IOBuffer* buf, int buf_len, 158 const net::CompletionCallback& callback) { 159 if (!entry_) 160 return 0; 161 162 std::string headers; 163 const char *data; 164 size_t len; 165 if (index == 0) { // Stream 0 is the headers. 166 net::HttpResponseInfo response_info; 167 bool truncated; 168 if (!net::HttpCache::ParseResponseInfo(buf->data(), buf_len, 169 &response_info, &truncated)) 170 return 0; 171 172 // Skip this entry if it was truncated (results in an empty file). 173 if (truncated) 174 return buf_len; 175 176 // Remove the size headers. 177 response_info.headers->RemoveHeader("transfer-encoding"); 178 response_info.headers->RemoveHeader("content-length"); 179 response_info.headers->RemoveHeader("x-original-url"); 180 181 // Convert the headers into a string ending with LF. 182 GetNormalizedHeaders(response_info, &headers); 183 184 // Append a header for the original URL. 185 std::string url = entry_url_; 186 // strip off the "XXGET" which may be in the key. 187 std::string::size_type pos(0); 188 if ((pos = url.find("http")) != 0) { 189 if (pos != std::string::npos) 190 url = url.substr(pos); 191 } 192 std::string x_original_url = "X-Original-Url: " + url + "\r\n"; 193 // we know that the last two bytes are CRLF. 194 headers.replace(headers.length() - 2, 0, x_original_url); 195 196 data = headers.c_str(); 197 len = headers.size(); 198 } else if (index == 1) { 199 data = buf->data(); 200 len = buf_len; 201 } else { 202 return 0; 203 } 204 205 #ifdef WIN32_LARGE_FILENAME_SUPPORT 206 DWORD bytes; 207 if (!WriteFile(entry_, data, len, &bytes, 0)) 208 return 0; 209 210 return bytes; 211 #else 212 return fwrite(data, 1, len, entry_); 213 #endif 214 } 215 216 void DiskDumper::CloseEntry(disk_cache::Entry* entry, base::Time last_used, 217 base::Time last_modified) { 218 #ifdef WIN32_LARGE_FILENAME_SUPPORT 219 CloseHandle(entry_); 220 #else 221 base::CloseFile(entry_); 222 #endif 223 } 224