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