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
17CacheDumper::CacheDumper(disk_cache::Backend* cache)
18    : cache_(cache) {
19}
20
21int 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
27int 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
33void 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.
44bool 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
72DiskDumper::DiskDumper(const base::FilePath& path)
73    : path_(path), entry_(NULL) {
74  base::CreateDirectory(path);
75}
76
77int 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!
136void 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
156int 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
216void 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