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