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
32using content::BrowserThread;
33
34namespace chromeos {
35namespace {
36
37const char kMimeTypeForRFC822[] = "message/rfc822";
38const char kMimeTypeForMHTML[] = "multipart/related";
39
40// Helper for obtaining FileSystemContext, FileSystemURL, and mime type on the
41// UI thread.
42class 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
140ExternalFileURLRequestJob::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
150void 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
167void 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
195void 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
220void 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
236void 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
278void 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
287bool 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
293bool 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
306bool 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
340ExternalFileURLRequestJob::~ExternalFileURLRequestJob() {
341}
342
343void 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