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
37namespace storage {
38
39namespace {
40
41bool 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
53BlobURLRequestJob::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
74void BlobURLRequestJob::Start() {
75  // Continue asynchronously.
76  base::MessageLoop::current()->PostTask(
77      FROM_HERE,
78      base::Bind(&BlobURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
79}
80
81void BlobURLRequestJob::Kill() {
82  DeleteCurrentFileReader();
83
84  net::URLRequestJob::Kill();
85  weak_factory_.InvalidateWeakPtrs();
86}
87
88bool 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
118bool 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
125void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
126  if (response_info_)
127    *info = *response_info_;
128}
129
130int BlobURLRequestJob::GetResponseCode() const {
131  if (!response_info_)
132    return -1;
133
134  return response_info_->headers->response_code();
135}
136
137void 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
157BlobURLRequestJob::~BlobURLRequestJob() {
158  STLDeleteValues(&index_to_reader_);
159}
160
161void 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
179bool 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
192void 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
215void 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
241void 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
285void 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
308bool 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
341void 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
350void 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
367bool 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
379bool 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
402void 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
424void 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
432int BlobURLRequestJob::BytesReadCompleted() {
433  int bytes_read = read_buf_->BytesConsumed();
434  read_buf_ = NULL;
435  return bytes_read;
436}
437
438int 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
453bool 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
464void 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
471void 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
505void 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
551FileStreamReader* 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
562void 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