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