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