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_util.h" 31 #include "chrome/browser/profiles/profile.h" 32 #include "chrome/browser/profiles/profile_manager.h" 33 #include "chrome/common/chrome_constants.h" 34 #include "chrome/common/chrome_paths_internal.h" 35 #include "chrome/common/pref_names.h" 36 #include "chrome/common/url_constants.h" 37 #include "chromeos/chromeos_constants.h" 38 #include "content/public/browser/browser_thread.h" 39 #include "google_apis/drive/gdata_wapi_parser.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 kDot = '.'; 63 const char kEscapedChars[] = "_"; 64 65 const base::FilePath& GetDriveMyDriveMountPointPath() { 66 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mydrive_mount_path, 67 (kDriveMyDriveMountPointPath)); 68 return drive_mydrive_mount_path; 69 } 70 71 std::string ReadStringFromGDocFile(const base::FilePath& file_path, 72 const std::string& key) { 73 const int64 kMaxGDocSize = 4096; 74 int64 file_size = 0; 75 if (!base::GetFileSize(file_path, &file_size) || 76 file_size > kMaxGDocSize) { 77 LOG(WARNING) << "File too large to be a GDoc file " << file_path.value(); 78 return std::string(); 79 } 80 81 JSONFileValueSerializer reader(file_path); 82 std::string error_message; 83 scoped_ptr<base::Value> root_value(reader.Deserialize(NULL, &error_message)); 84 if (!root_value) { 85 LOG(WARNING) << "Failed to parse " << file_path.value() << " as JSON." 86 << " error = " << error_message; 87 return std::string(); 88 } 89 90 base::DictionaryValue* dictionary_value = NULL; 91 std::string result; 92 if (!root_value->GetAsDictionary(&dictionary_value) || 93 !dictionary_value->GetString(key, &result)) { 94 LOG(WARNING) << "No value for the given key is stored in " 95 << file_path.value() << ". key = " << key; 96 return std::string(); 97 } 98 99 return result; 100 } 101 102 // Returns DriveIntegrationService instance, if Drive is enabled. 103 // Otherwise, NULL. 104 DriveIntegrationService* GetIntegrationServiceByProfile(Profile* profile) { 105 DriveIntegrationService* service = 106 DriveIntegrationServiceFactory::FindForProfile(profile); 107 if (!service || !service->IsMounted()) 108 return NULL; 109 return service; 110 } 111 112 } // namespace 113 114 const base::FilePath& GetDriveGrandRootPath() { 115 CR_DEFINE_STATIC_LOCAL(base::FilePath, grand_root_path, 116 (util::kDriveGrandRootDirName)); 117 return grand_root_path; 118 } 119 120 const base::FilePath& GetDriveMyDriveRootPath() { 121 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_root_path, 122 (util::kDriveMyDriveRootPath)); 123 return drive_root_path; 124 } 125 126 const base::FilePath& GetDriveMountPointPath() { 127 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mount_path, 128 (base::FilePath::FromUTF8Unsafe(kDriveMountPointPath))); 129 return drive_mount_path; 130 } 131 132 FileSystemInterface* GetFileSystemByProfile(Profile* profile) { 133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 134 135 DriveIntegrationService* integration_service = 136 GetIntegrationServiceByProfile(profile); 137 return integration_service ? integration_service->file_system() : NULL; 138 } 139 140 FileSystemInterface* GetFileSystemByProfileId(void* profile_id) { 141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 142 143 // |profile_id| needs to be checked with ProfileManager::IsValidProfile 144 // before using it. 145 Profile* profile = reinterpret_cast<Profile*>(profile_id); 146 if (!g_browser_process->profile_manager()->IsValidProfile(profile)) 147 return NULL; 148 return GetFileSystemByProfile(profile); 149 } 150 151 DriveAppRegistry* GetDriveAppRegistryByProfile(Profile* profile) { 152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 153 154 DriveIntegrationService* integration_service = 155 GetIntegrationServiceByProfile(profile); 156 return integration_service ? 157 integration_service->drive_app_registry() : 158 NULL; 159 } 160 161 DriveServiceInterface* GetDriveServiceByProfile(Profile* profile) { 162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 163 164 DriveIntegrationService* integration_service = 165 GetIntegrationServiceByProfile(profile); 166 return integration_service ? integration_service->drive_service() : NULL; 167 } 168 169 bool IsSpecialResourceId(const std::string& resource_id) { 170 return resource_id == kDriveGrandRootLocalId || 171 resource_id == kDriveOtherDirLocalId; 172 } 173 174 ResourceEntry CreateMyDriveRootEntry(const std::string& root_resource_id) { 175 ResourceEntry mydrive_root; 176 mydrive_root.mutable_file_info()->set_is_directory(true); 177 mydrive_root.set_resource_id(root_resource_id); 178 mydrive_root.set_parent_local_id(util::kDriveGrandRootLocalId); 179 mydrive_root.set_title(util::kDriveMyDriveRootDirName); 180 return mydrive_root; 181 } 182 183 const std::string& GetDriveMountPointPathAsString() { 184 CR_DEFINE_STATIC_LOCAL(std::string, drive_mount_path_string, 185 (kDriveMountPointPath)); 186 return drive_mount_path_string; 187 } 188 189 GURL FilePathToDriveURL(const base::FilePath& path) { 190 std::string url(base::StringPrintf("%s:%s", 191 chrome::kDriveScheme, 192 path.AsUTF8Unsafe().c_str())); 193 return GURL(url); 194 } 195 196 base::FilePath DriveURLToFilePath(const GURL& url) { 197 if (!url.is_valid() || url.scheme() != chrome::kDriveScheme) 198 return base::FilePath(); 199 std::string path_string = net::UnescapeURLComponent( 200 url.GetContent(), net::UnescapeRule::NORMAL); 201 return base::FilePath::FromUTF8Unsafe(path_string); 202 } 203 204 void MaybeSetDriveURL(Profile* profile, const base::FilePath& path, GURL* url) { 205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 206 207 if (!IsUnderDriveMountPoint(path)) 208 return; 209 210 FileSystemInterface* file_system = GetFileSystemByProfile(profile); 211 if (!file_system) 212 return; 213 214 *url = FilePathToDriveURL(util::ExtractDrivePath(path)); 215 } 216 217 bool IsUnderDriveMountPoint(const base::FilePath& path) { 218 return GetDriveMountPointPath() == path || 219 GetDriveMountPointPath().IsParent(path); 220 } 221 222 bool NeedsNamespaceMigration(const base::FilePath& path) { 223 // Before migration, "My Drive" which was represented as "drive. 224 // The user might use some path pointing a directory in "My Drive". 225 // e.g. "drive/downloads_dir" 226 // We changed the path for the "My Drive" to "drive/root", hence the user pref 227 // pointing to the old path needs update to the new path. 228 // e.g. "drive/root/downloads_dir" 229 // If |path| already points to some directory in "drive/root", there's no need 230 // to update it. 231 return IsUnderDriveMountPoint(path) && 232 !(GetDriveMyDriveMountPointPath() == path || 233 GetDriveMyDriveMountPointPath().IsParent(path)); 234 } 235 236 base::FilePath ConvertToMyDriveNamespace(const base::FilePath& path) { 237 DCHECK(NeedsNamespaceMigration(path)); 238 239 // Need to migrate "/special/drive(.*)" to "/special/drive/root(.*)". 240 // Append the relative path from "/special/drive". 241 base::FilePath new_path(GetDriveMyDriveMountPointPath()); 242 GetDriveMountPointPath().AppendRelativePath(path, &new_path); 243 DVLOG(1) << "Migrate download.default_directory setting from " 244 << path.AsUTF8Unsafe() << " to " << new_path.AsUTF8Unsafe(); 245 DCHECK(!NeedsNamespaceMigration(new_path)); 246 return new_path; 247 } 248 249 base::FilePath ExtractDrivePath(const base::FilePath& path) { 250 if (!IsUnderDriveMountPoint(path)) 251 return base::FilePath(); 252 253 base::FilePath drive_path = GetDriveGrandRootPath(); 254 GetDriveMountPointPath().AppendRelativePath(path, &drive_path); 255 return drive_path; 256 } 257 258 base::FilePath ExtractDrivePathFromFileSystemUrl( 259 const fileapi::FileSystemURL& url) { 260 if (!url.is_valid() || url.type() != fileapi::kFileSystemTypeDrive) 261 return base::FilePath(); 262 return ExtractDrivePath(url.path()); 263 } 264 265 base::FilePath GetCacheRootPath(Profile* profile) { 266 base::FilePath cache_base_path; 267 chrome::GetUserCacheDirectory(profile->GetPath(), &cache_base_path); 268 base::FilePath cache_root_path = 269 cache_base_path.Append(chromeos::kDriveCacheDirname); 270 return cache_root_path.Append(kFileCacheVersionDir); 271 } 272 273 std::string EscapeCacheFileName(const std::string& filename) { 274 // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape 275 std::string escaped; 276 for (size_t i = 0; i < filename.size(); ++i) { 277 char c = filename[i]; 278 if (c == '%' || c == '.' || c == '/') { 279 base::StringAppendF(&escaped, "%%%02X", c); 280 } else { 281 escaped.push_back(c); 282 } 283 } 284 return escaped; 285 } 286 287 std::string UnescapeCacheFileName(const std::string& filename) { 288 std::string unescaped; 289 for (size_t i = 0; i < filename.size(); ++i) { 290 char c = filename[i]; 291 if (c == '%' && i + 2 < filename.length()) { 292 c = (HexDigitToInt(filename[i + 1]) << 4) + 293 HexDigitToInt(filename[i + 2]); 294 i += 2; 295 } 296 unescaped.push_back(c); 297 } 298 return unescaped; 299 } 300 301 std::string NormalizeFileName(const std::string& input) { 302 DCHECK(IsStringUTF8(input)); 303 304 std::string output; 305 if (!base::ConvertToUtf8AndNormalize(input, base::kCodepageUTF8, &output)) 306 output = input; 307 base::ReplaceChars(output, kSlash, std::string(kEscapedChars), &output); 308 if (!output.empty() && output.find_first_not_of(kDot, 0) == std::string::npos) 309 output = kEscapedChars; 310 return output; 311 } 312 313 void PrepareWritableFileAndRun(Profile* profile, 314 const base::FilePath& path, 315 const PrepareWritableFileCallback& callback) { 316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 317 DCHECK(!callback.is_null()); 318 319 FileSystemInterface* file_system = GetFileSystemByProfile(profile); 320 if (!file_system || !IsUnderDriveMountPoint(path)) { 321 content::BrowserThread::GetBlockingPool()->PostTask( 322 FROM_HERE, base::Bind(callback, FILE_ERROR_FAILED, base::FilePath())); 323 return; 324 } 325 326 WriteOnCacheFile(file_system, 327 ExtractDrivePath(path), 328 std::string(), // mime_type 329 callback); 330 } 331 332 void EnsureDirectoryExists(Profile* profile, 333 const base::FilePath& directory, 334 const FileOperationCallback& callback) { 335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 336 DCHECK(!callback.is_null()); 337 if (IsUnderDriveMountPoint(directory)) { 338 FileSystemInterface* file_system = GetFileSystemByProfile(profile); 339 DCHECK(file_system); 340 file_system->CreateDirectory( 341 ExtractDrivePath(directory), 342 true /* is_exclusive */, 343 true /* is_recursive */, 344 callback); 345 } else { 346 base::MessageLoopProxy::current()->PostTask( 347 FROM_HERE, base::Bind(callback, FILE_ERROR_OK)); 348 } 349 } 350 351 void EmptyFileOperationCallback(FileError error) { 352 } 353 354 bool CreateGDocFile(const base::FilePath& file_path, 355 const GURL& url, 356 const std::string& resource_id) { 357 std::string content = base::StringPrintf( 358 "{\"url\": \"%s\", \"resource_id\": \"%s\"}", 359 url.spec().c_str(), resource_id.c_str()); 360 return file_util::WriteFile(file_path, content.data(), content.size()) == 361 static_cast<int>(content.size()); 362 } 363 364 bool HasGDocFileExtension(const base::FilePath& file_path) { 365 return google_apis::ResourceEntry::ClassifyEntryKindByFileExtension( 366 file_path) & 367 google_apis::ResourceEntry::KIND_OF_HOSTED_DOCUMENT; 368 } 369 370 GURL ReadUrlFromGDocFile(const base::FilePath& file_path) { 371 return GURL(ReadStringFromGDocFile(file_path, "url")); 372 } 373 374 std::string ReadResourceIdFromGDocFile(const base::FilePath& file_path) { 375 return ReadStringFromGDocFile(file_path, "resource_id"); 376 } 377 378 bool IsDriveEnabledForProfile(Profile* profile) { 379 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 380 381 if (!chromeos::IsProfileAssociatedWithGaiaAccount(profile)) 382 return false; 383 384 // Disable Drive if preference is set. This can happen with commandline flag 385 // --disable-drive or enterprise policy, or with user settings. 386 if (profile->GetPrefs()->GetBoolean(prefs::kDisableDrive)) 387 return false; 388 389 return true; 390 } 391 392 ConnectionStatusType GetDriveConnectionStatus(Profile* profile) { 393 drive::DriveServiceInterface* const drive_service = 394 drive::util::GetDriveServiceByProfile(profile); 395 396 if (!drive_service) 397 return DRIVE_DISCONNECTED_NOSERVICE; 398 if (net::NetworkChangeNotifier::IsOffline()) 399 return DRIVE_DISCONNECTED_NONETWORK; 400 if (!drive_service->CanSendRequest()) 401 return DRIVE_DISCONNECTED_NOTREADY; 402 403 const bool is_connection_cellular = 404 net::NetworkChangeNotifier::IsConnectionCellular( 405 net::NetworkChangeNotifier::GetConnectionType()); 406 const bool disable_sync_over_celluar = 407 profile->GetPrefs()->GetBoolean(prefs::kDisableDriveOverCellular); 408 409 if (is_connection_cellular && disable_sync_over_celluar) 410 return DRIVE_CONNECTED_METERED; 411 return DRIVE_CONNECTED; 412 } 413 414 } // namespace util 415 } // namespace drive 416