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/common/chrome_paths_internal.h" 6 7#import <Foundation/Foundation.h> 8#include <string.h> 9 10#include <string> 11 12#include "base/base_paths.h" 13#include "base/logging.h" 14#import "base/mac/foundation_util.h" 15#import "base/mac/scoped_nsautorelease_pool.h" 16#include "base/memory/scoped_ptr.h" 17#include "base/path_service.h" 18#include "chrome/common/chrome_constants.h" 19 20#if !defined(OS_IOS) 21#import "base/mac/mac_util.h" 22#endif 23 24namespace { 25 26#if !defined(OS_IOS) 27const base::FilePath* g_override_versioned_directory = NULL; 28 29// Return a retained (NOT autoreleased) NSBundle* as the internal 30// implementation of chrome::OuterAppBundle(), which should be the only 31// caller. 32NSBundle* OuterAppBundleInternal() { 33 base::mac::ScopedNSAutoreleasePool pool; 34 35 if (!base::mac::AmIBundled()) { 36 // If unbundled (as in a test), there's no app bundle. 37 return nil; 38 } 39 40 if (!base::mac::IsBackgroundOnlyProcess()) { 41 // Shortcut: in the browser process, just return the main app bundle. 42 return [[NSBundle mainBundle] retain]; 43 } 44 45 // From C.app/Contents/Versions/1.2.3.4, go up three steps to get to C.app. 46 base::FilePath versioned_dir = chrome::GetVersionedDirectory(); 47 base::FilePath outer_app_dir = versioned_dir.DirName().DirName().DirName(); 48 const char* outer_app_dir_c = outer_app_dir.value().c_str(); 49 NSString* outer_app_dir_ns = [NSString stringWithUTF8String:outer_app_dir_c]; 50 51 return [[NSBundle bundleWithPath:outer_app_dir_ns] retain]; 52} 53#endif // !defined(OS_IOS) 54 55char* ProductDirNameForBundle(NSBundle* chrome_bundle) { 56 const char* product_dir_name = NULL; 57#if !defined(OS_IOS) 58 base::mac::ScopedNSAutoreleasePool pool; 59 60 NSString* product_dir_name_ns = 61 [chrome_bundle objectForInfoDictionaryKey:@"CrProductDirName"]; 62 product_dir_name = [product_dir_name_ns fileSystemRepresentation]; 63#else 64 DCHECK(!chrome_bundle); 65#endif 66 67 if (!product_dir_name) { 68#if defined(GOOGLE_CHROME_BUILD) 69 product_dir_name = "Google/Chrome"; 70#else 71 product_dir_name = "Chromium"; 72#endif 73 } 74 75 // Leaked, but the only caller initializes a static with this result, so it 76 // only happens once, and that's OK. 77 return strdup(product_dir_name); 78} 79 80// ProductDirName returns the name of the directory inside 81// ~/Library/Application Support that should hold the product application 82// data. This can be overridden by setting the CrProductDirName key in the 83// outer browser .app's Info.plist. The default is "Google/Chrome" for 84// officially-branded builds, and "Chromium" for unbranded builds. For the 85// official canary channel, the Info.plist will have CrProductDirName set 86// to "Google/Chrome Canary". 87std::string ProductDirName() { 88#if defined(OS_IOS) 89 static const char* product_dir_name = ProductDirNameForBundle(nil); 90#else 91 // Use OuterAppBundle() to get the main app's bundle. This key needs to live 92 // in the main app's bundle because it will be set differently on the canary 93 // channel, and the autoupdate system dictates that there can be no 94 // differences between channels within the versioned directory. This would 95 // normally use base::mac::FrameworkBundle(), but that references the 96 // framework bundle within the versioned directory. Ordinarily, the profile 97 // should not be accessed from non-browser processes, but those processes do 98 // attempt to get the profile directory, so direct them to look in the outer 99 // browser .app's Info.plist for the CrProductDirName key. 100 static const char* product_dir_name = 101 ProductDirNameForBundle(chrome::OuterAppBundle()); 102#endif 103 return std::string(product_dir_name); 104} 105 106bool GetDefaultUserDataDirectoryForProduct(const std::string& product_dir, 107 base::FilePath* result) { 108 bool success = false; 109 if (result && PathService::Get(base::DIR_APP_DATA, result)) { 110 *result = result->Append(product_dir); 111 success = true; 112 } 113 return success; 114} 115 116} // namespace 117 118namespace chrome { 119 120bool GetDefaultUserDataDirectory(base::FilePath* result) { 121 return GetDefaultUserDataDirectoryForProduct(ProductDirName(), result); 122} 123 124bool GetUserDocumentsDirectory(base::FilePath* result) { 125 return base::mac::GetUserDirectory(NSDocumentDirectory, result); 126} 127 128void GetUserCacheDirectory(const base::FilePath& profile_dir, 129 base::FilePath* result) { 130 // If the profile directory is under ~/Library/Application Support, 131 // use a suitable cache directory under ~/Library/Caches. For 132 // example, a profile directory of ~/Library/Application 133 // Support/Google/Chrome/MyProfileName would use the cache directory 134 // ~/Library/Caches/Google/Chrome/MyProfileName. 135 136 // Default value in cases where any of the following fails. 137 *result = profile_dir; 138 139 base::FilePath app_data_dir; 140 if (!PathService::Get(base::DIR_APP_DATA, &app_data_dir)) 141 return; 142 base::FilePath cache_dir; 143 if (!PathService::Get(base::DIR_CACHE, &cache_dir)) 144 return; 145 if (!app_data_dir.AppendRelativePath(profile_dir, &cache_dir)) 146 return; 147 148 *result = cache_dir; 149} 150 151bool GetUserDownloadsDirectory(base::FilePath* result) { 152 return base::mac::GetUserDirectory(NSDownloadsDirectory, result); 153} 154 155bool GetUserMusicDirectory(base::FilePath* result) { 156 return base::mac::GetUserDirectory(NSMusicDirectory, result); 157} 158 159bool GetUserPicturesDirectory(base::FilePath* result) { 160 return base::mac::GetUserDirectory(NSPicturesDirectory, result); 161} 162 163bool GetUserVideosDirectory(base::FilePath* result) { 164 return base::mac::GetUserDirectory(NSMoviesDirectory, result); 165} 166 167#if !defined(OS_IOS) 168 169base::FilePath GetVersionedDirectory() { 170 if (g_override_versioned_directory) 171 return *g_override_versioned_directory; 172 173 // Start out with the path to the running executable. 174 base::FilePath path; 175 PathService::Get(base::FILE_EXE, &path); 176 177 // One step up to MacOS, another to Contents. 178 path = path.DirName().DirName(); 179 DCHECK_EQ(path.BaseName().value(), "Contents"); 180 181 if (base::mac::IsBackgroundOnlyProcess()) { 182 // path identifies the helper .app's Contents directory in the browser 183 // .app's versioned directory. Go up two steps to get to the browser 184 // .app's versioned directory. 185 path = path.DirName().DirName(); 186 DCHECK_EQ(path.BaseName().value(), kChromeVersion); 187 } else { 188 // Go into the versioned directory. 189 path = path.Append("Versions").Append(kChromeVersion); 190 } 191 192 return path; 193} 194 195void SetOverrideVersionedDirectory(const base::FilePath* path) { 196 if (path != g_override_versioned_directory) { 197 delete g_override_versioned_directory; 198 g_override_versioned_directory = path; 199 } 200} 201 202base::FilePath GetFrameworkBundlePath() { 203 // It's tempting to use +[NSBundle bundleWithIdentifier:], but it's really 204 // slow (about 30ms on 10.5 and 10.6), despite Apple's documentation stating 205 // that it may be more efficient than +bundleForClass:. +bundleForClass: 206 // itself takes 1-2ms. Getting an NSBundle from a path, on the other hand, 207 // essentially takes no time at all, at least when the bundle has already 208 // been loaded as it will have been in this case. The FilePath operations 209 // needed to compute the framework's path are also effectively free, so that 210 // is the approach that is used here. NSBundle is also documented as being 211 // not thread-safe, and thread safety may be a concern here. 212 213 // The framework bundle is at a known path and name from the browser .app's 214 // versioned directory. 215 return GetVersionedDirectory().Append(kFrameworkName); 216} 217 218bool GetLocalLibraryDirectory(base::FilePath* result) { 219 return base::mac::GetLocalDirectory(NSLibraryDirectory, result); 220} 221 222bool GetUserLibraryDirectory(base::FilePath* result) { 223 return base::mac::GetUserDirectory(NSLibraryDirectory, result); 224} 225 226bool GetUserApplicationsDirectory(base::FilePath* result) { 227 return base::mac::GetUserDirectory(NSApplicationDirectory, result); 228} 229 230bool GetGlobalApplicationSupportDirectory(base::FilePath* result) { 231 return base::mac::GetLocalDirectory(NSApplicationSupportDirectory, result); 232} 233 234NSBundle* OuterAppBundle() { 235 // Cache this. Foundation leaks it anyway, and this should be the only call 236 // to OuterAppBundleInternal(). 237 static NSBundle* bundle = OuterAppBundleInternal(); 238 return bundle; 239} 240 241bool GetUserDataDirectoryForBrowserBundle(NSBundle* bundle, 242 base::FilePath* result) { 243 scoped_ptr<char, base::FreeDeleter> 244 product_dir_name(ProductDirNameForBundle(bundle)); 245 return GetDefaultUserDataDirectoryForProduct(product_dir_name.get(), result); 246} 247 248#endif // !defined(OS_IOS) 249 250bool ProcessNeedsProfileDir(const std::string& process_type) { 251 // For now we have no reason to forbid this on other MacOS as we don't 252 // have the roaming profile troubles there. 253 return true; 254} 255 256} // namespace chrome 257