• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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