xref: /aosp_15_r20/external/cronet/net/base/platform_mime_util_apple.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 "net/base/platform_mime_util.h"
6
7#import <Foundation/Foundation.h>
8#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
9
10#include <string>
11
12#include "base/apple/bridging.h"
13#include "base/apple/foundation_util.h"
14#include "base/apple/scoped_cftyperef.h"
15#include "base/notreached.h"
16#include "base/strings/sys_string_conversions.h"
17#include "build/build_config.h"
18
19#if BUILDFLAG(IS_IOS)
20#include <MobileCoreServices/MobileCoreServices.h>
21#else
22#include <CoreServices/CoreServices.h>
23#endif  // BUILDFLAG(IS_IOS)
24
25namespace net {
26
27bool PlatformMimeUtil::GetPlatformMimeTypeFromExtension(
28    const base::FilePath::StringType& ext,
29    std::string* result) const {
30  std::string ext_nodot = ext;
31  if (ext_nodot.length() >= 1 && ext_nodot[0] == L'.') {
32    ext_nodot.erase(ext_nodot.begin());
33  }
34
35  // TODO(crbug.com/1227419): Remove iOS availability check when cronet
36  // deployment target is bumped to 14.
37  if (@available(macOS 11, iOS 14, *)) {
38    UTType* uttype =
39        [UTType typeWithFilenameExtension:base::SysUTF8ToNSString(ext_nodot)];
40    // Dynamic UTTypes are made by the system in the event that there's a
41    // non-identifiable mime type. For now, we should treat dynamic UTTypes as a
42    // nonstandard format.
43    if (uttype.dynamic || uttype.preferredMIMEType == nil) {
44      return false;
45    }
46    *result = base::SysNSStringToUTF8(uttype.preferredMIMEType);
47    return true;
48  }
49#if (BUILDFLAG(IS_MAC) &&                                    \
50     MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0) || \
51    (BUILDFLAG(IS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
52  else {
53    base::apple::ScopedCFTypeRef<CFStringRef> ext_ref(
54        base::SysUTF8ToCFStringRef(ext_nodot));
55    if (!ext_ref) {
56      return false;
57    }
58    base::apple::ScopedCFTypeRef<CFStringRef> uti(
59        UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
60                                              ext_ref.get(),
61                                              /*inConformingToUTI=*/nullptr));
62    if (!uti) {
63      return false;
64    }
65    base::apple::ScopedCFTypeRef<CFStringRef> mime_ref(
66        UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
67    if (!mime_ref) {
68      return false;
69    }
70
71    *result = base::SysCFStringRefToUTF8(mime_ref.get());
72    return true;
73  }
74#else
75  NOTREACHED();
76  return false;
77#endif  // (BUILDFLAG(IS_MAC) && MAC_OS_X_VERSION_MIN_REQUIRED <
78        // MAC_OS_VERSION_11_0) || (BUILDFLAG(IS_IOS) &&
79        // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
80}
81
82bool PlatformMimeUtil::GetPlatformPreferredExtensionForMimeType(
83    const std::string& mime_type,
84    base::FilePath::StringType* ext) const {
85  // TODO(crbug.com/1227419): Remove iOS availability check when cronet
86  // deployment target is bumped to 14.
87  if (@available(macOS 11, iOS 14, *)) {
88    UTType* uttype =
89        [UTType typeWithMIMEType:base::SysUTF8ToNSString(mime_type)];
90    if (uttype.dynamic || uttype.preferredFilenameExtension == nil) {
91      return false;
92    }
93    *ext = base::SysNSStringToUTF8(uttype.preferredFilenameExtension);
94    return true;
95  }
96#if (BUILDFLAG(IS_MAC) &&                                    \
97     MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0) || \
98    (BUILDFLAG(IS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
99  else {
100    base::apple::ScopedCFTypeRef<CFStringRef> mime_ref(
101        base::SysUTF8ToCFStringRef(mime_type));
102    if (!mime_ref) {
103      return false;
104    }
105    base::apple::ScopedCFTypeRef<CFStringRef> uti(
106        UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,
107                                              mime_ref.get(),
108                                              /*inConformingToUTI=*/nullptr));
109    if (!uti) {
110      return false;
111    }
112    base::apple::ScopedCFTypeRef<CFStringRef> ext_ref(
113        UTTypeCopyPreferredTagWithClass(uti.get(),
114                                        kUTTagClassFilenameExtension));
115    if (!ext_ref) {
116      return false;
117    }
118
119    *ext = base::SysCFStringRefToUTF8(ext_ref.get());
120    return true;
121  }
122
123#else
124  NOTREACHED();
125  return false;
126#endif  // (BUILDFLAG(IS_MAC) && MAC_OS_X_VERSION_MIN_REQUIRED <
127        // MAC_OS_VERSION_11_0) || (BUILDFLAG(IS_IOS) &&
128        // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
129}
130
131void PlatformMimeUtil::GetPlatformExtensionsForMimeType(
132    const std::string& mime_type,
133    std::unordered_set<base::FilePath::StringType>* extensions) const {
134  // TODO(crbug.com/1227419): Remove iOS availability check when cronet
135  // deployment target is bumped to 14.
136  if (@available(macOS 11, iOS 14, *)) {
137    NSArray<UTType*>* types =
138        [UTType typesWithTag:base::SysUTF8ToNSString(mime_type)
139                    tagClass:UTTagClassMIMEType
140            conformingToType:nil];
141    bool extensions_found = false;
142    if (types) {
143      NSInteger numberOfTypes = (NSInteger)types.count;
144      for (NSInteger i = 0; i < numberOfTypes; ++i) {
145        UTType* type = types[i];
146        if (!type || type.preferredFilenameExtension == nil) {
147          continue;
148        }
149        extensions_found = true;
150        NSArray<NSString*>* extensions_list =
151            type.tags[UTTagClassFilenameExtension];
152        for (NSString* extension in extensions_list) {
153          extensions->insert(base::SysNSStringToUTF8(extension));
154        }
155      }
156    }
157
158    if (extensions_found) {
159      return;
160    }
161
162    base::FilePath::StringType ext;
163    if (GetPlatformPreferredExtensionForMimeType(mime_type, &ext)) {
164      extensions->insert(ext);
165    }
166  }
167#if (BUILDFLAG(IS_MAC) &&                                    \
168     MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0) || \
169    (BUILDFLAG(IS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
170  else {
171    base::apple::ScopedCFTypeRef<CFStringRef> mime_ref(
172        base::SysUTF8ToCFStringRef(mime_type));
173    if (mime_ref) {
174      bool extensions_found = false;
175      base::apple::ScopedCFTypeRef<CFArrayRef> types(
176          UTTypeCreateAllIdentifiersForTag(kUTTagClassMIMEType, mime_ref.get(),
177                                           nullptr));
178      if (types) {
179        for (CFIndex i = 0; i < CFArrayGetCount(types.get()); i++) {
180          base::apple::ScopedCFTypeRef<CFArrayRef> extensions_list(
181              UTTypeCopyAllTagsWithClass(
182                  base::apple::CFCast<CFStringRef>(
183                      CFArrayGetValueAtIndex(types.get(), i)),
184                  kUTTagClassFilenameExtension));
185          if (!extensions_list) {
186            continue;
187          }
188          extensions_found = true;
189          for (NSString* extension in base::apple::CFToNSPtrCast(
190                   extensions_list.get())) {
191            extensions->insert(base::SysNSStringToUTF8(extension));
192          }
193        }
194      }
195      if (extensions_found) {
196        return;
197      }
198    }
199
200    // Huh? Give up.
201    base::FilePath::StringType ext;
202    if (GetPlatformPreferredExtensionForMimeType(mime_type, &ext)) {
203      extensions->insert(ext);
204    }
205  }
206#endif  // (BUILDFLAG(IS_MAC) && MAC_OS_X_VERSION_MIN_REQUIRED <
207        // MAC_OS_VERSION_11_0) || (BUILDFLAG(IS_IOS) &&
208        // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
209}
210
211}  // namespace net
212