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