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.
5 #include "chrome/browser/chromeos/fileapi/external_file_url_request_job.h"
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/file_manager/fileapi_util.h"
16 #include "chrome/browser/chromeos/fileapi/external_file_url_util.h"
17 #include "chrome/browser/extensions/api/file_handlers/mime_util.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/storage_partition.h"
21 #include "content/public/common/url_constants.h"
22 #include "net/base/net_errors.h"
23 #include "net/http/http_byte_range.h"
24 #include "net/http/http_request_headers.h"
25 #include "net/http/http_response_info.h"
26 #include "net/http/http_util.h"
27 #include "net/url_request/url_request.h"
28 #include "net/url_request/url_request_status.h"
29 #include "storage/browser/fileapi/external_mount_points.h"
30 #include "storage/browser/fileapi/file_system_backend.h"
31 #include "storage/browser/fileapi/file_system_context.h"
32 #include "storage/browser/fileapi/file_system_operation_runner.h"
33 #include "storage/browser/fileapi/isolated_context.h"
35 using content::BrowserThread
;
40 const char kMimeTypeForRFC822
[] = "message/rfc822";
41 const char kMimeTypeForMHTML
[] = "multipart/related";
43 // Helper for obtaining FileSystemContext, FileSystemURL, and mime type on the
47 // The scoped pointer to control lifetime of the instance itself. The pointer
48 // is passed to callback functions and binds the lifetime of the instance to
49 // the callback's lifetime.
50 typedef scoped_ptr
<URLHelper
> Lifetime
;
52 URLHelper(void* profile_id
,
54 const ExternalFileURLRequestJob::HelperCallback
& callback
)
55 : profile_id_(profile_id
), url_(url
), callback_(callback
) {
56 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
57 Lifetime
lifetime(this);
58 BrowserThread::PostTask(BrowserThread::UI
,
60 base::Bind(&URLHelper::RunOnUIThread
,
61 base::Unretained(this),
62 base::Passed(&lifetime
)));
66 void RunOnUIThread(Lifetime lifetime
) {
67 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
68 Profile
* const profile
= reinterpret_cast<Profile
*>(profile_id_
);
69 if (!g_browser_process
->profile_manager()->IsValidProfile(profile
)) {
70 ReplyResult(net::ERR_FAILED
);
73 content::StoragePartition
* const storage
=
74 content::BrowserContext::GetStoragePartitionForSite(profile
, url_
);
77 scoped_refptr
<storage::FileSystemContext
> context
=
78 storage
->GetFileSystemContext();
79 DCHECK(context
.get());
81 // Obtain the absolute path in the file system.
82 const base::FilePath virtual_path
= ExternalFileURLToVirtualPath(url_
);
84 // Obtain the file system URL.
85 file_system_url_
= file_manager::util::CreateIsolatedURLFromVirtualPath(
86 *context
, /* empty origin */ GURL(), virtual_path
);
88 // Check if the obtained path providing external file URL or not.
89 if (!file_system_url_
.is_valid()) {
90 ReplyResult(net::ERR_INVALID_URL
);
94 isolated_file_system_scope_
.reset(
95 new ExternalFileURLRequestJob::IsolatedFileSystemScope(
96 file_system_url_
.filesystem_id()));
98 if (!IsExternalFileURLType(file_system_url_
.type())) {
99 ReplyResult(net::ERR_FAILED
);
103 file_system_context_
= context
;
104 extensions::app_file_handler_util::GetMimeTypeForLocalPath(
106 file_system_url_
.path(),
107 base::Bind(&URLHelper::OnGotMimeTypeOnUIThread
,
108 base::Unretained(this),
109 base::Passed(&lifetime
)));
112 void OnGotMimeTypeOnUIThread(Lifetime lifetime
,
113 const std::string
& mime_type
) {
114 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
115 mime_type_
= mime_type
;
117 if (mime_type_
== kMimeTypeForRFC822
)
118 mime_type_
= kMimeTypeForMHTML
;
120 ReplyResult(net::OK
);
123 void ReplyResult(net::Error error
) {
124 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
126 BrowserThread::PostTask(
129 base::Bind(callback_
,
131 file_system_context_
,
132 base::Passed(&isolated_file_system_scope_
),
137 void* const profile_id_
;
139 const ExternalFileURLRequestJob::HelperCallback callback_
;
140 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
141 scoped_ptr
<ExternalFileURLRequestJob::IsolatedFileSystemScope
>
142 isolated_file_system_scope_
;
143 storage::FileSystemURL file_system_url_
;
144 std::string mime_type_
;
146 DISALLOW_COPY_AND_ASSIGN(URLHelper
);
151 ExternalFileURLRequestJob::IsolatedFileSystemScope::IsolatedFileSystemScope(
152 const std::string
& file_system_id
)
153 : file_system_id_(file_system_id
) {
156 ExternalFileURLRequestJob::IsolatedFileSystemScope::~IsolatedFileSystemScope() {
157 storage::IsolatedContext::GetInstance()->RevokeFileSystem(file_system_id_
);
160 ExternalFileURLRequestJob::ExternalFileURLRequestJob(
162 net::URLRequest
* request
,
163 net::NetworkDelegate
* network_delegate
)
164 : net::URLRequestJob(request
, network_delegate
),
165 profile_id_(profile_id
),
167 weak_ptr_factory_(this) {
170 void ExternalFileURLRequestJob::SetExtraRequestHeaders(
171 const net::HttpRequestHeaders
& headers
) {
172 std::string range_header
;
173 if (headers
.GetHeader(net::HttpRequestHeaders::kRange
, &range_header
)) {
174 // Note: We only support single range requests.
175 std::vector
<net::HttpByteRange
> ranges
;
176 if (net::HttpUtil::ParseRangeHeader(range_header
, &ranges
) &&
177 ranges
.size() == 1) {
178 byte_range_
= ranges
[0];
180 // Failed to parse Range: header, so notify the error.
181 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
182 net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
));
187 void ExternalFileURLRequestJob::Start() {
188 DVLOG(1) << "Starting request";
189 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
190 DCHECK(!stream_reader_
);
192 // We only support GET request.
193 if (request()->method() != "GET") {
194 LOG(WARNING
) << "Failed to start request: " << request()->method()
195 << " method is not supported";
196 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
197 net::ERR_METHOD_NOT_SUPPORTED
));
201 // Check if the scheme is correct.
202 if (!request()->url().SchemeIs(content::kExternalFileScheme
)) {
203 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
204 net::ERR_INVALID_URL
));
209 new URLHelper(profile_id_
,
211 base::Bind(&ExternalFileURLRequestJob::OnHelperResultObtained
,
212 weak_ptr_factory_
.GetWeakPtr()));
215 void ExternalFileURLRequestJob::OnHelperResultObtained(
217 const scoped_refptr
<storage::FileSystemContext
>& file_system_context
,
218 scoped_ptr
<IsolatedFileSystemScope
> isolated_file_system_scope
,
219 const storage::FileSystemURL
& file_system_url
,
220 const std::string
& mime_type
) {
221 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
223 if (error
!= net::OK
) {
225 net::URLRequestStatus(net::URLRequestStatus::FAILED
, error
));
229 DCHECK(file_system_context
.get());
230 file_system_context_
= file_system_context
;
231 isolated_file_system_scope_
= isolated_file_system_scope
.Pass();
232 file_system_url_
= file_system_url
;
233 mime_type_
= mime_type
;
235 // Check if the entry has a redirect URL.
236 file_system_context_
->external_backend()->GetRedirectURLForContents(
238 base::Bind(&ExternalFileURLRequestJob::OnRedirectURLObtained
,
239 weak_ptr_factory_
.GetWeakPtr()));
242 void ExternalFileURLRequestJob::OnRedirectURLObtained(
243 const GURL
& redirect_url
) {
244 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
245 redirect_url_
= redirect_url
;
246 if (!redirect_url_
.is_empty()) {
247 NotifyHeadersComplete();
251 // Obtain file system context.
252 file_system_context_
->operation_runner()->GetMetadata(
254 base::Bind(&ExternalFileURLRequestJob::OnFileInfoObtained
,
255 weak_ptr_factory_
.GetWeakPtr()));
258 void ExternalFileURLRequestJob::OnFileInfoObtained(
259 base::File::Error result
,
260 const base::File::Info
& file_info
) {
261 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
263 if (result
== base::File::FILE_ERROR_NOT_FOUND
) {
264 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
265 net::ERR_FILE_NOT_FOUND
));
269 if (result
!= base::File::FILE_OK
|| file_info
.is_directory
||
270 file_info
.size
< 0) {
272 net::URLRequestStatus(net::URLRequestStatus::FAILED
, net::ERR_FAILED
));
276 // Compute content size.
277 if (!byte_range_
.ComputeBounds(file_info
.size
)) {
278 NotifyStartError(net::URLRequestStatus(
279 net::URLRequestStatus::FAILED
, net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
));
282 const int64 offset
= byte_range_
.first_byte_position();
284 byte_range_
.last_byte_position() + 1 - byte_range_
.first_byte_position();
285 set_expected_content_size(size
);
286 remaining_bytes_
= size
;
288 // Create file stream reader.
289 stream_reader_
= file_system_context_
->CreateFileStreamReader(
290 file_system_url_
, offset
, size
, base::Time());
291 if (!stream_reader_
) {
292 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
293 net::ERR_FILE_NOT_FOUND
));
297 NotifyHeadersComplete();
300 void ExternalFileURLRequestJob::Kill() {
301 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
303 stream_reader_
.reset();
304 isolated_file_system_scope_
.reset();
305 file_system_context_
= NULL
;
306 net::URLRequestJob::Kill();
307 weak_ptr_factory_
.InvalidateWeakPtrs();
310 bool ExternalFileURLRequestJob::GetMimeType(std::string
* mime_type
) const {
311 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
312 mime_type
->assign(mime_type_
);
313 return !mime_type
->empty();
316 bool ExternalFileURLRequestJob::IsRedirectResponse(GURL
* location
,
317 int* http_status_code
) {
318 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
319 if (redirect_url_
.is_empty())
322 // Redirect a hosted document.
323 *location
= redirect_url_
;
324 const int kHttpFound
= 302;
325 *http_status_code
= kHttpFound
;
329 bool ExternalFileURLRequestJob::ReadRawData(net::IOBuffer
* buf
,
332 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
333 DCHECK(stream_reader_
);
335 if (remaining_bytes_
== 0) {
340 const int result
= stream_reader_
->Read(
342 std::min
<int64
>(buf_size
, remaining_bytes_
),
343 base::Bind(&ExternalFileURLRequestJob::OnReadCompleted
,
344 weak_ptr_factory_
.GetWeakPtr()));
346 if (result
== net::ERR_IO_PENDING
) {
347 // The data is not yet available.
348 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING
, 0));
353 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
, result
));
357 // Reading has been finished immediately.
358 *bytes_read
= result
;
359 remaining_bytes_
-= result
;
363 ExternalFileURLRequestJob::~ExternalFileURLRequestJob() {
366 void ExternalFileURLRequestJob::OnReadCompleted(int read_result
) {
367 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
369 if (read_result
< 0) {
370 DCHECK_NE(read_result
, net::ERR_IO_PENDING
);
372 net::URLRequestStatus(net::URLRequestStatus::FAILED
, read_result
));
375 remaining_bytes_
-= read_result
;
376 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status.
377 NotifyReadComplete(read_result
);
380 } // namespace chromeos