// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/mac/foundation_util.h" #include <stdlib.h> #include <string.h> #include "base/files/file_path.h" #include "base/logging.h" #include "base/mac/bundle_locations.h" #include "base/mac/mac_logging.h" #include "base/strings/sys_string_conversions.h" #if !defined(OS_IOS) extern "C" { CFTypeID SecACLGetTypeID(); CFTypeID SecTrustedApplicationGetTypeID(); Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj); } // extern "C" #endif namespace base { namespace mac { namespace { bool g_override_am_i_bundled = false; bool g_override_am_i_bundled_value = false; bool UncachedAmIBundled() { #if defined(OS_IOS) // All apps are bundled on iOS. return true; #else if (g_override_am_i_bundled) return g_override_am_i_bundled_value; // Yes, this is cheap. return [[base::mac::OuterBundle() bundlePath] hasSuffix:@".app"]; #endif } } // namespace bool AmIBundled() { // If the return value is not cached, this function will return different // values depending on when it's called. This confuses some client code, see // http://crbug.com/63183 . static bool result = UncachedAmIBundled(); DCHECK_EQ(result, UncachedAmIBundled()) << "The return value of AmIBundled() changed. This will confuse tests. " << "Call SetAmIBundled() override manually if your test binary " << "delay-loads the framework."; return result; } void SetOverrideAmIBundled(bool value) { #if defined(OS_IOS) // It doesn't make sense not to be bundled on iOS. if (!value) NOTREACHED(); #endif g_override_am_i_bundled = true; g_override_am_i_bundled_value = value; } bool IsBackgroundOnlyProcess() { // This function really does want to examine NSBundle's idea of the main // bundle dictionary. It needs to look at the actual running .app's // Info.plist to access its LSUIElement property. NSDictionary* info_dictionary = [base::mac::MainBundle() infoDictionary]; return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO; } FilePath PathForFrameworkBundleResource(CFStringRef resourceName) { NSBundle* bundle = base::mac::FrameworkBundle(); NSString* resourcePath = [bundle pathForResource:(NSString*)resourceName ofType:nil]; return NSStringToFilePath(resourcePath); } OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) { OSType creator = kUnknownType; CFBundleGetPackageInfo(bundle, NULL, &creator); return creator; } OSType CreatorCodeForApplication() { CFBundleRef bundle = CFBundleGetMainBundle(); if (!bundle) return kUnknownType; return CreatorCodeForCFBundleRef(bundle); } bool GetSearchPathDirectory(NSSearchPathDirectory directory, NSSearchPathDomainMask domain_mask, FilePath* result) { DCHECK(result); NSArray* dirs = NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES); if ([dirs count] < 1) { return false; } *result = NSStringToFilePath([dirs objectAtIndex:0]); return true; } bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) { return GetSearchPathDirectory(directory, NSLocalDomainMask, result); } bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) { return GetSearchPathDirectory(directory, NSUserDomainMask, result); } FilePath GetUserLibraryPath() { FilePath user_library_path; if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) { DLOG(WARNING) << "Could not get user library path"; } return user_library_path; } // Takes a path to an (executable) binary and tries to provide the path to an // application bundle containing it. It takes the outermost bundle that it can // find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). // |exec_name| - path to the binary // returns - path to the application bundle, or empty on error FilePath GetAppBundlePath(const FilePath& exec_name) { const char kExt[] = ".app"; const size_t kExtLength = arraysize(kExt) - 1; // Split the path into components. std::vector<std::string> components; exec_name.GetComponents(&components); // It's an error if we don't get any components. if (!components.size()) return FilePath(); // Don't prepend '/' to the first component. std::vector<std::string>::const_iterator it = components.begin(); std::string bundle_name = *it; DCHECK_GT(it->length(), 0U); // If the first component ends in ".app", we're already done. if (it->length() > kExtLength && !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) return FilePath(bundle_name); // The first component may be "/" or "//", etc. Only append '/' if it doesn't // already end in '/'. if (bundle_name[bundle_name.length() - 1] != '/') bundle_name += '/'; // Go through the remaining components. for (++it; it != components.end(); ++it) { DCHECK_GT(it->length(), 0U); bundle_name += *it; // If the current component ends in ".app", we're done. if (it->length() > kExtLength && !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) return FilePath(bundle_name); // Separate this component from the next one. bundle_name += '/'; } return FilePath(); } #define TYPE_NAME_FOR_CF_TYPE_DEFN(TypeCF) \ std::string TypeNameForCFType(TypeCF##Ref) { \ return #TypeCF; \ } TYPE_NAME_FOR_CF_TYPE_DEFN(CFArray); TYPE_NAME_FOR_CF_TYPE_DEFN(CFBag); TYPE_NAME_FOR_CF_TYPE_DEFN(CFBoolean); TYPE_NAME_FOR_CF_TYPE_DEFN(CFData); TYPE_NAME_FOR_CF_TYPE_DEFN(CFDate); TYPE_NAME_FOR_CF_TYPE_DEFN(CFDictionary); TYPE_NAME_FOR_CF_TYPE_DEFN(CFNull); TYPE_NAME_FOR_CF_TYPE_DEFN(CFNumber); TYPE_NAME_FOR_CF_TYPE_DEFN(CFSet); TYPE_NAME_FOR_CF_TYPE_DEFN(CFString); TYPE_NAME_FOR_CF_TYPE_DEFN(CFURL); TYPE_NAME_FOR_CF_TYPE_DEFN(CFUUID); TYPE_NAME_FOR_CF_TYPE_DEFN(CGColor); TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont); TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun); #undef TYPE_NAME_FOR_CF_TYPE_DEFN void NSObjectRetain(void* obj) { id<NSObject> nsobj = static_cast<id<NSObject> >(obj); [nsobj retain]; } void NSObjectRelease(void* obj) { id<NSObject> nsobj = static_cast<id<NSObject> >(obj); [nsobj release]; } void* CFTypeRefToNSObjectAutorelease(CFTypeRef cf_object) { // When GC is on, NSMakeCollectable marks cf_object for GC and autorelease // is a no-op. // // In the traditional GC-less environment, NSMakeCollectable is a no-op, // and cf_object is autoreleased, balancing out the caller's ownership claim. // // NSMakeCollectable returns nil when used on a NULL object. return [NSMakeCollectable(cf_object) autorelease]; } static const char* base_bundle_id; const char* BaseBundleID() { if (base_bundle_id) { return base_bundle_id; } #if defined(GOOGLE_CHROME_BUILD) return "com.google.Chrome"; #else return "org.chromium.Chromium"; #endif } void SetBaseBundleID(const char* new_base_bundle_id) { if (new_base_bundle_id != base_bundle_id) { free((void*)base_bundle_id); base_bundle_id = new_base_bundle_id ? strdup(new_base_bundle_id) : NULL; } } // Definitions for the corresponding CF_TO_NS_CAST_DECL macros in // foundation_util.h. #define CF_TO_NS_CAST_DEFN(TypeCF, TypeNS) \ \ TypeNS* CFToNSCast(TypeCF##Ref cf_val) { \ DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \ TypeNS* ns_val = \ const_cast<TypeNS*>(reinterpret_cast<const TypeNS*>(cf_val)); \ return ns_val; \ } \ \ TypeCF##Ref NSToCFCast(TypeNS* ns_val) { \ TypeCF##Ref cf_val = reinterpret_cast<TypeCF##Ref>(ns_val); \ DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \ return cf_val; \ } #define CF_TO_NS_MUTABLE_CAST_DEFN(name) \ CF_TO_NS_CAST_DEFN(CF##name, NS##name) \ \ NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val) { \ DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \ NSMutable##name* ns_val = reinterpret_cast<NSMutable##name*>(cf_val); \ return ns_val; \ } \ \ CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val) { \ CFMutable##name##Ref cf_val = \ reinterpret_cast<CFMutable##name##Ref>(ns_val); \ DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \ return cf_val; \ } CF_TO_NS_MUTABLE_CAST_DEFN(Array); CF_TO_NS_MUTABLE_CAST_DEFN(AttributedString); CF_TO_NS_CAST_DEFN(CFCalendar, NSCalendar); CF_TO_NS_MUTABLE_CAST_DEFN(CharacterSet); CF_TO_NS_MUTABLE_CAST_DEFN(Data); CF_TO_NS_CAST_DEFN(CFDate, NSDate); CF_TO_NS_MUTABLE_CAST_DEFN(Dictionary); CF_TO_NS_CAST_DEFN(CFError, NSError); CF_TO_NS_CAST_DEFN(CFLocale, NSLocale); CF_TO_NS_CAST_DEFN(CFNumber, NSNumber); CF_TO_NS_CAST_DEFN(CFRunLoopTimer, NSTimer); CF_TO_NS_CAST_DEFN(CFTimeZone, NSTimeZone); CF_TO_NS_MUTABLE_CAST_DEFN(Set); CF_TO_NS_CAST_DEFN(CFReadStream, NSInputStream); CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream); CF_TO_NS_MUTABLE_CAST_DEFN(String); CF_TO_NS_CAST_DEFN(CFURL, NSURL); #if defined(OS_IOS) CF_TO_NS_CAST_DEFN(CTFont, UIFont); #else // The NSFont/CTFont toll-free bridging is broken when it comes to type // checking, so do some special-casing. // http://www.openradar.me/15341349 rdar://15341349 NSFont* CFToNSCast(CTFontRef cf_val) { NSFont* ns_val = const_cast<NSFont*>(reinterpret_cast<const NSFont*>(cf_val)); DCHECK(!cf_val || CTFontGetTypeID() == CFGetTypeID(cf_val) || (_CFIsObjC(CTFontGetTypeID(), cf_val) && [ns_val isKindOfClass:NSClassFromString(@"NSFont")])); return ns_val; } CTFontRef NSToCFCast(NSFont* ns_val) { CTFontRef cf_val = reinterpret_cast<CTFontRef>(ns_val); DCHECK(!cf_val || CTFontGetTypeID() == CFGetTypeID(cf_val) || [ns_val isKindOfClass:NSClassFromString(@"NSFont")]); return cf_val; } #endif #undef CF_TO_NS_CAST_DEFN #undef CF_TO_NS_MUTABLE_CAST_DEFN #define CF_CAST_DEFN(TypeCF) \ template<> TypeCF##Ref \ CFCast<TypeCF##Ref>(const CFTypeRef& cf_val) { \ if (cf_val == NULL) { \ return NULL; \ } \ if (CFGetTypeID(cf_val) == TypeCF##GetTypeID()) { \ return (TypeCF##Ref)(cf_val); \ } \ return NULL; \ } \ \ template<> TypeCF##Ref \ CFCastStrict<TypeCF##Ref>(const CFTypeRef& cf_val) { \ TypeCF##Ref rv = CFCast<TypeCF##Ref>(cf_val); \ DCHECK(cf_val == NULL || rv); \ return rv; \ } CF_CAST_DEFN(CFArray); CF_CAST_DEFN(CFBag); CF_CAST_DEFN(CFBoolean); CF_CAST_DEFN(CFData); CF_CAST_DEFN(CFDate); CF_CAST_DEFN(CFDictionary); CF_CAST_DEFN(CFNull); CF_CAST_DEFN(CFNumber); CF_CAST_DEFN(CFSet); CF_CAST_DEFN(CFString); CF_CAST_DEFN(CFURL); CF_CAST_DEFN(CFUUID); CF_CAST_DEFN(CGColor); CF_CAST_DEFN(CTRun); #if defined(OS_IOS) CF_CAST_DEFN(CTFont); #else // The NSFont/CTFont toll-free bridging is broken when it comes to type // checking, so do some special-casing. // http://www.openradar.me/15341349 rdar://15341349 template<> CTFontRef CFCast<CTFontRef>(const CFTypeRef& cf_val) { if (cf_val == NULL) { return NULL; } if (CFGetTypeID(cf_val) == CTFontGetTypeID()) { return (CTFontRef)(cf_val); } if (!_CFIsObjC(CTFontGetTypeID(), cf_val)) return NULL; id<NSObject> ns_val = reinterpret_cast<id>(const_cast<void*>(cf_val)); if ([ns_val isKindOfClass:NSClassFromString(@"NSFont")]) { return (CTFontRef)(cf_val); } return NULL; } template<> CTFontRef CFCastStrict<CTFontRef>(const CFTypeRef& cf_val) { CTFontRef rv = CFCast<CTFontRef>(cf_val); DCHECK(cf_val == NULL || rv); return rv; } #endif #if !defined(OS_IOS) CF_CAST_DEFN(SecACL); CF_CAST_DEFN(SecTrustedApplication); #endif #undef CF_CAST_DEFN std::string GetValueFromDictionaryErrorMessage( CFStringRef key, const std::string& expected_type, CFTypeRef value) { ScopedCFTypeRef<CFStringRef> actual_type_ref( CFCopyTypeIDDescription(CFGetTypeID(value))); return "Expected value for key " + base::SysCFStringRefToUTF8(key) + " to be " + expected_type + " but it was " + base::SysCFStringRefToUTF8(actual_type_ref) + " instead"; } NSString* FilePathToNSString(const FilePath& path) { if (path.empty()) return nil; return [NSString stringWithUTF8String:path.value().c_str()]; } FilePath NSStringToFilePath(NSString* str) { if (![str length]) return FilePath(); return FilePath([str fileSystemRepresentation]); } } // namespace mac } // namespace base std::ostream& operator<<(std::ostream& o, const CFStringRef string) { return o << base::SysCFStringRefToUTF8(string); } std::ostream& operator<<(std::ostream& o, const CFErrorRef err) { base::ScopedCFTypeRef<CFStringRef> desc(CFErrorCopyDescription(err)); base::ScopedCFTypeRef<CFDictionaryRef> user_info(CFErrorCopyUserInfo(err)); CFStringRef errorDesc = NULL; if (user_info.get()) { errorDesc = reinterpret_cast<CFStringRef>( CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey)); } o << "Code: " << CFErrorGetCode(err) << " Domain: " << CFErrorGetDomain(err) << " Desc: " << desc.get(); if(errorDesc) { o << "(" << errorDesc << ")"; } return o; }