• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/logging.h"
9 #include "base/threading/thread_restrictions.h"
10 #include "content/browser/loader/resource_request_info_impl.h"
11 #include "content/browser/loader/temporary_file_stream.h"
12 #include "content/public/browser/resource_controller.h"
13 #include "content/public/common/resource_response.h"
14 #include "net/base/file_stream.h"
15 #include "net/base/io_buffer.h"
16 #include "net/base/mime_sniffer.h"
17 #include "net/base/net_errors.h"
18 #include "storage/common/blob/shareable_file_reference.h"
19 
20 using storage::ShareableFileReference;
21 
22 namespace {
23 
24 // This class is similar to identically named classes in AsyncResourceHandler
25 // and BufferedResourceHandler, but not quite.
26 // TODO(ncbray) generalize and unify these cases?
27 // In general, it's a bad idea to point to a subbuffer (particularly with
28 // GrowableIOBuffer) because the backing IOBuffer may realloc its data.  In this
29 // particular case we know RedirectToFileResourceHandler will not realloc its
30 // buffer while a write is occurring, so we should be safe.  This property is
31 // somewhat fragile, however, and depending on it is dangerous.  A more
32 // principled approach would require significant refactoring, however, so for
33 // the moment we're relying on fragile properties.
34 class DependentIOBuffer : public net::WrappedIOBuffer {
35  public:
DependentIOBuffer(net::IOBuffer * backing,char * memory)36   DependentIOBuffer(net::IOBuffer* backing, char* memory)
37       : net::WrappedIOBuffer(memory),
38         backing_(backing) {
39   }
40  private:
~DependentIOBuffer()41   virtual ~DependentIOBuffer() {}
42 
43   scoped_refptr<net::IOBuffer> backing_;
44 };
45 
46 }  // namespace
47 
48 namespace content {
49 
50 static const int kInitialReadBufSize = 32768;
51 static const int kMaxReadBufSize = 524288;
52 
53 // A separate IO thread object to manage the lifetime of the net::FileStream and
54 // the ShareableFileReference. When the handler is destroyed, it asynchronously
55 // closes net::FileStream after all pending writes complete. Only after the
56 // stream is closed is the ShareableFileReference released, to ensure the
57 // temporary is not deleted before it is closed.
58 class RedirectToFileResourceHandler::Writer {
59  public:
Writer(RedirectToFileResourceHandler * handler,scoped_ptr<net::FileStream> file_stream,ShareableFileReference * deletable_file)60   Writer(RedirectToFileResourceHandler* handler,
61          scoped_ptr<net::FileStream> file_stream,
62          ShareableFileReference* deletable_file)
63       : handler_(handler),
64         file_stream_(file_stream.Pass()),
65         is_writing_(false),
66         deletable_file_(deletable_file) {
67     DCHECK(!deletable_file_->path().empty());
68   }
69 
is_writing() const70   bool is_writing() const { return is_writing_; }
path() const71   const base::FilePath& path() const { return deletable_file_->path(); }
72 
Write(net::IOBuffer * buf,int buf_len)73   int Write(net::IOBuffer* buf, int buf_len) {
74     DCHECK(!is_writing_);
75     DCHECK(handler_);
76     int result = file_stream_->Write(
77         buf, buf_len,
78         base::Bind(&Writer::DidWriteToFile, base::Unretained(this)));
79     if (result == net::ERR_IO_PENDING)
80       is_writing_ = true;
81     return result;
82   }
83 
Close()84   void Close() {
85     handler_ = NULL;
86     if (!is_writing_)
87       CloseAndDelete();
88   }
89 
90  private:
91   // Only DidClose can delete this.
~Writer()92   ~Writer() {
93   }
94 
DidWriteToFile(int result)95   void DidWriteToFile(int result) {
96     DCHECK(is_writing_);
97     is_writing_ = false;
98     if (handler_) {
99       handler_->DidWriteToFile(result);
100     } else {
101       CloseAndDelete();
102     }
103   }
104 
CloseAndDelete()105   void CloseAndDelete() {
106     DCHECK(!is_writing_);
107     int result = file_stream_->Close(base::Bind(&Writer::DidClose,
108                                                 base::Unretained(this)));
109     if (result != net::ERR_IO_PENDING)
110       DidClose(result);
111   }
112 
DidClose(int result)113   void DidClose(int result) {
114     delete this;
115   }
116 
117   RedirectToFileResourceHandler* handler_;
118 
119   scoped_ptr<net::FileStream> file_stream_;
120   bool is_writing_;
121 
122   // We create a ShareableFileReference that's deletable for the temp file
123   // created as a result of the download.
124   scoped_refptr<storage::ShareableFileReference> deletable_file_;
125 
126   DISALLOW_COPY_AND_ASSIGN(Writer);
127 };
128 
RedirectToFileResourceHandler(scoped_ptr<ResourceHandler> next_handler,net::URLRequest * request)129 RedirectToFileResourceHandler::RedirectToFileResourceHandler(
130     scoped_ptr<ResourceHandler> next_handler,
131     net::URLRequest* request)
132     : LayeredResourceHandler(request, next_handler.Pass()),
133       buf_(new net::GrowableIOBuffer()),
134       buf_write_pending_(false),
135       write_cursor_(0),
136       writer_(NULL),
137       next_buffer_size_(kInitialReadBufSize),
138       did_defer_(false),
139       completed_during_write_(false),
140       weak_factory_(this) {
141 }
142 
~RedirectToFileResourceHandler()143 RedirectToFileResourceHandler::~RedirectToFileResourceHandler() {
144   // Orphan the writer to asynchronously close and release the temporary file.
145   if (writer_) {
146     writer_->Close();
147     writer_ = NULL;
148   }
149 }
150 
151 void RedirectToFileResourceHandler::
SetCreateTemporaryFileStreamFunctionForTesting(const CreateTemporaryFileStreamFunction & create_temporary_file_stream)152     SetCreateTemporaryFileStreamFunctionForTesting(
153         const CreateTemporaryFileStreamFunction& create_temporary_file_stream) {
154   create_temporary_file_stream_ = create_temporary_file_stream;
155 }
156 
OnResponseStarted(ResourceResponse * response,bool * defer)157 bool RedirectToFileResourceHandler::OnResponseStarted(
158     ResourceResponse* response,
159     bool* defer) {
160   DCHECK(writer_);
161   response->head.download_file_path = writer_->path();
162   return next_handler_->OnResponseStarted(response, defer);
163 }
164 
OnWillStart(const GURL & url,bool * defer)165 bool RedirectToFileResourceHandler::OnWillStart(const GURL& url, bool* defer) {
166   DCHECK(!writer_);
167 
168   // Defer starting the request until we have created the temporary file.
169   // TODO(darin): This is sub-optimal.  We should not delay starting the
170   // network request like this.
171   will_start_url_ = url;
172   did_defer_ = *defer = true;
173   if (create_temporary_file_stream_.is_null()) {
174     CreateTemporaryFileStream(
175         base::Bind(&RedirectToFileResourceHandler::DidCreateTemporaryFile,
176                    weak_factory_.GetWeakPtr()));
177   } else {
178     create_temporary_file_stream_.Run(
179         base::Bind(&RedirectToFileResourceHandler::DidCreateTemporaryFile,
180                    weak_factory_.GetWeakPtr()));
181   }
182   return true;
183 }
184 
OnWillRead(scoped_refptr<net::IOBuffer> * buf,int * buf_size,int min_size)185 bool RedirectToFileResourceHandler::OnWillRead(
186     scoped_refptr<net::IOBuffer>* buf,
187     int* buf_size,
188     int min_size) {
189   DCHECK_EQ(-1, min_size);
190 
191   if (buf_->capacity() < next_buffer_size_)
192     buf_->SetCapacity(next_buffer_size_);
193 
194   // We should have paused this network request already if the buffer is full.
195   DCHECK(!BufIsFull());
196 
197   *buf = buf_.get();
198   *buf_size = buf_->RemainingCapacity();
199 
200   buf_write_pending_ = true;
201   return true;
202 }
203 
OnReadCompleted(int bytes_read,bool * defer)204 bool RedirectToFileResourceHandler::OnReadCompleted(int bytes_read,
205                                                     bool* defer) {
206   DCHECK(buf_write_pending_);
207   buf_write_pending_ = false;
208 
209   // We use the buffer's offset field to record the end of the buffer.
210   int new_offset = buf_->offset() + bytes_read;
211   DCHECK(new_offset <= buf_->capacity());
212   buf_->set_offset(new_offset);
213 
214   if (BufIsFull()) {
215     did_defer_ = *defer = true;
216 
217     if (buf_->capacity() == bytes_read) {
218       // The network layer has saturated our buffer in one read. Next time, we
219       // should give it a bigger buffer for it to fill.
220       next_buffer_size_ = std::min(next_buffer_size_ * 2, kMaxReadBufSize);
221     }
222   }
223 
224   return WriteMore();
225 }
226 
OnResponseCompleted(const net::URLRequestStatus & status,const std::string & security_info,bool * defer)227 void RedirectToFileResourceHandler::OnResponseCompleted(
228     const net::URLRequestStatus& status,
229     const std::string& security_info,
230     bool* defer) {
231   if (writer_ && writer_->is_writing()) {
232     completed_during_write_ = true;
233     completed_status_ = status;
234     completed_security_info_ = security_info;
235     did_defer_ = true;
236     *defer = true;
237     return;
238   }
239   next_handler_->OnResponseCompleted(status, security_info, defer);
240 }
241 
DidCreateTemporaryFile(base::File::Error error_code,scoped_ptr<net::FileStream> file_stream,ShareableFileReference * deletable_file)242 void RedirectToFileResourceHandler::DidCreateTemporaryFile(
243     base::File::Error error_code,
244     scoped_ptr<net::FileStream> file_stream,
245     ShareableFileReference* deletable_file) {
246   DCHECK(!writer_);
247   if (error_code != base::File::FILE_OK) {
248     controller()->CancelWithError(net::FileErrorToNetError(error_code));
249     return;
250   }
251 
252   writer_ = new Writer(this, file_stream.Pass(), deletable_file);
253 
254   // Resume the request.
255   DCHECK(did_defer_);
256   bool defer = false;
257   if (!next_handler_->OnWillStart(will_start_url_, &defer)) {
258     controller()->Cancel();
259   } else if (!defer) {
260     ResumeIfDeferred();
261   } else {
262     did_defer_ = false;
263   }
264   will_start_url_ = GURL();
265 }
266 
DidWriteToFile(int result)267 void RedirectToFileResourceHandler::DidWriteToFile(int result) {
268   bool failed = false;
269   if (result > 0) {
270     next_handler_->OnDataDownloaded(result);
271     write_cursor_ += result;
272     failed = !WriteMore();
273   } else {
274     failed = true;
275   }
276 
277   if (failed) {
278     DCHECK(!writer_->is_writing());
279     // TODO(davidben): Recover the error code from WriteMore or |result|, as
280     // appropriate.
281     if (completed_during_write_ && completed_status_.is_success()) {
282       // If the request successfully completed mid-write, but the write failed,
283       // convert the status to a failure for downstream.
284       completed_status_.set_status(net::URLRequestStatus::CANCELED);
285       completed_status_.set_error(net::ERR_FAILED);
286     }
287     if (!completed_during_write_)
288       controller()->CancelWithError(net::ERR_FAILED);
289   }
290 
291   if (completed_during_write_ && !writer_->is_writing()) {
292     // Resume shutdown now that all data has been written to disk. Note that
293     // this should run even in the |failed| case above, otherwise a failed write
294     // leaves the handler stuck.
295     bool defer = false;
296     next_handler_->OnResponseCompleted(completed_status_,
297                                        completed_security_info_,
298                                        &defer);
299     if (!defer) {
300       ResumeIfDeferred();
301     } else {
302       did_defer_ = false;
303     }
304   }
305 }
306 
WriteMore()307 bool RedirectToFileResourceHandler::WriteMore() {
308   DCHECK(writer_);
309   for (;;) {
310     if (write_cursor_ == buf_->offset()) {
311       // We've caught up to the network load, but it may be in the process of
312       // appending more data to the buffer.
313       if (!buf_write_pending_) {
314         if (BufIsFull())
315           ResumeIfDeferred();
316         buf_->set_offset(0);
317         write_cursor_ = 0;
318       }
319       return true;
320     }
321     if (writer_->is_writing())
322       return true;
323     DCHECK(write_cursor_ < buf_->offset());
324 
325     // Create a temporary buffer pointing to a subsection of the data buffer so
326     // that it can be passed to Write.  This code makes some crazy scary
327     // assumptions about object lifetimes, thread sharing, and that buf_ will
328     // not realloc durring the write due to how the state machine in this class
329     // works.
330     // Note that buf_ is also shared with the code that writes data into the
331     // cache, so modifying it can cause some pretty subtle race conditions:
332     // https://code.google.com/p/chromium/issues/detail?id=152076
333     // We're using DependentIOBuffer instead of DrainableIOBuffer to dodge some
334     // of these issues, for the moment.
335     // TODO(ncbray) make this code less crazy scary.
336     // Also note that Write may increase the refcount of "wrapped" deep in the
337     // bowels of its implementation, the use of scoped_refptr here is not
338     // spurious.
339     scoped_refptr<DependentIOBuffer> wrapped = new DependentIOBuffer(
340         buf_.get(), buf_->StartOfBuffer() + write_cursor_);
341     int write_len = buf_->offset() - write_cursor_;
342 
343     int rv = writer_->Write(wrapped.get(), write_len);
344     if (rv == net::ERR_IO_PENDING)
345       return true;
346     if (rv <= 0)
347       return false;
348     next_handler_->OnDataDownloaded(rv);
349     write_cursor_ += rv;
350   }
351 }
352 
BufIsFull() const353 bool RedirectToFileResourceHandler::BufIsFull() const {
354   // This is a hack to workaround BufferedResourceHandler's inability to
355   // deal with a ResourceHandler that returns a buffer size of less than
356   // 2 * net::kMaxBytesToSniff from its OnWillRead method.
357   // TODO(darin): Fix this retardation!
358   return buf_->RemainingCapacity() <= (2 * net::kMaxBytesToSniff);
359 }
360 
ResumeIfDeferred()361 void RedirectToFileResourceHandler::ResumeIfDeferred() {
362   if (did_defer_) {
363     did_defer_ = false;
364     controller()->Resume();
365   }
366 }
367 
368 }  // namespace content
369