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