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_path.h"
     14 #include "base/i18n/icu_string_conversions.h"
     15 #include "base/json/json_file_value_serializer.h"
     16 #include "base/logging.h"
     17 #include "base/memory/scoped_ptr.h"
     18 #include "base/message_loop/message_loop_proxy.h"
     19 #include "base/prefs/pref_service.h"
     20 #include "base/strings/string_number_conversions.h"
     21 #include "base/strings/string_util.h"
     22 #include "base/strings/stringprintf.h"
     23 #include "base/threading/sequenced_worker_pool.h"
     24 #include "chrome/browser/browser_process.h"
     25 #include "chrome/browser/chromeos/drive/drive.pb.h"
     26 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
     27 #include "chrome/browser/chromeos/drive/file_system_interface.h"
     28 #include "chrome/browser/chromeos/drive/job_list.h"
     29 #include "chrome/browser/chromeos/drive/write_on_cache_file.h"
     30 #include "chrome/browser/chromeos/profiles/profile_helper.h"
     31 #include "chrome/browser/chromeos/profiles/profile_util.h"
     32 #include "chrome/browser/profiles/profile.h"
     33 #include "chrome/browser/profiles/profile_manager.h"
     34 #include "chrome/common/chrome_constants.h"
     35 #include "chrome/common/chrome_paths_internal.h"
     36 #include "chrome/common/pref_names.h"
     37 #include "chrome/common/url_constants.h"
     38 #include "chromeos/chromeos_constants.h"
     39 #include "content/public/browser/browser_thread.h"
     40 #include "google_apis/drive/gdata_wapi_parser.h"
     41 #include "net/base/escape.h"
     42 #include "webkit/browser/fileapi/file_system_url.h"
     43 
     44 using content::BrowserThread;
     45 
     46 namespace drive {
     47 namespace util {
     48 
     49 namespace {
     50 
     51 std::string ReadStringFromGDocFile(const base::FilePath& file_path,
     52                                    const std::string& key) {
     53   const int64 kMaxGDocSize = 4096;
     54   int64 file_size = 0;
     55   if (!base::GetFileSize(file_path, &file_size) ||
     56       file_size > kMaxGDocSize) {
     57     LOG(WARNING) << "File too large to be a GDoc file " << file_path.value();
     58     return std::string();
     59   }
     60 
     61   JSONFileValueSerializer reader(file_path);
     62   std::string error_message;
     63   scoped_ptr<base::Value> root_value(reader.Deserialize(NULL, &error_message));
     64   if (!root_value) {
     65     LOG(WARNING) << "Failed to parse " << file_path.value() << " as JSON."
     66                  << " error = " << error_message;
     67     return std::string();
     68   }
     69 
     70   base::DictionaryValue* dictionary_value = NULL;
     71   std::string result;
     72   if (!root_value->GetAsDictionary(&dictionary_value) ||
     73       !dictionary_value->GetString(key, &result)) {
     74     LOG(WARNING) << "No value for the given key is stored in "
     75                  << file_path.value() << ". key = " << key;
     76     return std::string();
     77   }
     78 
     79   return result;
     80 }
     81 
     82 // Returns DriveIntegrationService instance, if Drive is enabled.
     83 // Otherwise, NULL.
     84 DriveIntegrationService* GetIntegrationServiceByProfile(Profile* profile) {
     85   DriveIntegrationService* service =
     86       DriveIntegrationServiceFactory::FindForProfile(profile);
     87   if (!service || !service->IsMounted())
     88     return NULL;
     89   return service;
     90 }
     91 
     92 }  // namespace
     93 
     94 const base::FilePath& GetDriveGrandRootPath() {
     95   CR_DEFINE_STATIC_LOCAL(base::FilePath, grand_root_path,
     96       (kDriveGrandRootDirName));
     97   return grand_root_path;
     98 }
     99 
    100 const base::FilePath& GetDriveMyDriveRootPath() {
    101   CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_root_path,
    102                          (FILE_PATH_LITERAL("drive/root")));
    103   return drive_root_path;
    104 }
    105 
    106 base::FilePath GetDriveMountPointPathForUserIdHash(
    107     const std::string user_id_hash) {
    108   static const base::FilePath::CharType kSpecialMountPointRoot[] =
    109       FILE_PATH_LITERAL("/special");
    110   static const char kDriveMountPointNameBase[] = "drive";
    111   return base::FilePath(kSpecialMountPointRoot).AppendASCII(
    112       net::EscapePath(kDriveMountPointNameBase +
    113                       (user_id_hash.empty() ? "" : "-" + user_id_hash)));
    114 }
    115 
    116 base::FilePath GetDriveMountPointPath(Profile* profile) {
    117   std::string id = chromeos::ProfileHelper::GetUserIdHashFromProfile(profile);
    118   if (id.empty() || id == chrome::kLegacyProfileDir) {
    119     // ProfileHelper::GetUserIdHashFromProfile works only when multi-profile is
    120     // enabled. In that case, we fall back to use UserManager (it basically just
    121     // returns currently active users's hash in such a case.) I still try
    122     // ProfileHelper first because it works better in tests.
    123     chromeos::User* const user =
    124         chromeos::UserManager::IsInitialized() ?
    125             chromeos::UserManager::Get()->GetUserByProfile(
    126                 profile->GetOriginalProfile()) : NULL;
    127     if (user)
    128       id = user->username_hash();
    129   }
    130   return GetDriveMountPointPathForUserIdHash(id);
    131 }
    132 
    133 FileSystemInterface* GetFileSystemByProfile(Profile* profile) {
    134   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    135 
    136   DriveIntegrationService* integration_service =
    137       GetIntegrationServiceByProfile(profile);
    138   return integration_service ? integration_service->file_system() : NULL;
    139 }
    140 
    141 FileSystemInterface* GetFileSystemByProfileId(void* profile_id) {
    142   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    143 
    144   // |profile_id| needs to be checked with ProfileManager::IsValidProfile
    145   // before using it.
    146   Profile* profile = reinterpret_cast<Profile*>(profile_id);
    147   if (!g_browser_process->profile_manager()->IsValidProfile(profile))
    148     return NULL;
    149   return GetFileSystemByProfile(profile);
    150 }
    151 
    152 DriveAppRegistry* GetDriveAppRegistryByProfile(Profile* profile) {
    153   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    154 
    155   DriveIntegrationService* integration_service =
    156       GetIntegrationServiceByProfile(profile);
    157   return integration_service ?
    158       integration_service->drive_app_registry() :
    159       NULL;
    160 }
    161 
    162 DriveServiceInterface* GetDriveServiceByProfile(Profile* profile) {
    163   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    164 
    165   DriveIntegrationService* integration_service =
    166       GetIntegrationServiceByProfile(profile);
    167   return integration_service ? integration_service->drive_service() : NULL;
    168 }
    169 
    170 GURL FilePathToDriveURL(const base::FilePath& path) {
    171   std::string url(base::StringPrintf("%s:%s",
    172                                      chrome::kDriveScheme,
    173                                      path.AsUTF8Unsafe().c_str()));
    174   return GURL(url);
    175 }
    176 
    177 base::FilePath DriveURLToFilePath(const GURL& url) {
    178   if (!url.is_valid() || url.scheme() != chrome::kDriveScheme)
    179     return base::FilePath();
    180   std::string path_string = net::UnescapeURLComponent(
    181       url.GetContent(), net::UnescapeRule::NORMAL);
    182   return base::FilePath::FromUTF8Unsafe(path_string);
    183 }
    184 
    185 void MaybeSetDriveURL(Profile* profile, const base::FilePath& path, GURL* url) {
    186   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    187 
    188   if (!IsUnderDriveMountPoint(path))
    189     return;
    190 
    191   FileSystemInterface* file_system = GetFileSystemByProfile(profile);
    192   if (!file_system)
    193     return;
    194 
    195   *url = FilePathToDriveURL(util::ExtractDrivePath(path));
    196 }
    197 
    198 bool IsUnderDriveMountPoint(const base::FilePath& path) {
    199   return !ExtractDrivePath(path).empty();
    200 }
    201 
    202 base::FilePath ExtractDrivePath(const base::FilePath& path) {
    203   std::vector<base::FilePath::StringType> components;
    204   path.GetComponents(&components);
    205   if (components.size() < 3)
    206     return base::FilePath();
    207   if (components[0] != FILE_PATH_LITERAL("/"))
    208     return base::FilePath();
    209   if (components[1] != FILE_PATH_LITERAL("special"))
    210     return base::FilePath();
    211   if (!StartsWithASCII(components[2], "drive", true))
    212     return base::FilePath();
    213 
    214   base::FilePath drive_path = GetDriveGrandRootPath();
    215   for (size_t i = 3; i < components.size(); ++i)
    216     drive_path = drive_path.Append(components[i]);
    217   return drive_path;
    218 }
    219 
    220 Profile* ExtractProfileFromPath(const base::FilePath& path) {
    221   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    222 
    223   const std::vector<Profile*>& profiles =
    224       g_browser_process->profile_manager()->GetLoadedProfiles();
    225   for (size_t i = 0; i < profiles.size(); ++i) {
    226     Profile* original_profile = profiles[i]->GetOriginalProfile();
    227     if (original_profile == profiles[i] &&
    228         !chromeos::ProfileHelper::IsSigninProfile(original_profile)) {
    229       const base::FilePath base = GetDriveMountPointPath(original_profile);
    230       if (base == path || base.IsParent(path))
    231         return original_profile;
    232     }
    233   }
    234   return NULL;
    235 }
    236 
    237 base::FilePath ExtractDrivePathFromFileSystemUrl(
    238     const fileapi::FileSystemURL& url) {
    239   if (!url.is_valid() || url.type() != fileapi::kFileSystemTypeDrive)
    240     return base::FilePath();
    241   return ExtractDrivePath(url.path());
    242 }
    243 
    244 base::FilePath GetCacheRootPath(Profile* profile) {
    245   base::FilePath cache_base_path;
    246   chrome::GetUserCacheDirectory(profile->GetPath(), &cache_base_path);
    247   base::FilePath cache_root_path =
    248       cache_base_path.Append(chromeos::kDriveCacheDirname);
    249   static const base::FilePath::CharType kFileCacheVersionDir[] =
    250       FILE_PATH_LITERAL("v1");
    251   return cache_root_path.Append(kFileCacheVersionDir);
    252 }
    253 
    254 std::string EscapeCacheFileName(const std::string& filename) {
    255   // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape
    256   std::string escaped;
    257   for (size_t i = 0; i < filename.size(); ++i) {
    258     char c = filename[i];
    259     if (c == '%' || c == '.' || c == '/') {
    260       base::StringAppendF(&escaped, "%%%02X", c);
    261     } else {
    262       escaped.push_back(c);
    263     }
    264   }
    265   return escaped;
    266 }
    267 
    268 std::string UnescapeCacheFileName(const std::string& filename) {
    269   std::string unescaped;
    270   for (size_t i = 0; i < filename.size(); ++i) {
    271     char c = filename[i];
    272     if (c == '%' && i + 2 < filename.length()) {
    273       c = (HexDigitToInt(filename[i + 1]) << 4) +
    274            HexDigitToInt(filename[i + 2]);
    275       i += 2;
    276     }
    277     unescaped.push_back(c);
    278   }
    279   return unescaped;
    280 }
    281 
    282 std::string NormalizeFileName(const std::string& input) {
    283   DCHECK(base::IsStringUTF8(input));
    284 
    285   std::string output;
    286   if (!base::ConvertToUtf8AndNormalize(input, base::kCodepageUTF8, &output))
    287     output = input;
    288   base::ReplaceChars(output, "/", "_", &output);
    289   if (!output.empty() && output.find_first_not_of('.', 0) == std::string::npos)
    290     output = "_";
    291   return output;
    292 }
    293 
    294 void PrepareWritableFileAndRun(Profile* profile,
    295                                const base::FilePath& path,
    296                                const PrepareWritableFileCallback& callback) {
    297   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    298   DCHECK(!callback.is_null());
    299 
    300   FileSystemInterface* file_system = GetFileSystemByProfile(profile);
    301   if (!file_system || !IsUnderDriveMountPoint(path)) {
    302     content::BrowserThread::GetBlockingPool()->PostTask(
    303         FROM_HERE, base::Bind(callback, FILE_ERROR_FAILED, base::FilePath()));
    304     return;
    305   }
    306 
    307   WriteOnCacheFile(file_system,
    308                    ExtractDrivePath(path),
    309                    std::string(), // mime_type
    310                    callback);
    311 }
    312 
    313 void EnsureDirectoryExists(Profile* profile,
    314                            const base::FilePath& directory,
    315                            const FileOperationCallback& callback) {
    316   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    317   DCHECK(!callback.is_null());
    318   if (IsUnderDriveMountPoint(directory)) {
    319     FileSystemInterface* file_system = GetFileSystemByProfile(profile);
    320     DCHECK(file_system);
    321     file_system->CreateDirectory(
    322         ExtractDrivePath(directory),
    323         true /* is_exclusive */,
    324         true /* is_recursive */,
    325         callback);
    326   } else {
    327     base::MessageLoopProxy::current()->PostTask(
    328         FROM_HERE, base::Bind(callback, FILE_ERROR_OK));
    329   }
    330 }
    331 
    332 void EmptyFileOperationCallback(FileError error) {
    333 }
    334 
    335 bool CreateGDocFile(const base::FilePath& file_path,
    336                     const GURL& url,
    337                     const std::string& resource_id) {
    338   std::string content = base::StringPrintf(
    339       "{\"url\": \"%s\", \"resource_id\": \"%s\"}",
    340       url.spec().c_str(), resource_id.c_str());
    341   return base::WriteFile(file_path, content.data(), content.size()) ==
    342       static_cast<int>(content.size());
    343 }
    344 
    345 bool HasGDocFileExtension(const base::FilePath& file_path) {
    346   return google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
    347       file_path) &
    348       google_apis::ResourceEntry::KIND_OF_HOSTED_DOCUMENT;
    349 }
    350 
    351 GURL ReadUrlFromGDocFile(const base::FilePath& file_path) {
    352   return GURL(ReadStringFromGDocFile(file_path, "url"));
    353 }
    354 
    355 std::string ReadResourceIdFromGDocFile(const base::FilePath& file_path) {
    356   return ReadStringFromGDocFile(file_path, "resource_id");
    357 }
    358 
    359 bool IsDriveEnabledForProfile(Profile* profile) {
    360   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    361 
    362   if (!chromeos::IsProfileAssociatedWithGaiaAccount(profile))
    363     return false;
    364 
    365   // Disable Drive if preference is set. This can happen with commandline flag
    366   // --disable-drive or enterprise policy, or with user settings.
    367   if (profile->GetPrefs()->GetBoolean(prefs::kDisableDrive))
    368     return false;
    369 
    370   return true;
    371 }
    372 
    373 ConnectionStatusType GetDriveConnectionStatus(Profile* profile) {
    374   drive::DriveServiceInterface* const drive_service =
    375       drive::util::GetDriveServiceByProfile(profile);
    376 
    377   if (!drive_service)
    378     return DRIVE_DISCONNECTED_NOSERVICE;
    379   if (net::NetworkChangeNotifier::IsOffline())
    380     return DRIVE_DISCONNECTED_NONETWORK;
    381   if (!drive_service->CanSendRequest())
    382     return DRIVE_DISCONNECTED_NOTREADY;
    383 
    384   const bool is_connection_cellular =
    385       net::NetworkChangeNotifier::IsConnectionCellular(
    386           net::NetworkChangeNotifier::GetConnectionType());
    387   const bool disable_sync_over_celluar =
    388       profile->GetPrefs()->GetBoolean(prefs::kDisableDriveOverCellular);
    389 
    390   if (is_connection_cellular && disable_sync_over_celluar)
    391     return DRIVE_CONNECTED_METERED;
    392   return DRIVE_CONNECTED;
    393 }
    394 
    395 }  // namespace util
    396 }  // namespace drive
    397