/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsComponentManagerUtils.h"
#include "nsOSHelperAppService.h"
#include "nsISupports.h"
#include "nsString.h"
#include "nsIMIMEInfo.h"
#include "nsMIMEInfoWin.h"
#include "nsMimeTypes.h"
#include "nsNativeCharsetUtils.h"
#include "nsLocalFile.h"
#include "nsIWindowsRegKey.h"
#include "nsXULAppAPI.h"

// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
#include <shellapi.h>
#include <shlwapi.h>

#define LOG(...) MOZ_LOG(sLog, mozilla::LogLevel::Debug, (__VA_ARGS__))

// helper methods: forward declarations...
static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType,
                                                    nsString& aFileExtension);

nsOSHelperAppService::nsOSHelperAppService()
    : nsExternalHelperAppService(), mAppAssoc(nullptr) {
  CoInitialize(nullptr);
  CoCreateInstance(CLSID_ApplicationAssociationRegistration, nullptr,
                   CLSCTX_INPROC, IID_IApplicationAssociationRegistration,
                   (void**)&mAppAssoc);
}

nsOSHelperAppService::~nsOSHelperAppService() {
  if (mAppAssoc) mAppAssoc->Release();
  mAppAssoc = nullptr;
  CoUninitialize();
}

// The windows registry provides a mime database key which lists a set of mime
// types and corresponding "Extension" values. we can use this to look up our
// mime type to see if there is a preferred extension for the mime type.
static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType,
                                                    nsString& aFileExtension) {
  nsAutoString mimeDatabaseKey;
  mimeDatabaseKey.AssignLiteral("MIME\\Database\\Content Type\\");

  AppendASCIItoUTF16(aMimeType, mimeDatabaseKey);

  nsCOMPtr<nsIWindowsRegKey> regKey =
      do_CreateInstance("@mozilla.org/windows-registry-key;1");
  if (!regKey) return NS_ERROR_NOT_AVAILABLE;

  nsresult rv =
      regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, mimeDatabaseKey,
                   nsIWindowsRegKey::ACCESS_QUERY_VALUE);

  if (NS_SUCCEEDED(rv))
    regKey->ReadStringValue(u"Extension"_ns, aFileExtension);

  return NS_OK;
}

