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
20using storage::ShareableFileReference;
21
22namespace {
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.
34class 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
48namespace content {
49
50static const int kInitialReadBufSize = 32768;
51static 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.
58class 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<storage::ShareableFileReference> deletable_file_;
125
126  DISALLOW_COPY_AND_ASSIGN(Writer);
127};
128
129RedirectToFileResourceHandler::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
143RedirectToFileResourceHandler::~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
151void RedirectToFileResourceHandler::
152    SetCreateTemporaryFileStreamFunctionForTesting(
153        const CreateTemporaryFileStreamFunction& create_temporary_file_stream) {
154  create_temporary_file_stream_ = create_temporary_file_stream;
155}
156
157bool 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
165bool 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
185bool 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
204bool 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
227void 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
242void 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
267void 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
307bool 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
353bool 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
361void RedirectToFileResourceHandler::ResumeIfDeferred() {
362  if (did_defer_) {
363    did_defer_ = false;
364    controller()->Resume();
365  }
366}
367
368}  // namespace content
369