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 24 namespace { 25 26 #if !defined(OS_IOS) 27 const 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. 32 NSBundle* 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 55 char* 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". 87 std::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 106 bool 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 118 namespace chrome { 119 120 bool GetDefaultUserDataDirectory(base::FilePath* result) { 121 return GetDefaultUserDataDirectoryForProduct(ProductDirName(), result); 122 } 123 124 bool GetUserDocumentsDirectory(base::FilePath* result) { 125 return base::mac::GetUserDirectory(NSDocumentDirectory, result); 126 } 127 128 void 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 151 bool GetUserDownloadsDirectory(base::FilePath* result) { 152 return base::mac::GetUserDirectory(NSDownloadsDirectory, result); 153 } 154 155 bool GetUserMusicDirectory(base::FilePath* result) { 156 return base::mac::GetUserDirectory(NSMusicDirectory, result); 157 } 158 159 bool GetUserPicturesDirectory(base::FilePath* result) { 160 return base::mac::GetUserDirectory(NSPicturesDirectory, result); 161 } 162 163 bool GetUserVideosDirectory(base::FilePath* result) { 164 return base::mac::GetUserDirectory(NSMoviesDirectory, result); 165 } 166 167 #if !defined(OS_IOS) 168 169 base::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 195 void 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 202 base::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 218 bool GetLocalLibraryDirectory(base::FilePath* result) { 219 return base::mac::GetLocalDirectory(NSLibraryDirectory, result); 220 } 221 222 bool GetGlobalApplicationSupportDirectory(base::FilePath* result) { 223 return base::mac::GetLocalDirectory(NSApplicationSupportDirectory, result); 224 } 225 226 NSBundle* OuterAppBundle() { 227 // Cache this. Foundation leaks it anyway, and this should be the only call 228 // to OuterAppBundleInternal(). 229 static NSBundle* bundle = OuterAppBundleInternal(); 230 return bundle; 231 } 232 233 bool GetUserDataDirectoryForBrowserBundle(NSBundle* bundle, 234 base::FilePath* result) { 235 scoped_ptr_malloc<char> product_dir_name(ProductDirNameForBundle(bundle)); 236 return GetDefaultUserDataDirectoryForProduct(product_dir_name.get(), result); 237 } 238 239 #endif // !defined(OS_IOS) 240 241 bool ProcessNeedsProfileDir(const std::string& process_type) { 242 // For now we have no reason to forbid this on other MacOS as we don't 243 // have the roaming profile troubles there. 244 return true; 245 } 246 247 } // namespace chrome 248