Home | History | Annotate | Download | only in mac
      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)
     17 extern "C" {
     18 CFTypeID SecACLGetTypeID();
     19 CFTypeID SecTrustedApplicationGetTypeID();
     20 Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj);
     21 }  // extern "C"
     22 #endif
     23 
     24 namespace base {
     25 namespace mac {
     26 
     27 namespace {
     28 
     29 bool g_override_am_i_bundled = false;
     30 bool g_override_am_i_bundled_value = false;
     31 
     32 bool 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 
     47 bool 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 
     59 void 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 
     69 bool 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 
     77 FilePath 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 
     84 OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) {
     85   OSType creator = kUnknownType;
     86   CFBundleGetPackageInfo(bundle, NULL, &creator);
     87   return creator;
     88 }
     89 
     90 OSType CreatorCodeForApplication() {
     91   CFBundleRef bundle = CFBundleGetMainBundle();
     92   if (!bundle)
     93     return kUnknownType;
     94 
     95   return CreatorCodeForCFBundleRef(bundle);
     96 }
     97 
     98 bool 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 
    111 bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) {
    112   return GetSearchPathDirectory(directory, NSLocalDomainMask, result);
    113 }
    114 
    115 bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) {
    116   return GetSearchPathDirectory(directory, NSUserDomainMask, result);
    117 }
    118 
    119 FilePath 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
    132 FilePath 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) \
    177 std::string TypeNameForCFType(TypeCF##Ref) { \
    178   return #TypeCF; \
    179 }
    180 
    181 TYPE_NAME_FOR_CF_TYPE_DEFN(CFArray);
    182 TYPE_NAME_FOR_CF_TYPE_DEFN(CFBag);
    183 TYPE_NAME_FOR_CF_TYPE_DEFN(CFBoolean);
    184 TYPE_NAME_FOR_CF_TYPE_DEFN(CFData);
    185 TYPE_NAME_FOR_CF_TYPE_DEFN(CFDate);
    186 TYPE_NAME_FOR_CF_TYPE_DEFN(CFDictionary);
    187 TYPE_NAME_FOR_CF_TYPE_DEFN(CFNull);
    188 TYPE_NAME_FOR_CF_TYPE_DEFN(CFNumber);
    189 TYPE_NAME_FOR_CF_TYPE_DEFN(CFSet);
    190 TYPE_NAME_FOR_CF_TYPE_DEFN(CFString);
    191 TYPE_NAME_FOR_CF_TYPE_DEFN(CFURL);
    192 TYPE_NAME_FOR_CF_TYPE_DEFN(CFUUID);
    193 
    194 TYPE_NAME_FOR_CF_TYPE_DEFN(CGColor);
    195 
    196 TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont);
    197 TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun);
    198 
    199 #undef TYPE_NAME_FOR_CF_TYPE_DEFN
    200 
    201 void NSObjectRetain(void* obj) {
    202   id<NSObject> nsobj = static_cast<id<NSObject> >(obj);
    203   [nsobj retain];
    204 }
    205 
    206 void NSObjectRelease(void* obj) {
    207   id<NSObject> nsobj = static_cast<id<NSObject> >(obj);
    208   [nsobj release];
    209 }
    210 
    211 void* 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 
    222 static const char* base_bundle_id;
    223 
    224 const 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 
    236 void 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 \
    247 TypeNS* 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 \
    254 TypeCF##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) \
    261 CF_TO_NS_CAST_DEFN(CF##name, NS##name) \
    262 \
    263 NSMutable##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 \
    269 CFMutable##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 
    276 CF_TO_NS_MUTABLE_CAST_DEFN(Array);
    277 CF_TO_NS_MUTABLE_CAST_DEFN(AttributedString);
    278 CF_TO_NS_CAST_DEFN(CFCalendar, NSCalendar);
    279 CF_TO_NS_MUTABLE_CAST_DEFN(CharacterSet);
    280 CF_TO_NS_MUTABLE_CAST_DEFN(Data);
    281 CF_TO_NS_CAST_DEFN(CFDate, NSDate);
    282 CF_TO_NS_MUTABLE_CAST_DEFN(Dictionary);
    283 CF_TO_NS_CAST_DEFN(CFError, NSError);
    284 CF_TO_NS_CAST_DEFN(CFLocale, NSLocale);
    285 CF_TO_NS_CAST_DEFN(CFNumber, NSNumber);
    286 CF_TO_NS_CAST_DEFN(CFRunLoopTimer, NSTimer);
    287 CF_TO_NS_CAST_DEFN(CFTimeZone, NSTimeZone);
    288 CF_TO_NS_MUTABLE_CAST_DEFN(Set);
    289 CF_TO_NS_CAST_DEFN(CFReadStream, NSInputStream);
    290 CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream);
    291 CF_TO_NS_MUTABLE_CAST_DEFN(String);
    292 CF_TO_NS_CAST_DEFN(CFURL, NSURL);
    293 
    294 #if defined(OS_IOS)
    295 CF_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
    300 NSFont* 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 
    310 CTFontRef 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) \
    323 template<> TypeCF##Ref \
    324 CFCast<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 \
    334 template<> TypeCF##Ref \
    335 CFCastStrict<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 
    341 CF_CAST_DEFN(CFArray);
    342 CF_CAST_DEFN(CFBag);
    343 CF_CAST_DEFN(CFBoolean);
    344 CF_CAST_DEFN(CFData);
    345 CF_CAST_DEFN(CFDate);
    346 CF_CAST_DEFN(CFDictionary);
    347 CF_CAST_DEFN(CFNull);
    348 CF_CAST_DEFN(CFNumber);
    349 CF_CAST_DEFN(CFSet);
    350 CF_CAST_DEFN(CFString);
    351 CF_CAST_DEFN(CFURL);
    352 CF_CAST_DEFN(CFUUID);
    353 
    354 CF_CAST_DEFN(CGColor);
    355 
    356 CF_CAST_DEFN(CTRun);
    357 
    358 #if defined(OS_IOS)
    359 CF_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
    364 template<> CTFontRef
    365 CFCast<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 
    383 template<> CTFontRef
    384 CFCastStrict<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)
    392 CF_CAST_DEFN(SecACL);
    393 CF_CAST_DEFN(SecTrustedApplication);
    394 #endif
    395 
    396 #undef CF_CAST_DEFN
    397 
    398 std::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 
    411 NSString* FilePathToNSString(const FilePath& path) {
    412   if (path.empty())
    413     return nil;
    414   return [NSString stringWithUTF8String:path.value().c_str()];
    415 }
    416 
    417 FilePath NSStringToFilePath(NSString* str) {
    418   if (![str length])
    419     return FilePath();
    420   return FilePath([str fileSystemRepresentation]);
    421 }
    422 
    423 }  // namespace mac
    424 }  // namespace base
    425 
    426 std::ostream& operator<<(std::ostream& o, const CFStringRef string) {
    427   return o << base::SysCFStringRefToUTF8(string);
    428 }
    429 
    430 std::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