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