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 "webkit/browser/fileapi/file_system_context.h"
6
7 #include "base/bind.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/stl_util.h"
10 #include "base/task_runner_util.h"
11 #include "url/gurl.h"
12 #include "webkit/browser/blob/file_stream_reader.h"
13 #include "webkit/browser/fileapi/copy_or_move_file_validator.h"
14 #include "webkit/browser/fileapi/external_mount_points.h"
15 #include "webkit/browser/fileapi/file_permission_policy.h"
16 #include "webkit/browser/fileapi/file_stream_writer.h"
17 #include "webkit/browser/fileapi/file_system_file_util.h"
18 #include "webkit/browser/fileapi/file_system_operation.h"
19 #include "webkit/browser/fileapi/file_system_operation_runner.h"
20 #include "webkit/browser/fileapi/file_system_options.h"
21 #include "webkit/browser/fileapi/file_system_quota_client.h"
22 #include "webkit/browser/fileapi/file_system_url.h"
23 #include "webkit/browser/fileapi/isolated_context.h"
24 #include "webkit/browser/fileapi/isolated_file_system_backend.h"
25 #include "webkit/browser/fileapi/mount_points.h"
26 #include "webkit/browser/fileapi/quota/quota_reservation.h"
27 #include "webkit/browser/fileapi/sandbox_file_system_backend.h"
28 #include "webkit/browser/quota/quota_manager.h"
29 #include "webkit/browser/quota/special_storage_policy.h"
30 #include "webkit/common/fileapi/file_system_info.h"
31 #include "webkit/common/fileapi/file_system_util.h"
32
33 using quota::QuotaClient;
34
35 namespace fileapi {
36
37 namespace {
38
CreateQuotaClient(FileSystemContext * context,bool is_incognito)39 QuotaClient* CreateQuotaClient(
40 FileSystemContext* context,
41 bool is_incognito) {
42 return new FileSystemQuotaClient(context, is_incognito);
43 }
44
45
DidGetMetadataForResolveURL(const base::FilePath & path,const FileSystemContext::ResolveURLCallback & callback,const FileSystemInfo & info,base::PlatformFileError error,const base::PlatformFileInfo & file_info)46 void DidGetMetadataForResolveURL(
47 const base::FilePath& path,
48 const FileSystemContext::ResolveURLCallback& callback,
49 const FileSystemInfo& info,
50 base::PlatformFileError error,
51 const base::PlatformFileInfo& file_info) {
52 if (error != base::PLATFORM_FILE_OK) {
53 callback.Run(error, FileSystemInfo(), base::FilePath(), false);
54 return;
55 }
56 callback.Run(error, info, path, file_info.is_directory);
57 }
58
59 } // namespace
60
61 // static
GetPermissionPolicy(FileSystemType type)62 int FileSystemContext::GetPermissionPolicy(FileSystemType type) {
63 switch (type) {
64 case kFileSystemTypeTemporary:
65 case kFileSystemTypePersistent:
66 case kFileSystemTypeSyncable:
67 return FILE_PERMISSION_SANDBOX;
68
69 case kFileSystemTypeDrive:
70 case kFileSystemTypeNativeForPlatformApp:
71 case kFileSystemTypeNativeLocal:
72 return FILE_PERMISSION_USE_FILE_PERMISSION;
73
74 case kFileSystemTypeRestrictedNativeLocal:
75 return FILE_PERMISSION_READ_ONLY |
76 FILE_PERMISSION_USE_FILE_PERMISSION;
77
78 // Following types are only accessed via IsolatedFileSystem, and
79 // don't have their own permission policies.
80 case kFileSystemTypeDeviceMedia:
81 case kFileSystemTypeDragged:
82 case kFileSystemTypeForTransientFile:
83 case kFileSystemTypeIphoto:
84 case kFileSystemTypeItunes:
85 case kFileSystemTypeNativeMedia:
86 case kFileSystemTypePicasa:
87 case kFileSystemTypePluginPrivate:
88 return FILE_PERMISSION_ALWAYS_DENY;
89
90 // Following types only appear as mount_type, and will not be
91 // queried for their permission policies.
92 case kFileSystemTypeIsolated:
93 case kFileSystemTypeExternal:
94 return FILE_PERMISSION_ALWAYS_DENY;
95
96 // Following types should not be used to access files by FileAPI clients.
97 case kFileSystemTypeTest:
98 case kFileSystemTypeSyncableForInternalSync:
99 case kFileSystemInternalTypeEnumEnd:
100 case kFileSystemInternalTypeEnumStart:
101 case kFileSystemTypeUnknown:
102 return FILE_PERMISSION_ALWAYS_DENY;
103 }
104 NOTREACHED();
105 return FILE_PERMISSION_ALWAYS_DENY;
106 }
107
FileSystemContext(base::SingleThreadTaskRunner * io_task_runner,base::SequencedTaskRunner * file_task_runner,ExternalMountPoints * external_mount_points,quota::SpecialStoragePolicy * special_storage_policy,quota::QuotaManagerProxy * quota_manager_proxy,ScopedVector<FileSystemBackend> additional_backends,const base::FilePath & partition_path,const FileSystemOptions & options)108 FileSystemContext::FileSystemContext(
109 base::SingleThreadTaskRunner* io_task_runner,
110 base::SequencedTaskRunner* file_task_runner,
111 ExternalMountPoints* external_mount_points,
112 quota::SpecialStoragePolicy* special_storage_policy,
113 quota::QuotaManagerProxy* quota_manager_proxy,
114 ScopedVector<FileSystemBackend> additional_backends,
115 const base::FilePath& partition_path,
116 const FileSystemOptions& options)
117 : io_task_runner_(io_task_runner),
118 default_file_task_runner_(file_task_runner),
119 quota_manager_proxy_(quota_manager_proxy),
120 sandbox_delegate_(new SandboxFileSystemBackendDelegate(
121 quota_manager_proxy,
122 file_task_runner,
123 partition_path,
124 special_storage_policy,
125 options)),
126 sandbox_backend_(new SandboxFileSystemBackend(
127 sandbox_delegate_.get())),
128 isolated_backend_(new IsolatedFileSystemBackend()),
129 plugin_private_backend_(new PluginPrivateFileSystemBackend(
130 file_task_runner,
131 partition_path,
132 special_storage_policy,
133 options)),
134 additional_backends_(additional_backends.Pass()),
135 external_mount_points_(external_mount_points),
136 partition_path_(partition_path),
137 is_incognito_(options.is_incognito()),
138 operation_runner_(new FileSystemOperationRunner(this)) {
139 RegisterBackend(sandbox_backend_.get());
140 RegisterBackend(isolated_backend_.get());
141 RegisterBackend(plugin_private_backend_.get());
142
143 for (ScopedVector<FileSystemBackend>::const_iterator iter =
144 additional_backends_.begin();
145 iter != additional_backends_.end(); ++iter) {
146 RegisterBackend(*iter);
147 }
148
149 if (quota_manager_proxy) {
150 // Quota client assumes all backends have registered.
151 quota_manager_proxy->RegisterClient(CreateQuotaClient(
152 this, options.is_incognito()));
153 }
154
155 sandbox_backend_->Initialize(this);
156 isolated_backend_->Initialize(this);
157 plugin_private_backend_->Initialize(this);
158 for (ScopedVector<FileSystemBackend>::const_iterator iter =
159 additional_backends_.begin();
160 iter != additional_backends_.end(); ++iter) {
161 (*iter)->Initialize(this);
162 }
163
164 // Additional mount points must be added before regular system-wide
165 // mount points.
166 if (external_mount_points)
167 url_crackers_.push_back(external_mount_points);
168 url_crackers_.push_back(ExternalMountPoints::GetSystemInstance());
169 url_crackers_.push_back(IsolatedContext::GetInstance());
170 }
171
DeleteDataForOriginOnFileThread(const GURL & origin_url)172 bool FileSystemContext::DeleteDataForOriginOnFileThread(
173 const GURL& origin_url) {
174 DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
175 DCHECK(origin_url == origin_url.GetOrigin());
176
177 bool success = true;
178 for (FileSystemBackendMap::iterator iter = backend_map_.begin();
179 iter != backend_map_.end();
180 ++iter) {
181 FileSystemBackend* backend = iter->second;
182 if (!backend->GetQuotaUtil())
183 continue;
184 if (backend->GetQuotaUtil()->DeleteOriginDataOnFileThread(
185 this, quota_manager_proxy(), origin_url, iter->first)
186 != base::PLATFORM_FILE_OK) {
187 // Continue the loop, but record the failure.
188 success = false;
189 }
190 }
191
192 return success;
193 }
194
195 scoped_refptr<QuotaReservation>
CreateQuotaReservationOnFileTaskRunner(const GURL & origin_url,FileSystemType type)196 FileSystemContext::CreateQuotaReservationOnFileTaskRunner(
197 const GURL& origin_url,
198 FileSystemType type) {
199 DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
200 FileSystemBackend* backend = GetFileSystemBackend(type);
201 if (!backend || !backend->GetQuotaUtil())
202 return scoped_refptr<QuotaReservation>();
203 return backend->GetQuotaUtil()->CreateQuotaReservationOnFileTaskRunner(
204 origin_url, type);
205 }
206
Shutdown()207 void FileSystemContext::Shutdown() {
208 if (!io_task_runner_->RunsTasksOnCurrentThread()) {
209 io_task_runner_->PostTask(
210 FROM_HERE, base::Bind(&FileSystemContext::Shutdown,
211 make_scoped_refptr(this)));
212 return;
213 }
214 operation_runner_->Shutdown();
215 }
216
217 FileSystemQuotaUtil*
GetQuotaUtil(FileSystemType type) const218 FileSystemContext::GetQuotaUtil(FileSystemType type) const {
219 FileSystemBackend* backend = GetFileSystemBackend(type);
220 if (!backend)
221 return NULL;
222 return backend->GetQuotaUtil();
223 }
224
GetAsyncFileUtil(FileSystemType type) const225 AsyncFileUtil* FileSystemContext::GetAsyncFileUtil(
226 FileSystemType type) const {
227 FileSystemBackend* backend = GetFileSystemBackend(type);
228 if (!backend)
229 return NULL;
230 return backend->GetAsyncFileUtil(type);
231 }
232
233 CopyOrMoveFileValidatorFactory*
GetCopyOrMoveFileValidatorFactory(FileSystemType type,base::PlatformFileError * error_code) const234 FileSystemContext::GetCopyOrMoveFileValidatorFactory(
235 FileSystemType type, base::PlatformFileError* error_code) const {
236 DCHECK(error_code);
237 *error_code = base::PLATFORM_FILE_OK;
238 FileSystemBackend* backend = GetFileSystemBackend(type);
239 if (!backend)
240 return NULL;
241 return backend->GetCopyOrMoveFileValidatorFactory(
242 type, error_code);
243 }
244
GetFileSystemBackend(FileSystemType type) const245 FileSystemBackend* FileSystemContext::GetFileSystemBackend(
246 FileSystemType type) const {
247 FileSystemBackendMap::const_iterator found = backend_map_.find(type);
248 if (found != backend_map_.end())
249 return found->second;
250 NOTREACHED() << "Unknown filesystem type: " << type;
251 return NULL;
252 }
253
IsSandboxFileSystem(FileSystemType type) const254 bool FileSystemContext::IsSandboxFileSystem(FileSystemType type) const {
255 FileSystemBackendMap::const_iterator found = backend_map_.find(type);
256 return found != backend_map_.end() && found->second->GetQuotaUtil();
257 }
258
GetUpdateObservers(FileSystemType type) const259 const UpdateObserverList* FileSystemContext::GetUpdateObservers(
260 FileSystemType type) const {
261 FileSystemBackend* backend = GetFileSystemBackend(type);
262 if (backend->GetQuotaUtil())
263 return backend->GetQuotaUtil()->GetUpdateObservers(type);
264 return NULL;
265 }
266
GetAccessObservers(FileSystemType type) const267 const AccessObserverList* FileSystemContext::GetAccessObservers(
268 FileSystemType type) const {
269 FileSystemBackend* backend = GetFileSystemBackend(type);
270 if (backend->GetQuotaUtil())
271 return backend->GetQuotaUtil()->GetAccessObservers(type);
272 return NULL;
273 }
274
GetFileSystemTypes(std::vector<FileSystemType> * types) const275 void FileSystemContext::GetFileSystemTypes(
276 std::vector<FileSystemType>* types) const {
277 types->clear();
278 for (FileSystemBackendMap::const_iterator iter = backend_map_.begin();
279 iter != backend_map_.end(); ++iter)
280 types->push_back(iter->first);
281 }
282
283 ExternalFileSystemBackend*
external_backend() const284 FileSystemContext::external_backend() const {
285 return static_cast<ExternalFileSystemBackend*>(
286 GetFileSystemBackend(kFileSystemTypeExternal));
287 }
288
OpenFileSystem(const GURL & origin_url,FileSystemType type,OpenFileSystemMode mode,const OpenFileSystemCallback & callback)289 void FileSystemContext::OpenFileSystem(
290 const GURL& origin_url,
291 FileSystemType type,
292 OpenFileSystemMode mode,
293 const OpenFileSystemCallback& callback) {
294 DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
295 DCHECK(!callback.is_null());
296
297 if (!FileSystemContext::IsSandboxFileSystem(type)) {
298 // Disallow opening a non-sandboxed filesystem.
299 callback.Run(GURL(), std::string(), base::PLATFORM_FILE_ERROR_SECURITY);
300 return;
301 }
302
303 FileSystemBackend* backend = GetFileSystemBackend(type);
304 if (!backend) {
305 callback.Run(GURL(), std::string(), base::PLATFORM_FILE_ERROR_SECURITY);
306 return;
307 }
308
309 backend->OpenFileSystem(origin_url, type, mode, callback);
310 }
311
ResolveURL(const FileSystemURL & url,const ResolveURLCallback & callback)312 void FileSystemContext::ResolveURL(
313 const FileSystemURL& url,
314 const ResolveURLCallback& callback) {
315 DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
316 DCHECK(!callback.is_null());
317
318 if (!FileSystemContext::IsSandboxFileSystem(url.type())) {
319 #ifdef OS_CHROMEOS
320 // Do not have to open a non-sandboxed filesystem.
321 // TODO(nhiroki): For now we assume this path is called only on ChromeOS,
322 // but this assumption may be broken in the future and we should handle
323 // more generally. http://crbug.com/304062.
324 FileSystemInfo info = GetFileSystemInfoForChromeOS(url.origin());
325 DidOpenFileSystemForResolveURL(
326 url, callback, info.root_url, info.name, base::PLATFORM_FILE_OK);
327 return;
328 #endif
329 callback.Run(base::PLATFORM_FILE_ERROR_SECURITY,
330 FileSystemInfo(), base::FilePath(), false);
331 return;
332 }
333
334 FileSystemBackend* backend = GetFileSystemBackend(url.type());
335 if (!backend) {
336 callback.Run(base::PLATFORM_FILE_ERROR_SECURITY,
337 FileSystemInfo(), base::FilePath(), false);
338 return;
339 }
340
341 backend->OpenFileSystem(
342 url.origin(), url.type(),
343 OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
344 base::Bind(&FileSystemContext::DidOpenFileSystemForResolveURL,
345 this, url, callback));
346 }
347
DeleteFileSystem(const GURL & origin_url,FileSystemType type,const StatusCallback & callback)348 void FileSystemContext::DeleteFileSystem(
349 const GURL& origin_url,
350 FileSystemType type,
351 const StatusCallback& callback) {
352 DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
353 DCHECK(origin_url == origin_url.GetOrigin());
354 DCHECK(!callback.is_null());
355
356 FileSystemBackend* backend = GetFileSystemBackend(type);
357 if (!backend) {
358 callback.Run(base::PLATFORM_FILE_ERROR_SECURITY);
359 return;
360 }
361 if (!backend->GetQuotaUtil()) {
362 callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
363 return;
364 }
365
366 base::PostTaskAndReplyWithResult(
367 default_file_task_runner(),
368 FROM_HERE,
369 // It is safe to pass Unretained(quota_util) since context owns it.
370 base::Bind(&FileSystemQuotaUtil::DeleteOriginDataOnFileThread,
371 base::Unretained(backend->GetQuotaUtil()),
372 make_scoped_refptr(this),
373 base::Unretained(quota_manager_proxy()),
374 origin_url,
375 type),
376 callback);
377 }
378
379 scoped_ptr<webkit_blob::FileStreamReader>
CreateFileStreamReader(const FileSystemURL & url,int64 offset,const base::Time & expected_modification_time)380 FileSystemContext::CreateFileStreamReader(
381 const FileSystemURL& url,
382 int64 offset,
383 const base::Time& expected_modification_time) {
384 if (!url.is_valid())
385 return scoped_ptr<webkit_blob::FileStreamReader>();
386 FileSystemBackend* backend = GetFileSystemBackend(url.type());
387 if (!backend)
388 return scoped_ptr<webkit_blob::FileStreamReader>();
389 return backend->CreateFileStreamReader(
390 url, offset, expected_modification_time, this);
391 }
392
CreateFileStreamWriter(const FileSystemURL & url,int64 offset)393 scoped_ptr<FileStreamWriter> FileSystemContext::CreateFileStreamWriter(
394 const FileSystemURL& url,
395 int64 offset) {
396 if (!url.is_valid())
397 return scoped_ptr<FileStreamWriter>();
398 FileSystemBackend* backend = GetFileSystemBackend(url.type());
399 if (!backend)
400 return scoped_ptr<FileStreamWriter>();
401 return backend->CreateFileStreamWriter(url, offset, this);
402 }
403
404 scoped_ptr<FileSystemOperationRunner>
CreateFileSystemOperationRunner()405 FileSystemContext::CreateFileSystemOperationRunner() {
406 return make_scoped_ptr(new FileSystemOperationRunner(this));
407 }
408
CrackURL(const GURL & url) const409 FileSystemURL FileSystemContext::CrackURL(const GURL& url) const {
410 return CrackFileSystemURL(FileSystemURL(url));
411 }
412
CreateCrackedFileSystemURL(const GURL & origin,FileSystemType type,const base::FilePath & path) const413 FileSystemURL FileSystemContext::CreateCrackedFileSystemURL(
414 const GURL& origin,
415 FileSystemType type,
416 const base::FilePath& path) const {
417 return CrackFileSystemURL(FileSystemURL(origin, type, path));
418 }
419
420 #if defined(OS_CHROMEOS)
EnableTemporaryFileSystemInIncognito()421 void FileSystemContext::EnableTemporaryFileSystemInIncognito() {
422 sandbox_backend_->set_enable_temporary_file_system_in_incognito(true);
423 }
424 #endif
425
CanServeURLRequest(const FileSystemURL & url) const426 bool FileSystemContext::CanServeURLRequest(const FileSystemURL& url) const {
427 // We never support accessing files in isolated filesystems via an URL.
428 if (url.mount_type() == kFileSystemTypeIsolated)
429 return false;
430 #if defined(OS_CHROMEOS)
431 if (url.type() == kFileSystemTypeTemporary &&
432 sandbox_backend_->enable_temporary_file_system_in_incognito()) {
433 return true;
434 }
435 #endif
436 return !is_incognito_ || !FileSystemContext::IsSandboxFileSystem(url.type());
437 }
438
OpenPluginPrivateFileSystem(const GURL & origin_url,FileSystemType type,const std::string & filesystem_id,const std::string & plugin_id,OpenFileSystemMode mode,const StatusCallback & callback)439 void FileSystemContext::OpenPluginPrivateFileSystem(
440 const GURL& origin_url,
441 FileSystemType type,
442 const std::string& filesystem_id,
443 const std::string& plugin_id,
444 OpenFileSystemMode mode,
445 const StatusCallback& callback) {
446 DCHECK(plugin_private_backend_);
447 plugin_private_backend_->OpenPrivateFileSystem(
448 origin_url, type, filesystem_id, plugin_id, mode, callback);
449 }
450
~FileSystemContext()451 FileSystemContext::~FileSystemContext() {
452 }
453
DeleteOnCorrectThread() const454 void FileSystemContext::DeleteOnCorrectThread() const {
455 if (!io_task_runner_->RunsTasksOnCurrentThread() &&
456 io_task_runner_->DeleteSoon(FROM_HERE, this)) {
457 return;
458 }
459 delete this;
460 }
461
CreateFileSystemOperation(const FileSystemURL & url,base::PlatformFileError * error_code)462 FileSystemOperation* FileSystemContext::CreateFileSystemOperation(
463 const FileSystemURL& url, base::PlatformFileError* error_code) {
464 if (!url.is_valid()) {
465 if (error_code)
466 *error_code = base::PLATFORM_FILE_ERROR_INVALID_URL;
467 return NULL;
468 }
469
470 FileSystemBackend* backend = GetFileSystemBackend(url.type());
471 if (!backend) {
472 if (error_code)
473 *error_code = base::PLATFORM_FILE_ERROR_FAILED;
474 return NULL;
475 }
476
477 base::PlatformFileError fs_error = base::PLATFORM_FILE_OK;
478 FileSystemOperation* operation =
479 backend->CreateFileSystemOperation(url, this, &fs_error);
480
481 if (error_code)
482 *error_code = fs_error;
483 return operation;
484 }
485
CrackFileSystemURL(const FileSystemURL & url) const486 FileSystemURL FileSystemContext::CrackFileSystemURL(
487 const FileSystemURL& url) const {
488 if (!url.is_valid())
489 return FileSystemURL();
490
491 // The returned value in case there is no crackers which can crack the url.
492 // This is valid situation for non isolated/external file systems.
493 FileSystemURL current = url;
494
495 // File system may be mounted multiple times (e.g., an isolated filesystem on
496 // top of an external filesystem). Hence cracking needs to be iterated.
497 for (;;) {
498 FileSystemURL cracked = current;
499 for (size_t i = 0; i < url_crackers_.size(); ++i) {
500 if (!url_crackers_[i]->HandlesFileSystemMountType(current.type()))
501 continue;
502 cracked = url_crackers_[i]->CrackFileSystemURL(current);
503 if (cracked.is_valid())
504 break;
505 }
506 if (cracked == current)
507 break;
508 current = cracked;
509 }
510 return current;
511 }
512
RegisterBackend(FileSystemBackend * backend)513 void FileSystemContext::RegisterBackend(FileSystemBackend* backend) {
514 const FileSystemType mount_types[] = {
515 kFileSystemTypeTemporary,
516 kFileSystemTypePersistent,
517 kFileSystemTypeIsolated,
518 kFileSystemTypeExternal,
519 };
520 // Register file system backends for public mount types.
521 for (size_t j = 0; j < ARRAYSIZE_UNSAFE(mount_types); ++j) {
522 if (backend->CanHandleType(mount_types[j])) {
523 const bool inserted = backend_map_.insert(
524 std::make_pair(mount_types[j], backend)).second;
525 DCHECK(inserted);
526 }
527 }
528 // Register file system backends for internal types.
529 for (int t = kFileSystemInternalTypeEnumStart + 1;
530 t < kFileSystemInternalTypeEnumEnd; ++t) {
531 FileSystemType type = static_cast<FileSystemType>(t);
532 if (backend->CanHandleType(type)) {
533 const bool inserted = backend_map_.insert(
534 std::make_pair(type, backend)).second;
535 DCHECK(inserted);
536 }
537 }
538 }
539
DidOpenFileSystemForResolveURL(const FileSystemURL & url,const FileSystemContext::ResolveURLCallback & callback,const GURL & filesystem_root,const std::string & filesystem_name,base::PlatformFileError error)540 void FileSystemContext::DidOpenFileSystemForResolveURL(
541 const FileSystemURL& url,
542 const FileSystemContext::ResolveURLCallback& callback,
543 const GURL& filesystem_root,
544 const std::string& filesystem_name,
545 base::PlatformFileError error) {
546 DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
547
548 if (error != base::PLATFORM_FILE_OK) {
549 callback.Run(error, FileSystemInfo(), base::FilePath(), false);
550 return;
551 }
552
553 fileapi::FileSystemInfo info(
554 filesystem_name, filesystem_root, url.mount_type());
555
556 // Extract the virtual path not containing a filesystem type part from |url|.
557 base::FilePath parent = CrackURL(filesystem_root).virtual_path();
558 base::FilePath child = url.virtual_path();
559 base::FilePath path;
560
561 if (parent.empty()) {
562 path = child;
563 } else if (parent != child) {
564 bool result = parent.AppendRelativePath(child, &path);
565 DCHECK(result);
566 }
567
568 operation_runner()->GetMetadata(
569 url, base::Bind(&DidGetMetadataForResolveURL, path, callback, info));
570 }
571
572 } // namespace fileapi
573