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