|  | // Copyright 2014 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/ash/file_manager/filesystem_api_util.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "ash/components/arc/session/arc_service_manager.h" | 
|  | #include "base/files/file.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/task/sequenced_task_runner.h" | 
|  | #include "chrome/browser/ash/arc/arc_util.h" | 
|  | #include "chrome/browser/ash/arc/fileapi/arc_content_file_system_url_util.h" | 
|  | #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_root.h" | 
|  | #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_root_map.h" | 
|  | #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h" | 
|  | #include "chrome/browser/ash/arc/fileapi/arc_file_system_operation_runner.h" | 
|  | #include "chrome/browser/ash/drive/drive_integration_service.h" | 
|  | #include "chrome/browser/ash/drive/file_system_util.h" | 
|  | #include "chrome/browser/ash/file_manager/app_id.h" | 
|  | #include "chrome/browser/ash/file_manager/fileapi_util.h" | 
|  | #include "chrome/browser/ash/file_system_provider/mount_path_util.h" | 
|  | #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h" | 
|  | #include "chrome/browser/extensions/extension_util.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "components/drive/file_errors.h" | 
|  | #include "components/drive/file_system_core_util.h" | 
|  | #include "content/public/browser/browser_task_traits.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "google_apis/common/task_util.h" | 
|  | #include "mojo/public/cpp/bindings/callback_helpers.h" | 
|  | #include "storage/browser/file_system/file_system_backend.h" | 
|  | #include "storage/browser/file_system/file_system_context.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace file_manager { | 
|  | namespace util { | 
|  | namespace { | 
|  |  | 
|  | void GetMimeTypeAfterGetMetadata( | 
|  | base::OnceCallback<void(const absl::optional<std::string>&)> callback, | 
|  | drive::FileError error, | 
|  | drivefs::mojom::FileMetadataPtr metadata) { | 
|  | if (error != drive::FILE_ERROR_OK || !metadata || | 
|  | metadata->content_mime_type.empty()) { | 
|  | std::move(callback).Run(absl::nullopt); | 
|  | return; | 
|  | } | 
|  | std::move(callback).Run(std::move(metadata->content_mime_type)); | 
|  | } | 
|  |  | 
|  | // Helper function used to implement GetNonNativeLocalPathMimeType. It extracts | 
|  | // the mime type from the passed metadata from a providing extension. | 
|  | void GetMimeTypeAfterGetMetadataForProvidedFileSystem( | 
|  | base::OnceCallback<void(const absl::optional<std::string>&)> callback, | 
|  | std::unique_ptr<ash::file_system_provider::EntryMetadata> metadata, | 
|  | base::File::Error result) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  |  | 
|  | if (result != base::File::FILE_OK || !metadata->mime_type.get()) { | 
|  | std::move(callback).Run(absl::nullopt); | 
|  | return; | 
|  | } | 
|  | std::move(callback).Run(*metadata->mime_type); | 
|  | } | 
|  |  | 
|  | // Helper function used to implement GetNonNativeLocalPathMimeType. It passes | 
|  | // the returned mime type to the callback. | 
|  | void GetMimeTypeAfterGetMimeTypeForArcContentFileSystem( | 
|  | base::OnceCallback<void(const absl::optional<std::string>&)> callback, | 
|  | const absl::optional<std::string>& mime_type) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | if (mime_type.has_value()) { | 
|  | std::move(callback).Run(mime_type.value()); | 
|  | } else { | 
|  | std::move(callback).Run(absl::nullopt); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OnResolveToContentUrl( | 
|  | base::OnceCallback<void(const absl::optional<std::string>&)> callback, | 
|  | Profile* profile, | 
|  | const base::FilePath& path, | 
|  | const GURL& content_url) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | DCHECK(profile); | 
|  |  | 
|  | if (content_url.is_valid()) { | 
|  | auto* runner = | 
|  | arc::ArcFileSystemOperationRunner::GetForBrowserContext(profile); | 
|  | if (!runner) { | 
|  | std::move(callback).Run(absl::nullopt); | 
|  | return; | 
|  | } | 
|  | runner->GetMimeType( | 
|  | content_url, | 
|  | base::BindOnce(&GetMimeTypeAfterGetMimeTypeForArcContentFileSystem, | 
|  | std::move(callback))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // If |content url| could not be parsed from documents provider special | 
|  | // path (i.e. absolute path is obfuscated), then lookup by extension in the | 
|  | // |kAndroidMimeTypeMappings| as a backup method. | 
|  | if (path.empty()) { | 
|  | LOG(ERROR) << "File path is empty"; | 
|  | std::move(callback).Run(absl::nullopt); | 
|  | return; | 
|  | } | 
|  | base::FilePath::StringType extension = | 
|  | base::ToLowerASCII(path.FinalExtension()); | 
|  | if (extension.empty()) { | 
|  | LOG(ERROR) << "File name is missing extension for path: " << path; | 
|  | std::move(callback).Run(absl::nullopt); | 
|  | return; | 
|  | } | 
|  | extension = extension.substr(1);  // Strip the leading dot. | 
|  | const std::string mime_type = arc::FindArcMimeTypeFromExtension(extension); | 
|  | if (mime_type.empty()) { | 
|  | LOG(ERROR) << "Could not find ARC mime type from extension: " << extension; | 
|  | std::move(callback).Run(absl::nullopt); | 
|  | return; | 
|  | } | 
|  | std::move(callback).Run(mime_type); | 
|  | } | 
|  |  | 
|  | // Helper function to converts a callback that takes boolean value to that takes | 
|  | // File::Error, by regarding FILE_OK as the only successful value. | 
|  | void BoolCallbackAsFileErrorCallback(base::OnceCallback<void(bool)> callback, | 
|  | base::File::Error error) { | 
|  | return std::move(callback).Run(error == base::File::FILE_OK); | 
|  | } | 
|  |  | 
|  | // Part of PrepareFileOnIOThread. It tries to create a new file if the given | 
|  | // |url| is not already inhabited. | 
|  | void PrepareFileAfterCheckExistOnIOThread( | 
|  | scoped_refptr<storage::FileSystemContext> file_system_context, | 
|  | const storage::FileSystemURL& url, | 
|  | storage::FileSystemOperation::StatusCallback callback, | 
|  | base::File::Error error) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | 
|  |  | 
|  | if (error != base::File::FILE_ERROR_NOT_FOUND) { | 
|  | std::move(callback).Run(error); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Call with the second argument |exclusive| set to false, meaning that it | 
|  | // is not an error even if the file already exists (it can happen if the file | 
|  | // is created after the previous FileExists call and before this CreateFile.) | 
|  | // | 
|  | // Note that the preceding call to FileExists is necessary for handling | 
|  | // read only filesystems that blindly rejects handling CreateFile(). | 
|  | file_system_context->operation_runner()->CreateFile(url, false, | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | // Checks whether a file exists at the given |url|, and try creating it if it | 
|  | // is not already there. | 
|  | void PrepareFileOnIOThread( | 
|  | scoped_refptr<storage::FileSystemContext> file_system_context, | 
|  | const storage::FileSystemURL& url, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | 
|  |  | 
|  | auto* const operation_runner = file_system_context->operation_runner(); | 
|  | operation_runner->FileExists( | 
|  | url, base::BindOnce(&PrepareFileAfterCheckExistOnIOThread, | 
|  | std::move(file_system_context), url, | 
|  | base::BindOnce(&BoolCallbackAsFileErrorCallback, | 
|  | std::move(callback)))); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | bool IsNonNativeFileSystemType(storage::FileSystemType type) { | 
|  | switch (type) { | 
|  | // Public enum values, also exposed to JavaScript. | 
|  | case storage::kFileSystemTypeTemporary: | 
|  | case storage::kFileSystemTypePersistent: | 
|  | case storage::kFileSystemTypeIsolated: | 
|  | case storage::kFileSystemTypeExternal: | 
|  | break; | 
|  |  | 
|  | // Everything else is a private (also known as internal) enum value. | 
|  |  | 
|  | case storage::kFileSystemInternalTypeEnumStart: | 
|  | case storage::kFileSystemInternalTypeEnumEnd: | 
|  | NOTREACHED(); | 
|  | break; | 
|  |  | 
|  | case storage::kFileSystemTypeLocal: | 
|  | case storage::kFileSystemTypeRestrictedLocal: | 
|  | case storage::kFileSystemTypeLocalMedia: | 
|  | case storage::kFileSystemTypeLocalForPlatformApp: | 
|  | case storage::kFileSystemTypeDriveFs: | 
|  | case storage::kFileSystemTypeSmbFs: | 
|  | case storage::kFileSystemTypeFuseBox: | 
|  | return false; | 
|  |  | 
|  | case storage::kFileSystemTypeUnknown: | 
|  | case storage::kFileSystemTypeTest: | 
|  | case storage::kFileSystemTypeDragged: | 
|  | case storage::kFileSystemTypeDeviceMedia: | 
|  | case storage::kFileSystemTypeSyncable: | 
|  | case storage::kFileSystemTypeSyncableForInternalSync: | 
|  | case storage::kFileSystemTypeForTransientFile: | 
|  | case storage::kFileSystemTypeProvided: | 
|  | case storage::kFileSystemTypeDeviceMediaAsFileStorage: | 
|  | case storage::kFileSystemTypeArcContent: | 
|  | case storage::kFileSystemTypeArcDocumentsProvider: | 
|  | break; | 
|  |  | 
|  | // We don't use a "default:" case. Whenever file_system_types.h gains a | 
|  | // new enum value, raise a compiler error (with -Werror,-Wswitch) unless | 
|  | // this switch statement is also updated. | 
|  | } | 
|  |  | 
|  | // The path indeed corresponds to a mount point not associated with a native | 
|  | // local path. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool IsUnderNonNativeLocalPath(Profile* profile, const base::FilePath& path) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  |  | 
|  | GURL url; | 
|  | if (!util::ConvertAbsoluteFilePathToFileSystemUrl( | 
|  | profile, path, util::GetFileManagerURL(), &url)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | storage::FileSystemURL filesystem_url = | 
|  | GetFileSystemContextForSourceURL(profile, GetFileManagerURL()) | 
|  | ->CrackURLInFirstPartyContext(url); | 
|  | if (!filesystem_url.is_valid()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return IsNonNativeFileSystemType(filesystem_url.type()); | 
|  | } | 
|  |  | 
|  | bool IsDriveLocalPath(Profile* profile, const base::FilePath& path) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  |  | 
|  | GURL url; | 
|  | if (!util::ConvertAbsoluteFilePathToFileSystemUrl( | 
|  | profile, path, util::GetFileManagerURL(), &url)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | storage::FileSystemURL filesystem_url = | 
|  | GetFileSystemContextForSourceURL(profile, GetFileManagerURL()) | 
|  | ->CrackURLInFirstPartyContext(url); | 
|  | if (!filesystem_url.is_valid()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return filesystem_url.type() == storage::kFileSystemTypeDriveFs; | 
|  | } | 
|  |  | 
|  | bool HasNonNativeMimeTypeProvider(Profile* profile, | 
|  | const base::FilePath& path) { | 
|  | auto* drive_integration_service = | 
|  | drive::util::GetIntegrationServiceByProfile(profile); | 
|  | return (drive_integration_service && | 
|  | drive_integration_service->GetMountPointPath().IsParent(path)) || | 
|  | IsUnderNonNativeLocalPath(profile, path); | 
|  | } | 
|  |  | 
|  | void GetNonNativeLocalPathMimeType( | 
|  | Profile* profile, | 
|  | const base::FilePath& path, | 
|  | base::OnceCallback<void(const absl::optional<std::string>&)> callback) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | DCHECK(HasNonNativeMimeTypeProvider(profile, path)); | 
|  |  | 
|  | auto* drive_integration_service = | 
|  | drive::util::GetIntegrationServiceByProfile(profile); | 
|  | base::FilePath drive_relative_path; | 
|  | if (drive_integration_service && | 
|  | drive_integration_service->GetRelativeDrivePath(path, | 
|  | &drive_relative_path)) { | 
|  | if (auto* drivefs = drive_integration_service->GetDriveFsInterface()) { | 
|  | drivefs->GetMetadata( | 
|  | drive_relative_path, | 
|  | mojo::WrapCallbackWithDefaultInvokeIfNotRun( | 
|  | base::BindOnce(&GetMimeTypeAfterGetMetadata, std::move(callback)), | 
|  | drive::FILE_ERROR_SERVICE_UNAVAILABLE, nullptr)); | 
|  |  | 
|  | return; | 
|  | } | 
|  | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (ash::file_system_provider::util::IsFileSystemProviderLocalPath(path)) { | 
|  | ash::file_system_provider::util::LocalPathParser parser(profile, path); | 
|  | if (!parser.Parse()) { | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | parser.file_system()->GetMetadata( | 
|  | parser.file_path(), | 
|  | ash::file_system_provider::ProvidedFileSystemInterface:: | 
|  | METADATA_FIELD_MIME_TYPE, | 
|  | base::BindOnce(&GetMimeTypeAfterGetMetadataForProvidedFileSystem, | 
|  | std::move(callback))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (arc::IsArcAllowedForProfile(profile)) { | 
|  | if (base::FilePath(arc::kContentFileSystemMountPointPath).IsParent(path)) { | 
|  | const GURL arc_url = arc::PathToArcUrl(path); | 
|  | if (!arc_url.is_valid()) { | 
|  | LOG(ERROR) << "ARC URL is invalid for path: " << path; | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto* runner = | 
|  | arc::ArcFileSystemOperationRunner::GetForBrowserContext(profile); | 
|  | if (!runner) { | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | return; | 
|  | } | 
|  | runner->GetMimeType( | 
|  | arc_url, | 
|  | base::BindOnce(&GetMimeTypeAfterGetMimeTypeForArcContentFileSystem, | 
|  | std::move(callback))); | 
|  | return; | 
|  | } else if (base::FilePath(arc::kDocumentsProviderMountPointPath) | 
|  | .IsParent(path)) { | 
|  | auto* root_map = | 
|  | arc::ArcDocumentsProviderRootMap::GetForArcBrowserContext(); | 
|  | if (!root_map) { | 
|  | LOG(ERROR) << "Could not find root map from ARC browser context"; | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string authority; | 
|  | std::string root_document_id; | 
|  | if (!arc::ParseDocumentsProviderPath(path, &authority, | 
|  | &root_document_id)) { | 
|  | LOG(ERROR) << "Failed to parse documents provider path: " << path; | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | return; | 
|  | } | 
|  | auto* root = root_map->Lookup(authority, root_document_id); | 
|  | if (!root) { | 
|  | LOG(ERROR) << "No root found for authority: " << authority | 
|  | << " document_id: " << root_document_id; | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | return; | 
|  | } | 
|  | root->ResolveToContentUrl( | 
|  | path, base::BindOnce(&OnResolveToContentUrl, std::move(callback), | 
|  | profile, path)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // We don't have a way to obtain metadata other than drive and FSP. Returns an | 
|  | // error with empty MIME type, that leads fallback guessing mime type from | 
|  | // file extensions. | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt)); | 
|  | } | 
|  |  | 
|  | void IsNonNativeLocalPathDirectory(Profile* profile, | 
|  | const base::FilePath& path, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | DCHECK(IsUnderNonNativeLocalPath(profile, path)); | 
|  |  | 
|  | util::CheckIfDirectoryExists( | 
|  | GetFileManagerFileSystemContext(profile), path, | 
|  | base::BindOnce(&BoolCallbackAsFileErrorCallback, std::move(callback))); | 
|  | } | 
|  |  | 
|  | void PrepareNonNativeLocalFileForWritableApp( | 
|  | Profile* profile, | 
|  | const base::FilePath& path, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | DCHECK(IsUnderNonNativeLocalPath(profile, path)); | 
|  |  | 
|  | GURL url; | 
|  | if (!util::ConvertAbsoluteFilePathToFileSystemUrl( | 
|  | profile, path, util::GetFileManagerURL(), &url)) { | 
|  | // Posting to the current thread, so that we always call back asynchronously | 
|  | // independent from whether or not the operation succeeds. | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), false)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | scoped_refptr<storage::FileSystemContext> const file_system_context = | 
|  | GetFileManagerFileSystemContext(profile); | 
|  | DCHECK(file_system_context); | 
|  | storage::ExternalFileSystemBackend* const backend = | 
|  | file_system_context->external_backend(); | 
|  | DCHECK(backend); | 
|  | const storage::FileSystemURL internal_url = | 
|  | backend->CreateInternalURL(file_system_context.get(), path); | 
|  |  | 
|  | content::GetIOThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&PrepareFileOnIOThread, file_system_context, internal_url, | 
|  | google_apis::CreateRelayCallback(std::move(callback)))); | 
|  | } | 
|  |  | 
|  | }  // namespace util | 
|  | }  // namespace file_manager |