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.
5 #include "content/browser/loader/redirect_to_file_resource_handler.h"
8 #include "base/files/file_util_proxy.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "base/platform_file.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "content/browser/loader/resource_dispatcher_host_impl.h"
14 #include "content/browser/loader/resource_request_info_impl.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/common/resource_response.h"
17 #include "net/base/file_stream.h"
18 #include "net/base/io_buffer.h"
19 #include "net/base/mime_sniffer.h"
20 #include "net/base/net_errors.h"
21 #include "webkit/common/blob/shareable_file_reference.h"
23 using webkit_blob::ShareableFileReference;
27 // This class is similar to identically named classes in AsyncResourceHandler
28 // and BufferedResourceHandler, but not quite.
29 // TODO(ncbray) generalize and unify these cases?
30 // In general, it's a bad idea to point to a subbuffer (particularly with
31 // GrowableIOBuffer) because the backing IOBuffer may realloc its data. In this
32 // particular case we know RedirectToFileResourceHandler will not realloc its
33 // buffer while a write is occurring, so we should be safe. This property is
34 // somewhat fragile, however, and depending on it is dangerous. A more
35 // principled approach would require significant refactoring, however, so for
36 // the moment we're relying on fragile properties.
37 class DependentIOBuffer : public net::WrappedIOBuffer {
39 DependentIOBuffer(net::IOBuffer* backing, char* memory)
40 : net::WrappedIOBuffer(memory),
44 virtual ~DependentIOBuffer() {}
46 scoped_refptr<net::IOBuffer> backing_;
53 static const int kInitialReadBufSize = 32768;
54 static const int kMaxReadBufSize = 524288;
56 RedirectToFileResourceHandler::RedirectToFileResourceHandler(
57 scoped_ptr<ResourceHandler> next_handler,
58 net::URLRequest* request,
59 ResourceDispatcherHostImpl* host)
60 : LayeredResourceHandler(request, next_handler.Pass()),
63 buf_(new net::GrowableIOBuffer()),
64 buf_write_pending_(false),
66 write_callback_pending_(false),
67 next_buffer_size_(kInitialReadBufSize),
69 completed_during_write_(false) {
72 RedirectToFileResourceHandler::~RedirectToFileResourceHandler() {
75 bool RedirectToFileResourceHandler::OnResponseStarted(
77 ResourceResponse* response,
79 if (response->head.error_code == net::OK ||
80 response->head.error_code == net::ERR_IO_PENDING) {
81 DCHECK(deletable_file_.get() && !deletable_file_->path().empty());
82 response->head.download_file_path = deletable_file_->path();
84 return next_handler_->OnResponseStarted(request_id, response, defer);
87 bool RedirectToFileResourceHandler::OnWillStart(int request_id,
90 if (!deletable_file_.get()) {
91 // Defer starting the request until we have created the temporary file.
92 // TODO(darin): This is sub-optimal. We should not delay starting the
93 // network request like this.
94 did_defer_ = *defer = true;
95 base::FileUtilProxy::CreateTemporary(
96 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
97 base::PLATFORM_FILE_ASYNC,
98 base::Bind(&RedirectToFileResourceHandler::DidCreateTemporaryFile,
99 weak_factory_.GetWeakPtr()));
102 return next_handler_->OnWillStart(request_id, url, defer);
105 bool RedirectToFileResourceHandler::OnWillRead(
107 scoped_refptr<net::IOBuffer>* buf,
110 DCHECK_EQ(-1, min_size);
112 if (buf_->capacity() < next_buffer_size_)
113 buf_->SetCapacity(next_buffer_size_);
115 // We should have paused this network request already if the buffer is full.
116 DCHECK(!BufIsFull());
119 *buf_size = buf_->RemainingCapacity();
121 buf_write_pending_ = true;
125 bool RedirectToFileResourceHandler::OnReadCompleted(int request_id,
128 DCHECK(buf_write_pending_);
129 buf_write_pending_ = false;
131 // We use the buffer's offset field to record the end of the buffer.
132 int new_offset = buf_->offset() + bytes_read;
133 DCHECK(new_offset <= buf_->capacity());
134 buf_->set_offset(new_offset);
137 did_defer_ = *defer = true;
139 if (buf_->capacity() == bytes_read) {
140 // The network layer has saturated our buffer in one read. Next time, we
141 // should give it a bigger buffer for it to fill.
142 next_buffer_size_ = std::min(next_buffer_size_ * 2, kMaxReadBufSize);
149 bool RedirectToFileResourceHandler::OnResponseCompleted(
151 const net::URLRequestStatus& status,
152 const std::string& security_info) {
153 if (write_callback_pending_) {
154 completed_during_write_ = true;
155 completed_status_ = status;
156 completed_security_info_ = security_info;
159 return next_handler_->OnResponseCompleted(request_id, status, security_info);
162 void RedirectToFileResourceHandler::DidCreateTemporaryFile(
163 base::PlatformFileError /*error_code*/,
164 base::PassPlatformFile file_handle,
165 const base::FilePath& file_path) {
166 deletable_file_ = ShareableFileReference::GetOrCreate(
168 ShareableFileReference::DELETE_ON_FINAL_RELEASE,
169 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get());
171 new net::FileStream(file_handle.ReleaseValue(),
172 base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC,
174 const ResourceRequestInfo* info = GetRequestInfo();
175 host_->RegisterDownloadedTempFile(
176 info->GetChildID(), info->GetRequestID(), deletable_file_.get());
180 void RedirectToFileResourceHandler::DidWriteToFile(int result) {
181 write_callback_pending_ = false;
182 int request_id = GetRequestID();
186 next_handler_->OnDataDownloaded(request_id, result);
187 write_cursor_ += result;
188 failed = !WriteMore();
195 } else if (completed_during_write_) {
196 if (next_handler_->OnResponseCompleted(request_id,
198 completed_security_info_)) {
204 bool RedirectToFileResourceHandler::WriteMore() {
205 DCHECK(file_stream_.get());
207 if (write_cursor_ == buf_->offset()) {
208 // We've caught up to the network load, but it may be in the process of
209 // appending more data to the buffer.
210 if (!buf_write_pending_) {
218 if (write_callback_pending_)
220 DCHECK(write_cursor_ < buf_->offset());
222 // Create a temporary buffer pointing to a subsection of the data buffer so
223 // that it can be passed to Write. This code makes some crazy scary
224 // assumptions about object lifetimes, thread sharing, and that buf_ will
225 // not realloc durring the write due to how the state machine in this class
227 // Note that buf_ is also shared with the code that writes data into the
228 // cache, so modifying it can cause some pretty subtle race conditions:
229 // https://code.google.com/p/chromium/issues/detail?id=152076
230 // We're using DependentIOBuffer instead of DrainableIOBuffer to dodge some
231 // of these issues, for the moment.
232 // TODO(ncbray) make this code less crazy scary.
233 // Also note that Write may increase the refcount of "wrapped" deep in the
234 // bowels of its implementation, the use of scoped_refptr here is not
236 scoped_refptr<DependentIOBuffer> wrapped = new DependentIOBuffer(
237 buf_.get(), buf_->StartOfBuffer() + write_cursor_);
238 int write_len = buf_->offset() - write_cursor_;
240 int rv = file_stream_->Write(
243 base::Bind(&RedirectToFileResourceHandler::DidWriteToFile,
244 base::Unretained(this)));
245 if (rv == net::ERR_IO_PENDING) {
246 write_callback_pending_ = true;
251 next_handler_->OnDataDownloaded(GetRequestID(), rv);
256 bool RedirectToFileResourceHandler::BufIsFull() const {
257 // This is a hack to workaround BufferedResourceHandler's inability to
258 // deal with a ResourceHandler that returns a buffer size of less than
259 // 2 * net::kMaxBytesToSniff from its OnWillRead method.
260 // TODO(darin): Fix this retardation!
261 return buf_->RemainingCapacity() <= (2 * net::kMaxBytesToSniff);
264 void RedirectToFileResourceHandler::ResumeIfDeferred() {
267 controller()->Resume();
271 } // namespace content