Home | History | Annotate | Download | only in drive
      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