nsresult nsOSHelperAppService::OSProtocolHandlerExists(
    const char* aProtocolScheme, bool* aHandlerExists) {
  // look up the protocol scheme in the windows registry....if we find a match
  // then we have a handler for it...
  *aHandlerExists = false;
  if (aProtocolScheme && *aProtocolScheme) {
    NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE);
    wchar_t* pResult = nullptr;
    NS_ConvertASCIItoUTF16 scheme(aProtocolScheme);
    // We are responsible for freeing returned strings.
    HRESULT hr = mAppAssoc->QueryCurrentDefault(scheme.get(), AT_URLPROTOCOL,
                                                AL_EFFECTIVE, &pResult);
    if (SUCCEEDED(hr)) {
      CoTaskMemFree(pResult);
      nsCOMPtr<nsIWindowsRegKey> regKey =
          do_CreateInstance("@mozilla.org/windows-registry-key;1");
      if (!regKey) {
        return NS_ERROR_NOT_AVAILABLE;
      }

      nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
                                 nsDependentString(scheme.get()),
                                 nsIWindowsRegKey::ACCESS_QUERY_VALUE);
      if (NS_FAILED(rv)) {
        // Open will fail if the registry key path doesn't exist.
        return NS_OK;
      }

      bool hasValue;
      rv = regKey->HasValue(u"URL Protocol"_ns, &hasValue);
      if (NS_FAILED(rv)) {
        return NS_ERROR_FAILURE;
      }
      if (!hasValue) {
        return NS_OK;
      }

      *aHandlerExists = true;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(
    const nsACString& aScheme, nsAString& _retval) {
  nsCOMPtr<nsIWindowsRegKey> regKey =
      do_CreateInstance("@mozilla.org/windows-registry-key;1");
  if (!regKey) return NS_ERROR_NOT_AVAILABLE;

  NS_ConvertASCIItoUTF16 buf(aScheme);

  wchar_t result[1024];
  DWORD resultSize = 1024;
  HRESULT hr = AssocQueryString(0x1000 /* ASSOCF_IS_PROTOCOL */,
                                ASSOCSTR_FRIENDLYAPPNAME, buf.get(), NULL,
                                result, &resultSize);
  if (SUCCEEDED(hr)) {
    _retval = result;
    return NS_OK;
  }

  NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE);
  wchar_t* pResult = nullptr;
  // We are responsible for freeing returned strings.
  hr = mAppAssoc->QueryCurrentDefault(buf.get(), AT_URLPROTOCOL, AL_EFFECTIVE,
                                      &pResult);
  if (SUCCEEDED(hr)) {
    nsCOMPtr<nsIFile> app;
    nsAutoString appInfo(pResult);
    CoTaskMemFree(pResult);
    if (NS_SUCCEEDED(GetDefaultAppInfo(appInfo, _retval, getter_AddRefs(app))))
      return NS_OK;
  }
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol(
    const nsACString& aScheme, bool* _retval) {
  *_retval = false;

  NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE);

  NS_ConvertASCIItoUTF16 buf(aScheme);

  // Find the progID
  wchar_t* pResult = nullptr;
  HRESULT hr = mAppAssoc->QueryCurrentDefault(buf.get(), AT_URLPROTOCOL,
                                              AL_EFFECTIVE, &pResult);
  if (FAILED(hr)) {
    return NS_ERROR_FAILURE;
  }
  nsAutoString progID(pResult);
  // We are responsible for freeing returned strings.
  CoTaskMemFree(pResult);

  // Find the default executable.
  nsAutoString description;
  nsCOMPtr<nsIFile> appFile;
  nsresult rv = GetDefaultAppInfo(progID, description, getter_AddRefs(appFile));
  if (NS_FAILED(rv)) {
    return rv;
  }
  // Determine if the default executable is our executable.
  nsCOMPtr<nsIFile> ourBinary;
  XRE_GetBinaryPath(getter_AddRefs(ourBinary));
  bool isSame = false;
  rv = appFile->Equals(ourBinary, &isSame);
  if (NS_FAILED(rv)) {
    return rv;
  }
  *_retval = isSame;
  return NS_OK;
}

// GetMIMEInfoFromRegistry: This function obtains the values of some of the
// nsIMIMEInfo attributes for the mimeType/extension associated with the input
// registry key.  The default entry for that key is the name of a registry key
// under HKEY_CLASSES_ROOT.  The default value for *that* key is the descriptive
// name of the type.  The EditFlags value is a binary value; the low order bit
// of the third byte of which indicates that the user does not need to be
// prompted.
//
// This function sets only the Description attribute of the input nsIMIMEInfo.
/* static */
nsresult nsOSHelperAppService::GetMIMEInfoFromRegistry(const nsString& fileType,
                                                       nsIMIMEInfo* pInfo) {
  nsresult rv = NS_OK;

  NS_ENSURE_ARG(pInfo);
  nsCOMPtr<nsIWindowsRegKey> regKey =
      do_CreateInstance("@mozilla.org/windows-registry-key;1");
  if (!regKey) return NS_ERROR_NOT_AVAILABLE;

  rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, fileType,
                    nsIWindowsRegKey::ACCESS_QUERY_VALUE);
  if (NS_FAILED(rv)) return NS_ERROR_FAILURE;

  // OK, the default value here is the description of the type.
  nsAutoString description;
  rv = regKey->ReadStringValue(u""_ns, description);
  if (NS_SUCCEEDED(rv)) pInfo->SetDescription(description);

  return NS_OK;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// method overrides used to gather information from the windows registry for
// various mime types.
////////////////////////////////////////////////////////////////////////////////////////////////

