| // 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 "build/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; |
| } |
| |
| BASE_EXPORT 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; |
| } |