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