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/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"
32 using content::BrowserThread
;
37 const char kMimeTypeForRFC822
[] = "message/rfc822";
38 const char kMimeTypeForMHTML
[] = "multipart/related";
40 // Check if the |url| points a valid location or not.
41 bool IsValidURL(const storage::FileSystemURL
& url
) {
43 case storage::kFileSystemTypeDrive
: {
44 const base::FilePath my_drive_path
=
45 drive::util::GetDriveMyDriveRootPath();
46 const base::FilePath drive_other_path
=
47 drive::util::GetDriveGrandRootPath().Append(
48 drive::util::kDriveOtherDirName
);
49 const base::FilePath url_drive_path
=
50 drive::util::ExtractDrivePathFromFileSystemUrl(url
);
51 return my_drive_path
== url_drive_path
||
52 my_drive_path
.IsParent(url_drive_path
) ||
53 drive_other_path
.IsParent(url_drive_path
);
60 // Helper for obtaining FileSystemContext, FileSystemURL, and mime type on the
64 // The scoped pointer to control lifetime of the instance itself. The pointer
65 // is passed to callback functions and binds the lifetime of the instance to
66 // the callback's lifetime.
67 typedef scoped_ptr
<URLHelper
> Lifetime
;
69 URLHelper(void* profile_id
,
71 const ExternalFileURLRequestJob::HelperCallback
& callback
)
72 : profile_id_(profile_id
), url_(url
), callback_(callback
) {
73 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
74 Lifetime
lifetime(this);
75 BrowserThread::PostTask(BrowserThread::UI
,
77 base::Bind(&URLHelper::RunOnUIThread
,
78 base::Unretained(this),
79 base::Passed(&lifetime
)));
83 void RunOnUIThread(Lifetime lifetime
) {
84 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
85 Profile
* const profile
= reinterpret_cast<Profile
*>(profile_id_
);
86 if (!g_browser_process
->profile_manager()->IsValidProfile(profile
)) {
87 ReplyResult(net::ERR_FAILED
);
90 content::StoragePartition
* const storage
=
91 content::BrowserContext::GetStoragePartitionForSite(profile
, url_
);
94 scoped_refptr
<storage::FileSystemContext
> context
=
95 storage
->GetFileSystemContext();
96 DCHECK(context
.get());
98 // Obtain the absolute path in the file system.
99 base::FilePath path
= drive::util::GetDriveMountPointPath(profile
);
100 drive::util::GetDriveGrandRootPath().AppendRelativePath(
101 ExternalFileURLToFilePath(url_
), &path
);
103 storage::ExternalFileSystemBackend
* const backend
=
104 context
->external_backend();
107 // Obtain the virtual path.
108 base::FilePath virtual_path
;
109 if (!backend
->GetVirtualPath(path
, &virtual_path
)) {
110 ReplyResult(net::ERR_FILE_NOT_FOUND
);
114 // Obtain the file system URL.
115 // TODO(hirono): After removing MHTML support, stop to use the special
117 // drive: scheme and use filesystem: URL directly. crbug.com/415455
118 file_system_url_
= context
->CreateCrackedFileSystemURL(
119 GURL(std::string(chrome::kExternalFileScheme
) + ":"),
120 storage::kFileSystemTypeExternal
,
122 if (!IsValidURL(file_system_url_
)) {
123 ReplyResult(net::ERR_INVALID_URL
);
127 file_system_context_
= context
;
129 extensions::app_file_handler_util::GetMimeTypeForLocalPath(
131 file_system_url_
.path(),
132 base::Bind(&URLHelper::OnGotMimeTypeOnUIThread
,
133 base::Unretained(this),
134 base::Passed(&lifetime
)));
137 void OnGotMimeTypeOnUIThread(Lifetime lifetime
,
138 const std::string
& mime_type
) {
139 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
140 mime_type_
= mime_type
;
142 if (mime_type_
== kMimeTypeForRFC822
)
143 mime_type_
= kMimeTypeForMHTML
;
145 ReplyResult(net::OK
);
148 void ReplyResult(net::Error error
) {
149 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
151 BrowserThread::PostTask(BrowserThread::IO
,
153 base::Bind(callback_
,
155 file_system_context_
,
160 void* const profile_id_
;
162 const ExternalFileURLRequestJob::HelperCallback callback_
;
163 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
164 storage::FileSystemURL file_system_url_
;
165 std::string mime_type_
;
167 DISALLOW_COPY_AND_ASSIGN(URLHelper
);
172 ExternalFileURLRequestJob::ExternalFileURLRequestJob(
174 net::URLRequest
* request
,
175 net::NetworkDelegate
* network_delegate
)
176 : net::URLRequestJob(request
, network_delegate
),
177 profile_id_(profile_id
),
179 weak_ptr_factory_(this) {
182 void ExternalFileURLRequestJob::SetExtraRequestHeaders(
183 const net::HttpRequestHeaders
& headers
) {
184 std::string range_header
;
185 if (headers
.GetHeader(net::HttpRequestHeaders::kRange
, &range_header
)) {
186 // Note: We only support single range requests.
187 std::vector
<net::HttpByteRange
> ranges
;
188 if (net::HttpUtil::ParseRangeHeader(range_header
, &ranges
) &&
189 ranges
.size() == 1) {
190 byte_range_
= ranges
[0];
192 // Failed to parse Range: header, so notify the error.
193 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
194 net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
));
199 void ExternalFileURLRequestJob::Start() {
200 DVLOG(1) << "Starting request";
201 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
202 DCHECK(!stream_reader_
);
204 // We only support GET request.
205 if (request()->method() != "GET") {
206 LOG(WARNING
) << "Failed to start request: " << request()->method()
207 << " method is not supported";
208 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
209 net::ERR_METHOD_NOT_SUPPORTED
));
213 // Check if the scheme is correct.
214 if (!request()->url().SchemeIs(chrome::kExternalFileScheme
)) {
215 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
216 net::ERR_INVALID_URL
));
221 new URLHelper(profile_id_
,
223 base::Bind(&ExternalFileURLRequestJob::OnHelperResultObtained
,
224 weak_ptr_factory_
.GetWeakPtr()));
227 void ExternalFileURLRequestJob::OnHelperResultObtained(
229 const scoped_refptr
<storage::FileSystemContext
>& file_system_context
,
230 const storage::FileSystemURL
& file_system_url
,
231 const std::string
& mime_type
) {
232 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
234 if (error
!= net::OK
) {
236 net::URLRequestStatus(net::URLRequestStatus::FAILED
, error
));
240 DCHECK(file_system_context
.get());
241 file_system_context_
= file_system_context
;
242 file_system_url_
= file_system_url
;
243 mime_type_
= mime_type
;
245 // Check if the entry has a redirect URL.
246 file_system_context_
->external_backend()->GetRedirectURLForContents(
248 base::Bind(&ExternalFileURLRequestJob::OnRedirectURLObtained
,
249 weak_ptr_factory_
.GetWeakPtr()));
252 void ExternalFileURLRequestJob::OnRedirectURLObtained(
253 const GURL
& redirect_url
) {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
255 redirect_url_
= redirect_url
;
256 if (!redirect_url_
.is_empty()) {
257 NotifyHeadersComplete();
261 // Obtain file system context.
262 file_system_context_
->operation_runner()->GetMetadata(
264 base::Bind(&ExternalFileURLRequestJob::OnFileInfoObtained
,
265 weak_ptr_factory_
.GetWeakPtr()));
268 void ExternalFileURLRequestJob::OnFileInfoObtained(
269 base::File::Error result
,
270 const base::File::Info
& file_info
) {
271 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
273 if (result
== base::File::FILE_ERROR_NOT_FOUND
) {
274 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
275 net::ERR_FILE_NOT_FOUND
));
279 if (result
!= base::File::FILE_OK
|| file_info
.is_directory
||
280 file_info
.size
< 0) {
282 net::URLRequestStatus(net::URLRequestStatus::FAILED
, net::ERR_FAILED
));
286 // Compute content size.
287 if (!byte_range_
.ComputeBounds(file_info
.size
)) {
288 NotifyStartError(net::URLRequestStatus(
289 net::URLRequestStatus::FAILED
, net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
));
292 const int64 offset
= byte_range_
.first_byte_position();
294 byte_range_
.last_byte_position() + 1 - byte_range_
.first_byte_position();
295 set_expected_content_size(size
);
296 remaining_bytes_
= size
;
298 // Create file stream reader.
299 stream_reader_
= file_system_context_
->CreateFileStreamReader(
300 file_system_url_
, offset
, size
, base::Time());
301 if (!stream_reader_
) {
302 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
303 net::ERR_FILE_NOT_FOUND
));
307 NotifyHeadersComplete();
310 void ExternalFileURLRequestJob::Kill() {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
313 stream_reader_
.reset();
314 file_system_context_
= NULL
;
315 net::URLRequestJob::Kill();
316 weak_ptr_factory_
.InvalidateWeakPtrs();
319 bool ExternalFileURLRequestJob::GetMimeType(std::string
* mime_type
) const {
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
321 mime_type
->assign(mime_type_
);
322 return !mime_type
->empty();
325 bool ExternalFileURLRequestJob::IsRedirectResponse(GURL
* location
,
326 int* http_status_code
) {
327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
328 if (redirect_url_
.is_empty())
331 // Redirect a hosted document.
332 *location
= redirect_url_
;
333 const int kHttpFound
= 302;
334 *http_status_code
= kHttpFound
;
338 bool ExternalFileURLRequestJob::ReadRawData(net::IOBuffer
* buf
,
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
342 DCHECK(stream_reader_
);
344 if (remaining_bytes_
== 0) {
349 const int result
= stream_reader_
->Read(
351 std::min
<int64
>(buf_size
, remaining_bytes_
),
352 base::Bind(&ExternalFileURLRequestJob::OnReadCompleted
,
353 weak_ptr_factory_
.GetWeakPtr()));
355 if (result
== net::ERR_IO_PENDING
) {
356 // The data is not yet available.
357 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING
, 0));
362 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
, result
));
366 // Reading has been finished immediately.
367 *bytes_read
= result
;
368 remaining_bytes_
-= result
;
372 ExternalFileURLRequestJob::~ExternalFileURLRequestJob() {
375 void ExternalFileURLRequestJob::OnReadCompleted(int read_result
) {
376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
378 if (read_result
< 0) {
379 DCHECK_NE(read_result
, net::ERR_IO_PENDING
);
381 net::URLRequestStatus(net::URLRequestStatus::FAILED
, read_result
));
384 remaining_bytes_
-= read_result
;
385 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status.
386 NotifyReadComplete(read_result
);
389 } // namespace chromeos