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