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 "base/mac/foundation_util.h"
6
7#include <stdlib.h>
8#include <string.h>
9
10#include "base/files/file_path.h"
11#include "base/logging.h"
12#include "base/mac/bundle_locations.h"
13#include "base/mac/mac_logging.h"
14#include "base/strings/sys_string_conversions.h"
15
16#if !defined(OS_IOS)
17extern "C" {
18CFTypeID SecACLGetTypeID();
19CFTypeID SecTrustedApplicationGetTypeID();
20Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj);
21}  // extern "C"
22#endif
23
24namespace base {
25namespace mac {
26
27namespace {
28
29bool g_override_am_i_bundled = false;
30bool g_override_am_i_bundled_value = false;
31
32bool UncachedAmIBundled() {
33#if defined(OS_IOS)
34  // All apps are bundled on iOS.
35  return true;
36#else
37  if (g_override_am_i_bundled)
38    return g_override_am_i_bundled_value;
39
40  // Yes, this is cheap.
41  return [[base::mac::OuterBundle() bundlePath] hasSuffix:@".app"];
42#endif
43}
44
45}  // namespace
46
47bool AmIBundled() {
48  // If the return value is not cached, this function will return different
49  // values depending on when it's called. This confuses some client code, see
50  // http://crbug.com/63183 .
51  static bool result = UncachedAmIBundled();
52  DCHECK_EQ(result, UncachedAmIBundled())
53      << "The return value of AmIBundled() changed. This will confuse tests. "
54      << "Call SetAmIBundled() override manually if your test binary "
55      << "delay-loads the framework.";
56  return result;
57}
58
59void SetOverrideAmIBundled(bool value) {
60#if defined(OS_IOS)
61  // It doesn't make sense not to be bundled on iOS.
62  if (!value)
63    NOTREACHED();
64#endif
65  g_override_am_i_bundled = true;
66  g_override_am_i_bundled_value = value;
67}
68
69bool IsBackgroundOnlyProcess() {
70  // This function really does want to examine NSBundle's idea of the main
71  // bundle dictionary.  It needs to look at the actual running .app's
72  // Info.plist to access its LSUIElement property.
73  NSDictionary* info_dictionary = [base::mac::MainBundle() infoDictionary];
74  return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO;
75}
76
77FilePath PathForFrameworkBundleResource(CFStringRef resourceName) {
78  NSBundle* bundle = base::mac::FrameworkBundle();
79  NSString* resourcePath = [bundle pathForResource:(NSString*)resourceName
80                                            ofType:nil];
81  return NSStringToFilePath(resourcePath);
82}
83
84OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) {
85  OSType creator = kUnknownType;
86  CFBundleGetPackageInfo(bundle, NULL, &creator);
87  return creator;
88}
89
90OSType CreatorCodeForApplication() {
91  CFBundleRef bundle = CFBundleGetMainBundle();
92  if (!bundle)
93    return kUnknownType;
94
95  return CreatorCodeForCFBundleRef(bundle);
96}
97
98bool GetSearchPathDirectory(NSSearchPathDirectory directory,
99                            NSSearchPathDomainMask domain_mask,
100                            FilePath* result) {
101  DCHECK(result);
102  NSArray* dirs =
103      NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES);
104  if ([dirs count] < 1) {
105    return false;
106  }
107  *result = NSStringToFilePath([dirs objectAtIndex:0]);
108  return true;
109}
110
111bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) {
112  return GetSearchPathDirectory(directory, NSLocalDomainMask, result);
113}
114
115bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) {
116  return GetSearchPathDirectory(directory, NSUserDomainMask, result);
117}
118
119FilePath GetUserLibraryPath() {
120  FilePath user_library_path;
121  if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) {
122    DLOG(WARNING) << "Could not get user library path";
123  }
124  return user_library_path;
125}
126
127// Takes a path to an (executable) binary and tries to provide the path to an
128// application bundle containing it. It takes the outermost bundle that it can
129// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app").
130//   |exec_name| - path to the binary
131//   returns - path to the application bundle, or empty on error
132FilePath GetAppBundlePath(const FilePath& exec_name) {
133  const char kExt[] = ".app";
134  const size_t kExtLength = arraysize(kExt) - 1;
135
136  // Split the path into components.
137  std::vector<std::string> components;
138  exec_name.GetComponents(&components);
139
140  // It's an error if we don't get any components.
141  if (!components.size())
142    return FilePath();
143
144  // Don't prepend '/' to the first component.
145  std::vector<std::string>::const_iterator it = components.begin();
146  std::string bundle_name = *it;
147  DCHECK_GT(it->length(), 0U);
148  // If the first component ends in ".app", we're already done.
149  if (it->length() > kExtLength &&
150      !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
151    return FilePath(bundle_name);
152
153  // The first component may be "/" or "//", etc. Only append '/' if it doesn't
154  // already end in '/'.
155  if (bundle_name[bundle_name.length() - 1] != '/')
156    bundle_name += '/';
157
158  // Go through the remaining components.
159  for (++it; it != components.end(); ++it) {
160    DCHECK_GT(it->length(), 0U);
161
162    bundle_name += *it;
163
164    // If the current component ends in ".app", we're done.
165    if (it->length() > kExtLength &&
166        !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
167      return FilePath(bundle_name);
168
169    // Separate this component from the next one.
170    bundle_name += '/';
171  }
172
173  return FilePath();
174}
175
176#define TYPE_NAME_FOR_CF_TYPE_DEFN(TypeCF) \
177std::string TypeNameForCFType(TypeCF##Ref) { \
178  return #TypeCF; \
179}
180
181TYPE_NAME_FOR_CF_TYPE_DEFN(CFArray);
182TYPE_NAME_FOR_CF_TYPE_DEFN(CFBag);
183TYPE_NAME_FOR_CF_TYPE_DEFN(CFBoolean);
184TYPE_NAME_FOR_CF_TYPE_DEFN(CFData);
185TYPE_NAME_FOR_CF_TYPE_DEFN(CFDate);
186TYPE_NAME_FOR_CF_TYPE_DEFN(CFDictionary);
187TYPE_NAME_FOR_CF_TYPE_DEFN(CFNull);
188TYPE_NAME_FOR_CF_TYPE_DEFN(CFNumber);
189TYPE_NAME_FOR_CF_TYPE_DEFN(CFSet);
190TYPE_NAME_FOR_CF_TYPE_DEFN(CFString);
191TYPE_NAME_FOR_CF_TYPE_DEFN(CFURL);
192TYPE_NAME_FOR_CF_TYPE_DEFN(CFUUID);
193
194TYPE_NAME_FOR_CF_TYPE_DEFN(CGColor);
195
196TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont);
197TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun);
198
199#undef TYPE_NAME_FOR_CF_TYPE_DEFN
200
201void NSObjectRetain(void* obj) {
202  id<NSObject> nsobj = static_cast<id<NSObject> >(obj);
203  [nsobj retain];
204}
205
206void NSObjectRelease(void* obj) {
207  id<NSObject> nsobj = static_cast<id<NSObject> >(obj);
208  [nsobj release];
209}
210
211void* CFTypeRefToNSObjectAutorelease(CFTypeRef cf_object) {
212  // When GC is on, NSMakeCollectable marks cf_object for GC and autorelease
213  // is a no-op.
214  //
215  // In the traditional GC-less environment, NSMakeCollectable is a no-op,
216  // and cf_object is autoreleased, balancing out the caller's ownership claim.
217  //
218  // NSMakeCollectable returns nil when used on a NULL object.
219  return [NSMakeCollectable(cf_object) autorelease];
220}
221
222static const char* base_bundle_id;
223
224const char* BaseBundleID() {
225  if (base_bundle_id) {
226    return base_bundle_id;
227  }
228
229#if defined(GOOGLE_CHROME_BUILD)
230  return "com.google.Chrome";
231#else
232  return "org.chromium.Chromium";
233#endif
234}
235
236void SetBaseBundleID(const char* new_base_bundle_id) {
237  if (new_base_bundle_id != base_bundle_id) {
238    free((void*)base_bundle_id);
239    base_bundle_id = new_base_bundle_id ? strdup(new_base_bundle_id) : NULL;
240  }
241}
242
243// Definitions for the corresponding CF_TO_NS_CAST_DECL macros in
244// foundation_util.h.
245#define CF_TO_NS_CAST_DEFN(TypeCF, TypeNS) \
246\
247TypeNS* CFToNSCast(TypeCF##Ref cf_val) { \
248  DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \
249  TypeNS* ns_val = \
250      const_cast<TypeNS*>(reinterpret_cast<const TypeNS*>(cf_val)); \
251  return ns_val; \
252} \
253\
254TypeCF##Ref NSToCFCast(TypeNS* ns_val) { \
255  TypeCF##Ref cf_val = reinterpret_cast<TypeCF##Ref>(ns_val); \
256  DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \
257  return cf_val; \
258}
259
260#define CF_TO_NS_MUTABLE_CAST_DEFN(name) \
261CF_TO_NS_CAST_DEFN(CF##name, NS##name) \
262\
263NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val) { \
264  DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \
265  NSMutable##name* ns_val = reinterpret_cast<NSMutable##name*>(cf_val); \
266  return ns_val; \
267} \
268\
269CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val) { \
270  CFMutable##name##Ref cf_val = \
271      reinterpret_cast<CFMutable##name##Ref>(ns_val); \
272  DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \
273  return cf_val; \
274}
275
276CF_TO_NS_MUTABLE_CAST_DEFN(Array);
277CF_TO_NS_MUTABLE_CAST_DEFN(AttributedString);
278CF_TO_NS_CAST_DEFN(CFCalendar, NSCalendar);
279CF_TO_NS_MUTABLE_CAST_DEFN(CharacterSet);
280CF_TO_NS_MUTABLE_CAST_DEFN(Data);
281CF_TO_NS_CAST_DEFN(CFDate, NSDate);
282CF_TO_NS_MUTABLE_CAST_DEFN(Dictionary);
283CF_TO_NS_CAST_DEFN(CFError, NSError);
284CF_TO_NS_CAST_DEFN(CFLocale, NSLocale);
285CF_TO_NS_CAST_DEFN(CFNumber, NSNumber);
286CF_TO_NS_CAST_DEFN(CFRunLoopTimer, NSTimer);
287CF_TO_NS_CAST_DEFN(CFTimeZone, NSTimeZone);
288CF_TO_NS_MUTABLE_CAST_DEFN(Set);
289CF_TO_NS_CAST_DEFN(CFReadStream, NSInputStream);
290CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream);
291CF_TO_NS_MUTABLE_CAST_DEFN(String);
292CF_TO_NS_CAST_DEFN(CFURL, NSURL);
293
294#if defined(OS_IOS)
295CF_TO_NS_CAST_DEFN(CTFont, UIFont);
296#else
297// The NSFont/CTFont toll-free bridging is broken when it comes to type
298// checking, so do some special-casing.
299// http://www.openradar.me/15341349 rdar://15341349
300NSFont* CFToNSCast(CTFontRef cf_val) {
301  NSFont* ns_val =
302      const_cast<NSFont*>(reinterpret_cast<const NSFont*>(cf_val));
303  DCHECK(!cf_val ||
304         CTFontGetTypeID() == CFGetTypeID(cf_val) ||
305         (_CFIsObjC(CTFontGetTypeID(), cf_val) &&
306          [ns_val isKindOfClass:NSClassFromString(@"NSFont")]));
307  return ns_val;
308}
309
310CTFontRef NSToCFCast(NSFont* ns_val) {
311  CTFontRef cf_val = reinterpret_cast<CTFontRef>(ns_val);
312  DCHECK(!cf_val ||
313         CTFontGetTypeID() == CFGetTypeID(cf_val) ||
314         [ns_val isKindOfClass:NSClassFromString(@"NSFont")]);
315  return cf_val;
316}
317#endif
318
319#undef CF_TO_NS_CAST_DEFN
320#undef CF_TO_NS_MUTABLE_CAST_DEFN
321
322#define CF_CAST_DEFN(TypeCF) \
323template<> TypeCF##Ref \
324CFCast<TypeCF##Ref>(const CFTypeRef& cf_val) { \
325  if (cf_val == NULL) { \
326    return NULL; \
327  } \
328  if (CFGetTypeID(cf_val) == TypeCF##GetTypeID()) { \
329    return (TypeCF##Ref)(cf_val); \
330  } \
331  return NULL; \
332} \
333\
334template<> TypeCF##Ref \
335CFCastStrict<TypeCF##Ref>(const CFTypeRef& cf_val) { \
336  TypeCF##Ref rv = CFCast<TypeCF##Ref>(cf_val); \
337  DCHECK(cf_val == NULL || rv); \
338  return rv; \
339}
340
341CF_CAST_DEFN(CFArray);
342CF_CAST_DEFN(CFBag);
343CF_CAST_DEFN(CFBoolean);
344CF_CAST_DEFN(CFData);
345CF_CAST_DEFN(CFDate);
346CF_CAST_DEFN(CFDictionary);
347CF_CAST_DEFN(CFNull);
348CF_CAST_DEFN(CFNumber);
349CF_CAST_DEFN(CFSet);
350CF_CAST_DEFN(CFString);
351CF_CAST_DEFN(CFURL);
352CF_CAST_DEFN(CFUUID);
353
354CF_CAST_DEFN(CGColor);
355
356CF_CAST_DEFN(CTRun);
357
358#if defined(OS_IOS)
359CF_CAST_DEFN(CTFont);
360#else
361// The NSFont/CTFont toll-free bridging is broken when it comes to type
362// checking, so do some special-casing.
363// http://www.openradar.me/15341349 rdar://15341349
364template<> CTFontRef
365CFCast<CTFontRef>(const CFTypeRef& cf_val) {
366  if (cf_val == NULL) {
367    return NULL;
368  }
369  if (CFGetTypeID(cf_val) == CTFontGetTypeID()) {
370    return (CTFontRef)(cf_val);
371  }
372
373  if (!_CFIsObjC(CTFontGetTypeID(), cf_val))
374    return NULL;
375
376  id<NSObject> ns_val = reinterpret_cast<id>(const_cast<void*>(cf_val));
377  if ([ns_val isKindOfClass:NSClassFromString(@"NSFont")]) {
378    return (CTFontRef)(cf_val);
379  }
380  return NULL;
381}
382
383template<> CTFontRef
384CFCastStrict<CTFontRef>(const CFTypeRef& cf_val) {
385  CTFontRef rv = CFCast<CTFontRef>(cf_val);
386  DCHECK(cf_val == NULL || rv);
387  return rv;
388}
389#endif
390
391#if !defined(OS_IOS)
392CF_CAST_DEFN(SecACL);
393CF_CAST_DEFN(SecTrustedApplication);
394#endif
395
396#undef CF_CAST_DEFN
397
398std::string GetValueFromDictionaryErrorMessage(
399    CFStringRef key, const std::string& expected_type, CFTypeRef value) {
400  ScopedCFTypeRef<CFStringRef> actual_type_ref(
401      CFCopyTypeIDDescription(CFGetTypeID(value)));
402  return "Expected value for key " +
403      base::SysCFStringRefToUTF8(key) +
404      " to be " +
405      expected_type +
406      " but it was " +
407      base::SysCFStringRefToUTF8(actual_type_ref) +
408      " instead";
409}
410
411NSString* FilePathToNSString(const FilePath& path) {
412  if (path.empty())
413    return nil;
414  return [NSString stringWithUTF8String:path.value().c_str()];
415}
416
417FilePath NSStringToFilePath(NSString* str) {
418  if (![str length])
419    return FilePath();
420  return FilePath([str fileSystemRepresentation]);
421}
422
423}  // namespace mac
424}  // namespace base
425
426std::ostream& operator<<(std::ostream& o, const CFStringRef string) {
427  return o << base::SysCFStringRefToUTF8(string);
428}
429
430std::ostream& operator<<(std::ostream& o, const CFErrorRef err) {
431  base::ScopedCFTypeRef<CFStringRef> desc(CFErrorCopyDescription(err));
432  base::ScopedCFTypeRef<CFDictionaryRef> user_info(CFErrorCopyUserInfo(err));
433  CFStringRef errorDesc = NULL;
434  if (user_info.get()) {
435    errorDesc = reinterpret_cast<CFStringRef>(
436        CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey));
437  }
438  o << "Code: " << CFErrorGetCode(err)
439    << " Domain: " << CFErrorGetDomain(err)
440    << " Desc: " << desc.get();
441  if(errorDesc) {
442    o << "(" << errorDesc << ")";
443  }
444  return o;
445}
446