blob: 15fc15ba307e18f438852f00f41b2f5ecf7ff85f [file] [log] [blame]
// 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;
}