• Home
  • 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 "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/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 
IsFileType(BlobData::Item::Type type)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 
BlobURLRequestJob(net::URLRequest * request,net::NetworkDelegate * network_delegate,BlobData * blob_data,fileapi::FileSystemContext * file_system_context,base::MessageLoopProxy * file_thread_proxy)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       blob_data_(blob_data),
55       file_system_context_(file_system_context),
56       file_thread_proxy_(file_thread_proxy),
57       total_size_(0),
58       remaining_bytes_(0),
59       pending_get_file_info_count_(0),
60       current_item_index_(0),
61       current_item_offset_(0),
62       error_(false),
63       byte_range_set_(false),
64       weak_factory_(this) {
65   DCHECK(file_thread_proxy_.get());
66 }
67 
Start()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 
Kill()75 void BlobURLRequestJob::Kill() {
76   DeleteCurrentFileReader();
77 
78   net::URLRequestJob::Kill();
79   weak_factory_.InvalidateWeakPtrs();
80 }
81 
ReadRawData(net::IOBuffer * dest,int dest_size,int * bytes_read)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 
GetMimeType(std::string * mime_type) const112 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 
GetResponseInfo(net::HttpResponseInfo * info)119 void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
120   if (response_info_)
121     *info = *response_info_;
122 }
123 
GetResponseCode() const124 int BlobURLRequestJob::GetResponseCode() const {
125   if (!response_info_)
126     return -1;
127 
128   return response_info_->headers->response_code();
129 }
130 
SetExtraRequestHeaders(const net::HttpRequestHeaders & headers)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 
~BlobURLRequestJob()151 BlobURLRequestJob::~BlobURLRequestJob() {
152   STLDeleteValues(&index_to_reader_);
153 }
154 
DidStart()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 
AddItemLength(size_t index,int64 item_length)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 
CountSize()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 
DidCountSize(int error)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 
DidGetFileItemLength(size_t index,int64 result)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 
Seek(int64 offset)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 
ReadItem()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 
AdvanceItem()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 
AdvanceBytesRead(int result)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 
ReadBytesItem(const BlobData::Item & item,int bytes_to_read)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 
ReadFileItem(FileStreamReader * reader,int bytes_to_read)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 
DidReadFile(int result)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 
DeleteCurrentFileReader()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 
BytesReadCompleted()426 int BlobURLRequestJob::BytesReadCompleted() {
427   int bytes_read = read_buf_->BytesConsumed();
428   read_buf_ = NULL;
429   return bytes_read;
430 }
431 
ComputeBytesToRead() const432 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 
ReadLoop(int * bytes_read)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 
NotifySuccess()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 
NotifyFailure(int error_code)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 
HeadersCompleted(net::HttpStatusCode status_code)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 
GetFileStreamReader(size_t index)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 
CreateFileStreamReader(size_t index,int64 additional_offset)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 = FileStreamReader::CreateForLocalFile(
555           file_thread_proxy_.get(),
556           item.path(),
557           item.offset() + additional_offset,
558           item.expected_modification_time());
559       break;
560     case BlobData::Item::TYPE_FILE_FILESYSTEM:
561       reader = file_system_context_->CreateFileStreamReader(
562           fileapi::FileSystemURL(
563               file_system_context_->CrackURL(item.filesystem_url())),
564           item.offset() + additional_offset,
565           item.expected_modification_time()).release();
566       break;
567     default:
568       NOTREACHED();
569   }
570   DCHECK(reader);
571   index_to_reader_[index] = reader;
572 }
573 
574 }  // namespace webkit_blob
575