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