xref: /aosp_15_r20/external/cronet/base/apple/foundation_util.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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