// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/platform_mime_util.h"

#import <Foundation/Foundation.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>

#include <string>

#include "base/mac/foundation_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/notreached.h"
#include "base/strings/sys_string_conversions.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_IOS)
#include <MobileCoreServices/MobileCoreServices.h>
#else
#include <CoreServices/CoreServices.h>
#endif  // BUILDFLAG(IS_IOS)

namespace net {

bool PlatformMimeUtil::GetPlatformMimeTypeFromExtension(
    const base::FilePath::StringType& ext,
    std::string* result) const {
  std::string ext_nodot = ext;
  if (ext_nodot.length() >= 1 && ext_nodot[0] == L'.') {
    ext_nodot.erase(ext_nodot.begin());
  }

  // TODO(crbug.com/1227419): Remove iOS availability check when cronet
  // deployment target is bumped to 14.
  if (@available(macOS 11, iOS 14, *)) {
    UTType* uttype =
        [UTType typeWithFilenameExtension:base::SysUTF8ToNSString(ext_nodot)];
    // Dynamic UTTypes are made by the system in the event that there's a
    // non-identifiable mime type. For now, we should treat dynamic UTTypes as a
    // nonstandard format.
    if ([uttype isDynamic] || uttype.preferredMIMEType == nil) {
      return false;
    }
    *result = base::SysNSStringToUTF8(uttype.preferredMIMEType);
    return true;
  }
#if (BUILDFLAG(IS_MAC) &&                                    \
     MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0) || \
    (BUILDFLAG(IS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
  else {
    base::ScopedCFTypeRef<CFStringRef> ext_ref(
        base::SysUTF8ToCFStringRef(ext_nodot));
    if (!ext_ref) {
      return false;
    }
    base::ScopedCFTypeRef<CFStringRef> uti(
        UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
                                              ext_ref, nullptr));
    if (!uti) {
      return false;
    }
    base::ScopedCFTypeRef<CFStringRef> mime_ref(
        UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
    if (!mime_ref) {
      return false;
    }

    *result = base::SysCFStringRefToUTF8(mime_ref);
    return true;
  }
#else
  NOTREACHED();
  return false;
#endif  // (BUILDFLAG(IS_MAC) && MAC_OS_X_VERSION_MIN_REQUIRED <
        // MAC_OS_VERSION_11_0) || (BUILDFLAG(IS_IOS) &&
        // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
}

bool PlatformMimeUtil::GetPlatformPreferredExtensionForMimeType(
    const std::string& mime_type,
    base::FilePath::StringType* ext) const {
  // TODO(crbug.com/1227419): Remove iOS availability check when cronet
  // deployment target is bumped to 14.
  if (@available(macOS 11, iOS 14, *)) {
    UTType* uttype =
        [UTType typeWithMIMEType:base::SysUTF8ToNSString(mime_type)];
    if ([uttype isDynamic] || uttype.preferredFilenameExtension == nil) {
      return false;
    }
    *ext = base::SysNSStringToUTF8(uttype.preferredFilenameExtension);
    return true;
  }
#if (BUILDFLAG(IS_MAC) &&                                    \
     MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0) || \
    (BUILDFLAG(IS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
  else {
    base::ScopedCFTypeRef<CFStringRef> mime_ref(
        base::SysUTF8ToCFStringRef(mime_type));
    if (!mime_ref) {
      return false;
    }
    base::ScopedCFTypeRef<CFStringRef> uti(
        UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mime_ref,
                                              nullptr));
    if (!uti) {
      return false;
    }
    base::ScopedCFTypeRef<CFStringRef> ext_ref(
        UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension));
    if (!ext_ref) {
      return false;
    }

    *ext = base::SysCFStringRefToUTF8(ext_ref);
    return true;
  }

#else
  NOTREACHED();
  return false;
#endif  // (BUILDFLAG(IS_MAC) && MAC_OS_X_VERSION_MIN_REQUIRED <
        // MAC_OS_VERSION_11_0) || (BUILDFLAG(IS_IOS) &&
        // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
}

void PlatformMimeUtil::GetPlatformExtensionsForMimeType(
    const std::string& mime_type,
    std::unordered_set<base::FilePath::StringType>* extensions) const {
  // TODO(crbug.com/1227419): Remove iOS availability check when cronet
  // deployment target is bumped to 14.
  if (@available(macOS 11, iOS 14, *)) {
    NSArray<UTType*>* types =
        [UTType typesWithTag:base::SysUTF8ToNSString(mime_type)
                    tagClass:UTTagClassMIMEType
            conformingToType:nil];
    bool extensions_found = false;
    if (types) {
      NSInteger numberOfTypes = (NSInteger)types.count;
      for (NSInteger i = 0; i < numberOfTypes; ++i) {
        UTType* type = types[i];
        if (!type || type.preferredFilenameExtension == nil) {
          continue;
        }
        extensions_found = true;
        NSArray<NSString*>* extensions_list =
            type.tags[UTTagClassFilenameExtension];
        for (NSString* extension in extensions_list) {
          extensions->insert(base::SysNSStringToUTF8(extension));
        }
      }
    }

    if (extensions_found) {
      return;
    }

    base::FilePath::StringType ext;
    if (GetPlatformPreferredExtensionForMimeType(mime_type, &ext)) {
      extensions->insert(ext);
    }
  }
#if (BUILDFLAG(IS_MAC) &&                                    \
     MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0) || \
    (BUILDFLAG(IS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
  else {
    base::ScopedCFTypeRef<CFStringRef> mime_ref(
        base::SysUTF8ToCFStringRef(mime_type));
    if (mime_ref) {
      bool extensions_found = false;
      base::ScopedCFTypeRef<CFArrayRef> types(UTTypeCreateAllIdentifiersForTag(
          kUTTagClassMIMEType, mime_ref, nullptr));
      if (types) {
        for (CFIndex i = 0; i < CFArrayGetCount(types); i++) {
          base::ScopedCFTypeRef<CFArrayRef> extensions_list(
              UTTypeCopyAllTagsWithClass(base::mac::CFCast<CFStringRef>(
                                             CFArrayGetValueAtIndex(types, i)),
                                         kUTTagClassFilenameExtension));
          if (!extensions_list) {
            continue;
          }
          extensions_found = true;
          for (NSString* extension in base::mac::CFToNSCast(extensions_list)) {
            extensions->insert(base::SysNSStringToUTF8(extension));
          }
        }
      }
      if (extensions_found) {
        return;
      }
    }

    // Huh? Give up.
    base::FilePath::StringType ext;
    if (GetPlatformPreferredExtensionForMimeType(mime_type, &ext)) {
      extensions->insert(ext);
    }
  }
#endif  // (BUILDFLAG(IS_MAC) && MAC_OS_X_VERSION_MIN_REQUIRED <
        // MAC_OS_VERSION_11_0) || (BUILDFLAG(IS_IOS) &&
        // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)
}

}  // namespace net