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