1// Copyright 2012 The Chromium Authors 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/apple/foundation_util.h" 6 7#include <CoreFoundation/CoreFoundation.h> 8#import <Foundation/Foundation.h> 9#include <stddef.h> 10#include <stdlib.h> 11#include <string.h> 12 13#include <vector> 14 15#include "base/apple/bridging.h" 16#include "base/apple/bundle_locations.h" 17#include "base/apple/osstatus_logging.h" 18#include "base/apple/scoped_cftyperef.h" 19#include "base/check.h" 20#include "base/containers/adapters.h" 21#include "base/files/file_path.h" 22#include "base/logging.h" 23#include "base/numerics/checked_math.h" 24#include "base/numerics/safe_conversions.h" 25#include "base/ranges/algorithm.h" 26#include "base/strings/string_util.h" 27#include "base/strings/sys_string_conversions.h" 28#include "build/branding_buildflags.h" 29#include "build/build_config.h" 30 31#if !BUILDFLAG(IS_IOS) 32#import <AppKit/AppKit.h> 33#endif 34 35extern "C" { 36CFTypeID SecKeyGetTypeID(); 37} // extern "C" 38 39namespace base::apple { 40 41namespace { 42 43bool g_cached_am_i_bundled_called = false; 44bool g_cached_am_i_bundled_value = false; 45bool g_override_am_i_bundled = false; 46bool g_override_am_i_bundled_value = false; 47 48bool UncachedAmIBundled() { 49#if BUILDFLAG(IS_IOS) 50 // All apps are bundled on iOS. 51 return true; 52#else 53 if (g_override_am_i_bundled) { 54 return g_override_am_i_bundled_value; 55 } 56 57 // Yes, this is cheap. 58 return [apple::OuterBundle().bundlePath hasSuffix:@".app"]; 59#endif 60} 61 62bool CFURLIsFileURL(CFURLRef url) { 63 ScopedCFTypeRef<CFStringRef> scheme(CFURLCopyScheme(url)); 64 return CFStringCompare(scheme.get(), CFSTR("file"), 65 kCFCompareCaseInsensitive) == kCFCompareEqualTo; 66} 67 68} // namespace 69 70bool AmIBundled() { 71 // If the return value is not cached, this function will return different 72 // values depending on when it's called. This confuses some client code, see 73 // http://crbug.com/63183 . 74 if (!g_cached_am_i_bundled_called) { 75 g_cached_am_i_bundled_called = true; 76 g_cached_am_i_bundled_value = UncachedAmIBundled(); 77 } 78 DCHECK_EQ(g_cached_am_i_bundled_value, UncachedAmIBundled()) 79 << "The return value of AmIBundled() changed. This will confuse tests. " 80 << "Call SetAmIBundled() override manually if your test binary " 81 << "delay-loads the framework."; 82 return g_cached_am_i_bundled_value; 83} 84 85void SetOverrideAmIBundled(bool value) { 86#if BUILDFLAG(IS_IOS) 87 // It doesn't make sense not to be bundled on iOS. 88 CHECK(value); 89#endif 90 g_override_am_i_bundled = true; 91 g_override_am_i_bundled_value = value; 92} 93 94BASE_EXPORT void ClearAmIBundledCache() { 95 g_cached_am_i_bundled_called = false; 96} 97 98bool IsBackgroundOnlyProcess() { 99 // This function really does want to examine NSBundle's idea of the main 100 // bundle dictionary. It needs to look at the actual running .app's 101 // Info.plist to access its LSUIElement property. 102 @autoreleasepool { 103 NSDictionary* info_dictionary = [apple::MainBundle() infoDictionary]; 104 return [info_dictionary[@"LSUIElement"] boolValue] != NO; 105 } 106} 107 108FilePath PathForFrameworkBundleResource(const char* resource_name) { 109 NSBundle* bundle = apple::FrameworkBundle(); 110 NSURL* resource_url = [bundle URLForResource:@(resource_name) 111 withExtension:nil]; 112 return NSURLToFilePath(resource_url); 113} 114 115OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) { 116 OSType creator = kUnknownType; 117 CFBundleGetPackageInfo(bundle, /*packageType=*/nullptr, &creator); 118 return creator; 119} 120 121OSType CreatorCodeForApplication() { 122 CFBundleRef bundle = CFBundleGetMainBundle(); 123 if (!bundle) { 124 return kUnknownType; 125 } 126 127 return CreatorCodeForCFBundleRef(bundle); 128} 129 130bool GetSearchPathDirectory(NSSearchPathDirectory directory, 131 NSSearchPathDomainMask domain_mask, 132 FilePath* result) { 133 DCHECK(result); 134 NSArray<NSString*>* dirs = 135 NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES); 136 if (dirs.count < 1) { 137 return false; 138 } 139 *result = NSStringToFilePath(dirs[0]); 140 return true; 141} 142 143bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) { 144 return GetSearchPathDirectory(directory, NSLocalDomainMask, result); 145} 146 147bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) { 148 return GetSearchPathDirectory(directory, NSUserDomainMask, result); 149} 150 151FilePath GetUserLibraryPath() { 152 FilePath user_library_path; 153 if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) { 154 DLOG(WARNING) << "Could not get user library path"; 155 } 156 return user_library_path; 157} 158 159FilePath GetUserDocumentPath() { 160 FilePath user_document_path; 161 if (!GetUserDirectory(NSDocumentDirectory, &user_document_path)) { 162 DLOG(WARNING) << "Could not get user document path"; 163 } 164 return user_document_path; 165} 166 167// Takes a path to an (executable) binary and tries to provide the path to an 168// application bundle containing it. It takes the outermost bundle that it can 169// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). 170// |exec_name| - path to the binary 171// returns - path to the application bundle, or empty on error 172FilePath GetAppBundlePath(const FilePath& exec_name) { 173 const char kExt[] = ".app"; 174 const size_t kExtLength = std::size(kExt) - 1; 175 176 // Split the path into components. 177 std::vector<std::string> components = exec_name.GetComponents(); 178 179 // It's an error if we don't get any components. 180 if (components.empty()) { 181 return FilePath(); 182 } 183 184 // Don't prepend '/' to the first component. 185 std::vector<std::string>::const_iterator it = components.begin(); 186 std::string bundle_name = *it; 187 DCHECK_GT(it->length(), 0U); 188 // If the first component ends in ".app", we're already done. 189 if (it->length() > kExtLength && 190 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) { 191 return FilePath(bundle_name); 192 } 193 194 // The first component may be "/" or "//", etc. Only append '/' if it doesn't 195 // already end in '/'. 196 if (bundle_name.back() != '/') { 197 bundle_name += '/'; 198 } 199 200 // Go through the remaining components. 201 for (++it; it != components.end(); ++it) { 202 DCHECK_GT(it->length(), 0U); 203 204 bundle_name += *it; 205 206 // If the current component ends in ".app", we're done. 207 if (it->length() > kExtLength && 208 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) { 209 return FilePath(bundle_name); 210 } 211 212 // Separate this component from the next one. 213 bundle_name += '/'; 214 } 215 216 return FilePath(); 217} 218 219// Takes a path to an (executable) binary and tries to provide the path to an 220// application bundle containing it. It takes the innermost bundle that it can 221// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces 222// "/Foo/Bar.app/.../Baz.app"). 223// |exec_name| - path to the binary 224// returns - path to the application bundle, or empty on error 225FilePath GetInnermostAppBundlePath(const FilePath& exec_name) { 226 static constexpr char kExt[] = ".app"; 227 static constexpr size_t kExtLength = std::size(kExt) - 1; 228 229 // Split the path into components. 230 std::vector<std::string> components = exec_name.GetComponents(); 231 232 // It's an error if we don't get any components. 233 if (components.empty()) { 234 return FilePath(); 235 } 236 237 auto app = ranges::find_if( 238 Reversed(components), [](const std::string& component) -> bool { 239 return component.size() > kExtLength && EndsWith(component, kExt); 240 }); 241 242 if (app == components.rend()) { 243 return FilePath(); 244 } 245 246 // Remove all path components after the final ".app" extension. 247 components.erase(app.base(), components.end()); 248 249 std::string bundle_path; 250 for (const std::string& component : components) { 251 // Don't prepend a slash if this is the first component or if the 252 // previous component ended with a slash, which can happen when dealing 253 // with an absolute path. 254 if (!bundle_path.empty() && bundle_path.back() != '/') { 255 bundle_path += '/'; 256 } 257 258 bundle_path += component; 259 } 260 261 return FilePath(bundle_path); 262} 263 264#define TYPE_NAME_FOR_CF_TYPE_DEFN(TypeCF) \ 265 std::string TypeNameForCFType(TypeCF##Ref) { \ 266 return #TypeCF; \ 267 } 268 269TYPE_NAME_FOR_CF_TYPE_DEFN(CFArray) 270TYPE_NAME_FOR_CF_TYPE_DEFN(CFBag) 271TYPE_NAME_FOR_CF_TYPE_DEFN(CFBoolean) 272TYPE_NAME_FOR_CF_TYPE_DEFN(CFData) 273TYPE_NAME_FOR_CF_TYPE_DEFN(CFDate) 274TYPE_NAME_FOR_CF_TYPE_DEFN(CFDictionary) 275TYPE_NAME_FOR_CF_TYPE_DEFN(CFNull) 276TYPE_NAME_FOR_CF_TYPE_DEFN(CFNumber) 277TYPE_NAME_FOR_CF_TYPE_DEFN(CFSet) 278TYPE_NAME_FOR_CF_TYPE_DEFN(CFString) 279TYPE_NAME_FOR_CF_TYPE_DEFN(CFURL) 280TYPE_NAME_FOR_CF_TYPE_DEFN(CFUUID) 281 282TYPE_NAME_FOR_CF_TYPE_DEFN(CGColor) 283 284TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont) 285TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun) 286 287#if !BUILDFLAG(IS_IOS) 288TYPE_NAME_FOR_CF_TYPE_DEFN(SecAccessControl) 289TYPE_NAME_FOR_CF_TYPE_DEFN(SecCertificate) 290TYPE_NAME_FOR_CF_TYPE_DEFN(SecKey) 291TYPE_NAME_FOR_CF_TYPE_DEFN(SecPolicy) 292#endif 293 294#undef TYPE_NAME_FOR_CF_TYPE_DEFN 295 296static const char* base_bundle_id; 297 298const char* BaseBundleID() { 299 if (base_bundle_id) { 300 return base_bundle_id; 301 } 302 303#if BUILDFLAG(GOOGLE_CHROME_BRANDING) 304 return "com.google.Chrome"; 305#else 306 return "org.chromium.Chromium"; 307#endif 308} 309 310void SetBaseBundleID(const char* new_base_bundle_id) { 311 if (new_base_bundle_id != base_bundle_id) { 312 free((void*)base_bundle_id); 313 base_bundle_id = new_base_bundle_id ? strdup(new_base_bundle_id) : nullptr; 314 } 315} 316 317#define CF_CAST_DEFN(TypeCF) \ 318 template <> \ 319 TypeCF##Ref CFCast<TypeCF##Ref>(const CFTypeRef& cf_val) { \ 320 if (cf_val == nullptr) { \ 321 return nullptr; \ 322 } \ 323 if (CFGetTypeID(cf_val) == TypeCF##GetTypeID()) { \ 324 return (TypeCF##Ref)(cf_val); \ 325 } \ 326 return nullptr; \ 327 } \ 328 \ 329 template <> \ 330 TypeCF##Ref CFCastStrict<TypeCF##Ref>(const CFTypeRef& cf_val) { \ 331 TypeCF##Ref rv = CFCast<TypeCF##Ref>(cf_val); \ 332 CHECK(cf_val == nullptr || rv); \ 333 return rv; \ 334 } 335 336CF_CAST_DEFN(CFArray) 337CF_CAST_DEFN(CFBag) 338CF_CAST_DEFN(CFBoolean) 339CF_CAST_DEFN(CFData) 340CF_CAST_DEFN(CFDate) 341CF_CAST_DEFN(CFDictionary) 342CF_CAST_DEFN(CFNull) 343CF_CAST_DEFN(CFNumber) 344CF_CAST_DEFN(CFSet) 345CF_CAST_DEFN(CFString) 346CF_CAST_DEFN(CFURL) 347CF_CAST_DEFN(CFUUID) 348 349CF_CAST_DEFN(CGColor) 350 351CF_CAST_DEFN(CTFont) 352CF_CAST_DEFN(CTFontDescriptor) 353CF_CAST_DEFN(CTRun) 354 355CF_CAST_DEFN(SecCertificate) 356 357#if !BUILDFLAG(IS_IOS) 358CF_CAST_DEFN(SecAccessControl) 359CF_CAST_DEFN(SecKey) 360CF_CAST_DEFN(SecPolicy) 361#endif 362 363#undef CF_CAST_DEFN 364 365std::string GetValueFromDictionaryErrorMessage(CFStringRef key, 366 const std::string& expected_type, 367 CFTypeRef value) { 368 ScopedCFTypeRef<CFStringRef> actual_type_ref( 369 CFCopyTypeIDDescription(CFGetTypeID(value))); 370 return "Expected value for key " + SysCFStringRefToUTF8(key) + " to be " + 371 expected_type + " but it was " + 372 SysCFStringRefToUTF8(actual_type_ref.get()) + " instead"; 373} 374 375NSURL* FilePathToNSURL(const FilePath& path) { 376 return apple::CFToNSOwnershipCast(FilePathToCFURL(path).release()); 377} 378 379NSString* FilePathToNSString(const FilePath& path) { 380 return apple::CFToNSOwnershipCast(FilePathToCFString(path).release()); 381} 382 383FilePath NSStringToFilePath(NSString* str) { 384 return CFStringToFilePath(apple::NSToCFPtrCast(str)); 385} 386 387FilePath NSURLToFilePath(NSURL* url) { 388 return CFURLToFilePath(apple::NSToCFPtrCast(url)); 389} 390 391ScopedCFTypeRef<CFURLRef> FilePathToCFURL(const FilePath& path) { 392 if (path.empty()) { 393 return ScopedCFTypeRef<CFURLRef>(); 394 } 395 396 ScopedCFTypeRef<CFStringRef> path_string( 397 CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, 398 path.value().c_str())); 399 if (!path_string) { 400 return ScopedCFTypeRef<CFURLRef>(); 401 } 402 403 return ScopedCFTypeRef<CFURLRef>(CFURLCreateWithFileSystemPath( 404 kCFAllocatorDefault, path_string.get(), kCFURLPOSIXPathStyle, 405 /*isDirectory=*/FALSE)); 406} 407 408ScopedCFTypeRef<CFStringRef> FilePathToCFString(const FilePath& path) { 409 if (path.empty()) { 410 return ScopedCFTypeRef<CFStringRef>(); 411 } 412 413 return ScopedCFTypeRef<CFStringRef>( 414 CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, 415 path.value().c_str())); 416} 417 418FilePath CFStringToFilePath(CFStringRef str) { 419 if (!str || CFStringGetLength(str) == 0) { 420 return FilePath(); 421 } 422 423 return FilePath(FilePath::GetHFSDecomposedForm(str)); 424} 425 426FilePath CFURLToFilePath(CFURLRef url) { 427 if (!url || !CFURLIsFileURL(url)) { 428 return FilePath(); 429 } 430 431 ScopedCFTypeRef<CFStringRef> path( 432 CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle)); 433 if (!path) { 434 return FilePath(); 435 } 436 437 return CFStringToFilePath(path.get()); 438} 439 440bool CFRangeToNSRange(CFRange range, NSRange* range_out) { 441 NSUInteger end; 442 if (IsValueInRangeForNumericType<NSUInteger>(range.location) && 443 IsValueInRangeForNumericType<NSUInteger>(range.length) && 444 CheckAdd(range.location, range.length).AssignIfValid(&end) && 445 IsValueInRangeForNumericType<NSUInteger>(end)) { 446 *range_out = NSMakeRange(static_cast<NSUInteger>(range.location), 447 static_cast<NSUInteger>(range.length)); 448 return true; 449 } 450 return false; 451} 452 453span<const uint8_t> CFDataToSpan(CFDataRef data) { 454 return NSDataToSpan(apple::CFToNSPtrCast(data)); 455} 456 457span<uint8_t> CFMutableDataToSpan(CFMutableDataRef data) { 458 return NSMutableDataToSpan(apple::CFToNSPtrCast(data)); 459} 460 461} // namespace base::apple 462 463std::ostream& operator<<(std::ostream& o, const CFStringRef string) { 464 return o << base::SysCFStringRefToUTF8(string); 465} 466 467std::ostream& operator<<(std::ostream& o, const CFErrorRef err) { 468 base::apple::ScopedCFTypeRef<CFStringRef> desc(CFErrorCopyDescription(err)); 469 base::apple::ScopedCFTypeRef<CFDictionaryRef> user_info( 470 CFErrorCopyUserInfo(err)); 471 CFStringRef errorDesc = nullptr; 472 if (user_info.get()) { 473 errorDesc = reinterpret_cast<CFStringRef>( 474 CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey)); 475 } 476 o << "Code: " << CFErrorGetCode(err) << " Domain: " << CFErrorGetDomain(err) 477 << " Desc: " << desc.get(); 478 if (errorDesc) { 479 o << "(" << errorDesc << ")"; 480 } 481 return o; 482} 483 484std::ostream& operator<<(std::ostream& o, CFRange range) { 485 return o << NSStringFromRange( 486 NSMakeRange(static_cast<NSUInteger>(range.location), 487 static_cast<NSUInteger>(range.length))); 488} 489 490std::ostream& operator<<(std::ostream& o, id obj) { 491 return obj ? o << [obj description].UTF8String : o << "(nil)"; 492} 493 494std::ostream& operator<<(std::ostream& o, NSRange range) { 495 return o << NSStringFromRange(range); 496} 497 498std::ostream& operator<<(std::ostream& o, SEL selector) { 499 return o << NSStringFromSelector(selector); 500} 501 502#if !BUILDFLAG(IS_IOS) 503std::ostream& operator<<(std::ostream& o, NSPoint point) { 504 return o << NSStringFromPoint(point); 505} 506std::ostream& operator<<(std::ostream& o, NSRect rect) { 507 return o << NSStringFromRect(rect); 508} 509std::ostream& operator<<(std::ostream& o, NSSize size) { 510 return o << NSStringFromSize(size); 511} 512#endif 513