file_system_util.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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 "chrome/browser/chromeos/drive/file_system_util.h" 6 7#include <string> 8 9#include "base/basictypes.h" 10#include "base/bind.h" 11#include "base/bind_helpers.h" 12#include "base/file_util.h" 13#include "base/files/file_enumerator.h" 14#include "base/files/file_path.h" 15#include "base/files/scoped_platform_file_closer.h" 16#include "base/i18n/icu_string_conversions.h" 17#include "base/json/json_file_value_serializer.h" 18#include "base/logging.h" 19#include "base/md5.h" 20#include "base/memory/scoped_ptr.h" 21#include "base/message_loop/message_loop_proxy.h" 22#include "base/prefs/pref_service.h" 23#include "base/strings/string_number_conversions.h" 24#include "base/strings/string_util.h" 25#include "base/strings/stringprintf.h" 26#include "base/threading/sequenced_worker_pool.h" 27#include "chrome/browser/browser_process.h" 28#include "chrome/browser/chromeos/drive/drive.pb.h" 29#include "chrome/browser/chromeos/drive/drive_integration_service.h" 30#include "chrome/browser/chromeos/drive/file_system_interface.h" 31#include "chrome/browser/chromeos/drive/job_list.h" 32#include "chrome/browser/chromeos/drive/write_on_cache_file.h" 33#include "chrome/browser/chromeos/profiles/profile_util.h" 34#include "chrome/browser/google_apis/gdata_wapi_parser.h" 35#include "chrome/browser/profiles/profile.h" 36#include "chrome/browser/profiles/profile_manager.h" 37#include "chrome/common/chrome_constants.h" 38#include "chrome/common/chrome_paths_internal.h" 39#include "chrome/common/pref_names.h" 40#include "chrome/common/url_constants.h" 41#include "chromeos/chromeos_constants.h" 42#include "content/public/browser/browser_thread.h" 43#include "net/base/escape.h" 44#include "webkit/browser/fileapi/file_system_url.h" 45 46using content::BrowserThread; 47 48namespace drive { 49namespace util { 50 51namespace { 52 53const char kDriveMountPointPath[] = "/special/drive"; 54 55const base::FilePath::CharType kDriveMyDriveMountPointPath[] = 56 FILE_PATH_LITERAL("/special/drive/root"); 57 58const base::FilePath::CharType kDriveMyDriveRootPath[] = 59 FILE_PATH_LITERAL("drive/root"); 60 61const base::FilePath::CharType kFileCacheVersionDir[] = 62 FILE_PATH_LITERAL("v1"); 63 64const char kSlash[] = "/"; 65const char kEscapedSlash[] = "\xE2\x88\x95"; 66 67const base::FilePath& GetDriveMyDriveMountPointPath() { 68 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mydrive_mount_path, 69 (kDriveMyDriveMountPointPath)); 70 return drive_mydrive_mount_path; 71} 72 73std::string ReadStringFromGDocFile(const base::FilePath& file_path, 74 const std::string& key) { 75 const int64 kMaxGDocSize = 4096; 76 int64 file_size = 0; 77 if (!file_util::GetFileSize(file_path, &file_size) || 78 file_size > kMaxGDocSize) { 79 DLOG(INFO) << "File too large to be a GDoc file " << file_path.value(); 80 return std::string(); 81 } 82 83 JSONFileValueSerializer reader(file_path); 84 std::string error_message; 85 scoped_ptr<base::Value> root_value(reader.Deserialize(NULL, &error_message)); 86 if (!root_value) { 87 DLOG(INFO) << "Failed to parse " << file_path.value() << " as JSON." 88 << " error = " << error_message; 89 return std::string(); 90 } 91 92 base::DictionaryValue* dictionary_value = NULL; 93 std::string result; 94 if (!root_value->GetAsDictionary(&dictionary_value) || 95 !dictionary_value->GetString(key, &result)) { 96 DLOG(INFO) << "No value for the given key is stored in " 97 << file_path.value() << ". key = " << key; 98 return std::string(); 99 } 100 101 return result; 102} 103 104// Moves all files under |directory_from| to |directory_to|. 105void MoveAllFilesFromDirectory(const base::FilePath& directory_from, 106 const base::FilePath& directory_to) { 107 base::FileEnumerator enumerator(directory_from, false, // not recursive 108 base::FileEnumerator::FILES); 109 for (base::FilePath file_from = enumerator.Next(); !file_from.empty(); 110 file_from = enumerator.Next()) { 111 const base::FilePath file_to = directory_to.Append(file_from.BaseName()); 112 if (!base::PathExists(file_to)) // Do not overwrite existing files. 113 base::Move(file_from, file_to); 114 } 115} 116 117// Returns DriveIntegrationService instance, if Drive is enabled. 118// Otherwise, NULL. 119DriveIntegrationService* GetIntegrationServiceByProfile(Profile* profile) { 120 // TODO(hidehiko): GetForProfile will return DriveIntegrationService 121 // regardless of mounting state. Needs to check the mounting state 122 // later. crbug.com/284972. 123 return DriveIntegrationServiceFactory::GetForProfile(profile); 124} 125 126} // namespace 127 128const base::FilePath& GetDriveGrandRootPath() { 129 CR_DEFINE_STATIC_LOCAL(base::FilePath, grand_root_path, 130 (util::kDriveGrandRootDirName)); 131 return grand_root_path; 132} 133 134const base::FilePath& GetDriveMyDriveRootPath() { 135 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_root_path, 136 (util::kDriveMyDriveRootPath)); 137 return drive_root_path; 138} 139 140const base::FilePath& GetDriveMountPointPath() { 141 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mount_path, 142 (base::FilePath::FromUTF8Unsafe(kDriveMountPointPath))); 143 return drive_mount_path; 144} 145 146FileSystemInterface* GetFileSystemByProfile(Profile* profile) { 147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 148 149 DriveIntegrationService* integration_service = 150 GetIntegrationServiceByProfile(profile); 151 return integration_service ? integration_service->file_system() : NULL; 152} 153 154FileSystemInterface* GetFileSystemByProfileId(void* profile_id) { 155 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 156 157 // |profile_id| needs to be checked with ProfileManager::IsValidProfile 158 // before using it. 159 Profile* profile = reinterpret_cast<Profile*>(profile_id); 160 if (!g_browser_process->profile_manager()->IsValidProfile(profile)) 161 return NULL; 162 return GetFileSystemByProfile(profile); 163} 164 165DriveAppRegistry* GetDriveAppRegistryByProfile(Profile* profile) { 166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 167 168 DriveIntegrationService* integration_service = 169 GetIntegrationServiceByProfile(profile); 170 return integration_service ? 171 integration_service->drive_app_registry() : 172 NULL; 173} 174 175DriveServiceInterface* GetDriveServiceByProfile(Profile* profile) { 176 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 177 178 DriveIntegrationService* integration_service = 179 GetIntegrationServiceByProfile(profile); 180 return integration_service ? integration_service->drive_service() : NULL; 181} 182 183bool IsSpecialResourceId(const std::string& resource_id) { 184 return resource_id == kDriveGrandRootSpecialResourceId || 185 resource_id == kDriveOtherDirSpecialResourceId; 186} 187 188ResourceEntry CreateMyDriveRootEntry(const std::string& root_resource_id) { 189 ResourceEntry mydrive_root; 190 mydrive_root.mutable_file_info()->set_is_directory(true); 191 mydrive_root.set_resource_id(root_resource_id); 192 mydrive_root.set_parent_local_id(util::kDriveGrandRootSpecialResourceId); 193 mydrive_root.set_title(util::kDriveMyDriveRootDirName); 194 return mydrive_root; 195} 196 197const std::string& GetDriveMountPointPathAsString() { 198 CR_DEFINE_STATIC_LOCAL(std::string, drive_mount_path_string, 199 (kDriveMountPointPath)); 200 return drive_mount_path_string; 201} 202 203GURL FilePathToDriveURL(const base::FilePath& path) { 204 std::string url(base::StringPrintf("%s:%s", 205 chrome::kDriveScheme, 206 path.AsUTF8Unsafe().c_str())); 207 return GURL(url); 208} 209 210base::FilePath DriveURLToFilePath(const GURL& url) { 211 if (!url.is_valid() || url.scheme() != chrome::kDriveScheme) 212 return base::FilePath(); 213 std::string path_string = net::UnescapeURLComponent( 214 url.path(), net::UnescapeRule::NORMAL); 215 return base::FilePath::FromUTF8Unsafe(path_string); 216} 217 218void MaybeSetDriveURL(Profile* profile, const base::FilePath& path, GURL* url) { 219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 220 221 if (!IsUnderDriveMountPoint(path)) 222 return; 223 224 FileSystemInterface* file_system = GetFileSystemByProfile(profile); 225 if (!file_system) 226 return; 227 228 *url = FilePathToDriveURL(util::ExtractDrivePath(path)); 229} 230 231bool IsUnderDriveMountPoint(const base::FilePath& path) { 232 return GetDriveMountPointPath() == path || 233 GetDriveMountPointPath().IsParent(path); 234} 235 236bool NeedsNamespaceMigration(const base::FilePath& path) { 237 // Before migration, "My Drive" which was represented as "drive. 238 // The user might use some path pointing a directory in "My Drive". 239 // e.g. "drive/downloads_dir" 240 // We changed the path for the "My Drive" to "drive/root", hence the user pref 241 // pointing to the old path needs update to the new path. 242 // e.g. "drive/root/downloads_dir" 243 // If |path| already points to some directory in "drive/root", there's no need 244 // to update it. 245 return IsUnderDriveMountPoint(path) && 246 !(GetDriveMyDriveMountPointPath() == path || 247 GetDriveMyDriveMountPointPath().IsParent(path)); 248} 249 250base::FilePath ConvertToMyDriveNamespace(const base::FilePath& path) { 251 DCHECK(NeedsNamespaceMigration(path)); 252 253 // Need to migrate "/special/drive(.*)" to "/special/drive/root(.*)". 254 // Append the relative path from "/special/drive". 255 base::FilePath new_path(GetDriveMyDriveMountPointPath()); 256 GetDriveMountPointPath().AppendRelativePath(path, &new_path); 257 DVLOG(1) << "Migrate download.default_directory setting from " 258 << path.AsUTF8Unsafe() << " to " << new_path.AsUTF8Unsafe(); 259 DCHECK(!NeedsNamespaceMigration(new_path)); 260 return new_path; 261} 262 263base::FilePath ExtractDrivePath(const base::FilePath& path) { 264 if (!IsUnderDriveMountPoint(path)) 265 return base::FilePath(); 266 267 base::FilePath drive_path = GetDriveGrandRootPath(); 268 GetDriveMountPointPath().AppendRelativePath(path, &drive_path); 269 return drive_path; 270} 271 272base::FilePath ExtractDrivePathFromFileSystemUrl( 273 const fileapi::FileSystemURL& url) { 274 if (!url.is_valid() || url.type() != fileapi::kFileSystemTypeDrive) 275 return base::FilePath(); 276 return ExtractDrivePath(url.path()); 277} 278 279base::FilePath GetCacheRootPath(Profile* profile) { 280 base::FilePath cache_base_path; 281 chrome::GetUserCacheDirectory(profile->GetPath(), &cache_base_path); 282 base::FilePath cache_root_path = 283 cache_base_path.Append(chromeos::kDriveCacheDirname); 284 return cache_root_path.Append(kFileCacheVersionDir); 285} 286 287std::string EscapeCacheFileName(const std::string& filename) { 288 // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape 289 std::string escaped; 290 for (size_t i = 0; i < filename.size(); ++i) { 291 char c = filename[i]; 292 if (c == '%' || c == '.' || c == '/') { 293 base::StringAppendF(&escaped, "%%%02X", c); 294 } else { 295 escaped.push_back(c); 296 } 297 } 298 return escaped; 299} 300 301std::string UnescapeCacheFileName(const std::string& filename) { 302 std::string unescaped; 303 for (size_t i = 0; i < filename.size(); ++i) { 304 char c = filename[i]; 305 if (c == '%' && i + 2 < filename.length()) { 306 c = (HexDigitToInt(filename[i + 1]) << 4) + 307 HexDigitToInt(filename[i + 2]); 308 i += 2; 309 } 310 unescaped.push_back(c); 311 } 312 return unescaped; 313} 314 315std::string NormalizeFileName(const std::string& input) { 316 DCHECK(IsStringUTF8(input)); 317 318 std::string output; 319 if (!base::ConvertToUtf8AndNormalize(input, base::kCodepageUTF8, &output)) 320 output = input; 321 ReplaceChars(output, kSlash, std::string(kEscapedSlash), &output); 322 return output; 323} 324 325void MigrateCacheFilesFromOldDirectories( 326 const base::FilePath& cache_root_directory, 327 const base::FilePath::StringType& cache_file_directory_name) { 328 const base::FilePath persistent_directory = 329 cache_root_directory.AppendASCII("persistent"); 330 const base::FilePath tmp_directory = 331 cache_root_directory.AppendASCII("tmp"); 332 if (!base::PathExists(persistent_directory)) 333 return; 334 335 const base::FilePath cache_file_directory = 336 cache_root_directory.Append(cache_file_directory_name); 337 338 // Move all files inside "persistent" to "files". 339 MoveAllFilesFromDirectory(persistent_directory, cache_file_directory); 340 base::DeleteFile(persistent_directory, true /* recursive */); 341 342 // Move all files inside "tmp" to "files". 343 MoveAllFilesFromDirectory(tmp_directory, cache_file_directory); 344} 345 346void PrepareWritableFileAndRun(Profile* profile, 347 const base::FilePath& path, 348 const PrepareWritableFileCallback& callback) { 349 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 350 DCHECK(!callback.is_null()); 351 352 FileSystemInterface* file_system = GetFileSystemByProfile(profile); 353 if (!file_system || !IsUnderDriveMountPoint(path)) { 354 content::BrowserThread::GetBlockingPool()->PostTask( 355 FROM_HERE, base::Bind(callback, FILE_ERROR_FAILED, base::FilePath())); 356 return; 357 } 358 359 WriteOnCacheFile(file_system, 360 ExtractDrivePath(path), 361 std::string(), // mime_type 362 callback); 363} 364 365void EnsureDirectoryExists(Profile* profile, 366 const base::FilePath& directory, 367 const FileOperationCallback& callback) { 368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 369 DCHECK(!callback.is_null()); 370 if (IsUnderDriveMountPoint(directory)) { 371 FileSystemInterface* file_system = GetFileSystemByProfile(profile); 372 DCHECK(file_system); 373 file_system->CreateDirectory( 374 ExtractDrivePath(directory), 375 true /* is_exclusive */, 376 true /* is_recursive */, 377 callback); 378 } else { 379 base::MessageLoopProxy::current()->PostTask( 380 FROM_HERE, base::Bind(callback, FILE_ERROR_OK)); 381 } 382} 383 384void EmptyFileOperationCallback(FileError error) { 385} 386 387bool CreateGDocFile(const base::FilePath& file_path, 388 const GURL& url, 389 const std::string& resource_id) { 390 std::string content = base::StringPrintf( 391 "{\"url\": \"%s\", \"resource_id\": \"%s\"}", 392 url.spec().c_str(), resource_id.c_str()); 393 return file_util::WriteFile(file_path, content.data(), content.size()) == 394 static_cast<int>(content.size()); 395} 396 397bool HasGDocFileExtension(const base::FilePath& file_path) { 398 return google_apis::ResourceEntry::ClassifyEntryKindByFileExtension( 399 file_path) & 400 google_apis::ResourceEntry::KIND_OF_HOSTED_DOCUMENT; 401} 402 403GURL ReadUrlFromGDocFile(const base::FilePath& file_path) { 404 return GURL(ReadStringFromGDocFile(file_path, "url")); 405} 406 407std::string ReadResourceIdFromGDocFile(const base::FilePath& file_path) { 408 return ReadStringFromGDocFile(file_path, "resource_id"); 409} 410 411std::string GetMd5Digest(const base::FilePath& file_path) { 412 const int kBufferSize = 512 * 1024; // 512kB. 413 414 base::PlatformFile file = base::CreatePlatformFile( 415 file_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, 416 NULL, NULL); 417 if (file == base::kInvalidPlatformFileValue) 418 return std::string(); 419 base::ScopedPlatformFileCloser file_closer(&file); 420 421 base::MD5Context context; 422 base::MD5Init(&context); 423 424 scoped_ptr<char[]> buffer(new char[kBufferSize]); 425 while (true) { 426 int result = base::ReadPlatformFileCurPosNoBestEffort( 427 file, buffer.get(), kBufferSize); 428 429 if (result < 0) { 430 // Found an error. 431 return std::string(); 432 } 433 434 if (result == 0) { 435 // End of file. 436 break; 437 } 438 439 base::MD5Update(&context, base::StringPiece(buffer.get(), result)); 440 } 441 442 base::MD5Digest digest; 443 base::MD5Final(&digest, &context); 444 return MD5DigestToBase16(digest); 445} 446 447bool IsDriveEnabledForProfile(Profile* profile) { 448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 449 450 if (!chromeos::IsProfileAssociatedWithGaiaAccount(profile)) 451 return false; 452 453 // Disable Drive if preference is set. This can happen with commandline flag 454 // --disable-drive or enterprise policy, or with user settings. 455 if (profile->GetPrefs()->GetBoolean(prefs::kDisableDrive)) 456 return false; 457 458 return true; 459} 460 461} // namespace util 462} // namespace drive 463