1 // Copyright 2013 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/file_manager/fileapi_util.h" 6 7 #include "base/files/file.h" 8 #include "base/files/file_path.h" 9 #include "chrome/browser/chromeos/drive/file_system_util.h" 10 #include "chrome/browser/chromeos/file_manager/app_id.h" 11 #include "chrome/browser/extensions/extension_util.h" 12 #include "chrome/browser/profiles/profile.h" 13 #include "content/public/browser/browser_thread.h" 14 #include "content/public/browser/render_view_host.h" 15 #include "content/public/browser/site_instance.h" 16 #include "content/public/browser/storage_partition.h" 17 #include "extensions/common/extension.h" 18 #include "google_apis/drive/task_util.h" 19 #include "net/base/escape.h" 20 #include "url/gurl.h" 21 #include "webkit/browser/fileapi/file_system_context.h" 22 #include "webkit/browser/fileapi/open_file_system_mode.h" 23 #include "webkit/common/fileapi/file_system_util.h" 24 25 using content::BrowserThread; 26 27 namespace file_manager { 28 namespace util { 29 30 namespace { 31 32 GURL ConvertRelativeFilePathToFileSystemUrl(const base::FilePath& relative_path, 33 const std::string& extension_id) { 34 GURL base_url = fileapi::GetFileSystemRootURI( 35 extensions::Extension::GetBaseURLFromExtensionId(extension_id), 36 fileapi::kFileSystemTypeExternal); 37 return GURL(base_url.spec() + 38 net::EscapeUrlEncodedData(relative_path.AsUTF8Unsafe(), 39 false)); // Space to %20 instead of +. 40 } 41 42 // Creates an ErrorDefinition with an error set to |error|. 43 EntryDefinition CreateEntryDefinitionWithError(base::File::Error error) { 44 EntryDefinition result; 45 result.error = error; 46 return result; 47 } 48 49 // Helper class for performing conversions from file definitions to entry 50 // definitions. It is possible to do it without a class, but the code would be 51 // crazy and super tricky. 52 // 53 // This class copies the input |file_definition_list|, 54 // so there is no need to worry about validity of passed |file_definition_list| 55 // reference. Also, it automatically deletes itself after converting finished, 56 // or if shutdown is invoked during ResolveURL(). Must be called on UI thread. 57 class FileDefinitionListConverter { 58 public: 59 FileDefinitionListConverter(Profile* profile, 60 const std::string& extension_id, 61 const FileDefinitionList& file_definition_list, 62 const EntryDefinitionListCallback& callback); 63 ~FileDefinitionListConverter() {} 64 65 private: 66 // Converts the element under the iterator to an entry. First, converts 67 // the virtual path to an URL, and calls OnResolvedURL(). In case of error 68 // calls OnIteratorConverted with an error entry definition. 69 void ConvertNextIterator(scoped_ptr<FileDefinitionListConverter> self_deleter, 70 FileDefinitionList::const_iterator iterator); 71 72 // Creates an entry definition from the URL as well as the file definition. 73 // Then, calls OnIteratorConverted with the created entry definition. 74 void OnResolvedURL(scoped_ptr<FileDefinitionListConverter> self_deleter, 75 FileDefinitionList::const_iterator iterator, 76 base::File::Error error, 77 const fileapi::FileSystemInfo& info, 78 const base::FilePath& file_path, 79 fileapi::FileSystemContext::ResolvedEntryType type); 80 81 // Called when the iterator is converted. Adds the |entry_definition| to 82 // |results_| and calls ConvertNextIterator() for the next element. 83 void OnIteratorConverted(scoped_ptr<FileDefinitionListConverter> self_deleter, 84 FileDefinitionList::const_iterator iterator, 85 const EntryDefinition& entry_definition); 86 87 scoped_refptr<fileapi::FileSystemContext> file_system_context_; 88 const std::string extension_id_; 89 const FileDefinitionList file_definition_list_; 90 const EntryDefinitionListCallback callback_; 91 scoped_ptr<EntryDefinitionList> result_; 92 }; 93 94 FileDefinitionListConverter::FileDefinitionListConverter( 95 Profile* profile, 96 const std::string& extension_id, 97 const FileDefinitionList& file_definition_list, 98 const EntryDefinitionListCallback& callback) 99 : extension_id_(extension_id), 100 file_definition_list_(file_definition_list), 101 callback_(callback), 102 result_(new EntryDefinitionList) { 103 DCHECK_CURRENTLY_ON(BrowserThread::UI); 104 105 // File browser APIs are meant to be used only from extension context, so 106 // the extension's site is the one in whose file system context the virtual 107 // path should be found. 108 GURL site = extensions::util::GetSiteForExtensionId(extension_id_, profile); 109 file_system_context_ = 110 content::BrowserContext::GetStoragePartitionForSite( 111 profile, site)->GetFileSystemContext(); 112 113 // Deletes the converter, once the scoped pointer gets out of scope. It is 114 // either, if the conversion is finished, or ResolveURL() is terminated, and 115 // the callback not called because of shutdown. 116 scoped_ptr<FileDefinitionListConverter> self_deleter(this); 117 ConvertNextIterator(self_deleter.Pass(), file_definition_list_.begin()); 118 } 119 120 void FileDefinitionListConverter::ConvertNextIterator( 121 scoped_ptr<FileDefinitionListConverter> self_deleter, 122 FileDefinitionList::const_iterator iterator) { 123 if (iterator == file_definition_list_.end()) { 124 // The converter object will be destroyed since |self_deleter| gets out of 125 // scope. 126 callback_.Run(result_.Pass()); 127 return; 128 } 129 130 if (!file_system_context_.get()) { 131 OnIteratorConverted(self_deleter.Pass(), 132 iterator, 133 CreateEntryDefinitionWithError( 134 base::File::FILE_ERROR_INVALID_OPERATION)); 135 return; 136 } 137 138 fileapi::FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( 139 extensions::Extension::GetBaseURLFromExtensionId(extension_id_), 140 fileapi::kFileSystemTypeExternal, 141 iterator->virtual_path); 142 DCHECK(url.is_valid()); 143 144 // The converter object will be deleted if the callback is not called because 145 // of shutdown during ResolveURL(). 146 file_system_context_->ResolveURL( 147 url, 148 base::Bind(&FileDefinitionListConverter::OnResolvedURL, 149 base::Unretained(this), 150 base::Passed(&self_deleter), 151 iterator)); 152 } 153 154 void FileDefinitionListConverter::OnResolvedURL( 155 scoped_ptr<FileDefinitionListConverter> self_deleter, 156 FileDefinitionList::const_iterator iterator, 157 base::File::Error error, 158 const fileapi::FileSystemInfo& info, 159 const base::FilePath& file_path, 160 fileapi::FileSystemContext::ResolvedEntryType type) { 161 DCHECK_CURRENTLY_ON(BrowserThread::UI); 162 163 if (error != base::File::FILE_OK) { 164 OnIteratorConverted(self_deleter.Pass(), 165 iterator, 166 CreateEntryDefinitionWithError(error)); 167 return; 168 } 169 170 EntryDefinition entry_definition; 171 entry_definition.file_system_root_url = info.root_url.spec(); 172 entry_definition.file_system_name = info.name; 173 switch (type) { 174 case fileapi::FileSystemContext::RESOLVED_ENTRY_FILE: 175 entry_definition.is_directory = false; 176 break; 177 case fileapi::FileSystemContext::RESOLVED_ENTRY_DIRECTORY: 178 entry_definition.is_directory = true; 179 break; 180 case fileapi::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND: 181 entry_definition.is_directory = iterator->is_directory; 182 break; 183 } 184 entry_definition.error = base::File::FILE_OK; 185 186 // Construct a target Entry.fullPath value from the virtual path and the 187 // root URL. Eg. Downloads/A/b.txt -> A/b.txt. 188 const base::FilePath root_virtual_path = 189 file_system_context_->CrackURL(info.root_url).virtual_path(); 190 DCHECK(root_virtual_path == iterator->virtual_path || 191 root_virtual_path.IsParent(iterator->virtual_path)); 192 base::FilePath full_path; 193 root_virtual_path.AppendRelativePath(iterator->virtual_path, &full_path); 194 entry_definition.full_path = full_path; 195 196 OnIteratorConverted(self_deleter.Pass(), iterator, entry_definition); 197 } 198 199 void FileDefinitionListConverter::OnIteratorConverted( 200 scoped_ptr<FileDefinitionListConverter> self_deleter, 201 FileDefinitionList::const_iterator iterator, 202 const EntryDefinition& entry_definition) { 203 result_->push_back(entry_definition); 204 ConvertNextIterator(self_deleter.Pass(), ++iterator); 205 } 206 207 // Helper function to return the converted definition entry directly, without 208 // the redundant container. 209 void OnConvertFileDefinitionDone( 210 const EntryDefinitionCallback& callback, 211 scoped_ptr<EntryDefinitionList> entry_definition_list) { 212 DCHECK_EQ(1u, entry_definition_list->size()); 213 callback.Run(entry_definition_list->at(0)); 214 } 215 216 // Used to implement CheckIfDirectoryExists(). 217 void CheckIfDirectoryExistsOnIOThread( 218 scoped_refptr<fileapi::FileSystemContext> file_system_context, 219 const GURL& url, 220 const fileapi::FileSystemOperationRunner::StatusCallback& callback) { 221 DCHECK_CURRENTLY_ON(BrowserThread::IO); 222 223 fileapi::FileSystemURL file_system_url = file_system_context->CrackURL(url); 224 file_system_context->operation_runner()->DirectoryExists( 225 file_system_url, callback); 226 } 227 228 } // namespace 229 230 EntryDefinition::EntryDefinition() { 231 } 232 233 EntryDefinition::~EntryDefinition() { 234 } 235 236 fileapi::FileSystemContext* GetFileSystemContextForExtensionId( 237 Profile* profile, 238 const std::string& extension_id) { 239 GURL site = extensions::util::GetSiteForExtensionId(extension_id, profile); 240 return content::BrowserContext::GetStoragePartitionForSite(profile, site)-> 241 GetFileSystemContext(); 242 } 243 244 fileapi::FileSystemContext* GetFileSystemContextForRenderViewHost( 245 Profile* profile, 246 content::RenderViewHost* render_view_host) { 247 content::SiteInstance* site_instance = render_view_host->GetSiteInstance(); 248 return content::BrowserContext::GetStoragePartition(profile, site_instance)-> 249 GetFileSystemContext(); 250 } 251 252 base::FilePath ConvertDrivePathToRelativeFileSystemPath( 253 Profile* profile, 254 const std::string& extension_id, 255 const base::FilePath& drive_path) { 256 // "/special/drive-xxx" 257 base::FilePath path = drive::util::GetDriveMountPointPath(profile); 258 // appended with (|drive_path| - "drive"). 259 drive::util::GetDriveGrandRootPath().AppendRelativePath(drive_path, &path); 260 261 base::FilePath relative_path; 262 ConvertAbsoluteFilePathToRelativeFileSystemPath(profile, 263 extension_id, 264 path, 265 &relative_path); 266 return relative_path; 267 } 268 269 GURL ConvertDrivePathToFileSystemUrl(Profile* profile, 270 const base::FilePath& drive_path, 271 const std::string& extension_id) { 272 const base::FilePath relative_path = 273 ConvertDrivePathToRelativeFileSystemPath(profile, extension_id, 274 drive_path); 275 if (relative_path.empty()) 276 return GURL(); 277 return ConvertRelativeFilePathToFileSystemUrl(relative_path, extension_id); 278 } 279 280 bool ConvertAbsoluteFilePathToFileSystemUrl(Profile* profile, 281 const base::FilePath& absolute_path, 282 const std::string& extension_id, 283 GURL* url) { 284 base::FilePath relative_path; 285 if (!ConvertAbsoluteFilePathToRelativeFileSystemPath(profile, 286 extension_id, 287 absolute_path, 288 &relative_path)) { 289 return false; 290 } 291 *url = ConvertRelativeFilePathToFileSystemUrl(relative_path, extension_id); 292 return true; 293 } 294 295 bool ConvertAbsoluteFilePathToRelativeFileSystemPath( 296 Profile* profile, 297 const std::string& extension_id, 298 const base::FilePath& absolute_path, 299 base::FilePath* virtual_path) { 300 // File browser APIs are meant to be used only from extension context, so the 301 // extension's site is the one in whose file system context the virtual path 302 // should be found. 303 GURL site = extensions::util::GetSiteForExtensionId(extension_id, profile); 304 fileapi::ExternalFileSystemBackend* backend = 305 content::BrowserContext::GetStoragePartitionForSite(profile, site)-> 306 GetFileSystemContext()->external_backend(); 307 if (!backend) 308 return false; 309 310 // Find if this file path is managed by the external backend. 311 if (!backend->GetVirtualPath(absolute_path, virtual_path)) 312 return false; 313 314 return true; 315 } 316 317 void ConvertFileDefinitionListToEntryDefinitionList( 318 Profile* profile, 319 const std::string& extension_id, 320 const FileDefinitionList& file_definition_list, 321 const EntryDefinitionListCallback& callback) { 322 DCHECK_CURRENTLY_ON(BrowserThread::UI); 323 324 // The converter object destroys itself. 325 new FileDefinitionListConverter( 326 profile, extension_id, file_definition_list, callback); 327 } 328 329 void ConvertFileDefinitionToEntryDefinition( 330 Profile* profile, 331 const std::string& extension_id, 332 const FileDefinition& file_definition, 333 const EntryDefinitionCallback& callback) { 334 DCHECK_CURRENTLY_ON(BrowserThread::UI); 335 336 FileDefinitionList file_definition_list; 337 file_definition_list.push_back(file_definition); 338 ConvertFileDefinitionListToEntryDefinitionList( 339 profile, 340 extension_id, 341 file_definition_list, 342 base::Bind(&OnConvertFileDefinitionDone, callback)); 343 } 344 345 void CheckIfDirectoryExists( 346 scoped_refptr<fileapi::FileSystemContext> file_system_context, 347 const GURL& url, 348 const fileapi::FileSystemOperationRunner::StatusCallback& callback) { 349 DCHECK_CURRENTLY_ON(BrowserThread::UI); 350 351 // Check the existence of directory using file system API implementation on 352 // behalf of the file manager app. We need to grant access beforehand. 353 fileapi::ExternalFileSystemBackend* backend = 354 file_system_context->external_backend(); 355 DCHECK(backend); 356 backend->GrantFullAccessToExtension(kFileManagerAppId); 357 358 BrowserThread::PostTask( 359 BrowserThread::IO, FROM_HERE, 360 base::Bind(&CheckIfDirectoryExistsOnIOThread, 361 file_system_context, 362 url, 363 google_apis::CreateRelayCallback(callback))); 364 } 365 366 } // namespace util 367 } // namespace file_manager 368