Home | History | Annotate | Download | only in blob
      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 "webkit/browser/blob/blob_url_request_job.h"
      6 
      7 #include <limits>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/bind.h"
     11 #include "base/compiler_specific.h"
     12 #include "base/files/file_util_proxy.h"
     13 #include "base/format_macros.h"
     14 #include "base/message_loop/message_loop.h"
     15 #include "base/message_loop/message_loop_proxy.h"
     16 #include "base/stl_util.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "net/base/io_buffer.h"
     20 #include "net/base/net_errors.h"
     21 #include "net/http/http_request_headers.h"
     22 #include "net/http/http_response_headers.h"
     23 #include "net/http/http_response_info.h"
     24 #include "net/http/http_util.h"
     25 #include "net/url_request/url_request.h"
     26 #include "net/url_request/url_request_context.h"
     27 #include "net/url_request/url_request_error_job.h"
     28 #include "net/url_request/url_request_status.h"
     29 #include "webkit/browser/blob/file_stream_reader.h"
     30 #include "webkit/browser/fileapi/file_system_context.h"
     31 #include "webkit/browser/fileapi/file_system_url.h"
     32 
     33 namespace webkit_blob {
     34 
     35 namespace {
     36 
     37 bool IsFileType(BlobData::Item::Type type) {
     38   switch (type) {
     39     case BlobData::Item::TYPE_FILE:
     40     case BlobData::Item::TYPE_FILE_FILESYSTEM:
     41       return true;
     42     default:
     43       return false;
     44   }
     45 }
     46 
     47 }  // namespace
     48 
     49 BlobURLRequestJob::BlobURLRequestJob(
     50     net::URLRequest* request,
     51     net::NetworkDelegate* network_delegate,
     52     BlobData* blob_data,
     53     fileapi::FileSystemContext* file_system_context,
     54     base::MessageLoopProxy* file_thread_proxy)
     55     : net::URLRequestJob(request, network_delegate),
     56       blob_data_(blob_data),
     57       file_system_context_(file_system_context),
     58       file_thread_proxy_(file_thread_proxy),
     59       total_size_(0),
     60       remaining_bytes_(0),
     61       pending_get_file_info_count_(0),
     62       current_item_index_(0),
     63       current_item_offset_(0),
     64       error_(false),
     65       byte_range_set_(false),
     66       weak_factory_(this) {
     67   DCHECK(file_thread_proxy_.get());
     68 }
     69 
     70 void BlobURLRequestJob::Start() {
     71   // Continue asynchronously.
     72   base::MessageLoop::current()->PostTask(
     73       FROM_HERE,
     74       base::Bind(&BlobURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
     75 }
     76 
     77 void BlobURLRequestJob::Kill() {
     78   DeleteCurrentFileReader();
     79 
     80   net::URLRequestJob::Kill();
     81   weak_factory_.InvalidateWeakPtrs();
     82 }
     83 
     84 bool BlobURLRequestJob::ReadRawData(net::IOBuffer* dest,
     85                                     int dest_size,
     86                                     int* bytes_read) {
     87   DCHECK_NE(dest_size, 0);
     88   DCHECK(bytes_read);
     89   DCHECK_GE(remaining_bytes_, 0);
     90 
     91   // Bail out immediately if we encounter an error.
     92   if (error_) {
     93     *bytes_read = 0;
     94     return true;
     95   }
     96 
     97   if (remaining_bytes_ < dest_size)
     98     dest_size = static_cast<int>(remaining_bytes_);
     99 
    100   // If we should copy zero bytes because |remaining_bytes_| is zero, short
    101   // circuit here.
    102   if (!dest_size) {
    103     *bytes_read = 0;
    104     return true;
    105   }
    106 
    107   // Keep track of the buffer.
    108   DCHECK(!read_buf_.get());
    109   read_buf_ = new net::DrainableIOBuffer(dest, dest_size);
    110 
    111   return ReadLoop(bytes_read);
    112 }
    113 
    114 bool BlobURLRequestJob::GetMimeType(std::string* mime_type) const {
    115   if (!response_info_)
    116     return false;
    117 
    118   return response_info_->headers->GetMimeType(mime_type);
    119 }
    120 
    121 void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
    122   if (response_info_)
    123     *info = *response_info_;
    124 }
    125 
    126 int BlobURLRequestJob::GetResponseCode() const {
    127   if (!response_info_)
    128     return -1;
    129 
    130   return response_info_->headers->response_code();
    131 }
    132 
    133 void BlobURLRequestJob::SetExtraRequestHeaders(
    134     const net::HttpRequestHeaders& headers) {
    135   std::string range_header;
    136   if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
    137     // We only care about "Range" header here.
    138     std::vector<net::HttpByteRange> ranges;
    139     if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
    140       if (ranges.size() == 1) {
    141         byte_range_set_ = true;
    142         byte_range_ = ranges[0];
    143       } else {
    144         // We don't support multiple range requests in one single URL request,
    145         // because we need to do multipart encoding here.
    146         // TODO(jianli): Support multipart byte range requests.
    147         NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
    148       }
    149     }
    150   }
    151 }
    152 
    153 BlobURLRequestJob::~BlobURLRequestJob() {
    154   STLDeleteValues(&index_to_reader_);
    155 }
    156 
    157 void BlobURLRequestJob::DidStart() {
    158   error_ = false;
    159 
    160   // We only support GET request per the spec.
    161   if (request()->method() != "GET") {
    162     NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
    163     return;
    164   }
    165 
    166   // If the blob data is not present, bail out.
    167   if (!blob_data_.get()) {
    168     NotifyFailure(net::ERR_FILE_NOT_FOUND);
    169     return;
    170   }
    171 
    172   CountSize();
    173 }
    174 
    175 bool BlobURLRequestJob::AddItemLength(size_t index, int64 item_length) {
    176   if (item_length > kint64max - total_size_) {
    177     NotifyFailure(net::ERR_FAILED);
    178     return false;
    179   }
    180 
    181   // Cache the size and add it to the total size.
    182   DCHECK_LT(index, item_length_list_.size());
    183   item_length_list_[index] = item_length;
    184   total_size_ += item_length;
    185   return true;
    186 }
    187 
    188 void BlobURLRequestJob::CountSize() {
    189   pending_get_file_info_count_ = 0;
    190   total_size_ = 0;
    191   item_length_list_.resize(blob_data_->items().size());
    192 
    193   for (size_t i = 0; i < blob_data_->items().size(); ++i) {
    194     const BlobData::Item& item = blob_data_->items().at(i);
    195     if (IsFileType(item.type())) {
    196       ++pending_get_file_info_count_;
    197       GetFileStreamReader(i)->GetLength(
    198           base::Bind(&BlobURLRequestJob::DidGetFileItemLength,
    199                      weak_factory_.GetWeakPtr(), i));
    200       continue;
    201     }
    202 
    203     if (!AddItemLength(i, item.length()))
    204       return;
    205   }
    206 
    207   if (pending_get_file_info_count_ == 0)
    208     DidCountSize(net::OK);
    209 }
    210 
    211 void BlobURLRequestJob::DidCountSize(int error) {
    212   DCHECK(!error_);
    213 
    214   // If an error occured, bail out.
    215   if (error != net::OK) {
    216     NotifyFailure(error);
    217     return;
    218   }
    219 
    220   // Apply the range requirement.
    221   if (!byte_range_.ComputeBounds(total_size_)) {
    222     NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
    223     return;
    224   }
    225 
    226   remaining_bytes_ = byte_range_.last_byte_position() -
    227                      byte_range_.first_byte_position() + 1;
    228   DCHECK_GE(remaining_bytes_, 0);
    229 
    230   // Do the seek at the beginning of the request.
    231   if (byte_range_.first_byte_position())
    232     Seek(byte_range_.first_byte_position());
    233 
    234   NotifySuccess();
    235 }
    236 
    237 void BlobURLRequestJob::DidGetFileItemLength(size_t index, int64 result) {
    238   // Do nothing if we have encountered an error.
    239   if (error_)
    240     return;
    241 
    242   if (result == net::ERR_UPLOAD_FILE_CHANGED) {
    243     NotifyFailure(net::ERR_FILE_NOT_FOUND);
    244     return;
    245   } else if (result < 0) {
    246     NotifyFailure(result);
    247     return;
    248   }
    249 
    250   DCHECK_LT(index, blob_data_->items().size());
    251   const BlobData::Item& item = blob_data_->items().at(index);
    252   DCHECK(IsFileType(item.type()));
    253 
    254   uint64 file_length = result;
    255   uint64 item_offset = item.offset();
    256   uint64 item_length = item.length();
    257 
    258   if (item_offset > file_length) {
    259     NotifyFailure(net::ERR_FILE_NOT_FOUND);
    260     return;
    261   }
    262 
    263   uint64 max_length = file_length - item_offset;
    264 
    265   // If item length is -1, we need to use the file size being resolved
    266   // in the real time.
    267   if (item_length == static_cast<uint64>(-1)) {
    268     item_length = max_length;
    269   } else if (item_length > max_length) {
    270     NotifyFailure(net::ERR_FILE_NOT_FOUND);
    271     return;
    272   }
    273 
    274   if (!AddItemLength(index, item_length))
    275     return;
    276 
    277   if (--pending_get_file_info_count_ == 0)
    278     DidCountSize(net::OK);
    279 }
    280 
    281 void BlobURLRequestJob::Seek(int64 offset) {
    282   // Skip the initial items that are not in the range.
    283   for (current_item_index_ = 0;
    284        current_item_index_ < blob_data_->items().size() &&
    285            offset >= item_length_list_[current_item_index_];
    286        ++current_item_index_) {
    287     offset -= item_length_list_[current_item_index_];
    288   }
    289 
    290   // Set the offset that need to jump to for the first item in the range.
    291   current_item_offset_ = offset;
    292 
    293   if (offset == 0)
    294     return;
    295 
    296   // Adjust the offset of the first stream if it is of file type.
    297   const BlobData::Item& item = blob_data_->items().at(current_item_index_);
    298   if (IsFileType(item.type())) {
    299     DeleteCurrentFileReader();
    300     CreateFileStreamReader(current_item_index_, offset);
    301   }
    302 }
    303 
    304 bool BlobURLRequestJob::ReadItem() {
    305   // Are we done with reading all the blob data?
    306   if (remaining_bytes_ == 0)
    307     return true;
    308 
    309   // If we get to the last item but still expect something to read, bail out
    310   // since something is wrong.
    311   if (current_item_index_ >= blob_data_->items().size()) {
    312     NotifyFailure(net::ERR_FAILED);
    313     return false;
    314   }
    315 
    316   // Compute the bytes to read for current item.
    317   int bytes_to_read = ComputeBytesToRead();
    318 
    319   // If nothing to read for current item, advance to next item.
    320   if (bytes_to_read == 0) {
    321     AdvanceItem();
    322     return ReadItem();
    323   }
    324 
    325   // Do the reading.
    326   const BlobData::Item& item = blob_data_->items().at(current_item_index_);
    327   if (item.type() == BlobData::Item::TYPE_BYTES)
    328     return ReadBytesItem(item, bytes_to_read);
    329   if (IsFileType(item.type())) {
    330     return ReadFileItem(GetFileStreamReader(current_item_index_),
    331                         bytes_to_read);
    332   }
    333   NOTREACHED();
    334   return false;
    335 }
    336 
    337 void BlobURLRequestJob::AdvanceItem() {
    338   // Close the file if the current item is a file.
    339   DeleteCurrentFileReader();
    340 
    341   // Advance to the next item.
    342   current_item_index_++;
    343   current_item_offset_ = 0;
    344 }
    345 
    346 void BlobURLRequestJob::AdvanceBytesRead(int result) {
    347   DCHECK_GT(result, 0);
    348 
    349   // Do we finish reading the current item?
    350   current_item_offset_ += result;
    351   if (current_item_offset_ == item_length_list_[current_item_index_])
    352     AdvanceItem();
    353 
    354   // Subtract the remaining bytes.
    355   remaining_bytes_ -= result;
    356   DCHECK_GE(remaining_bytes_, 0);
    357 
    358   // Adjust the read buffer.
    359   read_buf_->DidConsume(result);
    360   DCHECK_GE(read_buf_->BytesRemaining(), 0);
    361 }
    362 
    363 bool BlobURLRequestJob::ReadBytesItem(const BlobData::Item& item,
    364                                       int bytes_to_read) {
    365   DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
    366 
    367   memcpy(read_buf_->data(),
    368          item.bytes() + item.offset() + current_item_offset_,
    369          bytes_to_read);
    370 
    371   AdvanceBytesRead(bytes_to_read);
    372   return true;
    373 }
    374 
    375 bool BlobURLRequestJob::ReadFileItem(FileStreamReader* reader,
    376                                      int bytes_to_read) {
    377   DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
    378   DCHECK(reader);
    379   const int result = reader->Read(
    380       read_buf_.get(),
    381       bytes_to_read,
    382       base::Bind(&BlobURLRequestJob::DidReadFile, base::Unretained(this)));
    383   if (result >= 0) {
    384     // Data is immediately available.
    385     if (GetStatus().is_io_pending())
    386       DidReadFile(result);
    387     else
    388       AdvanceBytesRead(result);
    389     return true;
    390   }
    391   if (result == net::ERR_IO_PENDING)
    392     SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
    393   else
    394     NotifyFailure(result);
    395   return false;
    396 }
    397 
    398 void BlobURLRequestJob::DidReadFile(int result) {
    399   if (result <= 0) {
    400     NotifyFailure(net::ERR_FAILED);
    401     return;
    402   }
    403   SetStatus(net::URLRequestStatus());  // Clear the IO_PENDING status
    404 
    405   AdvanceBytesRead(result);
    406 
    407   // If the read buffer is completely filled, we're done.
    408   if (!read_buf_->BytesRemaining()) {
    409     int bytes_read = BytesReadCompleted();
    410     NotifyReadComplete(bytes_read);
    411     return;
    412   }
    413 
    414   // Otherwise, continue the reading.
    415   int bytes_read = 0;
    416   if (ReadLoop(&bytes_read))
    417     NotifyReadComplete(bytes_read);
    418 }
    419 
    420 void BlobURLRequestJob::DeleteCurrentFileReader() {
    421   IndexToReaderMap::iterator found = index_to_reader_.find(current_item_index_);
    422   if (found != index_to_reader_.end() && found->second) {
    423     delete found->second;
    424     index_to_reader_.erase(found);
    425   }
    426 }
    427 
    428 int BlobURLRequestJob::BytesReadCompleted() {
    429   int bytes_read = read_buf_->BytesConsumed();
    430   read_buf_ = NULL;
    431   return bytes_read;
    432 }
    433 
    434 int BlobURLRequestJob::ComputeBytesToRead() const {
    435   int64 current_item_length = item_length_list_[current_item_index_];
    436 
    437   int64 item_remaining = current_item_length - current_item_offset_;
    438   int64 buf_remaining = read_buf_->BytesRemaining();
    439   int64 max_remaining = std::numeric_limits<int>::max();
    440 
    441   int64 min = std::min(std::min(std::min(item_remaining,
    442                                          buf_remaining),
    443                                          remaining_bytes_),
    444                                          max_remaining);
    445 
    446   return static_cast<int>(min);
    447 }
    448 
    449 bool BlobURLRequestJob::ReadLoop(int* bytes_read) {
    450   // Read until we encounter an error or could not get the data immediately.
    451   while (remaining_bytes_ > 0 && read_buf_->BytesRemaining() > 0) {
    452     if (!ReadItem())
    453       return false;
    454   }
    455 
    456   *bytes_read = BytesReadCompleted();
    457   return true;
    458 }
    459 
    460 void BlobURLRequestJob::NotifySuccess() {
    461   net::HttpStatusCode status_code = net::HTTP_OK;
    462   if (byte_range_set_ && byte_range_.IsValid())
    463     status_code = net::HTTP_PARTIAL_CONTENT;
    464   HeadersCompleted(status_code);
    465 }
    466 
    467 void BlobURLRequestJob::NotifyFailure(int error_code) {
    468   error_ = true;
    469 
    470   // If we already return the headers on success, we can't change the headers
    471   // now. Instead, we just error out.
    472   if (response_info_) {
    473     NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
    474                                      error_code));
    475     return;
    476   }
    477 
    478   net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
    479   switch (error_code) {
    480     case net::ERR_ACCESS_DENIED:
    481       status_code = net::HTTP_FORBIDDEN;
    482       break;
    483     case net::ERR_FILE_NOT_FOUND:
    484       status_code = net::HTTP_NOT_FOUND;
    485       break;
    486     case net::ERR_METHOD_NOT_SUPPORTED:
    487       status_code = net::HTTP_METHOD_NOT_ALLOWED;
    488       break;
    489     case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
    490       status_code = net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE;
    491       break;
    492     case net::ERR_FAILED:
    493       break;
    494     default:
    495       DCHECK(false);
    496       break;
    497   }
    498   HeadersCompleted(status_code);
    499 }
    500 
    501 void BlobURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
    502   std::string status("HTTP/1.1 ");
    503   status.append(base::IntToString(status_code));
    504   status.append(" ");
    505   status.append(net::GetHttpReasonPhrase(status_code));
    506   status.append("\0\0", 2);
    507   net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
    508 
    509   if (status_code == net::HTTP_OK || status_code == net::HTTP_PARTIAL_CONTENT) {
    510     std::string content_length_header(net::HttpRequestHeaders::kContentLength);
    511     content_length_header.append(": ");
    512     content_length_header.append(base::Int64ToString(remaining_bytes_));
    513     headers->AddHeader(content_length_header);
    514     if (status_code == net::HTTP_PARTIAL_CONTENT) {
    515       DCHECK(byte_range_set_);
    516       DCHECK(byte_range_.IsValid());
    517       std::string content_range_header(net::HttpResponseHeaders::kContentRange);
    518       content_range_header.append(": bytes ");
    519       content_range_header.append(base::StringPrintf(
    520           "%" PRId64 "-%" PRId64,
    521           byte_range_.first_byte_position(), byte_range_.last_byte_position()));
    522       content_range_header.append("/");
    523       content_range_header.append(base::StringPrintf("%" PRId64, total_size_));
    524       headers->AddHeader(content_range_header);
    525     }
    526     if (!blob_data_->content_type().empty()) {
    527       std::string content_type_header(net::HttpRequestHeaders::kContentType);
    528       content_type_header.append(": ");
    529       content_type_header.append(blob_data_->content_type());
    530       headers->AddHeader(content_type_header);
    531     }
    532     if (!blob_data_->content_disposition().empty()) {
    533       std::string content_disposition_header("Content-Disposition: ");
    534       content_disposition_header.append(blob_data_->content_disposition());
    535       headers->AddHeader(content_disposition_header);
    536     }
    537   }
    538 
    539   response_info_.reset(new net::HttpResponseInfo());
    540   response_info_->headers = headers;
    541 
    542   set_expected_content_size(remaining_bytes_);
    543 
    544   NotifyHeadersComplete();
    545 }
    546 
    547 FileStreamReader* BlobURLRequestJob::GetFileStreamReader(size_t index) {
    548   DCHECK_LT(index, blob_data_->items().size());
    549   const BlobData::Item& item = blob_data_->items().at(index);
    550   if (!IsFileType(item.type()))
    551     return NULL;
    552   if (index_to_reader_.find(index) == index_to_reader_.end())
    553     CreateFileStreamReader(index, 0);
    554   DCHECK(index_to_reader_[index]);
    555   return index_to_reader_[index];
    556 }
    557 
    558 void BlobURLRequestJob::CreateFileStreamReader(size_t index,
    559                                                int64 additional_offset) {
    560   DCHECK_LT(index, blob_data_->items().size());
    561   const BlobData::Item& item = blob_data_->items().at(index);
    562   DCHECK(IsFileType(item.type()));
    563   DCHECK_EQ(0U, index_to_reader_.count(index));
    564 
    565   FileStreamReader* reader = NULL;
    566   switch (item.type()) {
    567     case BlobData::Item::TYPE_FILE:
    568       reader = FileStreamReader::CreateForLocalFile(
    569           file_thread_proxy_.get(),
    570           item.path(),
    571           item.offset() + additional_offset,
    572           item.expected_modification_time());
    573       break;
    574     case BlobData::Item::TYPE_FILE_FILESYSTEM:
    575       reader = file_system_context_->CreateFileStreamReader(
    576           fileapi::FileSystemURL(
    577               file_system_context_->CrackURL(item.filesystem_url())),
    578           item.offset() + additional_offset,
    579           item.expected_modification_time()).release();
    580       break;
    581     default:
    582       NOTREACHED();
    583   }
    584   DCHECK(reader);
    585   index_to_reader_[index] = reader;
    586 }
    587 
    588 }  // namespace webkit_blob
    589