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