/// Looks up the type for the extension aExt and compares it to aType
/* static */
bool nsOSHelperAppService::typeFromExtEquals(const char16_t* aExt,
                                             const char* aType) {
  if (!aType) return false;
  nsAutoString fileExtToUse;
  if (aExt[0] != char16_t('.')) fileExtToUse = char16_t('.');

  fileExtToUse.Append(aExt);

  bool eq = false;
  nsCOMPtr<nsIWindowsRegKey> regKey =
      do_CreateInstance("@mozilla.org/windows-registry-key;1");
  if (!regKey) return eq;

  nsresult rv =
      regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, fileExtToUse,
                   nsIWindowsRegKey::ACCESS_QUERY_VALUE);
  if (NS_FAILED(rv)) return eq;

  nsAutoString type;
  rv = regKey->ReadStringValue(u"Content Type"_ns, type);
  if (NS_SUCCEEDED(rv)) eq = type.LowerCaseEqualsASCII(aType);

  return eq;
}

// The "real" name of a given helper app (as specified by the path to the
// executable file held in various registry keys) is stored n the VERSIONINFO
// block in the file's resources. We need to find the path to the executable
// and then retrieve the "FileDescription" field value from the file.
nsresult nsOSHelperAppService::GetDefaultAppInfo(
    const nsAString& aAppInfo, nsAString& aDefaultDescription,
    nsIFile** aDefaultApplication) {
  nsAutoString handlerCommand;

  // If all else fails, use the file type key name, which will be
  // something like "pngfile" for .pngs, "WMVFile" for .wmvs, etc.
  aDefaultDescription = aAppInfo;
  *aDefaultApplication = nullptr;

  if (aAppInfo.IsEmpty()) return NS_ERROR_FAILURE;

  // aAppInfo may be a file, file path, program id, or
  // Applications reference -
  // c:\dir\app.exe
  // Applications\appfile.exe/dll (shell\open...)
  // ProgID.progid (shell\open...)

  nsAutoString handlerKeyName(aAppInfo);

  nsCOMPtr<nsIWindowsRegKey> chkKey =
      do_CreateInstance("@mozilla.org/windows-registry-key;1");
  if (!chkKey) return NS_ERROR_FAILURE;

  nsresult rv =
      chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, handlerKeyName,
                   nsIWindowsRegKey::ACCESS_QUERY_VALUE);
  if (NS_FAILED(rv)) {
    // It's a file system path to a handler
    handlerCommand.Assign(aAppInfo);
  } else {
    handlerKeyName.AppendLiteral("\\shell\\open\\command");
    nsCOMPtr<nsIWindowsRegKey> regKey =
        do_CreateInstance("@mozilla.org/windows-registry-key;1");
    if (!regKey) return NS_ERROR_FAILURE;

    nsresult rv =
        regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, handlerKeyName,
                     nsIWindowsRegKey::ACCESS_QUERY_VALUE);
    if (NS_FAILED(rv)) return NS_ERROR_FAILURE;

    // OK, the default value here is the description of the type.
    rv = regKey->ReadStringValue(u""_ns, handlerCommand);
    if (NS_FAILED(rv)) {
      // Check if there is a DelegateExecute string
      nsAutoString delegateExecute;
      rv = regKey->ReadStringValue(u"DelegateExecute"_ns, delegateExecute);
      NS_ENSURE_SUCCESS(rv, rv);

      // Look for InProcServer32
      nsAutoString delegateExecuteRegPath;
      delegateExecuteRegPath.AssignLiteral("CLSID\\");
      delegateExecuteRegPath.Append(delegateExecute);
      delegateExecuteRegPath.AppendLiteral("\\InProcServer32");
      rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
                        delegateExecuteRegPath,
                        nsIWindowsRegKey::ACCESS_QUERY_VALUE);
      if (NS_SUCCEEDED(rv)) {
        rv = chkKey->ReadStringValue(u""_ns, handlerCommand);
      }

      if (NS_FAILED(rv)) {
        // Look for LocalServer32
        delegateExecuteRegPath.AssignLiteral("CLSID\\");
        delegateExecuteRegPath.Append(delegateExecute);
        delegateExecuteRegPath.AppendLiteral("\\LocalServer32");
        rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
                          delegateExecuteRegPath,
                          nsIWindowsRegKey::ACCESS_QUERY_VALUE);
        NS_ENSURE_SUCCESS(rv, rv);
        rv = chkKey->ReadStringValue(u""_ns, handlerCommand);
        NS_ENSURE_SUCCESS(rv, rv);
      }
    }

    LOG("AppInfo \"%s\" - Determined handler command: %s\n",
        NS_ConvertUTF16toUTF8(aAppInfo).get(),
        NS_ConvertUTF16toUTF8(handlerCommand).get());
  }

  // XXX FIXME: If this fails, the UI will display the full command
  // string.
  // There are some rare cases this can happen - ["url.dll" -foo]
  // for example won't resolve correctly to the system dir. The
  // subsequent launch of the helper app will work though.
  nsCOMPtr<nsILocalFileWin> lf = new nsLocalFile();
  rv = lf->InitWithCommandLine(handlerCommand);
  NS_ENSURE_SUCCESS(rv, rv);
  lf.forget(aDefaultApplication);

  wchar_t friendlyName[1024];
  DWORD friendlyNameSize = 1024;
  HRESULT hr = AssocQueryString(ASSOCF_NONE, ASSOCSTR_FRIENDLYAPPNAME,
                                PromiseFlatString(aAppInfo).get(), NULL,
                                friendlyName, &friendlyNameSize);
  if (SUCCEEDED(hr) && friendlyNameSize > 1) {
    aDefaultDescription.Assign(friendlyName, friendlyNameSize - 1);
  }

  if (MOZ_LOG_TEST(sLog, mozilla::LogLevel::Debug)) {
    nsAutoString path;
    if (*aDefaultApplication) {
      (*aDefaultApplication)->GetPath(path);
    }

    LOG("AppInfo \"%s\" - default application (name: %s ; path: %s)\n",
        NS_ConvertUTF16toUTF8(aAppInfo).get(),
        NS_ConvertUTF16toUTF8(aDefaultDescription).get(),
        NS_ConvertUTF16toUTF8(path).get());
  }

  return NS_OK;
}

