| // 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 <stddef.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/macros.h" | |
| #include "base/numerics/safe_conversions.h" | |
| #include "base/strings/sys_string_conversions.h" | |
| #include "util/build_config.h" | |
| #if !defined(OS_IOS) | |
| #import <AppKit/AppKit.h> | |
| #endif | |
| extern "C" { | |
| CFTypeID SecKeyGetTypeID(); | |
| #if !defined(OS_IOS) | |
| CFTypeID SecACLGetTypeID(); | |
| CFTypeID SecTrustedApplicationGetTypeID(); | |
| Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj); | |
| #endif | |
| } // extern "C" | |
| namespace base { | |
| namespace mac { | |
| namespace { | |
| bool g_cached_am_i_bundled_called = false; | |
| bool g_cached_am_i_bundled_value = false; | |
| 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 . | |
| if (!g_cached_am_i_bundled_called) { | |
| g_cached_am_i_bundled_called = true; | |
| g_cached_am_i_bundled_value = UncachedAmIBundled(); | |
| } | |
| DCHECK_EQ(g_cached_am_i_bundled_value, UncachedAmIBundled()) | |
| << "The return value of AmIBundled() changed. This will confuse tests. " | |
| << "Call SetAmIBundled() override manually if your test binary " | |
| << "delay-loads the framework."; | |
| return g_cached_am_i_bundled_value; | |
| } | |
| 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; | |
| } | |
| void ClearAmIBundledCache() { | |
| g_cached_am_i_bundled_called = false; | |
| } | |
| 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[@"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<NSString*>* dirs = | |
| NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES); | |
| if ([dirs count] < 1) { | |
| return false; | |
| } | |
| *result = NSStringToFilePath(dirs[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.empty()) | |
| 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.back() != '/') | |
| 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); | |
| #if !defined(OS_IOS) | |
| TYPE_NAME_FOR_CF_TYPE_DEFN(SecKey); | |
| TYPE_NAME_FOR_CF_TYPE_DEFN(SecPolicy); | |
| #endif | |
| #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:[NSFont class]])); | |
| 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:[NSFont class]]); | |
| 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(CTFontDescriptor); | |
| 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:[NSFont class]]) { | |
| 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(SecKey); | |
| CF_CAST_DEFN(SecPolicy); | |
| 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 @(path.value().c_str()); // @() does UTF8 conversion. | |
| } | |
| FilePath NSStringToFilePath(NSString* str) { | |
| if (![str length]) | |
| return FilePath(); | |
| return FilePath([str fileSystemRepresentation]); | |
| } | |
| bool CFRangeToNSRange(CFRange range, NSRange* range_out) { | |
| if (base::IsValueInRangeForNumericType<decltype(range_out->location)>( | |
| range.location) && | |
| base::IsValueInRangeForNumericType<decltype(range_out->length)>( | |
| range.length) && | |
| base::IsValueInRangeForNumericType<decltype(range_out->location)>( | |
| range.location + range.length)) { | |
| *range_out = NSMakeRange(range.location, range.length); | |
| return true; | |
| } | |
| return false; | |
| } | |
| } // 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; | |
| } |