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