already_AddRefed<nsMIMEInfoWin> nsOSHelperAppService::GetByExtension(
    const nsString& aFileExt, const char* aTypeHint) {
  if (aFileExt.IsEmpty()) return nullptr;

  // Determine the mime type.
  nsAutoCString typeToUse;
  if (aTypeHint && *aTypeHint) {
    typeToUse.Assign(aTypeHint);
  } else if (!GetMIMETypeFromOSForExtension(NS_ConvertUTF16toUTF8(aFileExt),
                                            typeToUse)) {
    return nullptr;
  }

  RefPtr mimeInfo = mozilla::MakeRefPtr<nsMIMEInfoWin>(typeToUse);

  // Our extension APIs expect extensions without the '.', so normalize:
  uint32_t dotlessIndex = aFileExt.First() != char16_t('.') ? 0 : 1;
  nsAutoCString lowerFileExt =
      NS_ConvertUTF16toUTF8(Substring(aFileExt, dotlessIndex));
  ToLowerCase(lowerFileExt);
  mimeInfo->AppendExtension(lowerFileExt);
  mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);

  if (NS_FAILED(InternalSetDefaultsOnMIME(mimeInfo))) {
    return nullptr;
  }

  return mimeInfo.forget();
}

nsresult nsOSHelperAppService::InternalSetDefaultsOnMIME(
    nsMIMEInfoWin* aMIMEInfo) {
  NS_ENSURE_ARG(aMIMEInfo);

  nsAutoCString primaryExt;
  aMIMEInfo->GetPrimaryExtension(primaryExt);

  if (primaryExt.IsEmpty()) {
    return NS_ERROR_FAILURE;
  }

  // windows registry assumes your file extension is going to include the '.',
  // but our APIs don't have it, so add it:
  nsAutoString assocType = NS_ConvertUTF8toUTF16(primaryExt);
  if (assocType.First() != char16_t('.')) {
    assocType.Insert(char16_t('.'), 0);
  }

  nsAutoString appInfo;
  bool found;

  // Retrieve the default application for this extension
  NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE);
  wchar_t* pResult = nullptr;
  HRESULT hr = mAppAssoc->QueryCurrentDefault(assocType.get(), AT_FILEEXTENSION,
                                              AL_EFFECTIVE, &pResult);
  if (SUCCEEDED(hr)) {
    found = true;
    appInfo.Assign(pResult);
    CoTaskMemFree(pResult);
  } else {
    found = false;
  }

  // Bug 358297 - ignore the default handler, force the user to choose app
  if (appInfo.EqualsLiteral("XPSViewer.Document")) found = false;

  if (!found) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  // Get other nsIMIMEInfo fields from registry, if possible.
  nsAutoString defaultDescription;
  nsCOMPtr<nsIFile> defaultApplication;

  if (NS_FAILED(GetDefaultAppInfo(appInfo, defaultDescription,
                                  getter_AddRefs(defaultApplication)))) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  aMIMEInfo->SetDefaultDescription(defaultDescription);
  aMIMEInfo->SetDefaultApplicationHandler(defaultApplication);

  // Grab the general description
  GetMIMEInfoFromRegistry(appInfo, aMIMEInfo);
  return NS_OK;
}

