Home | History | Annotate | Download | only in fileapi
      1 // Copyright 2014 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 "chrome/browser/chromeos/fileapi/external_file_url_request_job.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 
     10 #include "base/bind.h"
     11 #include "base/logging.h"
     12 #include "base/memory/ref_counted.h"
     13 #include "chrome/browser/browser_process.h"
     14 #include "chrome/browser/chromeos/drive/file_system_util.h"
     15 #include "chrome/browser/chromeos/fileapi/external_file_url_util.h"
     16 #include "chrome/browser/extensions/api/file_handlers/mime_util.h"
     17 #include "chrome/browser/profiles/profile_manager.h"
     18 #include "chrome/common/url_constants.h"
     19 #include "content/public/browser/browser_thread.h"
     20 #include "content/public/browser/storage_partition.h"
     21 #include "net/base/net_errors.h"
     22 #include "net/http/http_byte_range.h"
     23 #include "net/http/http_request_headers.h"
     24 #include "net/http/http_response_info.h"
     25 #include "net/http/http_util.h"
     26 #include "net/url_request/url_request.h"
     27 #include "net/url_request/url_request_status.h"
     28 #include "storage/browser/fileapi/file_system_backend.h"
     29 #include "storage/browser/fileapi/file_system_context.h"
     30 #include "storage/browser/fileapi/file_system_operation_runner.h"
     31 
     32 using content::BrowserThread;
     33 
     34 namespace chromeos {
     35 namespace {
     36 
     37 const char kMimeTypeForRFC822[] = "message/rfc822";
     38 const char kMimeTypeForMHTML[] = "multipart/related";
     39 
     40 // Helper for obtaining FileSystemContext, FileSystemURL, and mime type on the
     41 // UI thread.
     42 class URLHelper {
     43  public:
     44   // The scoped pointer to control lifetime of the instance itself. The pointer
     45   // is passed to callback functions and binds the lifetime of the instance to
     46   // the callback's lifetime.
     47   typedef scoped_ptr<URLHelper> Lifetime;
     48 
     49   URLHelper(void* profile_id,
     50             const GURL& url,
     51             const ExternalFileURLRequestJob::HelperCallback& callback)
     52       : profile_id_(profile_id), url_(url), callback_(callback) {
     53     DCHECK_CURRENTLY_ON(BrowserThread::IO);
     54     Lifetime lifetime(this);
     55     BrowserThread::PostTask(BrowserThread::UI,
     56                             FROM_HERE,
     57                             base::Bind(&URLHelper::RunOnUIThread,
     58                                        base::Unretained(this),
     59                                        base::Passed(&lifetime)));
     60   }
     61 
     62  private:
     63   void RunOnUIThread(Lifetime lifetime) {
     64     DCHECK_CURRENTLY_ON(BrowserThread::UI);
     65     Profile* const profile = reinterpret_cast<Profile*>(profile_id_);
     66     if (!g_browser_process->profile_manager()->IsValidProfile(profile)) {
     67       ReplyResult(net::ERR_FAILED);
     68       return;
     69     }
     70     content::StoragePartition* const storage =
     71         content::BrowserContext::GetStoragePartitionForSite(profile, url_);
     72     DCHECK(storage);
     73 
     74     scoped_refptr<storage::FileSystemContext> context =
     75         storage->GetFileSystemContext();
     76     DCHECK(context.get());
     77 
     78     // Obtain the absolute path in the file system.
     79     const base::FilePath virtual_path = ExternalFileURLToVirtualPath(url_);
     80 
     81     // Obtain the file system URL.
     82     // TODO(hirono): After removing MHTML support, stop to use the special
     83     // drive: scheme and use filesystem: URL directly.  crbug.com/415455
     84     file_system_url_ = context->CreateCrackedFileSystemURL(
     85         GURL(std::string(chrome::kExternalFileScheme) + ":"),
     86         storage::kFileSystemTypeExternal,
     87         virtual_path);
     88 
     89     // Check if the obtained path providing external file URL or not.
     90     if (FileSystemURLToExternalFileURL(file_system_url_).is_empty()) {
     91       ReplyResult(net::ERR_INVALID_URL);
     92       return;
     93     }
     94 
     95     file_system_context_ = context;
     96 
     97     extensions::app_file_handler_util::GetMimeTypeForLocalPath(
     98         profile,
     99         file_system_url_.path(),
    100         base::Bind(&URLHelper::OnGotMimeTypeOnUIThread,
    101                    base::Unretained(this),
    102                    base::Passed(&lifetime)));
    103   }
    104 
    105   void OnGotMimeTypeOnUIThread(Lifetime lifetime,
    106                                const std::string& mime_type) {
    107     DCHECK_CURRENTLY_ON(BrowserThread::UI);
    108     mime_type_ = mime_type;
    109 
    110     if (mime_type_ == kMimeTypeForRFC822)
    111       mime_type_ = kMimeTypeForMHTML;
    112 
    113     ReplyResult(net::OK);
    114   }
    115 
    116   void ReplyResult(net::Error error) {
    117     DCHECK_CURRENTLY_ON(BrowserThread::UI);
    118 
    119     BrowserThread::PostTask(BrowserThread::IO,
    120                             FROM_HERE,
    121                             base::Bind(callback_,
    122                                        error,
    123                                        file_system_context_,
    124                                        file_system_url_,
    125                                        mime_type_));
    126   }
    127 
    128   void* const profile_id_;
    129   const GURL url_;
    130   const ExternalFileURLRequestJob::HelperCallback callback_;
    131   scoped_refptr<storage::FileSystemContext> file_system_context_;
    132   storage::FileSystemURL file_system_url_;
    133   std::string mime_type_;
    134 
    135   DISALLOW_COPY_AND_ASSIGN(URLHelper);
    136 };
    137 
    138 }  // namespace
    139 
    140 ExternalFileURLRequestJob::ExternalFileURLRequestJob(
    141     void* profile_id,
    142     net::URLRequest* request,
    143     net::NetworkDelegate* network_delegate)
    144     : net::URLRequestJob(request, network_delegate),
    145       profile_id_(profile_id),
    146       remaining_bytes_(0),
    147       weak_ptr_factory_(this) {
    148 }
    149 
    150 void ExternalFileURLRequestJob::SetExtraRequestHeaders(
    151     const net::HttpRequestHeaders& headers) {
    152   std::string range_header;
    153   if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
    154     // Note: We only support single range requests.
    155     std::vector<net::HttpByteRange> ranges;
    156     if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
    157         ranges.size() == 1) {
    158       byte_range_ = ranges[0];
    159     } else {
    160       // Failed to parse Range: header, so notify the error.
    161       NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
    162                                        net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
    163     }
    164   }
    165 }
    166 
    167 void ExternalFileURLRequestJob::Start() {
    168   DVLOG(1) << "Starting request";
    169   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    170   DCHECK(!stream_reader_);
    171 
    172   // We only support GET request.
    173   if (request()->method() != "GET") {
    174     LOG(WARNING) << "Failed to start request: " << request()->method()
    175                  << " method is not supported";
    176     NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
    177                                            net::ERR_METHOD_NOT_SUPPORTED));
    178     return;
    179   }
    180 
    181   // Check if the scheme is correct.
    182   if (!request()->url().SchemeIs(chrome::kExternalFileScheme)) {
    183     NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
    184                                            net::ERR_INVALID_URL));
    185     return;
    186   }
    187 
    188   // Owned by itself.
    189   new URLHelper(profile_id_,
    190                 request()->url(),
    191                 base::Bind(&ExternalFileURLRequestJob::OnHelperResultObtained,
    192                            weak_ptr_factory_.GetWeakPtr()));
    193 }
    194 
    195 void ExternalFileURLRequestJob::OnHelperResultObtained(
    196     net::Error error,
    197     const scoped_refptr<storage::FileSystemContext>& file_system_context,
    198     const storage::FileSystemURL& file_system_url,
    199     const std::string& mime_type) {
    200   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    201 
    202   if (error != net::OK) {
    203     NotifyStartError(
    204         net::URLRequestStatus(net::URLRequestStatus::FAILED, error));
    205     return;
    206   }
    207 
    208   DCHECK(file_system_context.get());
    209   file_system_context_ = file_system_context;
    210   file_system_url_ = file_system_url;
    211   mime_type_ = mime_type;
    212 
    213   // Check if the entry has a redirect URL.
    214   file_system_context_->external_backend()->GetRedirectURLForContents(
    215       file_system_url_,
    216       base::Bind(&ExternalFileURLRequestJob::OnRedirectURLObtained,
    217                  weak_ptr_factory_.GetWeakPtr()));
    218 }
    219 
    220 void ExternalFileURLRequestJob::OnRedirectURLObtained(
    221     const GURL& redirect_url) {
    222   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    223   redirect_url_ = redirect_url;
    224   if (!redirect_url_.is_empty()) {
    225     NotifyHeadersComplete();
    226     return;
    227   }
    228 
    229   // Obtain file system context.
    230   file_system_context_->operation_runner()->GetMetadata(
    231       file_system_url_,
    232       base::Bind(&ExternalFileURLRequestJob::OnFileInfoObtained,
    233                  weak_ptr_factory_.GetWeakPtr()));
    234 }
    235 
    236 void ExternalFileURLRequestJob::OnFileInfoObtained(
    237     base::File::Error result,
    238     const base::File::Info& file_info) {
    239   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    240 
    241   if (result == base::File::FILE_ERROR_NOT_FOUND) {
    242     NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
    243                                            net::ERR_FILE_NOT_FOUND));
    244     return;
    245   }
    246 
    247   if (result != base::File::FILE_OK || file_info.is_directory ||
    248       file_info.size < 0) {
    249     NotifyStartError(
    250         net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
    251     return;
    252   }
    253 
    254   // Compute content size.
    255   if (!byte_range_.ComputeBounds(file_info.size)) {
    256     NotifyStartError(net::URLRequestStatus(
    257         net::URLRequestStatus::FAILED, net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
    258     return;
    259   }
    260   const int64 offset = byte_range_.first_byte_position();
    261   const int64 size =
    262       byte_range_.last_byte_position() + 1 - byte_range_.first_byte_position();
    263   set_expected_content_size(size);
    264   remaining_bytes_ = size;
    265 
    266   // Create file stream reader.
    267   stream_reader_ = file_system_context_->CreateFileStreamReader(
    268       file_system_url_, offset, size, base::Time());
    269   if (!stream_reader_) {
    270     NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
    271                                            net::ERR_FILE_NOT_FOUND));
    272     return;
    273   }
    274 
    275   NotifyHeadersComplete();
    276 }
    277 
    278 void ExternalFileURLRequestJob::Kill() {
    279   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    280 
    281   stream_reader_.reset();
    282   file_system_context_ = NULL;
    283   net::URLRequestJob::Kill();
    284   weak_ptr_factory_.InvalidateWeakPtrs();
    285 }
    286 
    287 bool ExternalFileURLRequestJob::GetMimeType(std::string* mime_type) const {
    288   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    289   mime_type->assign(mime_type_);
    290   return !mime_type->empty();
    291 }
    292 
    293 bool ExternalFileURLRequestJob::IsRedirectResponse(GURL* location,
    294                                                    int* http_status_code) {
    295   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    296   if (redirect_url_.is_empty())
    297     return false;
    298 
    299   // Redirect a hosted document.
    300   *location = redirect_url_;
    301   const int kHttpFound = 302;
    302   *http_status_code = kHttpFound;
    303   return true;
    304 }
    305 
    306 bool ExternalFileURLRequestJob::ReadRawData(net::IOBuffer* buf,
    307                                             int buf_size,
    308                                             int* bytes_read) {
    309   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    310   DCHECK(stream_reader_);
    311 
    312   if (remaining_bytes_ == 0) {
    313     *bytes_read = 0;
    314     return true;
    315   }
    316 
    317   const int result = stream_reader_->Read(
    318       buf,
    319       std::min<int64>(buf_size, remaining_bytes_),
    320       base::Bind(&ExternalFileURLRequestJob::OnReadCompleted,
    321                  weak_ptr_factory_.GetWeakPtr()));
    322 
    323   if (result == net::ERR_IO_PENDING) {
    324     // The data is not yet available.
    325     SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
    326     return false;
    327   }
    328   if (result < 0) {
    329     // An error occurs.
    330     NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
    331     return false;
    332   }
    333 
    334   // Reading has been finished immediately.
    335   *bytes_read = result;
    336   remaining_bytes_ -= result;
    337   return true;
    338 }
    339 
    340 ExternalFileURLRequestJob::~ExternalFileURLRequestJob() {
    341 }
    342 
    343 void ExternalFileURLRequestJob::OnReadCompleted(int read_result) {
    344   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    345 
    346   if (read_result < 0) {
    347     DCHECK_NE(read_result, net::ERR_IO_PENDING);
    348     NotifyDone(
    349         net::URLRequestStatus(net::URLRequestStatus::FAILED, read_result));
    350   }
    351 
    352   remaining_bytes_ -= read_result;
    353   SetStatus(net::URLRequestStatus());  // Clear the IO_PENDING status.
    354   NotifyReadComplete(read_result);
    355 }
    356 
    357 }  // namespace chromeos
    358