- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / loader / redirect_to_file_resource_handler.cc
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 "content/browser/loader/redirect_to_file_resource_handler.h"
6
7 #include "base/bind.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"
22
23 using webkit_blob::ShareableFileReference;
24
25 namespace {
26
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 {
38  public:
39   DependentIOBuffer(net::IOBuffer* backing, char* memory)
40       : net::WrappedIOBuffer(memory),
41         backing_(backing) {
42   }
43  private:
44   virtual ~DependentIOBuffer() {}
45
46   scoped_refptr<net::IOBuffer> backing_;
47 };
48
49 }  // namespace
50
51 namespace content {
52
53 static const int kInitialReadBufSize = 32768;
54 static const int kMaxReadBufSize = 524288;
55
56 RedirectToFileResourceHandler::RedirectToFileResourceHandler(
57     scoped_ptr<ResourceHandler> next_handler,
58     net::URLRequest* request,
59     ResourceDispatcherHostImpl* host)
60     : LayeredResourceHandler(request, next_handler.Pass()),
61       weak_factory_(this),
62       host_(host),
63       buf_(new net::GrowableIOBuffer()),
64       buf_write_pending_(false),
65       write_cursor_(0),
66       write_callback_pending_(false),
67       next_buffer_size_(kInitialReadBufSize),
68       did_defer_(false),
69       completed_during_write_(false) {
70 }
71
72 RedirectToFileResourceHandler::~RedirectToFileResourceHandler() {
73 }
74
75 bool RedirectToFileResourceHandler::OnResponseStarted(
76     int request_id,
77     ResourceResponse* response,
78     bool* defer) {
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();
83   }
84   return next_handler_->OnResponseStarted(request_id, response, defer);
85 }
86
87 bool RedirectToFileResourceHandler::OnWillStart(int request_id,
88                                                 const GURL& url,
89                                                 bool* defer) {
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()));
100     return true;
101   }
102   return next_handler_->OnWillStart(request_id, url, defer);
103 }
104
105 bool RedirectToFileResourceHandler::OnWillRead(
106     int request_id,
107     scoped_refptr<net::IOBuffer>* buf,
108     int* buf_size,
109     int min_size) {
110   DCHECK_EQ(-1, min_size);
111
112   if (buf_->capacity() < next_buffer_size_)
113     buf_->SetCapacity(next_buffer_size_);
114
115   // We should have paused this network request already if the buffer is full.
116   DCHECK(!BufIsFull());
117
118   *buf = buf_.get();
119   *buf_size = buf_->RemainingCapacity();
120
121   buf_write_pending_ = true;
122   return true;
123 }
124
125 bool RedirectToFileResourceHandler::OnReadCompleted(int request_id,
126                                                     int bytes_read,
127                                                     bool* defer) {
128   DCHECK(buf_write_pending_);
129   buf_write_pending_ = false;
130
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);
135
136   if (BufIsFull()) {
137     did_defer_ = *defer = true;
138
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);
143     }
144   }
145
146   return WriteMore();
147 }
148
149 bool RedirectToFileResourceHandler::OnResponseCompleted(
150     int request_id,
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;
157     return false;
158   }
159   return next_handler_->OnResponseCompleted(request_id, status, security_info);
160 }
161
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(
167       file_path,
168       ShareableFileReference::DELETE_ON_FINAL_RELEASE,
169       BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get());
170   file_stream_.reset(
171       new net::FileStream(file_handle.ReleaseValue(),
172                           base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC,
173                           NULL));
174   const ResourceRequestInfo* info = GetRequestInfo();
175   host_->RegisterDownloadedTempFile(
176       info->GetChildID(), info->GetRequestID(), deletable_file_.get());
177   ResumeIfDeferred();
178 }
179
180 void RedirectToFileResourceHandler::DidWriteToFile(int result) {
181   write_callback_pending_ = false;
182   int request_id = GetRequestID();
183
184   bool failed = false;
185   if (result > 0) {
186     next_handler_->OnDataDownloaded(request_id, result);
187     write_cursor_ += result;
188     failed = !WriteMore();
189   } else {
190     failed = true;
191   }
192
193   if (failed) {
194     ResumeIfDeferred();
195   } else if (completed_during_write_) {
196     if (next_handler_->OnResponseCompleted(request_id,
197                                            completed_status_,
198                                            completed_security_info_)) {
199       ResumeIfDeferred();
200     }
201   }
202 }
203
204 bool RedirectToFileResourceHandler::WriteMore() {
205   DCHECK(file_stream_.get());
206   for (;;) {
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_) {
211         if (BufIsFull())
212           ResumeIfDeferred();
213         buf_->set_offset(0);
214         write_cursor_ = 0;
215       }
216       return true;
217     }
218     if (write_callback_pending_)
219       return true;
220     DCHECK(write_cursor_ < buf_->offset());
221
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
226     // works.
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
235     // spurious.
236     scoped_refptr<DependentIOBuffer> wrapped = new DependentIOBuffer(
237         buf_.get(), buf_->StartOfBuffer() + write_cursor_);
238     int write_len = buf_->offset() - write_cursor_;
239
240     int rv = file_stream_->Write(
241         wrapped.get(),
242         write_len,
243         base::Bind(&RedirectToFileResourceHandler::DidWriteToFile,
244                    base::Unretained(this)));
245     if (rv == net::ERR_IO_PENDING) {
246       write_callback_pending_ = true;
247       return true;
248     }
249     if (rv <= 0)
250       return false;
251     next_handler_->OnDataDownloaded(GetRequestID(), rv);
252     write_cursor_ += rv;
253   }
254 }
255
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);
262 }
263
264 void RedirectToFileResourceHandler::ResumeIfDeferred() {
265   if (did_defer_) {
266     did_defer_ = false;
267     controller()->Resume();
268   }
269 }
270
271 }  // namespace content