NS_IMETHODIMP
nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
                                        const nsACString& aFileExt,
                                        bool* aFound, nsIMIMEInfo** aMIMEInfo) {
  *aFound = false;

  const nsCString& flatType = PromiseFlatCString(aMIMEType);
  nsAutoString fileExtension;
  CopyUTF8toUTF16(aFileExt, fileExtension);

  /* XXX The octet-stream check is a gross hack to wallpaper over the most
   * common Win32 extension issues caused by the fix for bug 116938.  See bug
   * 120327, comment 271 for why this is needed.  Not even sure we
   * want to remove this once we have fixed all this stuff to work
   * right; any info we get from the OS on this type is pretty much
   * useless....
   */
  bool haveMeaningfulMimeType =
      !aMIMEType.IsEmpty() &&
      !aMIMEType.LowerCaseEqualsLiteral(APPLICATION_OCTET_STREAM);
  LOG("Extension lookup on '%S' with mimetype '%s'%s\n", fileExtension.getW(),
      flatType.get(), haveMeaningfulMimeType ? " (treated as meaningful)" : "");

  RefPtr<nsMIMEInfoWin> mi;

  // We should have *something* to go on here.
  nsAutoString extensionFromMimeType;
  if (haveMeaningfulMimeType) {
    GetExtensionFromWindowsMimeDatabase(aMIMEType, extensionFromMimeType);
  }
  if (fileExtension.IsEmpty() && extensionFromMimeType.IsEmpty()) {
    // Without an extension from the mimetype or the file, we can't
    // do anything here.
    mi = new nsMIMEInfoWin(flatType.get());
    mi.forget(aMIMEInfo);
    return NS_OK;
  }

  // Either fileExtension or extensionFromMimeType must now be non-empty.

  *aFound = true;

  // On Windows, we prefer the file extension for lookups over the mimetype,
  // because that's how windows does things.
  // If we have no file extension or it doesn't match the mimetype, use the
  // mime type's default file extension instead.
  bool usedMimeTypeExtensionForLookup = false;
  if (fileExtension.IsEmpty() ||
      (!extensionFromMimeType.IsEmpty() &&
       !typeFromExtEquals(fileExtension.get(), flatType.get()))) {
    usedMimeTypeExtensionForLookup = true;
    fileExtension = extensionFromMimeType;
    LOG("Now using '%s' mimetype's default file extension '%S' for lookup\n",
        flatType.get(), fileExtension.getW());
  }

  // If we have an extension, use it for lookup:
  mi = GetByExtension(fileExtension, flatType.get());
  LOG("Extension lookup on '%S' found: 0x%p\n", fileExtension.getW(), mi.get());

  if (mi) {
    bool hasDefault = false;
    mi->GetHasDefaultHandler(&hasDefault);
    // If we don't find a default handler description, see if we can find one
    // using the mimetype.
    if (!hasDefault && !usedMimeTypeExtensionForLookup) {
      RefPtr<nsMIMEInfoWin> miFromMimeType =
          GetByExtension(extensionFromMimeType, flatType.get());
      LOG("Mime-based ext. lookup for '%S' found 0x%p\n",
          extensionFromMimeType.getW(), miFromMimeType.get());
      if (miFromMimeType) {
        nsAutoString desc;
        miFromMimeType->GetDefaultDescription(desc);
        mi->SetDefaultDescription(desc);
      }
    }
    mi.forget(aMIMEInfo);
    return NS_OK;
  }

  // The extension didn't work. Try the extension from the mimetype if
  // different:
  if (!extensionFromMimeType.IsEmpty() && !usedMimeTypeExtensionForLookup) {
    mi = GetByExtension(extensionFromMimeType, flatType.get());
    LOG("Mime-based ext. lookup for '%S' found 0x%p\n",
        extensionFromMimeType.getW(), mi.get());
  }
  if (mi) {
    mi.forget(aMIMEInfo);
    return NS_OK;
  }
  // This didn't work either, so just return an empty dummy mimeinfo.
  *aFound = false;
  mi = new nsMIMEInfoWin(flatType.get());
  // If we didn't resort to the mime type's extension, we must have had a
  // valid extension, so stick its lowercase version on the mime info.
  if (!usedMimeTypeExtensionForLookup) {
    nsAutoCString lowerFileExt;
    ToLowerCase(aFileExt, lowerFileExt);
    mi->AppendExtension(lowerFileExt);
  }
  mi.forget(aMIMEInfo);
  return NS_OK;
}

