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