Home | History | Annotate | Download | only in loader
      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 "webkit/common/blob/shareable_file_reference.h"
     19 
     20 using webkit_blob::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:
     36   DependentIOBuffer(net::IOBuffer* backing, char* memory)
     37       : net::WrappedIOBuffer(memory),
     38         backing_(backing) {
     39   }
     40  private:
     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:
     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 
     70   bool is_writing() const { return is_writing_; }
     71   const base::FilePath& path() const { return deletable_file_->path(); }
     72 
     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 
     84   void Close() {
     85     handler_ = NULL;
     86     if (!is_writing_)
     87       CloseAndDelete();
     88   }
     89 
     90  private:
     91   // Only DidClose can delete this.
     92   ~Writer() {
     93   }
     94 
     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 
    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 
    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<webkit_blob::ShareableFileReference> deletable_file_;
    125 
    126   DISALLOW_COPY_AND_ASSIGN(Writer);
    127 };
    128 
    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 
    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::
    152     SetCreateTemporaryFileStreamFunctionForTesting(
    153         const CreateTemporaryFileStreamFunction& create_temporary_file_stream) {
    154   create_temporary_file_stream_ = create_temporary_file_stream;
    155 }
    156 
    157 bool RedirectToFileResourceHandler::OnResponseStarted(
    158     ResourceResponse* response,
    159     bool* defer) {
    160   if (response->head.error_code == net::OK ||
    161       response->head.error_code == net::ERR_IO_PENDING) {
    162     DCHECK(writer_);
    163     response->head.download_file_path = writer_->path();
    164   }
    165   return next_handler_->OnResponseStarted(response, defer);
    166 }
    167 
    168 bool RedirectToFileResourceHandler::OnWillStart(const GURL& url, bool* defer) {
    169   DCHECK(!writer_);
    170 
    171   // Defer starting the request until we have created the temporary file.
    172   // TODO(darin): This is sub-optimal.  We should not delay starting the
    173   // network request like this.
    174   will_start_url_ = url;
    175   did_defer_ = *defer = true;
    176   if (create_temporary_file_stream_.is_null()) {
    177     CreateTemporaryFileStream(
    178         base::Bind(&RedirectToFileResourceHandler::DidCreateTemporaryFile,
    179                    weak_factory_.GetWeakPtr()));
    180   } else {
    181     create_temporary_file_stream_.Run(
    182         base::Bind(&RedirectToFileResourceHandler::DidCreateTemporaryFile,
    183                    weak_factory_.GetWeakPtr()));
    184   }
    185   return true;
    186 }
    187 
    188 bool RedirectToFileResourceHandler::OnWillRead(
    189     scoped_refptr<net::IOBuffer>* buf,
    190     int* buf_size,
    191     int min_size) {
    192   DCHECK_EQ(-1, min_size);
    193 
    194   if (buf_->capacity() < next_buffer_size_)
    195     buf_->SetCapacity(next_buffer_size_);
    196 
    197   // We should have paused this network request already if the buffer is full.
    198   DCHECK(!BufIsFull());
    199 
    200   *buf = buf_.get();
    201   *buf_size = buf_->RemainingCapacity();
    202 
    203   buf_write_pending_ = true;
    204   return true;
    205 }
    206 
    207 bool RedirectToFileResourceHandler::OnReadCompleted(int bytes_read,
    208                                                     bool* defer) {
    209   DCHECK(buf_write_pending_);
    210   buf_write_pending_ = false;
    211 
    212   // We use the buffer's offset field to record the end of the buffer.
    213   int new_offset = buf_->offset() + bytes_read;
    214   DCHECK(new_offset <= buf_->capacity());
    215   buf_->set_offset(new_offset);
    216 
    217   if (BufIsFull()) {
    218     did_defer_ = *defer = true;
    219 
    220     if (buf_->capacity() == bytes_read) {
    221       // The network layer has saturated our buffer in one read. Next time, we
    222       // should give it a bigger buffer for it to fill.
    223       next_buffer_size_ = std::min(next_buffer_size_ * 2, kMaxReadBufSize);
    224     }
    225   }
    226 
    227   return WriteMore();
    228 }
    229 
    230 void RedirectToFileResourceHandler::OnResponseCompleted(
    231     const net::URLRequestStatus& status,
    232     const std::string& security_info,
    233     bool* defer) {
    234   if (writer_ && writer_->is_writing()) {
    235     completed_during_write_ = true;
    236     completed_status_ = status;
    237     completed_security_info_ = security_info;
    238     did_defer_ = true;
    239     *defer = true;
    240     return;
    241   }
    242   next_handler_->OnResponseCompleted(status, security_info, defer);
    243 }
    244 
    245 void RedirectToFileResourceHandler::DidCreateTemporaryFile(
    246     base::File::Error error_code,
    247     scoped_ptr<net::FileStream> file_stream,
    248     ShareableFileReference* deletable_file) {
    249   DCHECK(!writer_);
    250   if (error_code != base::File::FILE_OK) {
    251     controller()->CancelWithError(net::FileErrorToNetError(error_code));
    252     return;
    253   }
    254 
    255   writer_ = new Writer(this, file_stream.Pass(), deletable_file);
    256 
    257   // Resume the request.
    258   DCHECK(did_defer_);
    259   bool defer = false;
    260   if (!next_handler_->OnWillStart(will_start_url_, &defer)) {
    261     controller()->Cancel();
    262   } else if (!defer) {
    263     ResumeIfDeferred();
    264   } else {
    265     did_defer_ = false;
    266   }
    267   will_start_url_ = GURL();
    268 }
    269 
    270 void RedirectToFileResourceHandler::DidWriteToFile(int result) {
    271   bool failed = false;
    272   if (result > 0) {
    273     next_handler_->OnDataDownloaded(result);
    274     write_cursor_ += result;
    275     failed = !WriteMore();
    276   } else {
    277     failed = true;
    278   }
    279 
    280   if (failed) {
    281     DCHECK(!writer_->is_writing());
    282     // TODO(davidben): Recover the error code from WriteMore or |result|, as
    283     // appropriate.
    284     if (completed_during_write_ && completed_status_.is_success()) {
    285       // If the request successfully completed mid-write, but the write failed,
    286       // convert the status to a failure for downstream.
    287       completed_status_.set_status(net::URLRequestStatus::CANCELED);
    288       completed_status_.set_error(net::ERR_FAILED);
    289     }
    290     if (!completed_during_write_)
    291       controller()->CancelWithError(net::ERR_FAILED);
    292   }
    293 
    294   if (completed_during_write_ && !writer_->is_writing()) {
    295     // Resume shutdown now that all data has been written to disk. Note that
    296     // this should run even in the |failed| case above, otherwise a failed write
    297     // leaves the handler stuck.
    298     bool defer = false;
    299     next_handler_->OnResponseCompleted(completed_status_,
    300                                        completed_security_info_,
    301                                        &defer);
    302     if (!defer) {
    303       ResumeIfDeferred();
    304     } else {
    305       did_defer_ = false;
    306     }
    307   }
    308 }
    309 
    310 bool RedirectToFileResourceHandler::WriteMore() {
    311   DCHECK(writer_);
    312   for (;;) {
    313     if (write_cursor_ == buf_->offset()) {
    314       // We've caught up to the network load, but it may be in the process of
    315       // appending more data to the buffer.
    316       if (!buf_write_pending_) {
    317         if (BufIsFull())
    318           ResumeIfDeferred();
    319         buf_->set_offset(0);
    320         write_cursor_ = 0;
    321       }
    322       return true;
    323     }
    324     if (writer_->is_writing())
    325       return true;
    326     DCHECK(write_cursor_ < buf_->offset());
    327 
    328     // Create a temporary buffer pointing to a subsection of the data buffer so
    329     // that it can be passed to Write.  This code makes some crazy scary
    330     // assumptions about object lifetimes, thread sharing, and that buf_ will
    331     // not realloc durring the write due to how the state machine in this class
    332     // works.
    333     // Note that buf_ is also shared with the code that writes data into the
    334     // cache, so modifying it can cause some pretty subtle race conditions:
    335     // https://code.google.com/p/chromium/issues/detail?id=152076
    336     // We're using DependentIOBuffer instead of DrainableIOBuffer to dodge some
    337     // of these issues, for the moment.
    338     // TODO(ncbray) make this code less crazy scary.
    339     // Also note that Write may increase the refcount of "wrapped" deep in the
    340     // bowels of its implementation, the use of scoped_refptr here is not
    341     // spurious.
    342     scoped_refptr<DependentIOBuffer> wrapped = new DependentIOBuffer(
    343         buf_.get(), buf_->StartOfBuffer() + write_cursor_);
    344     int write_len = buf_->offset() - write_cursor_;
    345 
    346     int rv = writer_->Write(wrapped.get(), write_len);
    347     if (rv == net::ERR_IO_PENDING)
    348       return true;
    349     if (rv <= 0)
    350       return false;
    351     next_handler_->OnDataDownloaded(rv);
    352     write_cursor_ += rv;
    353   }
    354 }
    355 
    356 bool RedirectToFileResourceHandler::BufIsFull() const {
    357   // This is a hack to workaround BufferedResourceHandler's inability to
    358   // deal with a ResourceHandler that returns a buffer size of less than
    359   // 2 * net::kMaxBytesToSniff from its OnWillRead method.
    360   // TODO(darin): Fix this retardation!
    361   return buf_->RemainingCapacity() <= (2 * net::kMaxBytesToSniff);
    362 }
    363 
    364 void RedirectToFileResourceHandler::ResumeIfDeferred() {
    365   if (did_defer_) {
    366     did_defer_ = false;
    367     controller()->Resume();
    368   }
    369 }
    370 
    371 }  // namespace content
    372