NS_IMETHODIMP
nsOSHelperAppService::UpdateDefaultAppInfo(nsIMIMEInfo* aMIMEInfo) {
  InternalSetDefaultsOnMIME(static_cast<nsMIMEInfoWin*>(aMIMEInfo));
  return NS_OK;
}

NS_IMETHODIMP
nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString& aScheme,
                                                   bool* found,
                                                   nsIHandlerInfo** _retval) {
  NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");

  nsresult rv =
      OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(), found);
  if (NS_FAILED(rv)) return rv;

  nsMIMEInfoWin* handlerInfo =
      new nsMIMEInfoWin(aScheme, nsMIMEInfoBase::eProtocolInfo);
  NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
  NS_ADDREF(*_retval = handlerInfo);

  if (!*found) {
    // Code that calls this requires an object regardless if the OS has
    // something for us, so we return the empty object.
    return NS_OK;
  }

  nsAutoString desc;
  GetApplicationDescription(aScheme, desc);
  handlerInfo->SetDefaultDescription(desc);

  return NS_OK;
}

bool nsOSHelperAppService::GetMIMETypeFromOSForExtension(
    const nsACString& aExtension, nsACString& aMIMEType) {
  if (aExtension.IsEmpty()) return false;

  // windows registry assumes your file extension is going to include the '.'.
  // so make sure it's there...
  nsAutoString fileExtToUse;
  if (aExtension.First() != '.') fileExtToUse = char16_t('.');

  AppendUTF8toUTF16(aExtension, fileExtToUse);

  // Try to get an entry from the windows registry.
  nsCOMPtr<nsIWindowsRegKey> regKey =
      do_CreateInstance("@mozilla.org/windows-registry-key;1");
  if (!regKey) return false;

  nsresult rv =
      regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, fileExtToUse,
                   nsIWindowsRegKey::ACCESS_QUERY_VALUE);
  if (NS_FAILED(rv)) return false;

  nsAutoString mimeType;
  if (NS_FAILED(regKey->ReadStringValue(u"Content Type"_ns, mimeType)) ||
      mimeType.IsEmpty()) {
    return false;
  }
  // Content-Type is always in ASCII
  aMIMEType.Truncate();
  LossyAppendUTF16toASCII(mimeType, aMIMEType);
  return true;
}
